TCP 粘包处理

TCP通信特点

    1. TCP 是流式协议没有消息边界,客户端向服务器端发送一次数据,可能会被服务器端分成多次收到。客户端向服务器端发送多少数据。服务器端可能一次全部收到。

  • 2.保证传输的可靠性,顺序。

  • 3.TCP拥有拥塞控制,所以数据包可能会延后发送。

没有消息边界:

可以理解为水在一个水管里的流动,我们不知道哪段数据是一个我们需要的完整数据

收发有缓冲区:

比如:当水从一端流到了另一端,我们在收数据的时候,不可能每来一滴水就处理一次,这个缓冲区就相当于有了一个水桶,再接了一定的水之后内核再给数据交到用户空间,这样可以大大提升性能。

什么是 TCP 粘包?

TCP 粘包是指发送方发送的若干包数据 到 接收方接收时粘成一包,从接收缓冲区看,后一包数据的头紧接着前一包数据的尾。

TCP 出现粘包的原因?

发送方:发送方需要等缓冲区满才发送出去,造成粘包

接收方:接收方不及时接收缓冲区的包,造成多个包接收

参考资料:TCP粘包问题分析和解决(全)

Swoole怎么处理粘包

方式1: EOF 结束协议

通过约定结束符,来确定包数据是否发送完毕

开启open_eof_check=true,并用package_eof来设置一个完整数据结尾字符,同时设置自动拆分open_eof_split

示例:

$serv->set([
    'worker_num' => 2, //设置进程
    'open_eof_check' => true, // 打开EOF检测
    'package_eof' => "\r\n", // 设置EOF
    'open_eof_split' => true // 开启自动拆分
]);

注意:

  • 1、 要保证业务数据里不能出现package_eof设置的字符,否则将导致数据错误了。

  • 2、可以手动拆包,去掉open_eof_split,自行 explode("\r\n", $data),然后循环发送

方式2: 固定包头+包体协议

这种方式也非常常见,原理是通过约定数据流的前几个字节来表示一个完整的数据有多长,从第一个数据到达之后,先通过读取固定的几个字节,解出数据包的长度,然后按这个长度继续取出后面的数据,依次循环。

配置示例:

$client->set([
    'open_length_check' => true,//打开包长检测特性
    'package_max_length' => 1024*1024,//最大允许的包长度
    'package_length_type' => 'N', //长度字段的类型,对应pack()方法的format字段
    'package_length_offset' => 0,//从第几个字节开始是长度,比如包头长度为120字节,第10个字节为长度值,这里填入9
    'package_body_offset' => 4,//从第几个字节开始计算长度
]);
  • open_length_check:打开包长检测特性

  • package_length_type:长度字段的类型,固定包头中用一个4字节或2字节表示包体长度。

  • package_length_offset:从第几个字节开始是长度,比如包头长度为120字节,第10个字节为长度值,这里填入9(从0开始计数)

  • package_body_offset:从第几个字节开始计算长度,比如包头为长度为120字节,第10个字节为长度值,包体长度为1000。如果长度包含包头,这里填入0,如果不包含包头,这里填入120

  • package_max_length:最大允许的包长度。因为在一个请求包完整接收前,需要将所有数据保存在内存中,所以需要做保护。避免内存占用过大。

package_length_type 长度值的类型

长度值的类型,接受一个字符参数,与php的pack函数一致。目前swoole支持10种类型:

  • c:有符号、1字节

  • C:无符号、1字节

  • s:有符号、主机字节序、2字节

  • S:无符号、主机字节序、2字节

  • n:无符号、网络字节序、2字节 (常用)

  • N:无符号、网络字节序、4字节 (常用)

  • l:有符号、主机字节序、4字节(小写L)

  • L:无符号、主机字节序、4字节(大写L)

  • v:无符号、小端字节序、2字节

  • V:无符号、小端字节序、4字节

资料

swoole基础-swoole之粘包问题

swoole 粘包/网络通信协议设计

Last updated