tcp粘包问题解决
php
swoole
字数统计: 1.1k(字)
阅读时长: 5(分)
粘包形成原因
什么是 TCP 粘包?
TCP 粘包是指发送方发送的若干包数据 到 接收方接收时粘成一包,从接收缓冲区看,后一包数据的头紧接着前一包数据的尾。
TCP 出现粘包的原因?
发送方:
发送方需要等缓冲区满才发送出去,造成粘包
接收方:
接收方不及时接收缓冲区的包,造成多个包接收

粘包代码演示
tcpServer.php
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| <?php echo swoole_get_local_ip()['eth0'].":9503\n"; $server = new Swoole\Server("0.0.0.0", 9503);
$server->on('connect', function ($server, $fd){ echo "connection open: {$fd}\n"; });
$server->on('receive', function ($server, $fd, $reactor_id, $data) { echo "接收到信息".$data." \n"; $server->send($fd, "swoole: {$data}"); });
$server->on('close', function ($server, $fd) { echo "connection close: {$fd}\n"; });
$server->start();
|
tcpClient.php
1 2 3 4 5 6 7 8 9 10 11
| <?php $client = new Swoole\Client(SWOOLE_SOCK_TCP);
if (!$client->connect('127.0.0.1', 9503, -1)) { exit("connect failed. Error: {$client->errCode}\n"); }
for($i=0; $i<50; $i++) { $client->send('hello_'); } echo $client->recv();
|

通过截图,可以看出:
client 每次 send 一个 hello_ 字符串以后,server 接收到的字符串粘连在了一起
解决方案
1、特殊字符
根据客户端与服务端相互约定的特殊的符号,对接收的数据进行分割处理
2、固定包头+包体协议(主流)
通过与在数据传输之后会在tcp的数据包中携带上数据的长度,然后呢服务端就可以根据这个长度,对于数据进行截取
特殊字符
方案说明
EOF 结束协议
通过约定结束符,来确定包数据是否发送完毕。
开启open_eof_check=true,并用package_eof来设置一个完整数据结尾字符,同时设置自动拆分open_eof_split
注意:
1、要保证业务数据里不能出现package_eof设置的字符,否则将导致数据错误了。
2、可以手动拆包,去掉open_eof_split,自行 explode(“\r\n”, $data),然后循环发送
参考文献
swoole 文档:https://wiki.swoole.com/#/server/setting?id=open_eof_check
代码演示
tcpServer.php
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28
| <?php
echo swoole_get_local_ip()['eth0'].":9503\n"; $server = new Swoole\Server("0.0.0.0", 9503);
$server->set([ 'open_eof_check' => true, //打开EOF检测 'package_eof' => "\r\n", //设置EOF ]);
$server->on('connect', function ($server, $fd){ echo "connection open: {$fd}\n"; });
$server->on('receive', function ($server, $fd, $reactor_id, $data) { echo "接收到信息".$data." \n"; $server->send($fd, "swoole: {$data}"); });
$server->on('close', function ($server, $fd) { echo "connection close: {$fd}\n"; });
$server->start();
|
tcpClient.php
1 2 3 4 5 6 7 8 9 10 11 12 13
| <?php $client = new Swoole\Client(SWOOLE_SOCK_TCP);
if (!$client->connect('127.0.0.1', 9503, -1)) { exit("connect failed. Error: {$client->errCode}\n"); }
$end = "\r\n";
for($i=0; $i<50; $i++) { $client->send('hello_'.$end); } echo $client->recv();
|

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

参考文献
swoole相关配置文档:https://wiki.swoole.com/#/server/setting?id=open_length_check
php的pack函数文档:https://www.php.net/manual/zh/function.pack.php
代码演示
tcpServer.php
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36
| <?php echo swoole_get_local_ip()['eth0'].":9503\n"; $server = new Swoole\Server("0.0.0.0", 9503);
$server->set([ 'open_length_check' => true, 'package_max_length' => 2 * 1024 * 1024, 'package_length_type' => 'n', 'package_length_offset' => 0, 'package_body_offset' => 2 ]);
$server->on('connect', function ($server, $fd){ echo "connection open: {$fd}\n"; });
$server->on('receive', function ($server, $fd, $reactor_id, $data) { echo "接收到信息".$data." \n"; $server->send($fd, "swoole: {$data}"); });
$server->on('close', function ($server, $fd) { echo "connection close: {$fd}\n"; });
$server->start();
|
tcpClient.php
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| <?php $client = new Swoole\Client(SWOOLE_SOCK_TCP);
if (!$client->connect('127.0.0.1', 9503, -1)) { exit("connect failed. Error: {$client->errCode}\n"); }
for($i=0; $i<50; $i++) { $context = '123'; $len = pack("n", strlen($context)); $send = $len . $context; $client->send($send); } echo $client->recv();
|
