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