粘包问题

由于tcp的特性,可能会出现数据粘包情况,例如

  • A连接Server
  • A发送 hello
  • A又发送了一条 hello
  • Server可能会一次性收到一条”hellohello”的数据
  • Server也可能收到”he” ,”llohello”类似这样的中断数据

粘包解决

  • 通过标识EOF,例如http协议,通过\r\n\r\n 的方式去表示该数据已经完结,我们可以自定义一个协议,例如当接收到 “结尾666” 字符串时,代表该字符串已经结束,如果没有获取到,则存入缓冲区,等待结尾字符串,或者如果获取到多条,则通过该字符串剪切出其他数据
  • 定义消息头,通过特定长度的消息头进行获取,例如我们定义一个协议,前面10位字符串都代表着之后数据主体的长度,那么我们传输数据时,只需要000000000512346(前10位为协议头,表示了这条数据的大小,后面的为数据),每次我们读取只先读取10位,获取到消息长度,再读取消息长度那么多的数据,这样就可以保证数据的完整性了.(但是为了不被混淆,协议头也得像EOF一样标识)
  • 通过pack二进制处理,相当于于方法2,将数据通过二进制封装拼接进消息中,通过验证二进制数据去读取信息,sw采用的就是这种方式

::: warning 可查看swoole官方文档:https://wiki.swoole.com/wiki/page/287.html :::

实现粘包处理

服务端:

  1. <?php
  2. $subPort2 = $server->addlistener('0.0.0.0', 9503, SWOOLE_TCP);
  3. $subPort2->set(
  4. [
  5. 'open_length_check' => true,
  6. 'package_max_length' => 81920,
  7. 'package_length_type' => 'N',
  8. 'package_length_offset' => 0,
  9. 'package_body_offset' => 4,
  10. ]
  11. );
  12. $subPort2->on('connect', function (\swoole_server $server, int $fd, int $reactor_id) {
  13. echo "tcp服务2 fd:{$fd} 已连接\n";
  14. $str = '恭喜你连接成功服务器2';
  15. $server->send($fd, pack('N', strlen($str)) . $str);
  16. });
  17. $subPort2->on('close', function (\swoole_server $server, int $fd, int $reactor_id) {
  18. echo "tcp服务2 fd:{$fd} 已关闭\n";
  19. });
  20. $subPort2->on('receive', function (\swoole_server $server, int $fd, int $reactor_id, string $data) {
  21. echo "tcp服务2 fd:{$fd} 发送原始消息:{$data}\n";
  22. echo "tcp服务2 fd:{$fd} 发送消息:" . substr($data, '4') . "\n";
  23. });

客户端:

  1. <?php
  2. /**
  3. * Created by PhpStorm.
  4. * User: Tioncico
  5. * Date: 2019/3/6 0006
  6. * Time: 16:22
  7. */
  8. include "../vendor/autoload.php";
  9. define('EASYSWOOLE_ROOT', realpath(dirname(getcwd())));
  10. \EasySwoole\EasySwoole\Core::getInstance()->initialize();
  11. /**
  12. * tcp 客户端2,验证数据包,并处理粘包
  13. */
  14. go(function () {
  15. $client = new \Swoole\Client(SWOOLE_SOCK_TCP);
  16. $client->set(
  17. [
  18. 'open_length_check' => true,
  19. 'package_max_length' => 81920,
  20. 'package_length_type' => 'N',
  21. 'package_length_offset' => 0,
  22. 'package_body_offset' => 4,
  23. ]
  24. );
  25. if (!$client->connect('127.0.0.1', 9503, 0.5)) {
  26. exit("connect failed. Error: {$client->errCode}\n");
  27. }
  28. $str = 'hello world';
  29. $client->send(encode($str));
  30. $data = $client->recv();//服务器已经做了pack处理
  31. var_dump($data);//未处理数据,前面有4 (因为pack 类型为N)个字节的pack
  32. $data = decode($data);//需要自己剪切解析数据
  33. var_dump($data);
  34. // $client->close();
  35. });
  36. /**
  37. * 数据包 pack处理
  38. * encode
  39. * @param $str
  40. * @return string
  41. * @author Tioncico
  42. * Time: 9:50
  43. */
  44. function encode($str)
  45. {
  46. return pack('N', strlen($str)) . $str;
  47. }
  48. function decode($str)
  49. {
  50. $data = substr($str, '4');
  51. return $data;
  52. }