聊天室(上篇)GatewayWorker 基礎


前言

本文的目的是基於 GatewayWorker 官方手冊,梳理一次 GatewayWorker,並在實踐中與 MVC 框架整合的思路(附最終的項目源碼)。如果你已經理解了整合這一塊兒的知識,那么就可以關掉這個網頁了。時間蠻寶貴的~

這篇是上篇,梳理 GatewayWorker 基礎,下篇是 GatewayWorker 與 Laravel 整合聊天室。如果你具備了 GatewayWorker 基礎,請直接閱讀下篇

 

很久以前就想做一個聊天室了。查了下 "php 通信",找到了可用的東西:Socket、WebSocket、 Workerman 以及 GatewayWorker。Socket(接口)提供了一組端到端互相通信的接口,作為通信的核心功能。Websocket(協議)定義了通信中數據的封裝和顯示的格式,而且最大的特點是它支持服務端向客戶端的主動推送,這一點是 HTTP 做不到的。而 Workerman (框架)將這兩者很好地整合在了一起(當然不僅僅於此)。GatewayWorker(框架)是在 Workerman 的基礎上開發的 TCP 長連接應用框架,提供了單發、群發和廣播等接口,還可以客戶端和客戶端通信。

所以最終我選擇了 GatewayWorker 作為 Socket 監聽的服務端,Laravel 作為 HTTP 請求的業務處理框架,完成一個響應式的在線聊天室(項目地址在下一篇文章最后)。

 

GatewayWorker工作原理

先理解一下工作原理,可以對 GatewayWorker 有個整體的把握。這一塊兒其實手冊里已經詳細不啰嗦地解釋清楚了。我這里再理一下:

1、Register、Gateway、BusinessWorker 3 種進程依次啟動(因為支持多進程,所以我說“種”,而不是“個”)

2、Gateway 進程和 BusinessWorker 進程啟動后向 Register 服務進程發起長連接注冊自身。

3、Register 服務進程收到 Gateway 的注冊后,把所有 Gateway 進程的通訊地址寫入內存。

4、Register 服務進程收到 BusinessWorker 的注冊后,把內存中的 Gateway 進程通訊地址發給所有 BusinessWorker 進程。

5、BusinessWorker 進程收到所有 Gateway 進程的通訊地址后,嘗試連接 Gateway。

6、至此,所有 Gateway 和 BusinessWorker 進程就通過 Register 服務進程建立了長連接。

如果期間有新的 Gateway 注冊到 Register(一般是分布式部署加機器),新 Gateway 的通訊地址會被廣播給所有 BusinessWorker,BusinessWorker 收到通知后建立新連接。

如果期間有 Gateway 下線,Register 會收到通知、刪除這個 Gateway 的內部通訊地址,並將新的內部通訊地址列表廣播給所有 BusinessWorker,BusinessWorker 不再連接下線的 Gateway。

7、客戶端的連接事件和連接上的數據會經由 Gateway 轉發給 BusinessWorker,BusinessWorker 默認調用 Events.php 中 Events 類的 onConnect、onMessage、onClose 事件回調處理業務邏輯。

8、BusinessWorker 負責運行所有的業務邏輯,實際的處理邏輯默認在 Events.php 中實現。

 

GatewayWorker進程模型

GatewayWorker 是以進程的形式進駐內存的,了解了它的工作原理之后,有必要理解一下它的進程模型。

GatewayWorker 主要有 3 種進程:Register 進程、Gateway 進程和 BusinessWorker 進程。這 3 種進程分別對應了內核源碼中的 Register 類、Gateway 類和 BusinessWorker 類,並且它們都是基於 Workerman 框架的 Worker 類開發的,所以這 3 種進程都有一些公共的屬性,比如 name、count、onWorkerStart、onWorkerStop 等等。可以說,GatewayWorker 里所有的進程都是 Worker 進程。

 

1、Register進程

Register 進程主要負責 Gateway 進程 與 BusinessWorker 進程建立連接並內部通訊。

該進程由 Register 類實例化,並隨進程啟動進駐內存。

它可定制的只有實例化時指定自身所在的服務進程地址。包括 IP 和端口,並且目前只支持 text 協議。text 協議是 Workerman 框架定義的一種文本協議(協議格式為:數據包 + 換行符)。

 

2、Gateway進程

Gateway 進程主要負責客戶端的連接以及連接上的數據,並將所有的請求轉發給 BusinessWorker 進程進行處理。BusinessWorker 進程的所有處理結果都經由 Gateway 進程轉發給客戶端。

該進程由 Gateway 類實例化,並隨進程啟動進駐內存。

它可定制的有:

(1)實例化。指定協議、IP 和端口。

協議:目前支持的有 Websocket 協議text 協議Frame 協議自定義通訊協議和 裸 TCP 協議(不推薦,見通訊協議作用),不支持監聽 HTTP 協議。

IP:"0.0.0.0" 表示監聽本機所有網卡;"127.0.0.1"表示僅允許本機通過 127.0.0.1 訪問該進程;內網 IP 如 "192.168.11.2" 表示只允許該 IP 訪問;外網 IP 如 "110.110.110.110" 表示只允許該 IP 訪問。

端口:大於 1024 小於等於 65535。小於 1024 時需要 root 權限運行該進程。

(2)name:Gateway 進程名。以便在 Bash 等終端里查看區分。

(3)count:Gateway 進程數。充分利用多 CPU 資源。默認為 1。如何設置進程數,請參考這里

(4)lanIp:Gateway 進程所在服務器的內網 IP,默認填寫 "127.0.0.1" 即可。多服務器分布式部署 時要填寫真實 IP。無論如何都不能填寫 "0.0.0.0"。

(5)startPort:Gateway 進程啟動后監聽的起始端口(本機端口),用來給 BusinessWorker 進程提供連接服務,然后兩者通過這個端口建立通訊。假設進程數 count 為 4,起始端口 startPort 為 2003,則 會啟動 4 個 Gateway進程,各進程分別監聽 2003、2004、2005、2006 端口。

(6)registerAddress:向 Register 進程的注冊地址,格式為"IP + 端口",如 "127.0.0.1:1236"。和 BusinessWorker 進程指定的注冊地址要保持一致

(7)心跳設置:為了防止長時間不通訊被路由節點強行斷開或斷電斷網等極端事件,必須加心跳。相關屬性有 pingInterval、pingNotResponseLimit、pingInterval。詳細心跳設置請參考服務端到客戶端的心跳檢測

pingInterval:心跳間隔,單位秒,0 表示不發送心跳檢測。

pingNotResponseLimit:客戶端連續 $pingNotResponseLimit * $pingInterval 秒內不回應心跳則斷開連接。

pingData:心跳數據,可任意,客戶端能識別就行。

(8)onWorkerStart:Gateway 進程啟動后的回調函數。

(9)onWorkerStop:Gateway 進程關閉的回調函數。

(10)onConnect:當有客戶端連接上來時觸發。與 Events::onConnect 的區別是 Events::onConnect 方法運行在 BusinessWorker 進程上。而 Gateway::onConnect 方法是運行在Gateway 進程上,無法使用 \GatewayWorker\Lib\Gateway 類提供的接口。

(11)onClose:當有客戶端連接關閉時觸發。同樣與Events::onClose的區別是 Gateway::onClose 方法是運行在 Gateway 進程上,無法使用 \GatewayWorker\Lib\Gateway 類提供的接口。

 

3、BusinessWorker進程

BusinessWorker 進程負責運行業務邏輯。BusinessWorker 進程收到 Gateway 進程轉發來的事件和請求時,會默認調用 Events.php 中的 onConnect、onMessage、onClose 方法處理事件和數據。

該進程由 BusinessWorker 類實例化,並隨進程啟動進駐內存。

它可定制的有:

(1)name:BusinessWorker 進程名。以便在 Bash 等終端里查看區分。

(2)count:BusinessWorker 進程數。充分利用多 CPU 資源。默認為 1。如何設置進程數,請參考這里

(3)registerAddress:向 Register 進程的注冊地址,格式為"IP + 端口",如 "127.0.0.1:1236"。和 Gateway 進程指定的注冊地址要保持一致

(4)onWorkerStart:BusinessWorker 進程啟動后的回調函數

(5)onWorkerStop:BusinessWorker 進程關閉的回調函數。

(6)eventHandler:指定 BusinessWorker 進程里實際處理業務邏輯的類,默認是 Events。也就是默認使用 Events.php 中的 Events 類來處理業務。業務類至少要實現onMessage 靜態方法,onConnect 和 onClose 靜態方法可以不用實現。(如果使用了命名空間,建議填寫完全限定名稱的命名空間。)

 。

Events.php 

上面提到了 Events.php,它是實際處理業務邏輯的類 Events 所在的文件。我們在實際的開發中,只需要關注這一個文件。

Events 里有 5 個事件回調的處理方法,按照發生順序,依次是

  • onWorkerStart (Worker $businessWorker):當 BusinessWorker 進程啟動時觸發。每個進程生命周期內只觸發一次。
  • onConnect (string $client_id):當客戶端連接上 Gateway 進程時觸發(TCP 三層握手)。

  • onMesssge (string $client_id, mixed $recv_data):當客戶端發來數據,也就是 Gateway 進程收到數據后觸發。
  • onClose (string client_id):當客戶端連接斷開時觸發。無論是客戶端還是服務端主動斷開,都會觸發。

  • onWorkerStop (Worker $businessWorker):當 BusinessWorker 進程退出時觸發。每個進程生命周期內只觸發一次。

這里面我們常用到的是 onMessage 和 onClose 回調,其他比較少用。

上面的回調事件里有一個比較重要的參數:$client_id。client_id 是 20 個字符的定長字符串,用來全局標識一個 Socket 連接。每個客戶端連接都會被分配一個全局唯一的 client_id。客戶端關閉連接時,對應的 client_id 會失效。當客戶端再次打開一個 Socket 連接時,會被分配一個新的 client_id。

 

Lib\Gateway類提供的接口

既然(默認)在 Events.php 中處理實際的業務邏輯,回調的事件我們已經知道了。那么怎么向客戶端發送消息呢?

命名空間 \GatewayWorker\Lib\Gateway 指向的這個 Gateway 類,提供了一組單發、群發和廣播的接口,在 Events.php 中向客戶端發信的時候就可以使用這個類。它提供的接口非常豐富:

Gateway::sendToAll($data);      // 向所有客戶端發送數據
Gateway::sendToClient($client_id, $data);  // 向某個客戶端發送數據
Gateway::closeClient($client_id);      // 關閉某個客戶端
Gateway::isOnline($client_id);  // 判斷某客戶端連接是否在線 
Gateway::bindUid($client_id, $uid);    // 綁定 uid 與 client_id 
Gateway::unbindUid($client_id, $uid);  // 取消 uid 與 某個 client_id 的綁定
Gateway::isUidOnline($uid);      // 某個 uid 是否在線
Gateway::GetClientIdByUid();     // 獲取與 uid 綁定的 client_id 列表(一對多)
Gateway::sendToUid($uid, $data); // 向所有 uid 發送
Gateway::joinGroup($client_id, $group);  // 把該 client_id 加入群組
Gateway::leaveGroup($client_id, $group); // 將 client_id 離開群組
Gateway::sendToGroup($group, $data);     // 向某群組 group 發送
Gateway::getClientCountByGroup($group);  // 獲取某個組的在線連接數
Gateway::getClientSessionsByGroup($group); // 獲取某個組的連接信息
Gateway::getClientInfoByGroup($group);   // getClientSessionsByGroup 的別名
Gateway::getAllClientCount();     // 獲取所有的在線連接數
Gateway::getAllClientSessions();  // 獲取所有在線用戶的 session
Gateway::getAllClientInfo();      // getAllClientSessions 的別名
Gateway::setSession($client_id, $session);      // 設置 session,原 session 值會被覆蓋
Gateway::updateSession($client_id, $session);   // 更新 session,實際上是與舊的session合並
Gateway::getSession($client_id);  // 獲取某個 client_id的 session

 這里面比較重要的是 GatewayWorker 的超全局數組 $_SESSION。每個客戶端連接對應一個 Session 會話,並由 Gateway 進程存儲在內存里。示例如下,在收到客戶端消息時,打印所有在線連接的 Session:

use \GatewayWorker\Lib\Gateway;

class Events
{
    ...
    public onMessage($client_id, $message)
    {
        $_SESSION['name'] = $message['name']; // 操作當前用戶的 Session 時,直接使用 $_SESSION 即可
        var_export(Gateway::getAllClientSessions());
    }
    ...
}
打印出的數據類似如下:

array(
    '7f00000108fc00000008' => array('name' => 'Tom'),
    '7f00000108fc00000009' => array('name' => 'Joan')
)

注意上面的注釋,操作當前連接上的 Session 時,直接使用 $_SESSION['xx'] = 'xxx'; 的方式賦值即可,操作其他用戶的 Session 時用  Gateway::setSession 接口。

此外,如果你在 GatewayWorker 的進程模型里需要獲取客戶端、服務端的 IP,請使用 $_SERVER 數組。它由 Workerman 框架定義,內置了 5 個數組成員,數組 key 分別如下,詳細請參考文檔

REMOTE_ADDR   // 客戶端IP(如果客戶端處於局域網,則是客戶端所在局域網的出口IP)
REMOTE_PORT   // 客戶端端口(如果客戶端處於局域網,則是客戶端所在局域網的出口端口)
GATEWAY_ADDR  // Gateway 進程所在服務器的 IP
GATEWAY_PORT  // Geteway 監聽的端口,這對於多端口應用中在 Events.php 里區分客戶端連的是哪個端口非常有用。
GATEWAY_CLIENT_ID  // 全局唯一的客戶端 IP

好的。有關 GatewayWorker 框架的基礎暫時就梳理這么多。更多 GatewayWorker 開發和部署的細節或問題,比如心跳檢測、設置定時器、合理選擇多進程、分布式部署、定制通訊協議、啟用 wss 協議等等,都在文檔里有詳細的介紹。車在下面。

我感覺這一篇有點長了,所以將在下一篇開始梳理 GatewayWorker 與 Laravel 框架的整合。

 

相關鏈接

GatewayWorker 在線文檔:http://doc2.workerman.net/326102

Workerman 在線文檔:http://doc.workerman.net/

Workerman 官網:https://www.workerman.net/

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM