Swoole 多协议 多端口 的应用

2019年5月4日 347点热度 0人点赞 0条评论

第 81 篇文章

这是关于 Swoole 学习的第五篇文章:Swoole 多协议 多端口 的应用。

概述

主要参考官方的这两篇文章,进行实现的 Demo。

  • 网络通信协议设计:

    https://wiki.swoole.com/wiki/page/484.html

  • 多端口监听的使用:

    https://wiki.swoole.com/wiki/page/161.html

希望通过我提供的 Demo,能够对文档有更加深刻的理解。

网络通信协议设计

为什么需要通信协议?

官方:TCP协议在底层机制上解决了UDP协议的顺序和丢包重传问题。但相比UDP又带来了新的问题,TCP协议是流式的,数据包没有边界。应用程序使用TCP通信就会面临这些难题。因为TCP通信是流式的,在接收1个大数据包时,可能会被拆分成多个数据包发送。多次Send底层也可能会合并成一次进行发送。这里就需要2个操作来解决:分包 和 合包,所以TCP网络通信时需要设定通信协议。

Swoole 支持了2种类型的自定义网络通信协议 :EOF结束符协议、固定包头+包体协议。

EOF结束符协议

图片

先看下,未设置协议的效果:

图片

发送的每条数据长度都是 23,但在 onReceive 接收数据的时候每次接收的长度不一样,并没有按照想象的方式进行分包。

再看下,设置了EOF结束符协议的效果:

图片

发送的每条数据长度都是 23,在 onReceive 接收数据的时候每次接收的也是 23 ,完美。

主要设置项如下:

  1. 'package_max_length' => '8192',

  2. 'open_eof_split' => true,

  3. 'package_eof' => "\r\n"

不做解释,官方文档已经写的很清楚。

示例代码如下:

server.php

  1. <?php


  2. class Server

  3. {

  4. private $serv;


  5. public function __construct() {

  6. $this->serv = new swoole_server('0.0.0.0', 9501);

  7. $this->serv->set([

  8. 'worker_num' => 2, //开启2个worker进程

  9. 'max_request' => 4, //每个worker进程 max_request设置为4次

  10. 'dispatch_mode' => 2, //数据包分发策略 - 固定模式


  11. //EOF结束符协议

  12. 'package_max_length' => '8192',

  13. 'open_eof_split' => true,

  14. 'package_eof' => "\r\n"

  15. ]);


  16. $this->serv->on('Start', [$this, 'onStart']);

  17. $this->serv->on('Connect', [$this, 'onConnect']);

  18. $this->serv->on("Receive", [$this, 'onReceive']);

  19. $this->serv->on("Close", [$this, 'onClose']);


  20. $this->serv->start();

  21. }


  22. public function onStart($serv) {

  23. echo "#### onStart ####".PHP_EOL;

  24. echo "SWOOLE ".SWOOLE_VERSION . " 服务已启动".PHP_EOL;

  25. echo "swoole_cpu_num:".swoole_cpu_num().PHP_EOL;

  26. echo "master_pid: {$serv->master_pid}".PHP_EOL;

  27. echo "manager_pid: {$serv->manager_pid}".PHP_EOL;

  28. echo "########".PHP_EOL.PHP_EOL;

  29. }


  30. public function onConnect($serv, $fd) {

  31. echo "#### onConnect ####".PHP_EOL;

  32. echo "客户端:".$fd." 已连接".PHP_EOL;

  33. echo "########".PHP_EOL.PHP_EOL;

  34. }


  35. public function onReceive($serv, $fd, $from_id, $data) {

  36. echo "#### onReceive ####".PHP_EOL;

  37. var_dump($data);

  38. }


  39. public function onClose($serv, $fd) {

  40. echo "Client Close.".PHP_EOL;

  41. }

  42. }


  43. $server = new Server();

client.php

  1. <?php


  2. class Client

  3. {

  4. private $client;


  5. public function __construct() {

  6. $this->client = new swoole_client(SWOOLE_SOCK_TCP, SWOOLE_SOCK_ASYNC);


  7. $this->client->on('Connect', [$this, 'onConnect']);

  8. $this->client->on('Close', [$this, 'onClose']);

  9. $this->client->on('Error', [$this, 'onError']);

  10. }


  11. public function connect() {

  12. if(!$fp = $this->client->connect("127.0.0.1", 9501)) {

  13. echo "Error: {$fp->errMsg}[{$fp->errCode}]".PHP_EOL;

  14. return;

  15. }

  16. }


  17. public function onConnect() {


  18. fwrite(STDOUT, "发送测试数据(Y or N):");

  19. swoole_event_add(STDIN, function() {

  20. $msg = trim(fgets(STDIN));

  21. if ($msg == 'y') {

  22. $this->send();

  23. }

  24. fwrite(STDOUT, "发送测试数据(Y or N):");

  25. });

  26. }


  27. public function send() {

  28. $msg_info = "客户端发信息...\r\n";


  29. $i = 0;

  30. while ($i < 50) {

  31. var_dump($msg_info);

  32. $this->client->send($msg_info);

  33. $i++;

  34. }

  35. }


  36. public function onClose() {

  37. echo "Client close connection".PHP_EOL;

  38. }


  39. public function onError() {


  40. }

  41. }


  42. $client = new Client();

  43. $client->connect();

固定包头+包体协议

图片

先看下,未设置协议的效果:

图片

很明显,在 onReceive 接收到的数据,是少的。

再看下,设置协议的效果:

图片

主要设置项如下:

  1. 'open_length_check' => true,

  2. 'package_max_length' => '8192',

  3. 'package_length_type' => 'N',

  4. 'package_length_offset' => '0',

  5. 'package_body_offset' => '4',

不做解释,官方文档已经写的很清楚。

示例代码如下:

server.php

  1. <?php


  2. class Server

  3. {

  4. private $serv;


  5. public function __construct() {

  6. $this->serv = new swoole_server('0.0.0.0', 9501);

  7. $this->serv->set([

  8. 'worker_num' => 2, //开启2个worker进程

  9. 'max_request' => 4, //每个worker进程 max_request设置为4次

  10. 'dispatch_mode' => 2, //数据包分发策略 - 固定模式


  11. //固定包头+包体协议

  12. 'open_length_check' => true,

  13. 'package_max_length' => '8192',

  14. 'package_length_type' => 'N',

  15. 'package_length_offset' => '0',

  16. 'package_body_offset' => '4',

  17. ]);


  18. $this->serv->on('Start', [$this, 'onStart']);

  19. $this->serv->on('Connect', [$this, 'onConnect']);

  20. $this->serv->on("Receive", [$this, 'onReceive']);

  21. $this->serv->on("Close", [$this, 'onClose']);


  22. $this->serv->start();

  23. }


  24. public function onStart($serv) {

  25. echo "#### onStart ####".PHP_EOL;

  26. echo "swoole_cpu_num:".swoole_cpu_num().PHP_EOL;

  27. echo "SWOOLE ".SWOOLE_VERSION . " 服务已启动".PHP_EOL;

  28. echo "master_pid: {$serv->master_pid}".PHP_EOL;

  29. echo "manager_pid: {$serv->manager_pid}".PHP_EOL;

  30. echo "########".PHP_EOL.PHP_EOL;

  31. }


  32. public function onConnect($serv, $fd) {

  33. echo "#### onConnect ####".PHP_EOL;

  34. echo "客户端:".$fd." 已连接".PHP_EOL;

  35. echo "########".PHP_EOL.PHP_EOL;

  36. }


  37. public function onReceive($serv, $fd, $from_id, $data) {

  38. echo "#### onReceive ####".PHP_EOL;

  39. $length = unpack('N', $data)[1];

  40. echo "Length:".$length.PHP_EOL;

  41. $msg = substr($data, -$length);

  42. echo "Msg:".$msg.PHP_EOL;

  43. }


  44. public function onClose($serv, $fd) {

  45. echo "Client Close.".PHP_EOL;

  46. }

  47. }


  48. $server = new Server();

client.php

  1. <?php


  2. class Client

  3. {

  4. private $client;


  5. public function __construct() {

  6. $this->client = new swoole_client(SWOOLE_SOCK_TCP, SWOOLE_SOCK_ASYNC);


  7. $this->client->on('Connect', [$this, 'onConnect']);

  8. $this->client->on('Close', [$this, 'onClose']);

  9. $this->client->on('Error', [$this, 'onError']);

  10. }


  11. public function connect() {

  12. if(!$fp = $this->client->connect("127.0.0.1", 9501, 1)) {

  13. echo "Error: {$fp->errMsg}[{$fp->errCode}]".PHP_EOL;

  14. return;

  15. }

  16. }


  17. public function onConnect() {


  18. fwrite(STDOUT, "发送测试数据(Y or N):");

  19. swoole_event_add(STDIN, function() {

  20. $msg = trim(fgets(STDIN));

  21. if ($msg == 'y') {

  22. $this->send();

  23. }

  24. fwrite(STDOUT, "发送测试数据(Y or N):");

  25. });

  26. }


  27. public function send() {

  28. $msg = '客户端发的信息...';

  29. $msg_info = pack('N', strlen($msg)).$msg;


  30. $i = 0;

  31. while ($i < 50) {

  32. var_dump($msg_info);

  33. $this->client->send($msg_info);

  34. $i++;

  35. }

  36. }


  37. public function onClose() {

  38. echo "Client close connection".PHP_EOL;

  39. }


  40. public function onError() {


  41. }

  42. }


  43. $client = new Client();

  44. $client->connect();

多端口监听的使用

图片

上图,是示例代码中的端口监听:

  • 9501 onMessage 处理 WebSocket。

  • 9501 onRequest 处理 HTTP。

  • 9502 onReceive 处理 TCP。

  • 9503 onPacket 处理 UDP。

不多说,看下效果图:

图片

示例代码如下:

server.php

  1. <?php


  2. class Server

  3. {

  4. private $serv;


  5. public function __construct() {

  6. $this->serv = new swoole_websocket_server("0.0.0.0", 9501);

  7. $this->serv->set([

  8. 'worker_num' => 2, //开启2个worker进程

  9. 'max_request' => 4, //每个worker进程 max_request设置为4次

  10. 'task_worker_num' => 4, //开启4个task进程

  11. 'dispatch_mode' => 4, //数据包分发策略 - IP分配

  12. 'daemonize' => false, //守护进程(true/false)

  13. ]);


  14. $this->serv->on('Start', [$this, 'onStart']);

  15. $this->serv->on('Open', [$this, 'onOpen']);

  16. $this->serv->on("Message", [$this, 'onMessage']);

  17. $this->serv->on("Request", [$this, 'onRequest']);

  18. $this->serv->on("Close", [$this, 'onClose']);

  19. $this->serv->on("Task", [$this, 'onTask']);

  20. $this->serv->on("Finish", [$this, 'onFinish']);


  21. //监听 9502 端口

  22. $tcp = $this->serv->listen("0.0.0.0", 9502, SWOOLE_SOCK_TCP);

  23. $tcp->set([

  24. 'worker_num' => 2, //开启2个worker进程

  25. 'max_request' => 4, //每个worker进程 max_request设置为4次

  26. 'dispatch_mode' => 2, //数据包分发策略 - 固定模式


  27. //固定包头+包体协议

  28. 'open_length_check' => true,

  29. 'package_max_length' => '8192',

  30. 'package_length_type' => 'N',

  31. 'package_length_offset' => '0',

  32. 'package_body_offset' => '4',

  33. ]);

  34. $tcp->on("Receive", [$this, 'onReceive']);


  35. //监听 9503 端口

  36. $udp = $this->serv->listen("0.0.0.0", 9503, SWOOLE_SOCK_UDP);

  37. $udp->set([

  38. 'worker_num' => 2, //开启2个worker进程

  39. 'max_request' => 4, //每个worker进程 max_request设置为4次

  40. 'dispatch_mode' => 2, //数据包分发策略 - 固定模式

  41. ]);

  42. $udp->on("Packet", [$this, 'onPacket']);


  43. $this->serv->start();

  44. }


  45. public function onStart($serv) {

  46. echo "#### onStart ####".PHP_EOL;

  47. echo "SWOOLE ".SWOOLE_VERSION . " 服务已启动".PHP_EOL;

  48. echo "master_pid: {$serv->master_pid}".PHP_EOL;

  49. echo "manager_pid: {$serv->manager_pid}".PHP_EOL;

  50. echo "########".PHP_EOL.PHP_EOL;

  51. }


  52. public function onOpen($serv, $request) {

  53. echo "#### onOpen ####".PHP_EOL;

  54. echo "server: handshake success with fd{$request->fd}".PHP_EOL;

  55. $serv->task([

  56. 'type' => 'login'

  57. ]);

  58. echo "########".PHP_EOL.PHP_EOL;

  59. }


  60. public function onTask($serv, $task_id, $from_id, $data) {

  61. echo "#### onTask ####".PHP_EOL;

  62. echo "#{$serv->worker_id} onTask: [PID={$serv->worker_pid}]: task_id={$task_id}".PHP_EOL;

  63. $msg = '';

  64. switch ($data['type']) {

  65. case 'login':

  66. $msg = '我来了...';

  67. break;

  68. case 'speak':

  69. $msg = $data['msg'];

  70. break;

  71. }

  72. foreach ($serv->connections as $fd) {

  73. $connectionInfo = $serv->connection_info($fd);

  74. if (isset($connectionInfo['websocket_status']) && $connectionInfo['websocket_status'] == 3) {

  75. $serv->push($fd, $msg); //长度最大不得超过2M

  76. }

  77. }

  78. $serv->finish($data);

  79. echo "########".PHP_EOL.PHP_EOL;

  80. }


  81. public function onFinish($serv,$task_id, $data) {

  82. echo "#### onFinish ####".PHP_EOL;

  83. echo "Task {$task_id} 已完成".PHP_EOL;

  84. echo "########".PHP_EOL.PHP_EOL;

  85. }


  86. public function onClose($serv, $fd) {

  87. echo "#### onClose ####".PHP_EOL;

  88. echo "client {$fd} closed".PHP_EOL;

  89. echo "########".PHP_EOL.PHP_EOL;

  90. }


  91. public function onMessage($serv, $frame) {

  92. echo "#### onMessage ####".PHP_EOL;

  93. echo "receive from fd{$frame->fd}:{$frame->data},opcode:{$frame->opcode},fin:{$frame->finish}".PHP_EOL;

  94. $serv->task(['type' => 'speak', 'msg' => $frame->data]);

  95. echo "########".PHP_EOL.PHP_EOL;

  96. }


  97. public function onRequest($request, $response) {

  98. echo "#### onRequest ####".PHP_EOL;

  99. $response->header("Content-Type", "text/html; charset=utf-8");

  100. $server = $request->server;

  101. $path_info = $server['path_info'];

  102. $request_uri = $server['request_uri'];


  103. echo "PATH_INFO:".$path_info.PHP_EOL;


  104. if ($path_info == '/favicon.ico' || $request_uri == '/favicon.ico') {

  105. return $response->end();

  106. }


  107. $html = "<h1>你好 Swoole.</h1>";

  108. $response->end($html);

  109. }


  110. public function onReceive($serv, $fd, $from_id, $data) {

  111. echo "#### onReceive ####".PHP_EOL;


  112. $length = unpack('N', $data)[1];

  113. echo "Length:".$length.PHP_EOL;

  114. $msg = substr($data, -$length);

  115. echo "Msg:".$msg.PHP_EOL;

  116. }


  117. public function onPacket($serv, $data, $clientInfo) {

  118. echo "#### onPacket ####".PHP_EOL;

  119. $serv->sendto($clientInfo['address'], $clientInfo['port'], "Server ".$data);

  120. var_dump($clientInfo);

  121. }

  122. }


  123. $server = new Server();

4 个客户端连接的代码分别是:

1、9501 onMessage 处理 WebSocket。可以参考原来文章 Swoole WebSocket 的应用 中的代码即可。

2、9501 onRequest 处理 HTTP。可以参考原来文章 Swoole HTTP 的应用 中的代码即可。

3、9502 onReceive 处理 TCP。可以参考原来文章 Swoole Task 的应用 中的代码即可。

4、9503 onPacket 处理 UDP。

示例代码:

  1. netcat -u 10.211.55.4 9503

小结

一、多端口的应用场景是什么?

比如,开发一个直播网站,直播用一个端口,IM聊天用一个端口。

比如,开发一个RPC服务,数据通讯用一个端口,统计界面用一个端口。

推荐阅读

本文欢迎转发,转发请注明作者和出处,谢谢!

图片

54110Swoole 多协议 多端口 的应用

这个人很懒,什么都没留下

文章评论