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

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

一、整体功能概述

该代码基于 C# Windows Forms 框架,实现了一个基础的 TCP 服务端程序,核心功能包括:

  • 启动 / 停止 TCP 服务

  • 监听并接收多个客户端连接

  • 接收客户端发送的数据并在界面显示

  • (注释中预留)与串口设备交互并转发数据给客户端的扩展能力

  • (注释中预留)将接收到的客户端数据原封不动返回给客户端的回声功能

二、核心技术点与类库依赖

1. 关键命名空间

命名空间核心用途
System.Net提供 IP 地址(IPAddress)、网络端点(IPEndPoint)等基础网络类
System.Net.Sockets提供 TCP 通信核心类(TcpListenerTcpClientNetworkStream
System.Threading/System.Threading.Tasks实现多线程与异步任务,避免 UI 线程阻塞
System.Windows.Forms提供 Windows 图形界面控件(Form、Button、RichTextBox 等)
System.Text提供字符串与字节数组的编码转换(Encoding.UTF8

2. 核心组件说明

  • TcpListener:TCP 服务端监听组件,负责绑定 IP 和端口、监听客户端连接请求

  • TcpClient:表示与客户端的连接会话,每个客户端对应一个TcpClient实例

  • NetworkStream:基于TcpClient的数据流对象,用于实际的字节数据读写

  • CancellationTokenSource:用于控制异步任务的取消(如停止服务时终止监听和数据接收任务)

  • Invoke(Action):Windows Forms 线程安全调用,用于在非 UI 线程中更新 UI 控件

三、代码结构拆解

1. 全局变量定义

// TCP服务端监听对象
TcpListener tcpListener = null;
// 任务取消令牌源(控制异步任务停止)
CancellationTokenSource cts = null;
  • 作用:在整个 Form 生命周期内维护服务端监听状态和任务取消控制,避免局部变量被回收导致功能异常

2. 构造函数

public Form1()
{InitializeComponent(); // 初始化Windows Forms控件(自动生成)
}
  • 说明:默认构造函数,仅负责加载 Form 界面控件,无自定义逻辑

3. 核心功能方法详解

(1)启动服务按钮点击事件(入口方法)
private void button1_Click(object sender, EventArgs e)
{try{// 根据按钮文本判断执行"启动"或"停止"逻辑if (button1.Text == "启动"){StartServer(); // 启动服务端AccepRequest(); // 开始接收客户端连接}else{StopServer(); // 停止服务端}}catch (Exception ex){MessageBox.Show(ex.Message); // 捕获并显示异常信息}
}
  • 逻辑流程:通过按钮文本状态切换服务端启停,统一异常捕获避免程序崩溃

(2)启动服务端(StartServer
private void StartServer()
{try{// 1. 解析服务端IP地址(需替换为实际本地IP)IPAddress ip = IPAddress.Parse("172.16.0.28");// 2. 定义服务端端口(建议选择1024以上非知名端口)int port = 9999;// 3. 创建TcpListener实例并绑定IP和端口tcpListener = new TcpListener(ip, port);// 4. 启动监听(底层执行Socket.Bind和Socket.Listen)tcpListener.Start();// 5. 更新按钮文本为"关闭",提示服务已启动button1.Text = "关闭";}catch (Exception ex){throw ex; // 抛出异常,由上层按钮事件捕获处理}
}
  • 关键注意点:

    • IP 地址需为本地网卡实际 IP(如192.168.1.100),127.0.0.1仅本地调试可用

    • 端口需确保未被其他程序占用(可通过netstat -ano命令查看端口占用)

    • 若需监听所有网卡,可使用IPAddress.Any(如new TcpListener(IPAddress.Any, 9999)

(3)停止服务端(StopServer
private void StopServer()
{// 1. 取消所有异步任务(通过令牌源通知任务停止)cts?.Cancel();// 2. 停止TcpListener监听,释放端口资源tcpListener.Stop();// 3. 还原按钮文本为"启动",提示服务已停止button1.Text = "启动";
}
  • 资源释放逻辑:先终止任务再停止监听,避免任务在监听停止后仍尝试操作导致异常

(4)接收客户端连接(AccepRequest
private void AccepRequest()
{// 1. 初始化任务取消令牌源cts = new CancellationTokenSource();// 2. 启动异步任务(避免阻塞UI线程)Task.Run(async () =>{// 循环监听客户端连接(直到任务被取消)while (!cts.IsCancellationRequested){// 判断是否有客户端连接请求,无则跳过(非阻塞判断)if (!tcpListener.Pending()) continue;// 异步接收客户端连接(获取TcpClient实例)TcpClient tcpClient = await tcpListener.AcceptTcpClientAsync();// 为该客户端启动数据接收逻辑AccepData(tcpClient);}}, cts.Token); // 传入取消令牌,支持任务取消
}
  • 核心逻辑:

    • Task.Run开启后台任务,避免 UI 线程(如 Form)卡住

    • tcpListener.Pending():非阻塞判断是否有连接请求,替代同步等待(AcceptTcpClient()

    • 每个客户端连接对应一个TcpClient实例,通过AccepData单独处理数据交互

(5)接收客户端数据(AccepData
private void AccepData(TcpClient tcpClient)
{// 启动异步任务处理该客户端的数据接收Task.Run(async () =>{// 循环接收数据(直到任务取消或客户端断开)while (!cts.IsCancellationRequested){// 判断客户端是否连接且有可用数据,无则跳过if (!tcpClient.Connected || tcpClient.Available == 0) continue;// 1. 获取客户端数据流(基于TcpClient)NetworkStream stream = tcpClient.GetStream();// 2. 创建缓冲区(大小=客户端待接收数据量)byte[] buffer = new byte[tcpClient.Available];// 3. 异步读取数据(返回实际读取的字节数)int count = await stream.ReadAsync(buffer, 0, buffer.Length);// 4. 若读取字节数为0,说明客户端断开连接,跳过后续处理if (count == 0) continue;// 5. 线程安全更新UI(显示客户端IP、端口和数据)Invoke(new Action(() =>{// 将字节数组转换为UTF8字符串string data = Encoding.UTF8.GetString(buffer);// 获取客户端端点信息(IP:Port)string clientEndPoint = tcpClient.Client.RemoteEndPoint.ToString();// 在RichTextBox中追加数据richTextBox1.Text += $"{clientEndPoint},{data}" + Environment.NewLine;}));// 【扩展预留】与串口设备交互逻辑// - 发送数据到串口设备// - 接收串口设备响应// - 将响应转发给客户端// 【回声功能预留】将接收到的数据原封不动返回给客户端// stream.Write(buffer, 0, buffer.Length);}}, cts.Token);
}
  • 关键技术点:

    • 线程安全 UI 更新Invoke(Action)确保在 UI 线程更新RichTextBox,避免跨线程操作异常

    • 数据读取逻辑:

      • tcpClient.Available:获取客户端待接收数据长度,避免缓冲区浪费

      • stream.ReadAsync:异步读取数据,不阻塞当前任务

      • 读取字节数count == 0:TCP 协议中表示客户端正常断开连接

    • 客户端标识:通过tcpClient.Client.RemoteEndPoint获取客户端 IP 和端口,便于区分多客户端

四、关键问题与优化建议

1. 现有代码潜在问题

  • 硬编码 IP:IP 地址172.16.0.28硬编码,更换环境需修改代码,建议改为配置项或下拉选择

  • 无异常重试:服务启动失败(如端口占用)仅抛出异常,无重试机制

  • 客户端断开处理:未主动检测客户端断开(仅通过count == 0判断),长时间无数据时可能残留无效TcpClient实例

  • 缓冲区大小:依赖tcpClient.Available定义缓冲区,若数据量大可能导致内存占用过高,建议使用固定大小缓冲区(如 1024 字节)循环读取

  • 资源释放:未在Form关闭时释放TcpListenerCancellationTokenSource,可能导致资源泄漏

2. 优化方向

  1. IP 配置优化:添加TextBox让用户输入 IP 和端口,替代硬编码

  2. 异常处理增强:

    // 启动服务时增加端口占用检测
    try
    {tcpListener.Start();
    }
    catch (SocketException ex) when (ex.ErrorCode == 10048)
    {throw new Exception("端口已被占用,请更换端口后重试");
    }
  3. 客户端管理:维护List<TcpClient>集合,跟踪所有连接的客户端,在服务停止时主动关闭所有客户端

  4. 固定缓冲区读取:

    byte[] buffer = new byte[1024]; // 固定1024字节缓冲区
    int totalCount = 0;
    while (stream.DataAvailable)
    {int count = await stream.ReadAsync(buffer, totalCount, buffer.Length - totalCount);totalCount += count;if (totalCount == buffer.Length){// 缓冲区满,可扩展缓冲区或处理数据break;}
    }
  5. Form 关闭资源释放:

    private void Form1_FormClosing(object sender, FormClosingEventArgs e)
    {// 停止服务并释放资源StopServer();cts?.Dispose();tcpListener?.Stop();
    }

五、扩展场景说明

1. 串口设备交互(预留逻辑实现)

// 1. 假设已初始化SerialPort(需配置端口、波特率等)
SerialPort serialPort = new SerialPort("COM3", 9600, Parity.None, 8, StopBits.One);
// 2. 发送数据到串口设备
serialPort.Write(buffer, 0, buffer.Length);
// 3. 接收串口设备响应(需处理串口数据接收事件)
byte[] responseBuffer = new byte[serialPort.BytesToRead];
serialPort.Read(responseBuffer, 0, responseBuffer.Length);
// 4. 将响应转发给客户端
NetworkStream stream = tcpClient.GetStream();
await stream.WriteAsync(responseBuffer, 0, responseBuffer.Length);

2. 多客户端并发处理

现有代码已通过Task.Run为每个客户端创建独立任务,支持多客户端并发连接,但需注意:

  • 若客户端数量极多(如数百个),需考虑任务数量控制,避免系统资源耗尽

  • 可使用线程池(ThreadPool.QueueUserWorkItem)替代Task.Run,更高效管理线程资源

六、调试与测试建议

  1. 本地调试:将服务端 IP 改为127.0.0.1,使用Telnet或 C# TCP 客户端测试(如TcpClient client = new TcpClient("127.0.0.1", 9999)

  2. 局域网测试:确保服务端和客户端在同一局域网,客户端使用服务端实际 IP(如192.168.1.100)连接

  3. 端口检测:若启动失败,通过cmd执行netstat -ano | findstr "9999"查看端口占用进程,结束占用进程后重试

  4. 数据格式验证:若客户端发送非 UTF8 编码数据,需统一编码格式(如Encoding.DefaultEncoding.ASCII

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

相关文章:

  • 180课时吃透Go语言游戏后端开发6:Go语言的循环语句
  • wordpress+vps建站关键词语有哪些
  • 网站建设基本标准野花高清中文免费观看视频
  • hadoop-hdfs
  • VB6.0找不到该引用word,excel“Microsoft Excel 16.0 Object Library”解决方法
  • 读者-写者问题实现真正的写优先
  • 北京人力资源网站县区网站集约化建设
  • 从零开始,用WPS和DeepSeek打造数字人科普视频
  • netgear r6220 路由器,刷openwrt后,系统备份还原
  • 特价流量网站什么情况自己建设网站
  • 昂瑞微IPO前瞻:技术破局高端射频模组,国产替代第二波浪潮下的硬科技突围
  • 开源 全平台 哔哩哔哩缓存视频合并 Github地址:https://github.com/molihuan/hlbmerge_flutter
  • EPOLLONESHOT事件类型:多线程I/O中的“一次触发“机制
  • Github卡顿问题解决方案
  • 智慧园区数字孪生建设方案(WORD)
  • GitHub 热榜项目 - 日榜(2025-10-03)
  • 【QT常用技术讲解】自定义支持多选项的下拉框
  • 网址注册了怎么做网站小说网站自主建设
  • 基于PyTorch实现的MNIST手写数字识别神经网络笔记
  • 基于STM32单片机智能手表手环GSM短信上报GPS定位校时
  • 平台开发多少钱seo专员是什么意思
  • DAY23 单例设计模式、多例设计模式、枚举、工厂设计模式、动态代理
  • 在云服务器搭建部署私人饥荒联机版游戏服务器 [2025.10.3][ubuntu 24.04][腾讯云2核2G服务器]
  • 使用Go做一个分布式短链系统
  • 北京专业做网站设计公司全国高校教师网络培训中心
  • 元萝卜 1.0.9 | 免root支持XP模块,一键微信平板模式,游戏增强,应用多开
  • Unity Time参数:Maximum Particle Timestep
  • 网站运营包括哪些内容爱用建站怎么样
  • Java JVM --- JVM内存区域划分,类加载,GC垃圾回收
  • 做网站卖广告位赚钱吗最火的自媒体平台排名