C# 优雅的解决TCP Socket粘包、分包问题


一、概述

TCP是流性质数据的发送与接收,就像是一条河流,我们不能依靠它本身来区分哪部分水来自哪条山沟。但是往往,数据的解析就必须清楚的知道哪里是解析界限,不然发送的数据则像臭婆娘的烂线裤,难以把握。

二、探索问题

2.1 源码位置
2.3 说明文档

文档首页

三、解决思路

实际上,解决粘包、分包问题的思路有很多,例如:

  • Http:使用“\r\n”分割header,然后读取contentLength的Body。
  • WebSocket:采用特殊数据帧,用可变头表示载荷数据。
  • 等等。

而当我们自己搭建TCP服务器或客户端以后,如何优雅的解决这个问题呢?

常规思路其实也就三种:

  • 固定包头:以前几个固定字节作为包头,然后标识后续字节数量。
  • 固定长度:不管发送多少数据,客户端和服务器以固定长度接收。
  • 终止因子分割:约定数据以某种特定组合结束,可以理解为汉语的“句号”。

四、实现方式

要实现上述的算法,我们需要引入TouchSocket的Nuget包。然后服务器和客户端选择对应的适配器即可。

例如:

【服务器】
使用固定包头数据处理适配器(FixedHeaderPackageAdapter),然后设置包头由int标识,也就是4+n的数据格式。

TcpService service = new TcpService();
service.Connecting = (client, e) =>
{
   client.SetDataHandlingAdapter(new FixedHeaderPackageAdapter() { FixedHeaderType= FixedHeaderType.Int });
};

service.Received = (client, byteBlock, requestInfo) =>
{
    //从客户端收到信息
    string mes = byteBlock.ToString();
    Console.WriteLine($"已从{client.IP}:{client.Port}接收到信息:{mes}");//Name即IP+Port
    client.Send(byteBlock);
};

var config = new RRQMConfig();
config.SetListenIPHosts(new IPHost[] { new IPHost("127.0.0.1:7789"), new IPHost(7790) });//同时监听两个地址
service.Setup(config);
service.Start();

【客户端】
使用同样的适配器。

TcpClient tcpClient = new TcpClient();

tcpClient.Connecting = (client, e) =>
{
   client.SetDataHandlingAdapter(new FixedHeaderPackageAdapter() { FixedHeaderType= FixedHeaderType.Int });
};

tcpClient.Received = (client, byteBlock, obj) =>
{
    //从服务器收到信息
    string mes = Encoding.UTF8.GetString(byteBlock.Buffer, 0, byteBlock.Len);
    Console.WriteLine($"已从服务器接收到信息:{mes}");
};
tcpClient.Setup("127.0.0.1:7789");

tcpClient.Connect();

此时,服务器和客户端就已经拥有了处理粘包分包的能力了。

除此之外,可选的适配器还有:

  • 正常数据处理适配器(NormalDataHandlingAdapter)
  • 固定包头数据处理适配器(FixedHeaderPackageAdapter)
  • 固定长度数据处理适配器(FixedSizePackageAdapter)
  • 终止因子分割数据处理适配器(TerminatorPackageAdapter)

五、普通Socket如何实现

上述方法可以很简单的处理粘包分包,但是有时候可能大家不太想用TouchSocket组件,只想用原生Socket实现。那怎么实现呢?

实际上也比较简单。但是依然要引用TouchSocket库,然后独立使用适配器即可。

[Fact]
 public void AdapterCallbackShouldBeOk()
 {
     FixedHeaderPackageAdapter adapter = new FixedHeaderPackageAdapter();

     bool sendCallBack = false;
     bool receivedCallBack = false;

     byte[] sentData=null;
     adapter.SendCallBack = (buffer,offset,length,async) =>
     {
         //此处会回调发送的最终调用。例如:此处使用固定包头,则发送的数据为4+n的封装。
         sentData = new byte[length];
         Array.Copy(buffer,offset,sentData,0,length);

         if (length==4+4)
         {
             sendCallBack = true;
         }
         
     };

     adapter.ReceivedCalloTouc= (byteBlock,requestInfo) =>
     {
         //此处会回调接收的最终触发,例如:此处使用的固定包头,会解析4+n的数据为n。
         
         if (byteBlock.Len==4)
         {
             receivedCallBack = true;
         }
     };
    
     byte[] data = Encoding.UTF8.GetBytes("RRQM");

     adapter.SendInput(data,0,data.Length,false);//模拟输入,会在SendCallBack中输出最终要发送的数据。

     using (ByteBlock block=new ByteBlock())
     {
         block.Write(sentData);
         block.Pos = 0;
         adapter.ReceivedInput(block);//模拟输出,会在ReceivedCallBack中输出最终收到的实际数据。
     }

     Assert.True(sendCallBack);
     Assert.True(receivedCallBack);
 }
  • 5
    点赞
  • 23
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

若汝棋茗

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值