Swoole下TCP粘包问题的产生及解决办法
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);