本文的目的是基於 GatewayWorker 官方手冊,梳理一次 GatewayWorker,並在實踐中與 MVC 框架整合的思路(附最終的項目源碼)。若是你已經理解了整合這一起的知識,那麼就能夠關掉這個網頁了。時間蠻寶貴的~php
這篇是上篇,梳理 GatewayWorker 基礎,下篇是 GatewayWorker 與 Laravel 整合聊天室。若是你具有了 GatewayWorker 基礎,請直接閱讀下篇。html
好久之前就想作一個聊天室了。查了下 "php 通訊",找到了可用的東西:Socket、WebSocket、 Workerman 以及 GatewayWorker。Socket(接口)提供了一組端到端互相通訊的接口,做爲通訊的核心功能。Websocket(協議)定義了通訊中數據的封裝和顯示的格式,並且最大的特色是它支持服務端向客戶端的主動推送,這一點是 HTTP 作不到的。而 Workerman (框架)將這二者很好地整合在了一塊兒(固然不只僅於此)。GatewayWorker(框架)是在 Workerman 的基礎上開發的 TCP 長鏈接應用框架,提供了單發、羣發和廣播等接口,還能夠客戶端和客戶端通訊。數組
因此最終我選擇了 GatewayWorker 做爲 Socket 監聽的服務端,Laravel 做爲 HTTP 請求的業務處理框架,完成一個響應式的在線聊天室(項目地址在下一篇文章最後)。服務器
先理解一下工做原理,能夠對 GatewayWorker 有個總體的把握。這一起其實手冊裏已經詳細不囉嗦地解釋清楚了。我這裏再理一下:session
一、Register、Gateway、BusinessWorker 3 種進程依次啓動(由於支持多進程,因此我說「種」,而不是「個」)app
二、Gateway 進程和 BusinessWorker 進程啓動後向 Register 服務進程發起長鏈接註冊自身。框架
三、Register 服務進程收到 Gateway 的註冊後,把全部 Gateway 進程的通信地址寫入內存。socket
四、Register 服務進程收到 BusinessWorker 的註冊後,把內存中的 Gateway 進程通信地址發給全部 BusinessWorker 進程。分佈式
五、BusinessWorker 進程收到全部 Gateway 進程的通信地址後,嘗試鏈接 Gateway。函數
六、至此,全部 Gateway 和 BusinessWorker 進程就經過 Register 服務進程創建了長鏈接。
若是期間有新的 Gateway 註冊到 Register(通常是分佈式部署加機器),新 Gateway 的通信地址會被廣播給全部 BusinessWorker,BusinessWorker 收到通知後創建新鏈接。
若是期間有 Gateway 下線,Register 會收到通知、刪除這個 Gateway 的內部通信地址,並將新的內部通信地址列表廣播給全部 BusinessWorker,BusinessWorker 再也不鏈接下線的 Gateway。
七、客戶端的鏈接事件和鏈接上的數據會經由 Gateway 轉發給 BusinessWorker,BusinessWorker 默認調用 Events.php 中 Events 類的 onConnect、onMessage、onClose 事件回調處理業務邏輯。
八、BusinessWorker 負責運行全部的業務邏輯,實際的處理邏輯默認在 Events.php 中實現。
GatewayWorker 是以進程的形式進駐內存的,瞭解了它的工做原理以後,有必要理解一下它的進程模型。
GatewayWorker 主要有 3 種進程:Register 進程、Gateway 進程和 BusinessWorker 進程。這 3 種進程分別對應了內核源碼中的 Register 類、Gateway 類和 BusinessWorker 類,而且它們都是基於 Workerman 框架的 Worker 類開發的,因此這 3 種進程都有一些公共的屬性,好比 name、count、onWorkerStart、onWorkerStop 等等。能夠說,GatewayWorker 裏全部的進程都是 Worker 進程。
Register 進程主要負責 Gateway 進程 與 BusinessWorker 進程創建鏈接並內部通信。
該進程由 Register 類實例化,並隨進程啓動進駐內存。
它可定製的只有實例化時指定自身所在的服務進程地址。包括 IP 和端口,而且目前只支持 text 協議。text 協議是 Workerman 框架定義的一種文本協議(協議格式爲:數據包 + 換行符)。
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進程,各進程分別監聽 200三、200四、200五、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 類提供的接口。
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 所在的文件。咱們在實際的開發中,只須要關注這一個文件。
Events 裏有 5 個事件回調的處理方法,按照發生順序,依次是
onConnect (string $client_id):當客戶端鏈接上 Gateway 進程時觸發(TCP 三層握手)。
onClose (string client_id):當客戶端鏈接斷開時觸發。不管是客戶端仍是服務端主動斷開,都會觸發。
這裏面咱們經常使用到的是 onMessage 和 onClose 回調,其餘比較少用。
上面的回調事件裏有一個比較重要的參數:$client_id。client_id 是 20 個字符的定長字符串,用來全局標識一個 Socket 鏈接。每一個客戶端鏈接都會被分配一個全局惟一的 client_id。客戶端關閉鏈接時,對應的 client_id 會失效。當客戶端再次打開一個 Socket 鏈接時,會被分配一個新的 client_id。
既然(默認)在 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/