1. 程式人生 > 實用技巧 >Swoole從入門到入土(2)——TCP伺服器[初步接觸]

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服務端的初步接觸,這一節就先到這裡啦。大家下週見:)

--------------------------- 我是可愛的分割線 ----------------------------

最後博主借地宣傳一下,漳州程式設計小組招新了,這是一個面向漳州青少年資訊學/軟體設計的學習小組,有意向的同學點選連結,聯絡我吧。