C# Socket的粘包处理

本文介绍了一种处理TCP长消息的方法,通过逐段接收并拼接数据直至遇到消息结束符,确保完整消息的正确接收。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

当socket接收到数据后,会根据buffer的大小一点一点的接收数据,比如:

  1. 对方发来了1M的数据量过来,但是,本地的buffer只有1024字节,那就代表socket需要重复很多次才能真正收完这逻辑上的一整个消息。
  2. 对方发来了5条2个字符的消息,本地的buffer(大小1024字节)会将这5条消息全部收入囊下...

那么,如何处理呢?下面我以最简单的一种文本消息来demo

根据上面所描述的情况,最重要的关键落在了下面3个因素的处理上

  1. 消息的结尾标记
  2. 接收消息时判断结尾标记
  3. 当本次buffer中没有结尾标记时怎么处理

我把写好的核心算法贴出来:

        StringBuilder sb = new StringBuilder();             //这个是用来保存:接收到了的,但是还没有结束的消息
        public void ReceiveMessage(object state)            //这个函数会被以线程方式运行
        {
            Socket socket = (Socket)state;
            while(true)
            {
                byte[] buffer = new byte[receiveBufferSize];  //buffer大小,此处为1024
                int receivedSize=socket.Receive(buffer);

                string rawMsg=System.Text.Encoding.Default.GetString(buffer, 0, receivedSize);
                int rnFixLength = terminateString.Length;   //这个是指消息结束符的长度,此处为\r\n
                for(int i=0;i<rawMsg.Length;)               //遍历接收到的整个buffer文本
                {
                    if (i <= rawMsg.Length - rnFixLength)
                    {
                        if (rawMsg.Substring(i, rnFixLength) != terminateString)//非消息结束符,则加入sb
                        {
                            sb.Append(rawMsg[i]);
                            i++;
                        }
                        else
                        {
                            this.OnNewMessageReceived(sb.ToString());//找到了消息结束符,触发消息接收完成事件
                            sb.Clear();
                            i += rnFixLength;
                        }   
                    }
                    else
                    {
                        sb.Append(rawMsg[i]);
                        i++;
                    }
                }
            }
        }

这个组件的使用方法: 

           A2DTcpClient client = new A2DTcpClient("127.0.0.1", 5000);
            client.NewMessageReceived += new MessageReceived(client_NewMessageReceived);
            client.Connect();
            client.Send("HELLO");
            client.Close();



        static void client_NewMessageReceived(string msg)
        {
            Console.WriteLine(msg);
        }

 

 组件代码下载

 

### C#Socket 通信的问题及解决方案 #### 背景介绍 在基于 TCP 协议的网络通信中,由于其面向字节流的特点,数据传输过程中可能出现 **** 或者 **断** 的现象。具体来说,发送方可能将多条消息合并为一条发送给接收方(即),或者将一条完整的消息拆分为多部分依次传递(即断)。这种行为可能导致接收端无法正确解析接收到的数据。 对于 C# 开发中的 Socket 编程而言,解决这一问题的关键在于设计一种可靠的机制来区分每条独立的消息[^1]。 --- #### 方案一:固定长度的消息头 通过定义固定的头部结构,在每次发送数据前附加该头部信息,可以有效标记单条消息的边界。以下是实现细节: - 定义一个标准的消息格式,例如 `Header + Body` 结构。 - Header 含必要的元信息,比如消息体的实际大小或其他控制字段。 - 接收端按照预设规则读取 Header 数据,并据此判断后续 Body 的范围。 ##### 实现代码示例 ```csharp // 假设消息头长度为4字节,用于存储消息体长度 private const int HEADER_LENGTH = 4; public void SendWithFixedHeader(Socket clientSocket, string message) { byte[] bodyBytes = Encoding.UTF8.GetBytes(message); byte[] headerBytes = BitConverter.GetBytes(bodyBytes.Length); // 合并消息头和消息体 using (MemoryStream ms = new MemoryStream()) { ms.Write(headerBytes, 0, headerBytes.Length); ms.Write(bodyBytes, 0, bodyBytes.Length); // 发送完整数据 clientSocket.Send(ms.ToArray()); } } public string ReceiveWithFixedHeader(Socket clientSocket) { // 先读取消息头 byte[] headerBuffer = new byte[HEADER_LENGTH]; int bytesRead = ReadExact(clientSocket, headerBuffer, HEADER_LENGTH); if (bytesRead != HEADER_LENGTH) throw new Exception("Invalid header length."); int bodyLength = BitConverter.ToInt32(headerBuffer, 0); // 再读取消息体 byte[] bodyBuffer = new byte[bodyLength]; bytesRead = ReadExact(clientSocket, bodyBuffer, bodyLength); return Encoding.UTF8.GetString(bodyBuffer); } private static int ReadExact(Socket socket, byte[] buffer, int totalBytesToRead) { int bytesReceived = 0; while (bytesReceived < totalBytesToRead) { int receivedNow = socket.Receive(buffer, bytesReceived, totalBytesToRead - bytesReceived, SocketFlags.None); if (receivedNow == 0) break; // 远程主机关闭连接 bytesReceived += receivedNow; } return bytesReceived; } ``` 上述方法利用了固定长度的消息头来指示消息体的具体尺寸,从而解决了/断的问题[^2]。 --- #### 方案二:自定义分隔符 另一种常见方式是在各条消息之间插入特殊的分隔字符作为界限标志。这种方式简单易懂,但在实际应用中需注意避免分隔符本身成为合法内容的一部分而导致误判。 ##### 示例代码片段 ```csharp public void SendWithDelimiter(Socket clientSocket, string message) { byte[] delimiter = Encoding.UTF8.GetBytes("\r\n"); // 使用回车换行作为分隔符 byte[] msgBytes = Encoding.UTF8.GetBytes(message); using (MemoryStream ms = new MemoryStream()) { ms.Write(msgBytes, 0, msgBytes.Length); ms.Write(delimiter, 0, delimiter.Length); clientSocket.Send(ms.ToArray()); } } public IEnumerable<string> ReceiveWithDelimiter(Socket clientSocket, char delimiterChar) { List<byte> allData = new List<byte>(); byte[] tempBuffer = new byte[1024]; int bytesRead; do { bytesRead = clientSocket.Receive(tempBuffer); allData.AddRange(new ArraySegment<byte>(tempData, 0, bytesRead)); } while (bytesRead > 0); string combinedString = Encoding.UTF8.GetString(allData.ToArray()); return combinedString.Split(delimiterChar).Where(s => !string.IsNullOrEmpty(s)); // 返回分割后的字符串列表 } ``` 此方案适用于那些能够容忍额外开销的小型项目场景下[^3]。 --- #### 总结 无论是采用固定长度消息头还是引入特殊分隔符的方法都可以很好地应对 C#Socket 难题。然而需要注意的是,无论选择哪种策略都需要充分考虑性能因素以及潜在错误情况下的恢复能力等问题。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值