swoole的粘包问题

swoole的粘包问题

什么是 TCP 粘包

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

TCP 出现粘包的原因

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

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

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

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

 每个 socket 被创建后,都会分配两个缓冲区,输入缓冲区和输出缓冲区。
write()/send() 并不立即向网络中传输数据,而是先将数据写入缓冲区中,再由TCP协议将数据从缓冲区发送到目标机器。一旦将数据写入到缓冲区,函数就可以成功返回,不管它们有没有到达目标机器,也不管它们何时被发送到网络,这些都是TCP协议负责的事情。

TCP协议独立于 write()/send() 函数,数据有可能刚被写入缓冲区就发送到网络,也可能在缓冲区中不断积压,多次写入的数据被一次性发送到网络,这取决于当时的网络情况、当前线程是否空闲等诸多因素,不由程序员控制。

复现场景:

$server=new Swoole\Server("0.0.0.0",9801);
$server->set([
    'worker_num'=>2, //设置进程
    'heartbeat_check_interval' => 5,//心跳检测
    'heartbeat_idle_time' => 6,
]);
$server->on("connect",function ($server,$fd){
    //echo "ID{$fd}".PHP_EOL;
});

$server->on("close",function (){
   // echo "消息关闭".PHP_EOL;
});
$server->on("receive",function (swoole_server $server,int $fd,int $rid,string $data){
    var_dump($data);
});
$server->start(); 

这是server端代码

 $client = new Swoole\Client(SWOOLE_SOCK_TCP);

$client->connect("114.116.64.16","9801")||exit("连接失败");

$client->send(str_repeat('A', 10));
$client->send(str_repeat('B', 10));
$client->send(str_repeat('C', 10));
$client->send(str_repeat('D', 10));
$client->send(str_repeat('E', 10));
$client->send(str_repeat('F', 10));
$client->send(str_repeat('G', 10));
$client->send(str_repeat('H', 10));

这是client代码

我在client发送了多次数据,在server端接收打印,把clinet执行了6次结果如下图

string(80) "AAAAAAAAAABBBBBBBBBBCCCCCCCCCCDDDDDDDDDDEEEEEEEEEEFFFFFFFFFFGGGGGGGGGGHHHHHHHHHH"
string(80) "AAAAAAAAAABBBBBBBBBBCCCCCCCCCCDDDDDDDDDDEEEEEEEEEEFFFFFFFFFFGGGGGGGGGGHHHHHHHHHH"
string(80) "AAAAAAAAAABBBBBBBBBBCCCCCCCCCCDDDDDDDDDDEEEEEEEEEEFFFFFFFFFFGGGGGGGGGGHHHHHHHHHH"
string(10) "AAAAAAAAAA"
string(70) "BBBBBBBBBBCCCCCCCCCCDDDDDDDDDDEEEEEEEEEEFFFFFFFFFFGGGGGGGGGGHHHHHHHHHH"
string(80) "AAAAAAAAAABBBBBBBBBBCCCCCCCCCCDDDDDDDDDDEEEEEEEEEEFFFFFFFFFFGGGGGGGGGGHHHHHHHHHH"
string(80) "AAAAAAAAAABBBBBBBBBBCCCCCCCCCCDDDDDDDDDDEEEEEEEEEEFFFFFFFFFFGGGGGGGGGGHHHHHHHHHH"

server端并不是发送一次接收一次,出现了粘包现象

Swoole怎么处理粘包

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

file

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

固定包头+包体协议

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

file

相关配置:

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

注意: 如果没有按照固定的打包方式去打包数据或者数据包太大都会出现如下错误 file

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字节

字节序的理解

 计算机硬件有两种储存数据的方式:大端字节序(big endian)和小端字节序(little endian)。
大端字节序:高位字节在前,低位字节在后,这是人类读写数值的方法。
小端字节序:低位字节在前,高位字节在后,即以0x1122形式储存。

为什么要有字节序呢?
计算机电路先处理低位字节,效率比较高,因为计算都是从低位开始的。所以,计算机的内部处理都是小端字节序。

不过,人类还是习惯读写大端字节序。所以,除了计算机的内部处理,其他的场合几乎都是大端字节序,比如网络传输和文件储存
猜你喜欢