Swoole從入門到入土(2)——TCP伺服器[初步接觸]
我們知道Swoole彌補了PHP沒辦法實現長連線的短板,在接下來的話題中,我們會從TCP伺服器、HTTP伺服器、WebSocket伺服器、協程、管道、中介軟體等話題,一個個進行討論。
1、開篇
我們以Swoole一個最簡單的例子作為開篇:
//建立Server物件,監聽 127.0.0.1:9501 埠 $server = new Swoole\Server('0.0.0.0', 9501); //監聽連線進入事件 $server->on('Connect', function ($server, $fd) { echo "Client: Connect.\n"; }); //監聽資料接收事件 $server->on('Receive', function ($server, $fd, $from_id, $data) { $server->send($fd, "Server: " . $data); }); //監聽連線關閉事件 $server->on('Close', function ($server, $fd) { echo "Client: Close.\n"; }); //啟動伺服器 $server->start();
把這段程式碼,儲存為檔案swoole.php,接著我們用php命令執行:php swoole.php。這時我們就可以看到一個簡單的TCP伺服器在偵聽中。
程式碼的大致作用是:
1) 服務端發現客戶端有連線的時候,就會輸出文字“Client:Connect”
2) 一旦收到客戶端的訊息,就會向客戶端傳送“Server:訊息原文”
3) 連線斷開時,輸出文字“Client:Close”
2、驗證
現在為了讓大家更直觀的看到連線情況,我們用SockeTool工具進行實驗:
1) php swoole.php啟動服務端。
2) SocketTool建立TCP客戶端,填入服務端對應的IP和埠:
3) 測試傳送資料,並檢視接收資訊:
3、相關函式詳解
1) 建構函式
Swoole\Server(string$host = '0.0.0.0', int $port = 0, int $mode = SWOOLE_PROCESS, int $sockType = SWOOLE_SOCK_TCP): \Swoole\Server
$host:監聽的IP地址,可支援IPV4也可支援IPV6,0.0.0.0為IPV4的所有地址,::(相當於0:0:0:0:0:0:0:0)為IPV6的所有地址。
$port:監聽的埠,如果埠小於1024則需要root許可權。
$mode:SWOOLE_PROCESS(預設,多程序模式),SWOOLE_BASE(基要模式)。
$socketType:SWOOLE_SOCK_TCP(預設,IPV4 TCP)、SWOOLE_SOCK_TCP6、SWOOLE_SOCK_UDP、SWOOLE_SOCK_UDP6
2) 函式on:呼叫事件
Swoole\Server->on(string $event, mixed $callback): void
$event:事件名稱,不區分大小寫
$callback:回撥函式,各個事件的回撥函式的引數格式詳見事件說明。回撥函式可以是函式名的字串,類靜態方法,物件方法陣列,匿名函式。
3) 函式start:啟動伺服器,監聽所有指定的埠
Swoole\Server->start(): bool
本函式沒有引數,但有以下事項必須瞭解:
· 啟動成功後會建立 worker_num+2 個程序。Master 程序 +Manager 程序 +worker_num 個 Worker 程序。
· 啟動失敗會立即返回 false
· 啟動成功後將進入事件迴圈,等待客戶端連線請求。start 方法之後的程式碼不會執行
· 伺服器關閉後,start 函式返回 true,並繼續向下執行
· 設定了 task_worker_num 屬生值會增加相應數量的 Task 程序
· 方法列表中 start 之前的方法僅可在 start 呼叫前使用,在 start 之後的方法僅可在 onWorkerStart、onReceive 等事件回撥函式中使用
4) 函式send:向客戶端傳送資料
Swoole\Server->send(int $fd, string $data, int $serverSocket = -1): bool
$fd:客戶端檔案描述符,每個客戶端分配一個描述符,可以理解為客戶端的ID。
$data:傳送的資料,TCP 協議最大不得超過 2M,可修改 buffer_output_size 改變允許傳送的最大包長度。
$serverSocket:向 UnixSocket DGRAM 對端傳送資料時需要此項引數,TCP 客戶端不需要填寫。
5) 函式getClientInfo:獲取連線資訊
Swoole\Server->getClientInfo(int $fd, int $extraData, bool $ignoreError = false): bool|array
$fd:客戶端檔案描述符,每個客戶端分配一個描述符,可以理解為客戶端的ID。
$extraData:擴充套件資訊,保留引數,目前無任何效果。
$ignoreError:是否忽略錯誤,如果設定為 true,即使連線關閉也會返回連線的資訊。
注意:當使用 dispatch_mode = 1(輪循模式)或3(搶佔模式) 配置時,考慮到這種資料包分發策略用於無狀態服務,當連線斷開後相關資訊會直接從記憶體中刪除,所以 Server->getClientInfo 是獲取不到相關連線資訊的。
/***** 連線資訊資料示例 *****/ $fd_info = $server->getClientInfo($fd); var_dump($fd_info); array(7) { ["reactor_id"]=> int(3) ["server_fd"]=> int(14) ["server_port"]=> int(9501) ["remote_port"]=> int(19889) ["remote_ip"]=> string(9) "127.0.0.1" ["connect_time"]=> int(1390212495) ["last_time"]=> int(1390212760) }
------------ 常識科普時間 ------------
討論到這裡,我們先科普一下幾個名字:
Master 程序、Reactor 執行緒、Worker 程序、Task 程序、Manager 程序的區別與聯絡
Master程序:php swoole.php啟動的程序。
Reactor執行緒:(注意)這是一個執行緒,是在Master程序中建立的執行緒,負責處理網路IO,收發資料,不執行任何PHP程式碼。
Worker程序:接受由Reactor執行緒投遞過來的資料,處理後生成響應資料,再交給Reactor執行緒。多程序模式執行。可以是非同步非阻塞,也可以是同步阻塞。
TaskWorker程序:接受由Worker程序投遞的任務(通過Swoole\Server->task/taskwait/taskCo/taskWaitMulti 方法投遞),處理完成之後將結果返回給Worker程序(使用使用 Swoole\Server->finish)。以多程序方式執行,完全是同步阻塞模式。
Manager程序:負責建立 / 回收 worker/task 程序。
------------ 常識科普時間結束 ------------
4、相關事件詳解
1) 事件onConnect:有新的連線進入時,在 worker 程序中回撥。
function onConnect(Swoole\Server $server, int $fd, int $reactorId);
$server:伺服器server物件。
$fd:連線(客戶端)檔案描述符,可理解為標識客戶端的ID,每個客戶端分配一個唯一的描述符。
$reactorId:所在的reactor執行緒的ID。
注意:onConnect、onReceive和onClose三個事件有可能會併發執行,從而帶來異常。
當dispatch_mode為1(輪循模式)或3(搶佔模式)時,資料包可能會被投遞到不同的程序。連線相關的 PHP 物件資料,無法實現在 onConnect 回撥初始化資料,onClose 清理資料。
(:關於屬性以及屬性的設定下一篇會進行介紹:)
2) 事件onReceive:接收到資料時回撥此函式,發生在 worker 程序中。
function onReceive(Swoole\Server $server, int $fd, int $reactorId, string $data);
$server:伺服器server物件。
$fd:連線(客戶端)檔案描述符,可理解為標識客戶端的ID,每個客戶端分配一個唯一的描述符。
$reactorId:所在的reactor執行緒的ID。
$data:收到的資料內容,可以是文字,也可以是二進位制。
注意:TCP資料包存在粘包問題,使用底層提供的 open_eof_check/open_length_check/open_http_protocol 等配置可以保證資料包的完整性
不使用底層的協議處理,在 onReceive 後 PHP 程式碼中自行對資料分析,合併 / 拆分資料包。
(:關於TCP粘包問題的處理,以後的章節會進行介紹:)
3) 事件onClose:TCP 客戶端連線關閉後,在 worker 程序中回撥此函式。
function onClose(Swoole\Server $server, int $fd, int $reactorId);
$server:伺服器server物件。
$fd:連線(客戶端)檔案描述符,可理解為標識客戶端的ID,每個客戶端分配一個唯一的描述符。
$reactorId:所在的reactor執行緒的ID。當伺服器主動關閉連線時,底層會設定此引數為 -1,可以通過判斷 $reactorId < 0 來分辨關閉是由伺服器端還是客戶端發起的。只有在 PHP 程式碼中主動呼叫 close 方法被視為主動關閉。心跳檢測是由心跳檢測執行緒通知關閉的,關閉時 onClose 的 $reactorId 引數不為 -1。
注意:
-onClose 回撥函式如果發生了致命錯誤,會導致連線洩漏。通過 netstat 命令會看到大量 CLOSE_WAIT 狀態的 TCP 連線 。無論由客戶端發起 close 還是伺服器端主動呼叫 $server->close() 關閉連線,都會觸發此事件。因此只要連線關閉,就一定會回撥此函式
-onClose 中依然可以呼叫 getClientInfo 方法獲取到連線資訊,在 onClose 回撥函式執行完畢後才會呼叫 close 關閉 TCP 連線。這裡回撥 onClose 時表示客戶端連線已經關閉,所以無需執行 $server->close($fd)。程式碼中執行 $server->close($fd) 會丟擲 PHP 錯誤警告。
(:這一節又有一個問題“心跳檢測”,在以後的章節敬請期待啦:)
對於TCP服務端的初步接觸,這一節就先到這裡啦。大家下週見:)
--------------------------- 我是可愛的分割線 ----------------------------
最後博主借地宣傳一下,漳州程式設計小組招新了,這是一個面向漳州青少年資訊學/軟體設計的學習小組,有意向的同學點選連結,聯絡我吧。