Swoole tcp 请求包处理

tcp请求的包在请求和接收的时候都会存在分包(数据太大)、合包(数据太小)的状况,并发状况下会出现粘包现象(receive获取到的数据包含了多个/不足一个包数据)

Posted by 昆山吴彦祖 on 2021.12.28

tcp请求的包在请求和接收的时候都会存在分包(数据太大)、合包(数据太小)的状况,并发状况下会出现粘包现象(receive获取到的数据包含了多个/不足一个包数据);swoole提供了两种自定义通信协议来处理包边界问题,比较推荐的是 固定包头 + 包体协议

Swoole4 文档

PHP中pack、unpack的详细用法

为什么会粘包

数据太小:tcp出于性能考虑,发送数据前会先把太小的数据打包成一个固定大小的包一起发送,并发状况下就可能导致服务端接收到的数据 包含了多个客户端请求数据。

数据太大:同上,请求数据会被拆分成多个包发送,服务端接收到的数据其实只是请求数据的一部分而非完整数据。

这时候为了解决这个问题,我们就需要在服务端进行手动的 分包/合并包

而swoole提供了两种自定义的tcp数据通信协议用于服务端处理合包/分包问题:EOF 结束符协议 | 固定包头 + 包体协议

前者使用比较受限,通常比较推荐后者。下面是 固定包头+包体 的使用代码

服务端代码

//创建Server对象,监听 127.0.0.1:9501 端口
$server = new Swoole\\Server('127.0.0.1', 9501);
$server->set(array(
    'open_length_check' => true,
    'package_max_length' => 2*1024*1024, //默认2M,最小62kb,单位为字节,如果数据包长度大于这里最大长,会被丢弃
    'package_length_type' => 'N', //包长度 解析方式
    'package_length_offset' => 8, //跳过8个字节获取4字节(32位二进制)为包长度数据
    'package_body_offset' => 16,//跳过16个字节获取 包长度 字节为包数据
));
//监听连接进入事件
$server->on('Connect', function ($server, $fd) {
    echo "Client: Connect.\\n";
});

//监听数据接收事件
$server->on('Receive', function ($server, $fd, $reactor_id, $data) {
    var_dump($data);
    $tmp = unpack("Ntype/Nuid/Nlength", $data); 
    $unpacking = unpack("Ntype/Nuid/Nlength/Nserid/a{$tmp['length']}body", $data);
    var_dump($unpacking); 
    //$server->send($fd, "Server: {$unpacking['body']}\\n");
});

//监听连接关闭事件
$server->on('Close', function ($server, $fd) {
    echo "Client: Close.\\n";
});

//启动服务器
$server->start();

客户端代码

use Swoole\\Coroutine\\Client;
use function Swoole\\Coroutine\\run;

run(function () {
    $client = new Client(SWOOLE_SOCK_TCP);

    if (!$client->connect('127.0.0.1', 9501, 0.5))
    {
        echo "connect failed. Error: {$client->errCode}\\n";
    }
    
		//16进制或者10进制都无所谓,反正最终解析成2进制传输
		//除了长度和数据包本身的内容其他都是非必要自定义信息
    $type = 0x30;
    $uid = 0x123;
    $serid = 0x15;

    $data = "123456789012345678901234567890";
    $length = strlen($data);
    $head = pack("N4", $type, $uid, $length, $serid);// N4 4个32位(二进制)整数
    $body = pack("a{$length}", $data);
    $message = $head.$body;
    
    $client->send($message);
    
    $client->close();
});

swoole