当前位置: 首页 > news >正文

C# TCP 开发笔记(TcpListener/TcpClient)

TCP 服务端 Form3 代码笔记(基于 C# Windows Forms)

一、窗体核心功能定位

Form3 是一个独立的 TCP 服务端窗体,核心能力包括:

  1. 启动 / 停止 TCP 服务,监听指定 IP(127.0.0.1)和端口(9999)

  2. 异步接收多客户端连接,为每个客户端分配独立数据接收任务

  3. 线程安全的日志记录(区分普通信息、成功、错误等状态)

  4. 资源自动释放(服务停止、窗体关闭时释放网络资源)

  5. 附加功能:通过按钮打开 Form4 子窗体(非模态,不阻塞服务端操作)

二、核心变量定义(服务端基础配置)

变量名类型作用说明
DEFAULT_LOG_COLOR_*const int日志默认颜色的 ARGB 分量(编译时常量,解决默认参数非编译时常量问题)
DEFAULT_LOG_COLORstatic readonly Color日志默认颜色(灰色),由上述 ARGB 分量合成
_listenerTcpListenerTCP 监听器对象,负责监听客户端连接
ctsCancellationTokenSource任务取消令牌源,控制后台监听、数据接收任务的终止
_isServerRunningbool服务端运行状态标记(避免重复启动 / 停止操作)

三、核心功能模块拆解(带关键代码)

模块 1:服务端启动 / 停止按钮事件(入口逻辑)

功能逻辑
  • 点击按钮时先禁用按钮,防止并发操作

  • 根据 _isServerRunning 状态切换:未运行则启动服务,已运行则停止服务

  • 异常捕获并记录错误日志,最终恢复按钮可用性

关键代码
private async void button1_Click(object sender, EventArgs e)
{button1.Enabled = false; // 禁用按钮防并发try{if (!_isServerRunning){await StartServerAsync(); // 启动服务button1.Text = "停止服务端";AddServerLog("操作:开始启动服务端...");}else{await StopServerAsync(); // 停止服务button1.Text = "启动服务端";AddServerLog("操作:开始停止服务端...");}}catch (Exception ex){AddServerLog($"错误:{ex.Message}", Color.Red); // 错误日志标红// 异常后恢复按钮文本button1.Text = _isServerRunning ? "停止服务端" : "启动服务端";}finally{button1.Enabled = true; // 恢复按钮可用}
}

模块 2:异步启动服务 + 监听客户端连接

功能逻辑
  1. 配置服务端地址(本地回环地址 127.0.0.1 + 端口 9999)

  2. 初始化 TcpListener 并启动(最大挂起连接数 10)

  3. 创建取消令牌源,标记服务为运行状态

  4. 启动独立后台任务,循环监听客户端连接(非阻塞 UI)

  5. 捕获 SocketException,针对端口占用(10048 错误)给出明确提示

关键代码(监听客户端核心逻辑)
private async Task StartServerAsync()
{try{IPAddress serverIp = IPAddress.Parse("127.0.0.1");int serverPort = 9999;_listener = new TcpListener(serverIp, serverPort);_listener.Start(10); // 最大挂起连接数10
​cts = new CancellationTokenSource();_isServerRunning = true;AddServerLog($"成功:服务端启动,监听地址 {serverIp}:{serverPort}", Color.Green);
​// 异步循环监听客户端(独立任务,不阻塞UI)_ = Task.Run(async () =>{while (!cts.Token.IsCancellationRequested){if (_listener.Pending()) // 检查是否有等待连接的客户端(非阻塞){TcpClient client = await _listener.AcceptTcpClientAsync(); // 接收客户端连接string clientEndPoint = client.Client.RemoteEndPoint.ToString();AddServerLog($"连接:新客户端接入 - {clientEndPoint}");// 为每个客户端启动独立数据接收任务_ = ReceiveClientDataAsync(client, cts.Token);}else{await Task.Delay(100, cts.Token); // 无连接时短暂等待,减少CPU占用}}}, cts.Token);}catch (SocketException ex){if (ex.ErrorCode == 10048)throw new Exception($"端口 {9999} 已被占用,请关闭其他占用程序或更换端口");throw new Exception($"服务端启动失败:{ex.Message}");}
}

模块 3:异步接收单个客户端数据

功能逻辑
  • 为每个客户端创建独立的 NetworkStream 用于数据读写

  • 循环读取客户端数据(通过 DataAvailable 避免无效等待)

  • 读取到 0 字节表示客户端主动断开,触发资源释放

  • 捕获任务取消异常(服务端停止)和通信异常,分别处理

  • 最终通过 finally 块确保流和客户端连接资源释放

关键代码
private async Task ReceiveClientDataAsync(TcpClient client, CancellationToken token)
{string clientEndPoint = client.Client.RemoteEndPoint.ToString();NetworkStream stream = null;byte[] buffer = new byte[4096]; // 4KB固定缓冲区,适配多数场景
​try{stream = client.GetStream();while (!token.IsCancellationRequested && client.Connected){if (!stream.DataAvailable){await Task.Delay(50, token);continue;}
​int readCount = await stream.ReadAsync(buffer, 0, buffer.Length, token);if (readCount == 0) // 客户端主动断开{AddServerLog($"断开:客户端主动断开 - {clientEndPoint}");break;}
​// 解析数据(UTF8编码,需与客户端一致)string receiveData = Encoding.UTF8.GetString(buffer, 0, readCount);AddServerLog($"接收:来自 {clientEndPoint} 的数据 - {receiveData}", Color.Blue);Array.Clear(buffer, 0, buffer.Length); // 清空缓冲区,避免残留数据}}catch (OperationCanceledException){AddServerLog($"停止:客户端 {clientEndPoint} 接收任务已取消");}catch (Exception ex){AddServerLog($"错误:与客户端 {clientEndPoint} 通信异常 - {ex.Message}", Color.Red);}finally{stream?.Dispose(); // 释放流资源client?.Dispose(); // 释放客户端连接AddServerLog($"清理:客户端 {clientEndPoint} 连接已释放");}
}

模块 4:异步停止服务 + 资源释放

功能逻辑
  1. 取消后台任务(通过 cts.Cancel() 通知所有关联任务终止)

  2. 释放取消令牌源、TcpListener 资源

  3. 标记服务为停止状态,记录成功日志

  4. 短暂延迟(100ms)确保资源释放完成

关键代码
private async Task StopServerAsync()
{try{// 取消并释放取消令牌if (cts != null){cts.Cancel();cts.Dispose();cts = null;}
​// 停止监听并释放if (_listener != null){_listener.Stop();_listener = null;}
​_isServerRunning = false;AddServerLog("成功:服务端已停止", Color.Green);}catch (Exception ex){throw new Exception($"服务端停止失败:{ex.Message}");}await Task.Delay(100); // 确保资源释放完成
}

模块 5:线程安全的日志更新(核心优化点)

解决的问题
  • Windows Forms 控件只能由 UI 线程修改,后台任务(如数据接收)直接更新日志会报跨线程错误

  • 避免使用默认参数(Color 非编译时常量),通过方法重载实现默认日志颜色

核心设计(方法重载)
  1. 无参数重载:默认使用灰色日志,内部调用带颜色的重载

  2. 带颜色重载:检查是否跨线程,通过 BeginInvoke 异步委托到 UI 线程

  3. WriteLogToUI:实际写入日志(拼接时间戳、设置颜色、自动滚动到最新行)

关键代码
// 重载1:无颜色参数(默认灰色)
private void AddServerLog(string content)
{AddServerLog(content, Color.Gray);
}
​
// 重载2:带颜色参数(显式指定颜色)
private void AddServerLog(string content, Color color)
{if (richTextBox2.InvokeRequired) // 检查是否跨线程{richTextBox2.BeginInvoke(new Action(() =>{WriteLogToUI(content, color);}));}else{WriteLogToUI(content, color);}
}
​
// 实际写入UI(仅UI线程调用)
private void WriteLogToUI(string content, Color color)
{string log = $"[{DateTime.Now:yyyy-MM-dd HH:mm:ss}] {content}{Environment.NewLine}";richTextBox2.SelectionColor = color;richTextBox2.AppendText(log);richTextBox2.ScrollToCaret(); // 自动滚动到最新日志
}

模块 6:窗体关闭时资源释放(防内存泄漏)

功能逻辑
  • 窗体关闭前检查服务是否运行,若运行则先停止服务(避免强制关闭导致资源泄漏)

  • 通过 e.Cancel = true 取消当前关闭操作,待服务停止后重新关闭窗体

关键代码(注意:原事件名 Form1_FormClosing 需改为 Form3_FormClosing,与窗体名匹配)
private async void Form3_FormClosing(object sender, FormClosingEventArgs e)
{if (_isServerRunning){e.Cancel = true; // 取消当前关闭,先停止服务await StopServerAsync();this.Close(); // 服务停止后重新关闭窗体}
}

模块 7:附加功能(打开 Form4 子窗体)

功能逻辑
  • 通过 button2 点击事件打开 Form4,使用 Show() 非模态方式

  • 打开后仍可操作 Form3(如停止服务、查看日志),不阻塞服务端核心功能

关键代码
private void button2_Click(object sender, EventArgs e)
{Form4 form4 = new Form4();form4.Show(); // 非模态打开,不阻塞Form3
}

四、关键注意事项

  1. 端口占用问题:若启动服务时报 “10048 错误”,需关闭占用 9999 端口的程序,或修改 serverPort 为其他未占用端口

  2. 编码一致性:服务端用 Encoding.UTF8 解析数据,客户端需保持相同编码,否则会出现乱码

  3. 多客户端支持:每个客户端连接会启动独立的 ReceiveClientDataAsync 任务,任务间通过缓冲区隔离,互不干扰

  4. 任务取消机制:服务停止时通过 cts.Cancel() 终止所有后台任务,避免任务残留导致内存泄漏

  5. 控件名匹配:确保日志控件 NamerichTextBox2,按钮 Namebutton1/button2,否则会报空引用错误

五、功能测试流程

  1. 运行程序,打开 Form3

  2. 点击 “启动服务端”,日志显示 “服务端启动,监听地址 127.0.0.1:9999”(绿色)

  3. 启动 TCP 客户端(如之前的 Form2/Form4),连接 127.0.0.1:9999,Form3 日志显示 “新客户端接入”

  4. 客户端发送数据,Form3 日志显示 “接收来自 XXX 的数据”(蓝色)

  5. 点击 “停止服务端”,日志显示 “服务端已停止”(绿色),所有客户端连接被释放

  6. 点击 button2 可打开 Form4,同时可移动 / 操作 Form3,无阻塞

TCP 客户端 Form4 代码笔记(基于 C# Windows Forms)

一、窗体核心功能定位

Form4 是一个独立的 TCP 客户端窗体,主要功能包括:

  • 与 TCP 服务端(如 Form3)建立连接和断开连接

  • 向服务端发送数据并接收服务端响应

  • 线程安全的日志记录(区分不同状态的信息)

  • 自动处理网络异常和资源释放

  • 完全独立运行,不依赖其他窗体

二、核心变量定义

变量名类型作用说明
_tcpClientTcpClientTCP 客户端实例,负责与服务端建立连接
_clientCtsCancellationTokenSource取消令牌源,用于控制接收响应的后台任务
_clientStreamNetworkStream网络流,作为数据读写的通道
_isClientConnectedbool连接状态标记,避免重复操作

三、核心功能模块解析

1. 初始化配置(构造函数)

public Form4()
{InitializeComponent();// 窗体基础配置this.Text = "TCP客户端(Form2)";// 日志控件配置richTextBox1.ReadOnly = true;richTextBox1.ScrollBars = RichTextBoxScrollBars.Vertical;richTextBox1.WordWrap = true;// 未连接时禁用发送按钮button2.Enabled = false;// 绑定窗体关闭事件this.FormClosing += Form2_FormClosing;
}

2. 连接 / 断开服务端功能

连接 / 断开按钮点击事件
private async void button1_ClickAsync(object sender, EventArgs e)
{button1.Enabled = false; // 禁用按钮防止并发操作try{if (!_isClientConnected){// 连接服务端await ConnectToServerAsync();button1.Text = "断开服务端";button2.Enabled = true;AddClientLog("操作:开始连接服务端...");}else{// 断开服务端await DisconnectFromServerAsync();button1.Text = "连接服务端";button2.Enabled = false;AddClientLog("操作:开始断开服务端...");}}catch (SocketException ex){// 处理网络异常(使用switch-case兼容C# 7.3)string errorMsg;switch (ex.ErrorCode){case 10060:errorMsg = "连接超时:服务端未响应";break;case 10061:errorMsg = "连接被拒绝:服务端拒绝连接";break;case 10049:errorMsg = "IP地址无效:请确认IP格式正确";break;default:errorMsg = $"网络错误:{ex.Message}";break;}AddClientLog(errorMsg, Color.Red);// 恢复按钮状态button1.Text = _isClientConnected ? "断开服务端" : "连接服务端";}catch (Exception ex){AddClientLog($"操作失败:{ex.Message}", Color.Red);}finally{button1.Enabled = true; // 恢复按钮可用性}
}
异步连接服务端
private async Task ConnectToServerAsync()
{try{// 配置服务端地址和端口IPAddress serverIp = IPAddress.Parse("127.0.0.1");int serverPort = 9999;_tcpClient = new TcpClient();// 设置10秒连接超时var connectTask = _tcpClient.ConnectAsync(serverIp, serverPort);var timeoutTask = Task.Delay(10000);var completedTask = await Task.WhenAny(connectTask, timeoutTask);if (completedTask == timeoutTask){_tcpClient.Dispose();throw new TimeoutException("连接服务端超时(10秒)");}// 连接成功后的初始化_clientStream = _tcpClient.GetStream();_clientCts = new CancellationTokenSource();_isClientConnected = true;AddClientLog($"成功:已连接到服务端 {serverIp}:{serverPort}", Color.Green);// 启动接收响应的后台任务_ = ReceiveServerResponseAsync(_clientCts.Token);}catch (Exception ex){_tcpClient?.Dispose();throw new Exception($"连接服务端失败:{ex.Message}");}
}
异步断开服务端
private async Task DisconnectFromServerAsync()
{try{// 取消接收任务if (_clientCts != null){_clientCts.Cancel();_clientCts.Dispose();_clientCts = null;}// 释放流资源if (_clientStream != null){_clientStream.Dispose();_clientStream = null;}// 释放客户端连接if (_tcpClient != null){_tcpClient.Dispose();_tcpClient = null;}_isClientConnected = false;AddClientLog("成功:已断开与服务端的连接", Color.Green);}catch (Exception ex){throw new Exception($"断开服务端失败:{ex.Message}");}await Task.Delay(100); // 确保资源释放完成
}

3. 发送数据与接收响应功能

发送数据按钮点击事件
private async void button2_ClickAsync(object sender, EventArgs e)
{string sendData = textBox1.Text.Trim();if (string.IsNullOrEmpty(sendData)){MessageBox.Show("请输入要发送的数据!", "输入为空", MessageBoxButtons.OK, MessageBoxIcon.Warning);return;}if (!_isClientConnected || _tcpClient == null || !_tcpClient.Connected || _clientStream == null){MessageBox.Show("未连接到服务端,请先点击「连接服务端」按钮!", "连接失效", MessageBoxButtons.OK, MessageBoxIcon.Warning);return;}button2.Enabled = false;try{byte[] sendBuffer = Encoding.UTF8.GetBytes(sendData);lock (_clientStream){if (!_clientStream.CanWrite){throw new InvalidOperationException("网络流不可写,连接可能已断开");}_clientStream.WriteAsync(sendBuffer, 0, sendBuffer.Length);}string localEndPoint = _tcpClient.Client.LocalEndPoint.ToString();AddClientLog($"发送:我({localEndPoint})→ 服务端:{sendData}", Color.Blue);textBox1.Clear();}catch (Exception ex){AddClientLog($"发送失败:{ex.Message}", Color.Red);await DisconnectFromServerAsync();button1.Text = "连接服务端";button2.Enabled = false;}finally{button2.Enabled = true;}
}
异步接收服务端响应
private async Task ReceiveServerResponseAsync(CancellationToken token)
{byte[] receiveBuffer = new byte[4096];try{while (!token.IsCancellationRequested){// 检查连接状态if (!_isClientConnected || _tcpClient == null || !_tcpClient.Connected || _clientStream == null || !_clientStream.CanRead){AddClientLog("接收停止:连接已断开或流不可读", Color.Orange);break;}// 检查是否有可读取的数据if (!_clientStream.DataAvailable){await Task.Delay(100, token);continue;}// 读取数据int readCount = await _clientStream.ReadAsync(receiveBuffer, 0, receiveBuffer.Length, token);if (readCount == 0){AddClientLog("接收提示:服务端已主动断开连接", Color.Orange);await DisconnectFromServerAsync();button1.Text = "连接服务端";button2.Enabled = false;break;}// 解析数据byte[] validData = new byte[readCount];Array.Copy(receiveBuffer, validData, readCount);string responseData = Encoding.UTF8.GetString(validData);string serverEndPoint = _tcpClient.Client.RemoteEndPoint.ToString();AddClientLog($"接收:服务端({serverEndPoint})→ 我:{responseData}", Color.Purple);// 清空缓冲区Array.Clear(receiveBuffer, 0, receiveBuffer.Length);}}catch (OperationCanceledException){AddClientLog("接收任务:已主动取消", Color.Gray);}catch (Exception ex){AddClientLog($"接收异常:{ex.Message}", Color.Red);await DisconnectFromServerAsync();button1.Text = "连接服务端";button2.Enabled = false;}
}

4. 线程安全的日志更新

// 日志添加方法重载
private void AddClientLog(string content)
{AddClientLog(content, Color.Gray);
}private void AddClientLog(string content, Color color)
{if (richTextBox1.InvokeRequired){// 跨线程时异步委托到UI线程richTextBox1.BeginInvoke(new Action(() =>{WriteLogToUI(content, color);}));}else{WriteLogToUI(content, color);}
}// 实际写入日志到UI
private void WriteLogToUI(string content, Color color)
{richTextBox1.SelectionColor = color;string logWithTime = $"[{DateTime.Now:yyyy-MM-dd HH:mm:ss}] {content}{Environment.NewLine}";richTextBox1.AppendText(logWithTime);richTextBox1.ScrollToCaret(); // 自动滚动到最新日志
}

5. 窗体关闭时的资源清理

private async void Form2_FormClosing(object sender, FormClosingEventArgs e)
{if (_isClientConnected){e.Cancel = true; // 取消当前关闭操作await DisconnectFromServerAsync(); // 先断开连接this.Close(); // 资源释放后再关闭}
}

四、关键技术点说明

  1. 异步操作:所有网络操作都使用异步方法(如ConnectAsyncReadAsyncWriteAsync),避免阻塞 UI 线程,保证界面响应性。

  2. 任务取消机制:使用CancellationTokenSource控制后台接收任务,确保服务端断开时能正确终止接收线程。

  3. 连接超时处理:通过Task.WhenAny组合连接任务和延迟任务,实现 10 秒连接超时控制。

  4. 线程安全的 UI 更新:使用BeginInvoke确保所有 UI 操作在 UI 线程执行,避免跨线程操作异常。

  5. 资源释放:在finally块和窗体关闭事件中仔细释放所有网络资源(TcpClientNetworkStream等),避免内存泄漏。

  6. 异常处理:针对不同的网络异常(如连接超时、被拒绝、地址无效等)提供明确的错误提示。

  7. 状态管理:使用_isClientConnected标记连接状态,避免重复连接或断开操作,确保 UI 状态与实际状态一致。

五、使用流程

  1. 确保服务端(Form3)已启动并监听 9999 端口

  2. 在 Form4 中点击 "连接服务端" 按钮,连接到服务端

  3. 在文本框中输入要发送的数据,点击 "发送" 按钮

  4. 接收服务端响应会显示在日志区域

  5. 完成后点击 "断开服务端" 按钮,或直接关闭窗体(会自动断开连接)

六、注意事项

  1. 服务端 IP 和端口需与 Form3 保持一致(127.0.0.1:9999)

  2. 编码格式使用 UTF8,需与服务端保持一致

  3. 发送数据前必须先建立连接

  4. 若端口被占用,需修改端口号并确保服务端和客户端使用相同端口

  5. 网络异常时客户端会自动断开连接并更新 UI 状态

http://www.dtcms.com/a/438777.html

相关文章:

  • 成都网络优化公司排行榜网站的优化是什么
  • 山西网站建设多少钱怎么做旅游网站
  • JAVA第八学:继承和多态
  • 网站开发前端指什么太原本地网站搭建公司
  • FastAPI 路径操作依赖项
  • wordpress开发网站美业营销策划公司
  • 《强化学习数学原理》学习笔记5——压缩映射定理的证明
  • Mysql速成笔记2(DML)
  • 网站流量如何增加东莞服务
  • pv-pvc-sc存储卷进阶-sts-helm资源清单基础管理
  • 什么是网站站点建设介绍网上营销新观察网
  • 吃透大数据算法-字典编码(Dictionary Encoding)
  • 从pty驱动学习tty设备驱动加载
  • 车牌号黑名单校验功能实现说明
  • 【第五章:计算机视觉-项目实战之生成对抗网络实战】2.基于SRGAN的图像超分辨率实战-(1)实战1:人脸表情生成实战任务详解
  • 【双指针专题】之快乐数
  • 锦州滨海新区城市建设规划网站建设局是个好单位吗
  • 域名搭建网站域名一般在哪里购买
  • 拦截器Interceptor
  • 运营网站流程ui设计的定义
  • 可以做自己的单机网站网站客户案例的
  • 网络编程中UDP协议的广播和组播通信
  • STM32G474单片机开发入门(一)STM32G474RET6单片机详解
  • W3C 简介
  • 菲律宾宿务Cebu(宿雾)介绍
  • Python中如何实现多级缓存
  • 深入掌握 FluentMigrator:C#.NET 数据库迁移框架详解
  • 快速做网站套餐光谷网站建设哪家好
  • 基本定时器(TIM6、TIM7)的基本介绍
  • 荆州网站建设兼职旅游网站功能简介