处理网络粘包问题

104 阅读3分钟

1.当客户端发送多个数据包给服务器时,服务器底层的tcp接收缓冲区收到的数据为粘连在一起的,如下图所示

https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/bfe1cf080fdc49409150897de782e478~tplv-k3u1fbpfcp-jj-mark:0:0:0:0:q75.image#?w=672&h=174&s=8596&e=png&b=fdfdfd

当客户端发送两个Hello World!给服务器,服务器TCP接收缓冲区接收了两次,一次是Hello World!Hello, 第二次是World!。

2.粘包原因

因为TCP底层通信是面向字节流的,TCP只保证发送数据的准确性和顺序性,字节流以字节为单位,客户端每次发送N个字节给服务端,N取决于当前客户端的发送缓冲区是否有数据,比如发送缓冲区总大小为10个字节,当前有5个字节数据(上次要发送的数据比如’loveu’)未发送完,那么此时只有5个字节空闲空间,我们调用发送接口发送hello world!其实就是只能发送Hello给服务器,那么服务器一次性读取到的数据就很可能是loveuhello。而剩余的world!只能留给下一次发送,下一次服务器接收到的就是world!
如下图
https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/19fd7d4b24d74c50a3a8e7f7d87b8877~tplv-k3u1fbpfcp-jj-mark:0:0:0:0:q75.image#?w=529&h=212&s=9101&e=png&b=fefefe

这是最好理解的粘包问题的产生原因。还有一些其他的原因比如
1   客户端的发送频率远高于服务器的接收频率,就会导致数据在服务器的tcp接收缓冲区滞留形成粘连,比如客户端1s内连续发送了两个hello world!,服务器过了2s才接收数据,那一次性读出两个hello world!。
2   tcp底层的安全和效率机制不允许字节数特别少的小包发送频率过高,tcp会在底层累计数据长度到一定大小才一起发送,比如连续发送1字节的数据要累计到多个字节才发送,可以了解下tcp底层的Nagle算法。
3   再就是我们提到的最简单的情况,发送端缓冲区有上次未发送完的数据或者接收端的缓冲区里有未取出的数据导致数据粘连。

3.处理粘包

处理粘包的方式主要采用应用层定义收发包格式的方式,这个过程俗称切包处理,常用的协议被称为tlv协议(消息id+消息长度+消息内容),如下图
https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/6aa39a7fffaa421587299231bce0da50~tplv-k3u1fbpfcp-jj-mark:0:0:0:0:q75.image#?w=455&h=175&s=5522&e=png&b=fdfdfd

流程图:

image.png

image.png

4.粘包测试

为了测试粘包,需要制造粘包产生的现象,可以让客户端发送的频率高一些,服务器接收的频率低一些,这样造成前后端收发数据不一致导致多个数据包在服务器tcp缓冲区滞留产生粘包现象。
测试粘包之前,在服务器的CSession类里添加打印二进制数据的函数,便于查看缓冲区的数据

     `void CSession::PrintRecvData(char* data, int length) {`
         `stringstream ss;`
          `string result = "0x";`
         `for (int i = 0; i < length; i++) {`
        `string hexstr;`
         `ss << hex << std::setw(2) << std::setfill('0') << int(data[i]) << endl;`
         `ss >> hexstr;`
         `result += hexstr;`
         `}`
         `std::cout << "receive raw data is : " << result << endl;;`
        `}`

4.1 粘包测试还需要对线程睡眠2秒再进行收发操作,用来延迟接收对端数据制造粘包, chrono中的定时功能:blog.csdn.net/m0_64703894…

  1. std::chrono::milliseconds dura(2000);

  2. std::this_thread::sleep_for(dura);

    表示当前线程休眠一段时间,休眠期间不与其他线程竞争CPU,根据线程需求,等待 若干时间。基础线程介绍:www.cnblogs.com/yssjun/p/11…