這是關於 Swoole 學習的第五篇文章:Swoole 多協議 多端口 的應用。php
主要參考官方的這兩篇文章,進行實現的 Demo。html
但願經過我提供的 Demo,可以對文檔有更加深入的理解。web
爲何須要通訊協議?segmentfault
官方:TCP協議在底層機制上解決了UDP協議的順序和丟包重傳問題。但相比UDP又帶來了新的問題,TCP協議是流式的,數據包沒有邊界。應用程序使用TCP通訊就會面臨這些難題。由於TCP通訊是流式的,在接收1個大數據包時,可能會被拆分紅多個數據包發送。屢次Send底層也可能會合併成一次進行發送。這裏就須要2個操做來解決:分包 和 合包,因此TCP網絡通訊時須要設定通訊協議。
Swoole 支持了2種類型的自定義網絡通訊協議 :EOF結束符協議、固定包頭+包體協議。緩存
先看下,未設置協議的效果:安全
發送的每條數據長度都是 23,但在 onReceive 接收數據的時候每次接收的長度不同,並無按照想象的方式進行分包。websocket
再看下,設置了EOF結束符協議的效果:swoole
發送的每條數據長度都是 23,在 onReceive 接收數據的時候每次接收的也是 23 ,完美。網絡
主要設置項以下:socket
'package_max_length' => '8192', 'open_eof_split' => true, 'package_eof' => "\r\n"
不作解釋,官方文檔已經寫的很清楚。
示例代碼以下:
server.php
<?php class Server { private $serv; public function __construct() { $this->serv = new swoole_server('0.0.0.0', 9501); $this->serv->set([ 'worker_num' => 2, //開啓2個worker進程 'max_request' => 4, //每一個worker進程 max_request設置爲4次 'dispatch_mode' => 2, //數據包分發策略 - 固定模式 //EOF結束符協議 'package_max_length' => '8192', 'open_eof_split' => true, 'package_eof' => "\r\n" ]); $this->serv->on('Start', [$this, 'onStart']); $this->serv->on('Connect', [$this, 'onConnect']); $this->serv->on("Receive", [$this, 'onReceive']); $this->serv->on("Close", [$this, 'onClose']); $this->serv->start(); } public function onStart($serv) { echo "#### onStart ####".PHP_EOL; echo "SWOOLE ".SWOOLE_VERSION . " 服務已啓動".PHP_EOL; echo "swoole_cpu_num:".swoole_cpu_num().PHP_EOL; echo "master_pid: {$serv->master_pid}".PHP_EOL; echo "manager_pid: {$serv->manager_pid}".PHP_EOL; echo "########".PHP_EOL.PHP_EOL; } public function onConnect($serv, $fd) { echo "#### onConnect ####".PHP_EOL; echo "客戶端:".$fd." 已鏈接".PHP_EOL; echo "########".PHP_EOL.PHP_EOL; } public function onReceive($serv, $fd, $from_id, $data) { echo "#### onReceive ####".PHP_EOL; var_dump($data); } public function onClose($serv, $fd) { echo "Client Close.".PHP_EOL; } } $server = new Server();
client.php
<?php class Client { private $client; public function __construct() { $this->client = new swoole_client(SWOOLE_SOCK_TCP, SWOOLE_SOCK_ASYNC); $this->client->on('Connect', [$this, 'onConnect']); $this->client->on('Close', [$this, 'onClose']); $this->client->on('Error', [$this, 'onError']); } public function connect() { if(!$fp = $this->client->connect("127.0.0.1", 9501)) { echo "Error: {$fp->errMsg}[{$fp->errCode}]".PHP_EOL; return; } } public function onConnect() { fwrite(STDOUT, "發送測試數據(Y or N):"); swoole_event_add(STDIN, function() { $msg = trim(fgets(STDIN)); if ($msg == 'y') { $this->send(); } fwrite(STDOUT, "發送測試數據(Y or N):"); }); } public function send() { $msg_info = "客戶端發信息...\r\n"; $i = 0; while ($i < 50) { var_dump($msg_info); $this->client->send($msg_info); $i++; } } public function onClose() { echo "Client close connection".PHP_EOL; } public function onError() { } } $client = new Client(); $client->connect();
先看下,未設置協議的效果:
很明顯,在 onReceive 接收到的數據,是少的。
再看下,設置協議的效果:
主要設置項以下:
'open_length_check' => true, 'package_max_length' => '8192', 'package_length_type' => 'N', 'package_length_offset' => '0', 'package_body_offset' => '4',
不作解釋,官方文檔已經寫的很清楚。
示例代碼以下:
server.php
<?php class Server { private $serv; public function __construct() { $this->serv = new swoole_server('0.0.0.0', 9501); $this->serv->set([ 'worker_num' => 2, //開啓2個worker進程 'max_request' => 4, //每一個worker進程 max_request設置爲4次 'dispatch_mode' => 2, //數據包分發策略 - 固定模式 //固定包頭+包體協議 'open_length_check' => true, 'package_max_length' => '8192', 'package_length_type' => 'N', 'package_length_offset' => '0', 'package_body_offset' => '4', ]); $this->serv->on('Start', [$this, 'onStart']); $this->serv->on('Connect', [$this, 'onConnect']); $this->serv->on("Receive", [$this, 'onReceive']); $this->serv->on("Close", [$this, 'onClose']); $this->serv->start(); } public function onStart($serv) { echo "#### onStart ####".PHP_EOL; echo "swoole_cpu_num:".swoole_cpu_num().PHP_EOL; echo "SWOOLE ".SWOOLE_VERSION . " 服務已啓動".PHP_EOL; echo "master_pid: {$serv->master_pid}".PHP_EOL; echo "manager_pid: {$serv->manager_pid}".PHP_EOL; echo "########".PHP_EOL.PHP_EOL; } public function onConnect($serv, $fd) { echo "#### onConnect ####".PHP_EOL; echo "客戶端:".$fd." 已鏈接".PHP_EOL; echo "########".PHP_EOL.PHP_EOL; } public function onReceive($serv, $fd, $from_id, $data) { echo "#### onReceive ####".PHP_EOL; $length = unpack('N', $data)[1]; echo "Length:".$length.PHP_EOL; $msg = substr($data, -$length); echo "Msg:".$msg.PHP_EOL; } public function onClose($serv, $fd) { echo "Client Close.".PHP_EOL; } } $server = new Server();
client.php
<?php class Client { private $client; public function __construct() { $this->client = new swoole_client(SWOOLE_SOCK_TCP, SWOOLE_SOCK_ASYNC); $this->client->on('Connect', [$this, 'onConnect']); $this->client->on('Close', [$this, 'onClose']); $this->client->on('Error', [$this, 'onError']); } public function connect() { if(!$fp = $this->client->connect("127.0.0.1", 9501, 1)) { echo "Error: {$fp->errMsg}[{$fp->errCode}]".PHP_EOL; return; } } public function onConnect() { fwrite(STDOUT, "發送測試數據(Y or N):"); swoole_event_add(STDIN, function() { $msg = trim(fgets(STDIN)); if ($msg == 'y') { $this->send(); } fwrite(STDOUT, "發送測試數據(Y or N):"); }); } public function send() { $msg = '客戶端發的信息...'; $msg_info = pack('N', strlen($msg)).$msg; $i = 0; while ($i < 50) { var_dump($msg_info); $this->client->send($msg_info); $i++; } } public function onClose() { echo "Client close connection".PHP_EOL; } public function onError() { } } $client = new Client(); $client->connect();
上圖,是示例代碼中的端口監聽:
很少說,看下效果圖:
示例代碼以下:
server.php
<?php class Server { private $serv; public function __construct() { $this->serv = new swoole_websocket_server("0.0.0.0", 9501); $this->serv->set([ 'worker_num' => 2, //開啓2個worker進程 'max_request' => 4, //每一個worker進程 max_request設置爲4次 'task_worker_num' => 4, //開啓4個task進程 'dispatch_mode' => 4, //數據包分發策略 - IP分配 'daemonize' => false, //守護進程(true/false) ]); $this->serv->on('Start', [$this, 'onStart']); $this->serv->on('Open', [$this, 'onOpen']); $this->serv->on("Message", [$this, 'onMessage']); $this->serv->on("Request", [$this, 'onRequest']); $this->serv->on("Close", [$this, 'onClose']); $this->serv->on("Task", [$this, 'onTask']); $this->serv->on("Finish", [$this, 'onFinish']); //監聽 9502 端口 $tcp = $this->serv->listen("0.0.0.0", 9502, SWOOLE_SOCK_TCP); $tcp->set([ 'worker_num' => 2, //開啓2個worker進程 'max_request' => 4, //每一個worker進程 max_request設置爲4次 'dispatch_mode' => 2, //數據包分發策略 - 固定模式 //固定包頭+包體協議 'open_length_check' => true, 'package_max_length' => '8192', 'package_length_type' => 'N', 'package_length_offset' => '0', 'package_body_offset' => '4', ]); $tcp->on("Receive", [$this, 'onReceive']); //監聽 9503 端口 $udp = $this->serv->listen("0.0.0.0", 9503, SWOOLE_SOCK_UDP); $udp->set([ 'worker_num' => 2, //開啓2個worker進程 'max_request' => 4, //每一個worker進程 max_request設置爲4次 'dispatch_mode' => 2, //數據包分發策略 - 固定模式 ]); $udp->on("Packet", [$this, 'onPacket']); $this->serv->start(); } public function onStart($serv) { echo "#### onStart ####".PHP_EOL; echo "SWOOLE ".SWOOLE_VERSION . " 服務已啓動".PHP_EOL; echo "master_pid: {$serv->master_pid}".PHP_EOL; echo "manager_pid: {$serv->manager_pid}".PHP_EOL; echo "########".PHP_EOL.PHP_EOL; } public function onOpen($serv, $request) { echo "#### onOpen ####".PHP_EOL; echo "server: handshake success with fd{$request->fd}".PHP_EOL; $serv->task([ 'type' => 'login' ]); echo "########".PHP_EOL.PHP_EOL; } public function onTask($serv, $task_id, $from_id, $data) { echo "#### onTask ####".PHP_EOL; echo "#{$serv->worker_id} onTask: [PID={$serv->worker_pid}]: task_id={$task_id}".PHP_EOL; $msg = ''; switch ($data['type']) { case 'login': $msg = '我來了...'; break; case 'speak': $msg = $data['msg']; break; } foreach ($serv->connections as $fd) { $connectionInfo = $serv->connection_info($fd); if (isset($connectionInfo['websocket_status']) && $connectionInfo['websocket_status'] == 3) { $serv->push($fd, $msg); //長度最大不得超過2M } } $serv->finish($data); echo "########".PHP_EOL.PHP_EOL; } public function onFinish($serv,$task_id, $data) { echo "#### onFinish ####".PHP_EOL; echo "Task {$task_id} 已完成".PHP_EOL; echo "########".PHP_EOL.PHP_EOL; } public function onClose($serv, $fd) { echo "#### onClose ####".PHP_EOL; echo "client {$fd} closed".PHP_EOL; echo "########".PHP_EOL.PHP_EOL; } public function onMessage($serv, $frame) { echo "#### onMessage ####".PHP_EOL; echo "receive from fd{$frame->fd}:{$frame->data},opcode:{$frame->opcode},fin:{$frame->finish}".PHP_EOL; $serv->task(['type' => 'speak', 'msg' => $frame->data]); echo "########".PHP_EOL.PHP_EOL; } public function onRequest($request, $response) { echo "#### onRequest ####".PHP_EOL; $response->header("Content-Type", "text/html; charset=utf-8"); $server = $request->server; $path_info = $server['path_info']; $request_uri = $server['request_uri']; echo "PATH_INFO:".$path_info.PHP_EOL; if ($path_info == '/favicon.ico' || $request_uri == '/favicon.ico') { return $response->end(); } $html = "<h1>你好 Swoole.</h1>"; $response->end($html); } public function onReceive($serv, $fd, $from_id, $data) { echo "#### onReceive ####".PHP_EOL; $length = unpack('N', $data)[1]; echo "Length:".$length.PHP_EOL; $msg = substr($data, -$length); echo "Msg:".$msg.PHP_EOL; } public function onPacket($serv, $data, $clientInfo) { echo "#### onPacket ####".PHP_EOL; $serv->sendto($clientInfo['address'], $clientInfo['port'], "Server ".$data); var_dump($clientInfo); } } $server = new Server();
4 個客戶端鏈接的代碼分別是:
一、9501 onMessage 處理 WebSocket。能夠參考原來文章 Swoole WebSocket 的應用 中的代碼便可。
二、9501 onRequest 處理 HTTP。能夠參考原來文章 Swoole HTTP 的應用 中的代碼便可。
三、9502 onReceive 處理 TCP。能夠參考原來文章 Swoole Task 的應用 中的代碼便可。
四、9503 onPacket 處理 UDP。
示例代碼:
netcat -u 10.211.55.4 9503
1、多端口的應用場景是什麼?
好比,開發一個直播網站,直播用一個端口,IM聊天用一個端口。
好比,開發一個RPC服務,數據通信用一個端口,統計界面用一個端口。
本文歡迎轉發,轉發請註明做者和出處,謝謝!