C# TCP 客户端开发笔记(TcpClient)

一、整体功能概述

该代码基于 C# Windows Forms 框架,实现了一个 TCP 客户端程序,用于与 TCP 服务端进行通信,核心功能包括:

  • 连接 / 断开 TCP 服务端

  • 向服务端发送文本数据

  • 接收服务端返回的数据并在界面显示

  • 维护连接状态并提供用户交互反馈

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

1. 关键命名空间

  • System.Net:提供 IP 地址(IPAddress)和网络端点(IPEndPoint)支持

  • System.Net.Sockets:提供 TCP 客户端核心类(TcpClientNetworkStream

  • System.Threading.Tasks:支持异步操作,避免 UI 线程阻塞

  • System.Windows.Forms:提供 Windows 图形界面组件

  • System.Text:提供字符串与字节数组的编码转换

2. 核心组件说明

  • TcpClient:TCP 客户端核心类,负责与服务端建立连接和管理通信

  • IPEndPoint:表示网络端点(IP 地址 + 端口),用于标识客户端和服务端

  • NetworkStream :基于TcpClient的数据流,用于实际的数据传输

  • CancellationTokenSource:用于取消异步任务,特别是在断开连接时

  • Invoke(Action):确保在 UI 线程更新界面控件,避免跨线程操作异常

三、代码结构拆解

1. 全局变量定义

复制代码
// TCP客户端对象
TcpClient tcpClient = null;
// 服务端网络端点(IP+端口)
IPEndPoint remoteEP = null;
// 任务取消令牌源
CancellationTokenSource cts = null;

2. 构造函数

复制代码
public Form1()
{
    InitializeComponent(); // 初始化Windows Forms控件
}
  • 说明:默认构造函数,仅负责初始化界面控件

3. 核心功能方法详解

(1)连接 / 断开按钮点击事件
复制代码
private async void button1_Click(object sender, EventArgs e)
{
    try
    {
        // 根据按钮文本判断执行连接或断开操作
        if (button1.Text == "连接")
        {
            await ConnectServer(); // 连接服务器
            AcceptResponse(); // 开始接收服务器响应
        }
        else
        {
            DisConnectServer(); // 断开连接
        }
    }
    catch (Exception)
    {
        throw;
    }
}
  • 逻辑:通过按钮文本状态切换连接状态,使用async/await处理异步操作
(2)连接服务器(ConnectServer
复制代码
private async Task ConnectServer()
{
    // 创建本地端点(任意本地IP,系统自动分配端口)
    IPEndPoint LocalEP = new IPEndPoint(IPAddress.Any, 0);
    
    // 定义服务端端点(IP和端口)
    remoteEP = new IPEndPoint(IPAddress.Parse("172.16.0.28"), 9999);
    
    // 初始化TcpClient并绑定本地端点
    tcpClient = new TcpClient(LocalEP);
    
    // 异步连接到服务端
    await tcpClient.ConnectAsync(IPAddress.Parse("172.16.0.28"), 9999);
    
    // 更新按钮文本为"中断",表示已连接
    button1.Text = "中断";
}
  • 关键点:

    • IPAddress.Any:表示使用本地任意可用 IP 地址

    • 端口 0:表示让系统自动分配可用端口

    • ConnectAsync:异步连接方法,不会阻塞 UI 线程

    • 服务端 IP 和端口硬编码,实际应用中应改为可配置

(3)断开连接(DisConnectServer
复制代码
private void DisConnectServer()
{
    // 取消接收响应的任务
    cts?.Cancel();
    
    // 关闭TCP客户端连接
    tcpClient?.Close();
    
    // 更新按钮文本为"连接",表示已断开
    button1.Text = "连接";
}
  • 资源释放逻辑:先取消任务,再关闭连接,确保资源正确释放
(4)接收服务器响应(AcceptResponse
复制代码
private void AcceptResponse()
{
    // 初始化取消令牌源
    cts = new CancellationTokenSource();
    
    // 启动异步任务接收数据
    Task.Run(async () =>
    {
        while (!cts.IsCancellationRequested)
        {
            try
            {
                // 检查客户端是否连接且有可用数据
                if (!tcpClient.Connected || tcpClient.Available == 0) continue;
                
                // 获取网络流
                NetworkStream stream = tcpClient.GetStream();
                
                // 创建缓冲区(大小为可用数据量)
                byte[] buffer = new byte[tcpClient.Available];
                
                // 异步读取数据
                int count = await stream.ReadAsync(buffer, 0, buffer.Length);
                if (count == 0) continue;
                
                // 线程安全地更新UI
                Invoke(new Action(() =>
                {
                    string data = Encoding.UTF8.GetString(buffer);
                    richTextBox1.Text += $"{tcpClient.Client.RemoteEndPoint},{data}" + Environment.NewLine;
                }));
            }
            catch (Exception)
            {
                throw;
            }
        }
    }, cts.Token);
}
  • 核心逻辑:

    • 使用Task.Run在后台线程接收数据,避免阻塞 UI

    • tcpClient.Available:获取可读取的数据长度

    • ReadAsync:异步读取数据,非阻塞操作

    • Invoke:确保在 UI 线程更新richTextBox1控件

(5)发送数据按钮点击事件(button2_Click
复制代码
private void button2_Click(object sender, EventArgs e)
{
    // 检查客户端是否已连接
    if (tcpClient != null && tcpClient.Connected)
    {
        // 获取网络流
        NetworkStream stream = tcpClient.GetStream();
        
        // 获取输入框文本并转换为字节数组
        string msg = textBox1.Text;
        byte[] buffer = Encoding.UTF8.GetBytes(msg);
        
        // 发送数据
        stream.Write(buffer, 0, buffer.Length);
        
        // 在界面显示已发送的数据
        richTextBox1.Text = $"我是{tcpClient.Client.LocalEndPoint}{msg}" + Environment.NewLine;
    }
    else
    {
        // 未连接时提示用户
        MessageBox.Show("先连接服务器,再发送");
        return;
    }
}
  • 发送流程:

    1. 检查连接状态

    2. 获取输入文本

    3. 转换为字节数组(UTF8 编码)

    4. 通过网络流发送

    5. 在界面显示发送记录

四、界面控件与交互逻辑

  • button1:连接 / 断开按钮,文本状态表示当前连接状态

  • button2:发送按钮,点击时发送textBox1中的内容

  • textBox1:输入框,用于输入要发送的文本

  • richTextBox1:显示区域,展示发送和接收的消息

五、潜在问题与优化建议

1. 现有代码问题

  • 硬编码服务端地址:服务端 IP 和端口硬编码在代码中,不便于修改

  • 异常处理不完善:多数异常仅抛出未做处理,可能导致程序崩溃

  • 无连接状态实时检测:服务端异常断开时,客户端不能及时感知

  • 发送数据未使用异步stream.Write是同步方法,大数据量时可能卡顿 UI

  • 接收缓冲区设计 :依赖tcpClient.Available创建缓冲区,不适合处理大数据

2. 优化建议

  • 将服务端地址改为可配置:添加两个输入框让用户输入 IP 和端口

  • 完善异常处理:

    复制代码
    try
    {
        await tcpClient.ConnectAsync(ip, port);
    }
    catch (SocketException ex)
    {
        MessageBox.Show($"连接失败: {ex.Message}");
        return;
    }
  • 添加连接心跳检测:定期发送心跳包检测连接状态

  • 使用异步发送数据:

    复制代码
    await stream.WriteAsync(buffer, 0, buffer.Length);
  • 使用固定大小缓冲区:

    复制代码
    byte[] buffer = new byte[1024]; // 固定大小缓冲区
    int bytesRead;
    while ((bytesRead = await stream.ReadAsync(buffer, 0, buffer.Length)) > 0)
    {
        // 处理读取的数据
    }
  • 添加 Form 关闭时的资源释放:

    复制代码
    private void Form1_FormClosing(object sender, FormClosingEventArgs e)
    {
        DisConnectServer();
    }

六、使用与测试说明

  1. 确保服务端已启动并监听指定 IP 和端口

  2. 客户端点击 "连接" 按钮建立连接

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

  4. 接收的服务端响应会显示在下方文本区域

  5. 点击 "中断" 按钮断开与服务端的连接

该客户端与之前的服务端代码配合使用,可以实现基本的 TCP 通信功能,适合作为 C# 网络编程的入门示例。

相关推荐
不太可爱的叶某人7 小时前
【学习笔记】kafka权威指南——第6章 可靠的数据传递
笔记·学习·kafka
张人玉7 小时前
C# 通讯关键类的API
开发语言·c#
研猛男9 小时前
0、FreeRTOS编码和命名规则
笔记·stm32·freertos
能不能别报错10 小时前
K8s学习笔记(十六) 探针(Probe)
笔记·学习·kubernetes
初圣魔门首席弟子10 小时前
C++ STL 向量(vector)学习笔记:从基础到实战
c++·笔记·学习
iconball10 小时前
个人用云计算学习笔记 --20 (Nginx 服务器)
linux·运维·笔记·学习·云计算
生物小卡拉11 小时前
R脚本--表达矩阵与特征矩阵相关性分析
笔记·学习·机器学习
能不能别报错11 小时前
K8s学习笔记(十四) DaemonSet
笔记·学习·kubernetes
报错小能手11 小时前
linux学习笔记(19)进程间通讯——消息队列
linux·笔记·学习