摘要

本文使用 C# WinForm 实现 TCP 服务端与客户端通信,采用自定义长度头协议解决 TCP 粘包、半包问题,实现多客户端连接、消息收发与广播,适合新手学习 Socket 网络编程。

标签

C# Socket TCP通信 WinForm 网络编程 粘包


正文

一、前言

在 C# 开发中,Socket 是实现网络通信的基础。TCP 协议是流式传输,会出现粘包、半包问题,直接收发数据会导致消息错乱。本文实现:

  1. WinForm 服务端:监听端口、接收多客户端、消息广播
  2. WinForm 客户端:连接服务端、发送接收消息
  3. 自定义协议:4 字节数据长度 + 消息内容,彻底解决粘包

二、核心原理:解决 TCP 粘包

TCP 是流式协议,操作系统会合并 / 拆分数据包。约定格式:[4字节int:消息长度] + [UTF-8消息内容]

  • 发送:先写入长度,再写内容
  • 接收:先读 4 字节长度,再读取对应字节内容

三、项目结构

  1. SocketCommon:公共类库(消息打包解包)
  2. SocketServer:WinForm 服务端
  3. SocketClient:WinForm 客户端

四、完整源码

1. 公共类 MessageBody.cs(两个项目都引用)

using System;
using System.Text;

namespace SocketCommon
{
    /// <summary>
    /// 消息封装类,处理粘包
    /// </summary>
    public class MessageBody
    {
        public string Content { get; set; }

        /// <summary>
        /// 编码:4字节长度 + 内容
        /// </summary>
        public byte[] Encode()
        {
            byte[] data = Encoding.UTF8.GetBytes(Content);
            byte[] buffer = new byte[4 + data.Length];
            BitConverter.GetBytes(data.Length).CopyTo(buffer, 0);
            data.CopyTo(buffer, 4);
            return buffer;
        }

        /// <summary>
        /// 解码
        /// </summary>
        public static string Decode(byte[] buffer)
        {
            int len = BitConverter.ToInt32(buffer, 0);
            return Encoding.UTF8.GetString(buffer, 4, len);
        }
    }
}

2. WinForm 服务端 SocketServer

界面控件:

  • ListBox:lstLog
  • Button:btnStart(启动服务)
using System;
using System.Collections.Generic;
using System.Net;
using System.Net.Sockets;
using System.Threading;
using System.Windows.Forms;
using SocketCommon;

namespace SocketServer
{
    public partial class Form1 : Form
    {
        private Socket _serverSocket;
        private readonly List<Socket> _clientList = new List<Socket>();

        public Form1()
        {
            InitializeComponent();
            CheckForIllegalCrossThreadCalls = false;
        }

        private void btnStart_Click(object sender, EventArgs e)
        {
            _serverSocket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
            IPEndPoint ipPoint = new IPEndPoint(IPAddress.Any, 8888);
            _serverSocket.Bind(ipPoint);
            _serverSocket.Listen(10);
            Log("服务端启动成功,监听端口:8888");

            new Thread(AcceptClient) { IsBackground = true }.Start();
        }

        /// <summary>
        /// 持续接收客户端连接
        /// </summary>
        private void AcceptClient()
        {
            while (true)
            {
                Socket client = _serverSocket.Accept();
                _clientList.Add(client);
                Log($"客户端已连接:{client.RemoteEndPoint}");
                new Thread(() => ReceiveData(client)) { IsBackground = true }.Start();
            }
        }

        /// <summary>
        /// 接收客户端消息
        /// </summary>
        private void ReceiveData(Socket client)
        {
            byte[] buffer = new byte[1024 * 1024];
            while (true)
            {
                try
                {
                    int len = client.Receive(buffer);
                    if (len <= 0) break;

                    string msg = MessageBody.Decode(buffer);
                    Log($"收到消息:{msg}");
                    Broadcast(client, msg);
                }
                catch
                {
                    _clientList.Remove(client);
                    Log($"客户端断开:{client.RemoteEndPoint}");
                    client.Close();
                    break;
                }
            }
        }

        /// <summary>
        /// 广播消息给所有在线客户端
        /// </summary>
        private void Broadcast(Socket sender, string msg)
        {
            foreach (var client in _clientList)
            {
                if (client != sender && client.Connected)
                {
                    byte[] data = new MessageBody { Content = msg }.Encode();
                    client.Send(data);
                }
            }
        }

        private void Log(string text)
        {
            lstLog.Items.Add($"{DateTime.Now:HH:mm:ss} {text}");
        }
    }
}

3. WinForm 客户端 SocketClient

界面控件:

  • ListBox:lstLog
  • TextBox:txtSend
  • Button:btnConnectbtnSend
using System;
using System.Net;
using System.Net.Sockets;
using System.Threading;
using System.Windows.Forms;
using SocketCommon;

namespace SocketClient
{
    public partial class Form1 : Form
    {
        private Socket _clientSocket;

        public Form1()
        {
            InitializeComponent();
            CheckForIllegalCrossThreadCalls = false;
        }

        private void btnConnect_Click(object sender, EventArgs e)
        {
            _clientSocket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
            _clientSocket.Connect(IPAddress.Parse("127.0.0.1"), 8888);
            Log("连接服务端成功");

            new Thread(ReceiveData) { IsBackground = true }.Start();
        }

        private void btnSend_Click(object sender, EventArgs e)
        {
            if (string.IsNullOrWhiteSpace(txtSend.Text)) return;
            byte[] data = new MessageBody { Content = txtSend.Text }.Encode();
            _clientSocket.Send(data);
            Log($"发送:{txtSend.Text}");
            txtSend.Clear();
        }

        private void ReceiveData()
        {
            byte[] buffer = new byte[1024 * 1024];
            while (true)
            {
                try
                {
                    int len = _clientSocket.Receive(buffer);
                    if (len <= 0) break;
                    string msg = MessageBody.Decode(buffer);
                    Log($"收到广播:{msg}");
                }
                catch
                {
                    Log("与服务端断开连接");
                    break;
                }
            }
        }

        private void Log(string text)
        {
            lstLog.Items.Add($"{DateTime.Now:HH:mm:ss} {text}");
        }
    }
}

五、运行步骤

  1. 启动 SocketServer,点击【启动服务】
  2. 启动 SocketClient,点击【连接服务】
  3. 输入消息发送,支持多客户端广播
  4. 自动处理 TCP 粘包,消息不会错乱

六、总结

  1. TCP 是流协议,必须自定义长度协议解决粘包
  2. WinForm + Socket 适合做局域网聊天、设备通信
  3. 多线程接收客户端,保证通信不阻塞界面
  4. 可扩展:加数据库、登录、心跳包、断线重连

更多推荐