Swoole下TCP粘包问题的产生及解决办法

TCP在数据传输中,如果数据过大则会影响传输速度,则需要拆包。若数据过小,当我们连续而又快速的写入数据,数据会先保存到套接字缓冲区中,网卡会将多次写入的数据一并发送到服务器,此时就会发生粘包现象。即多个数据混合在一起发送。无法区分。

在这个过程中可能会出现3种情况:

  1 、正常:两个数据包逐一分开发送

  2 、粘包:两个包一同发送,

  3 、拆包:Server接收到不完整的或多出一部分的数据包

如下图展示(来自网络):

一:粘包现象展示

创建server.php

<?php
// swoole_tcp_server.php
//创建Server对象,监听 127.0.0.1:9501端口
$serv = new Swoole\Server("127.0.0.1", 9501);
$serv->set([
//心跳检测,每三秒检测一次,10秒没活动就断开
'heartbeat_idle_time'=>6,//连接最大的空闲时间
'heartbeat_check_interval'=>3 //服务器定时检查
]);
//监听连接进入事件
$serv->on('Connect', function ($serv, $fd) {
    echo "Client ".$fd.": Connect.\n";
});
//监听数据接收事件
$serv->on('Receive', function ($serv, $fd, $from_id, $data) {
    // 接收客户端的想你想
    echo "接收到".$fd."信息的".$data."\n";
    $serv->send($fd, "Server: ");
});
//监听连接关闭事件
$serv->on('Close', function ($serv, $fd) {
    echo "Client: ".$fd."Close.\n" ;
});
echo "启动swoole tcp server 访问地址 127.0.0.1:9501 \n";
//启动服务器
$serv->start();

创建client.php

<?php
/*
 * @Author: your name
 * @Date: 2021-03-24 10:56:50
 * @LastEditTime: 2021-04-20 15:13:24
 * @LastEditors: Please set LastEditors
 * @Description: In User Settings Edit
 * @FilePath: \www\swoole\tcp\client.php
 */
$client = new swoole_client(SWOOLE_SOCK_TCP);
//连接到服务器
if (!$client->connect('127.0.0.1', 9501, 0.5))
{
    die("connect failed.");
}
//向服务器发送数据
for ($i=0; $i < 10; $i++) {
    sleep(1);//注意,这里我先暂停1秒发送
    $client->send($i);
}
//从服务器接收数据
$data = $client->recv();
if (!$data)
{
die("recv failed.");
}
echo $data."\n";
//关闭连接
$client->close();
?>

第一次包含暂停:结果展示正确:

第二次此时去掉暂停:再次测试:

第二次出现的结果明显不是我期望打印的结果,此时发送的问题 就叫粘包。

 二:解决粘包问题

   1,EOF结束符协议

  即在每个数据包的结尾加上特殊的字符表示包已经结束。缺点:一定要确保该数据内不包含设定的字符。性能差。因为要遍历每个字节。

// 服务端代码加入配置
$serv->set([
    'open_eof_split' => true,
    'package_eof' => "\r\n",
]);

// 客户端代码加入配置
$client->set([
    'open_eof_split' => true,
    'package_eof' => "\r\n",
]);
//客户端数据发送修改
//向服务器发送数据
for ($i=0; $i < 10; $i++) {
    $client->send($i."\r\n");
}

  测试结果:

  1.1,换一种配置性能优于上面,但是只能解决分包问题,不能解决合包,具体设置如下

// 服务端配置
$serv-->set(array(
    'open_eof_check' => true, //主要是这个参数变化
    'package_eof' => "\r\n",
));
// 客户端配置
$client->set(array(
    'open_eof_check' => true,
    'package_eof' => "\r\n",
));

  2,固定包头+包体协议(建议采用

  采用php的pack和unpack函数计算出传输的字符串长度,通过substr获取数据。

// 服务端配置
$serv->set([
    // 'open_eof_check' => true,
    // 'package_eof' => "\r\n",
    'open_length_check' => true,
    'package_max_length' => 81920,
    'package_length_type' => 'n', //see php pack()
    'package_length_offset' => 0,
    'package_body_offset' => 2,
]);

// 客户端发送数据调整
//向服务器发送数据
for ($i=0; $i < 10; $i++) {
    $client->send(pack("n", strlen($i)) . $i);
}

  测试结果:

  此处类似于:参考pack,unpack函数解释。

// 发送数据
$message = 'hello world';
$sendMessage = pack("n", strlen($message)) . $message;
$client->send($sendMessage);


// 接受数据
$dataLength = unpack("n", $data)[1];
$message = substr($data, -$length);

参考链接:https://wiki.swoole.com/#/learn?id=tcp%e7%b2%98%e5%8c%85%e9%97%ae%e9%a2%98

posted @ 2021-04-20 17:35  wish_yang  阅读(398)  评论(0编辑  收藏  举报