2023.8.20 更新

更加完整的代码已经上传到github, EasyTcpSocket ,包含socket框架和完整的客户端服务端示例,框架用的.net 6.0,不过和Framework的写法也没什么区别。

还是那句话,边学边做的,仅供参考,切勿用于生产环境。

源码下载,学习的时候做的,现在可以做到一个服务端对应多个客户端同时接受消息,也解决了分包和粘包的问题,欢迎下载

(16条消息) 网络通信编程学习.7z-C#文档类资源-CSDN文库 https://download.csdn.net/download/Trinity_Force/44900216

什么是粘包分包

  1. TCP是 面向连接 的协议
  2. TCP是点到点的通信
  3. TCP提供可靠的传输服务
  4. TCP协议提供全双工的通信
  5. TCP协议面向 字节流 进行传输的,可以对用户的数据进行拆分或合并

TCP协议是面向字节流传输的,TCP协议会保证字节流传输时顺序不会改变,不会丢失内容,但是TCP协议会灵活的拆分或者合并用户Socket.Send(buffer)出来的内容,将小的数据整合发送或者是将大的数据拆开发送。

所以在实际的编程中就会出现服务端一次Receive就收到了客户端多次Send的数据(“粘包”),或者是客户端只Send了一次,服务端却要多次Receive才能完整接收。

粘包示例 :客户端发送了一万条“Hello”到服务端,结果服务端收到的是这样的

分包示例 :客户端发送了一大串“a”到服务端,结果服务端是分三次收到的。

自己自定义报文格式,发送时根据固定的格式封包,接收时再按照这个格式解包

1 数据包首部添加数据包长度

接收到数据时,先解析首部的“数据包长度”,再解析数据包内容,如果数据包内容的长度不足数据包首部规定的长度,则认为出现了“分包”,需要等待接收下一个数据包,直到传输完整。如果数据包内容的长度大于数据包首部规定的长度,则出现了“粘包”需要认为将粘包分开。

2 数据包结尾添加固定的分隔符

接收到数据后,如果出现结尾标识,则人为将粘包分开,如果一个包中没有结尾标识,则认为出现了“分包”,需要等待下一个数据包,直到出现结尾标识

客户端发送时的封包方法

private void BtnSend_Click(object sender, EventArgs e)
            byte[] dataToBeSend = GetSendData(TextSendData.Text.Trim());
            if (int.TryParse(textRepeatTimes.SelectedItem.ToString(), out int times))
                int dataSize = 0;
                for (int i = 0; i < times; i++)
                    dataSize += ClientSocket.Send(dataToBeSend);
                ShowReceiveDataWithDelegate($"共发送{dataSize}字节的数据");
        private byte[] GetSendData(string text)
            //数据包内容
            byte[] content = Encoding.Default.GetBytes(text);
            //数据包头部
            byte[] header = new byte[4];
            ConvertIntToByteArray(content.Length, ref header);
            //最终封装好的数据包,数据包首位 0 消息 1 文件,2-5位 数据长度
            byte[] dataToBeSend = new byte[content.Length + 5];
            dataToBeSend[0] = 0;
            Array.Copy(header, 0, dataToBeSend, 1, header.Length);
            Array.Copy(content, 0, dataToBeSend, 5, content.Length);
            return dataToBeSend;
        /// <summary>
        /// 把int32类型的数据转存到4个字节的byte数组中
        /// </summary>
        /// <param name="m">int32类型的数据
        /// <param name="arry">4个字节大小的byte数组
        /// <returns></returns>
        private bool ConvertIntToByteArray(Int32 m, ref byte[] arry)
            if (arry == null) return false;
            if (arry.Length < 4) return false;
            arry[0] = (byte)(m & 0xFF);
            arry[1] = (byte)((m & 0xFF00) >> 8);
            arry[2] = (byte)((m & 0xFF0000) >> 16);
            arry[3] = (byte)((m >> 24) & 0xFF);
            return true;

服务端解包类

using System;
using System.Collections.Generic;
namespace AsyncSocketServer
    /// <summary>
    /// SocketTCP通信解包类,包格式为:内容类型(1位)内容长度(4位)剩余。。
    /// 读取完DataList的数据后请务必执行Clear方法();
    /// </summary>
    public class SocketTcpPack
        /// <summary>
        /// 接收是否完成了
        /// </summary>
        public bool IsComplete = false;
        /// <summary>
        /// 接收缓存
        /// </summary>
        public byte[] Buffer;
        /// <summary>
        /// 下次接收从Buffer的哪里开始写入
        /// </summary>
        public int Offset = 0;
        /// <summary>
        /// 下次写入Buffer的长度
        /// </summary>
        public int Size;
        /// <summary>
        /// 接收到的数据
        /// </summary>
        public List<ReceiveDataModel> DataList = new List<ReceiveDataModel>();
        /// <summary>
        /// 缓存长度
        /// </summary>
        private readonly int BufferLength;
        public SocketTcpPack(int bufferLength = 1024)
            BufferLength = bufferLength;
            Buffer = new byte[BufferLength];
            Size = BufferLength;
        /// <summary>
        /// 处理接收到的数据
        /// </summary>
        /// <param name="currentDataSize">接收到的数据长度,Socket.Receive()方法返回的数值</param>
        public void UntiePack(int currentDataSize)
            //Size != BufferLength说明Buffer中保留了一些上次接收的数据,要把这部分数据长度加上
            int dataSize = currentDataSize;
            if (Size != BufferLength)
                dataSize += Offset;
            if (DataList.Count == 0)
                SplitData(Buffer, dataSize);
                //取出DataList中的最后一个元素,通过判断这个元素是否完整来判断是有分包需要补充完整
                ReceiveDataModel LastReceiveData = DataList[DataList.Count - 1];
                if (LastReceiveData.IsComplete)
                    SplitData(Buffer, dataSize);
                    //最后一个包的剩余长度
                    int remainingDataLength = LastReceiveData.DataLength - LastReceiveData.Content.Length;
                    //剩余长度 < 本次接收的数据长度,说明这一次接收就可以把上一个分包补充完整
                    if (remainingDataLength < dataSize)
                        int realLength = LastReceiveData.Content.Length;
                        byte[] b = new byte[LastReceiveData.DataLength];
                        Array.Copy(LastReceiveData.Content, 0, b, 0, LastReceiveData.Content.Length);
                        LastReceiveData.Content = b;
                        Array.Copy(Buffer, 0, LastReceiveData.Content, realLength, remainingDataLength);
                        //继续处理剩下的数据
                        byte[] last = new byte[dataSize - remainingDataLength];
                        Array.Copy(Buffer, remainingDataLength, last, 0, last.Length);
                        SplitData(last, last.Length);
                    //剩余长度 > 本次接收的数据长度,说明这一次接收还不能把上一个分包补充完整,还需要继续等待接收
                    else if (remainingDataLength > dataSize)
                        int realLength = LastReceiveData.Content.Length;
                        byte[] b = new byte[LastReceiveData.Content.Length + dataSize];
                        Array.Copy(LastReceiveData.Content, 0, b, 0, LastReceiveData.Content.Length);
                        LastReceiveData.Content = b;
                        Array.Copy(Buffer, 0, LastReceiveData.Content, realLength, dataSize);
                        Offset = 0;
                        Size = BufferLength;
                        Buffer = new byte[BufferLength];
                        int realLength = LastReceiveData.Content.Length;
                        byte[] b = new byte[LastReceiveData.DataLength];
                        Array.Copy(LastReceiveData.Content, 0, b, 0, LastReceiveData.Content.Length);
                        LastReceiveData.Content = b;
                        Array.Copy(Buffer, 0, LastReceiveData.Content, realLength, remainingDataLength);
                        Offset = 0;
                        Size = BufferLength;
                        Buffer = new byte[BufferLength];
                        IsComplete = true;
        /// <summary>
        /// 处理byte[]前5位就是包首部的这种数据
        /// </summary>
        /// <param name="data">byte[]</param>
        /// <param name="dataSize">内容的实际长度</param>
        private void SplitData(byte[] data, int dataSize)
            //长度 <= 5 说明包首部还没有接收完成,需要继续接收
            if (dataSize <= 5)
                byte[] temp = new byte[BufferLength];
                Array.Copy(data, 0, temp, 0, dataSize);
                Buffer = temp;
                Offset = dataSize;
                Size = BufferLength - dataSize;
                IsComplete = true;
                return;
            //包首部
            byte[] header = new byte[5];
            //包内容
            byte[] content = new byte[dataSize - 5];
            Array.Copy(data, 0, header, 0, 5);
            Array.Copy(data, 5, content, 0, dataSize - 5);
            //包内容长度
            int dataLength = BitConverter.ToInt32(header, 1);
            //dataLength < content.Length 说明本次接收的数据中已经包含一个完整的包,将这个完整的包取出后继续处理剩下的数据
            if (dataLength < content.Length)
                //发生了粘包
                byte[] b = new byte[dataLength];
                Array.Copy(content, 0, b, 0, dataLength);
                ReceiveDataModel receiveData = new ReceiveDataModel()
                    DataType = header[0],
                    DataLength = dataLength,
                    Content = b
                DataList.Add(receiveData);
                byte[] last = new byte[content.Length - dataLength];
                Array.Copy(content, dataLength, last, 0, last.Length);
                SplitData(last, last.Length);
            //dataLength >= content.Length 说明本次接收的数据不完整,保存后继续接收
            else if (dataLength >= content.Length)
                //发生了分包或者什么都没发生
                ReceiveDataModel receiveData = new ReceiveDataModel()
                    DataType = header[0],
                    DataLength = dataLength,
                    Content = content
                DataList.Add(receiveData);
                Offset = 0;
                Size = BufferLength;
                Buffer = new byte[BufferLength];
                if (dataLength == content.Length) IsComplete = true;
        public void Clear()
            if (DataList.Count > 0)
                DataList.Clear();
                IsComplete = false;

发送时调用这个类

using System;
using System.Collections.Generic;
using System.Net;
using System.Net.Sockets;
using System.Text;
using System.Windows.Forms;
namespace AsyncSocketServer
    public partial class Server : Form
        public Server()
            InitializeComponent();
        /// <summary>
        /// 存储客户端连接
        /// </summary>
        private Dictionary<string, Socket> ClientSocketList = new Dictionary<string, Socket>();
        /// <summary>
        /// 接收数据缓冲区
        /// </summary>
        private Dictionary<string, SocketTcpPack> ReceiveBufferDic = new Dictionary<string, SocketTcpPack>();
        /// <summary>
        /// 开始监听
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void BtnStartListen_Click(object sender, EventArgs e)
            if (!IPAddress.TryParse(TextIP.Text.Trim(), out IPAddress ip))
                MessageBox.Show("不正确的IP地址");
                return;
            if (!int.TryParse(TextPort.Text.Trim(), out int port))
                MessageBox.Show("不正确的端口号");
                return;
            //创建Socket
            Socket socketServer = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
            IPEndPoint pEndPoint = new IPEndPoint(ip, port);
            //绑定IP和端口
            socketServer.Bind(pEndPoint);
            //开始监听
            socketServer.Listen(10);
            ShowReceiveDataWithDelegate("监听成功");
            //接收连接
            socketServer.BeginAccept(Accept, socketServer);
        /// <summary>
        /// BeginAccept的回调
        /// </summary>
        /// <param name="result"></param>
        private void Accept(IAsyncResult result)
            Socket socket = (Socket)result.AsyncState;
            Socket clientSocket = socket.EndAccept(result);
            string clientIP = clientSocket.RemoteEndPoint.ToString();
            ClientSocketList.Add(clientIP, clientSocket);
            CmbSocket.BeginInvoke(new EventHandler(delegate
                CmbSocket.Items.Add(clientIP);
            ShowReceiveDataWithDelegate("连接成功");
            SocketTcpPack tcpPack = new SocketTcpPack(1024);
            ReceiveBufferDic.Add(clientIP, tcpPack);
            //开始接受客户端消息
            clientSocket.BeginReceive(ReceiveBufferDic[clientIP].Buffer, ReceiveBufferDic[clientIP].Offset, ReceiveBufferDic[clientIP].Size, SocketFlags.None, Receive, clientSocket);
            //接受下一个连接
            socket.BeginAccept(Accept, socket);
        /// <summary>
        /// BeginReceive的回调
        /// </summary>
        /// <param name="result"></param>
        private void Receive(IAsyncResult result)
            Socket socket = (Socket)result.AsyncState;
                string clientIP = socket.RemoteEndPoint.ToString();
                int dataSize = socket.EndReceive(result);
                if (dataSize > 0)
                    //对接收到的消息进行解包
                    ReceiveBufferDic[clientIP].UntiePack(dataSize);
                    if (ReceiveBufferDic[clientIP].IsComplete)
                        foreach (var item in ReceiveBufferDic[clientIP].DataList)
                            string str = Encoding.Default.GetString(item.Content, 0, item.DataLength);
                            ShowReceiveDataWithDelegate($"{socket.RemoteEndPoint}发来消息:{str}");
                        ReceiveBufferDic[clientIP].Clear();
                //接收下一条消息
                socket.BeginReceive(ReceiveBufferDic[clientIP].Buffer, ReceiveBufferDic[clientIP].Offset, ReceiveBufferDic[clientIP].Size, SocketFlags.None, Receive, socket);
            catch (SocketException)
                string ip = socket.RemoteEndPoint.ToString();
                Close(ip);
        private void BtnSend_Click(object sender, EventArgs e)
            if (CmbSocket.SelectedIndex < 0)
                MessageBox.Show("请选择客户端IP");
                return;
            string clientIP = CmbSocket.SelectedItem.ToString();
            Socket clientSocket = ClientSocketList[clientIP];
            byte[] data = Encoding.Default.GetBytes(TextSendData.Text.Trim());
            clientSocket.BeginSend(data, 0, data.Length, SocketFlags.None, (result) =>
                Socket socket = (Socket)result.AsyncState;
                socket.EndSend(result);
            }, clientSocket);
            TextSendData.Clear();
        private void ShowReceiveDataWithDelegate(string msgContent)
            IAsyncResult result = ListReveiveData.BeginInvoke(new EventHandler(delegate
                    ListReveiveData.Items.Add($"{ListReveiveData.Items.Count} {DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss")} :{msgContent}");
                    ListReveiveData.SelectedIndex = ListReveiveData.Items.Count - 1;
            ListReveiveData.EndInvoke(result);
        private void Close(string ip)
            ShowReceiveDataWithDelegate($"客户端{ip}断开连接");
            ClientSocketList.Remove(ip);
            ReceiveBufferDic.Remove(ip);
            CmbSocket.BeginInvoke(new EventHandler(delegate
                CmbSocket.Items.Remove(ip);
namespace AsyncSocketServer
/// <summary>
/// Socket接收 数据类型
/// </summary>
public class ReceiveDataModel
    /// <summary>
    /// 数据类型 0 文本,1 文件
    /// </summary>
    public byte DataType { get; set; }
    /// <summary>
    /// 数据长度
    /// </summary>
    public int DataLength { get; set; }
    /// <summary>
    /// 数据
    /// </summary>
    public byte[] Content { get; set; }
    public bool IsComplete
            if (DataLength == 0) return false;
            return DataLength == Content.Length;
                    源码下载,学习的时候做的,现在可以做到一个服务端对应多个客户端同时接受消息,也解决了分包和粘包的问题,欢迎下载(16条消息) 网络通信编程学习.7z-C#文档类资源-CSDN文库https://download.csdn.net/download/Trinity_Force/44900216什么是粘包分包TCP是面向连接的协议	TCP是点到点的通信	TCP提供可靠的传输服务	TCP协议提供全双工的通信	TCP协议面向字节流进行传输的,可以对用户的数据进行拆分或合并TCP...
				
最近在做Unity局域网时,用到了Socket通信基于TCP协议,然后使用异步方式,主要用到了BeginAccept和BeginReceive方法 然而就可以实现异步通信,然而还是要解决粘包分包问题 这里我先说明一下什么是分包粘包TCP提供面向连接的、可靠的数据流传输,所以当我们发送数据在短时间内比较频繁并且数据量比较小时,TCP为了优化内存资源,会将多条数据粘成几个包来进行处理,相比发...
我们知道如果Socket传输数据太频繁并且数据量级比较大,就很容易出现分包(一个包的内容分成了两份)、粘包(前一个包的内容分成了两份,其中一份连着下一个包的内容)的情况。 粘包处理方式有很多种,常见的三种是: 每个包都在头部增加一个当前传输包的int4字节大小作为包头。每次接收到数据先读取的包头,确定这一包的实际长度n,当接收够n+4长度的数据就是一个完整的包,再重复读取下一包的包头。(相对...
下面对上面的图进行解释: 1.正常情况:如果Socket Client 发送的数据包,在Socket Server端也是一个一个完整接收的,那个就不会出现粘包分包情况,数据正常读取。 2.粘包情况:Socket Client发送的数据包,在客户端发送和服务器接收的情况下都有可能发送,因为客户端发送的数据都是发送的一个缓冲buffer,然后由缓冲buffer最后刷到数据链路层的,那么就有可能把数据包2的一部分数据结合数据包1的全部被一起...
使用TCP长连接就会引入粘包问题粘包是指发送方发送的若干包数据到接收方接收时粘成一包,从接收缓冲区看,后一包数据的头紧接着前一包数据的尾。粘包可能由发送方造成,也可能由接收方造成。TCP为提高传输效率,发送方往往要收集到足够多的数据后才发送一包数据,造成多个数据包的粘连。如果接收进程不及时接收数据,已收到的数据就放在系统接收缓冲区,用户进程读取数据时就可能同时读到多个数据包。 粘包一般的解决办法是制定通讯协议,由协议来规定如何分包解包,因此在Scoket编程中需要定义分包、解包的逻辑。
最近在工作中遇到了要自己写Socket服务器和客户端的问题,我解决了关于TCP粘包问题。那么为什么会引起TCP粘包呢原因有以下两点 1、TCP是基于字节流的,虽然应用层和传输层之间的数据交互是大小不等的数据块,但是TCP把这些数据块仅仅看成一连串无结构的字节流,没有边界; 2、在TCP的首部没有表示数据长度的字段,基于上面两点,在使用TCP传输数据时,才有粘包或者拆包现象发生的可能 假如没有做处理的话,发过来的数据包会有三种情况 1.没有粘包(这个问题不讨论,是正常情况) 2.两个或多个整包粘在一起 private Socket listener; private const int BUFFER_SIZE = 1024; private byte[] buffer = new byte[BUFFER_SIZE]; public void Start() listener = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp); listener.Bind(new IPEndPoint(IPAddress.Any, 8080)); listener.Listen(10); Console.WriteLine("Server started on port 8080."); while (true) Socket client = listener.Accept(); Console.WriteLine("Client connected: " + client.RemoteEndPoint.ToString()); // Start receiving data from client client.BeginReceive(buffer, 0, BUFFER_SIZE, 0, new AsyncCallback(OnReceive), client); private void OnReceive(IAsyncResult ar) Socket client = (Socket)ar.AsyncState; int bytesRead = client.EndReceive(ar); if (bytesRead > 0) // Convert received bytes to string string receivedData = Encoding.UTF8.GetString(buffer, 0, bytesRead); // Process received data Console.WriteLine("Received data: " + receivedData); // Continue receiving data from client client.BeginReceive(buffer, 0, BUFFER_SIZE, 0, new AsyncCallback(OnReceive), client); // Client has disconnected Console.WriteLine("Client disconnected: " + client.RemoteEndPoint.ToString()); client.Close(); public class SocketClient private Socket client; private const int BUFFER_SIZE = 1024; private byte[] buffer = new byte[BUFFER_SIZE]; public void Connect() client = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp); client.BeginConnect(new IPEndPoint(IPAddress.Loopback, 8080), new AsyncCallback(OnConnect), null); private void OnConnect(IAsyncResult ar) Console.WriteLine("Connected to server."); client.EndConnect(ar); // Start receiving data from server client.BeginReceive(buffer, 0, BUFFER_SIZE, 0, new AsyncCallback(OnReceive), client); private void OnReceive(IAsyncResult ar) Socket client = (Socket)ar.AsyncState; int bytesRead = client.EndReceive(ar); if (bytesRead > 0) // Convert received bytes to string string receivedData = Encoding.UTF8.GetString(buffer, 0, bytesRead); // Process received data Console.WriteLine("Received data: " + receivedData); // Continue receiving data from server client.BeginReceive(buffer, 0, BUFFER_SIZE, 0, new AsyncCallback(OnReceive), client); // Server has disconnected Console.WriteLine("Server disconnected."); client.Close(); public void Send(string data) byte[] sendData = Encoding.UTF8.GetBytes(data); client.BeginSend(sendData, 0, sendData.Length, 0, new AsyncCallback(OnSend), null); private void OnSend(IAsyncResult ar) client.EndSend(ar); class Program static void Main(string[] args) // Start server SocketServer server = new SocketServer(); server.Start(); // Connect client to server SocketClient client = new SocketClient(); client.Connect(); // Send data to server client.Send("Hello, server!"); Console.ReadLine(); 这个示例中,我们使用了异步的 BeginReceive 和 BeginSend 方法来在接收和发送数据时不阻塞主线程。在接收数据时,我们先接收到一部分数据,然后判断是否收到了完整的数据,如果没有就继续接收。在发送数据时,我们将要发送的数据转换成字节数组后,使用 BeginSend 方法发送,然后在回调函数中调用 EndSend 方法结束发送。