1. 所需要的包
1 "workerman/gateway-worker": "^3.0", 2 "workerman/gatewayclient": "^3.0",
2. 創建啟動文件 這里使用 artisan
1 php artisan make:command GatewayWorker
3. 編寫啟動文件
1 <?php 2 3 namespace App\Console\Commands; 4 5 use App\Workerman\Events; 6 use GatewayWorker\BusinessWorker; 7 use Illuminate\Console\Command; 8 use Workerman\Worker; 9 use GatewayWorker\Gateway; 10 use GatewayWorker\Register; 11 12 class GatewayWorker extends Command 13 { 14 /** 15 * The name and signature of the console command. 16 * 17 * @var string 18 */ 19 protected $signature = 'GatewayWorker {action} {--daemon}'; 20 21 /** 22 * The console command description. 23 * 24 * @var string 25 */ 26 protected $description = 'Start a GatewayWorker Server.'; 27 28 /** 29 * constructor 30 */ 31 public function __construct() 32 { 33 parent::__construct(); 34 } 35 36 /** 37 * Execute the console command. 38 * 39 * 40 */ 41 public function handle() 42 { 43 global $argv; 44 45 if (!in_array($action = $this->argument('action'), ['start', 'stop', 'restart','reload','status'])) { 46 $this->error('Error Arguments'); 47 exit; 48 } 49 50 $argv[0] = 'gateway-worker:server'; 51 $argv[1] = $action; 52 $argv[2] = $this->option('daemon') ? '-d' : ''; 53 54 $this->start(); 55 } 56 57 private function start() 58 { 59 $this->startGateWay(); 60 $this->startBusinessWorker(); 61 $this->startRegister(); 62 Worker::runAll(); 63 } 64 65 private function startBusinessWorker() 66 { 67 $worker = new BusinessWorker(); 68 $worker->name = 'BusinessWorker'; #設置BusinessWorker進程的名稱 69 $worker->count = 4; #設置BusinessWorker進程的數量 70 $worker->registerAddress = '127.0.0.1:1236'; #注冊服務地址 71 $worker->eventHandler = Events::class; #設置使用哪個類來處理業務,業務類至少要實現onMessage靜態方法,onConnect和onClose靜態方法可以不用實現 72 } 73 74 private function startGateWay() 75 { 76 $gateway = new Gateway("websocket://0.0.0.0:1119"); 77 $gateway->name = 'Gateway'; #設置Gateway進程的名稱,方便status命令中查看統計 78 $gateway->count = 4; #進程的數量 79 $gateway->lanIp = '127.0.0.1'; #內網ip,多服務器分布式部署的時候需要填寫真實的內網ip 80 $gateway->startPort = 2000; #監聽本機端口的起始端口 81 $gateway->pingInterval = 30; 82 $gateway->pingNotResponseLimit = 0; #服務端主動發送心跳 83 $gateway->pingData = '{"mode":"heart"}'; 84 $gateway->registerAddress = '127.0.0.1:1236'; #注冊服務地址 85 } 86 87 private function startRegister() 88 { 89 new Register('text://0.0.0.0:1236'); 90 } 91 92 }
PS: Register 的端口 以及 Gateway 、BusinessWorker 的 registerAddress 3個端口必須一致,如果是單機部署 IP可以不指定默認為 127.0.0.1 具體的啟動參數 和進程、服務設置 參考官方文檔
4. 編寫業務處理類 Events
Events 類為業務處理的入口文件,當有客戶端事件發生時會觸發相應的回調 ,可實現5個靜態方法
onWorkerStart: 當businessWorker進程啟動時觸發。每個進程生命周期內都只會觸發一次。
onConnect: 當客戶端連接上gateway進程時(TCP三次握手完畢時)觸發的回調函數。
onWebSocketConnect: 當客戶端連接上gateway完成websocket握手時觸發的回調函數.
onMessage: 當客戶端發來數據(Gateway進程收到數據)后觸發的回調函數
onClose: 客戶端與Gateway進程的連接斷開時觸發。不管是客戶端主動斷開還是服務端主動斷開,都會觸發這個回調。一般在這里做一些數據清理工作。
onWorkerStop: 當businessWorker進程退出時觸發。每個進程生命周期內都只會觸發一次。
這里我再 app/ 目錄下 建立 Workerman/Events.php
1 <?php 2 3 namespace App\Workerman; 4 5 use GatewayWorker\Lib\Gateway; 6 use Illuminate\Foundation\Auth\Access\AuthorizesRequests; 7 8 class Events 9 { 10 use AuthorizesRequests; 11 12 /** 13 *進程啟動時觸發,每個進程生命周期只會觸發一次 14 *這里全局初始化工作,例如設置定時器,初始化redis等連接等 15 *不要在onWorkerStart內執行長時間阻塞或者耗時的操作,這樣會導致BusinessWorker無法及時與Gateway建立連接,造成應用異常 16 *無返回值,任何返回值都會被無視 17 * @param $businessWorker 18 */ 19 public static function onWorkerStart($businessWorker) 20 { 21 echo "Hello , This Is His App\n"; 22 } 23 24 25 26 /** 27 * 當客戶端連接上gateway進程時(TCP三次握手完畢時)觸發的回調函數。 28 * 無返回值,任何返回值都會被視為無效的 29 * $client_id是服務端自動生成的並且無法自定義。 30 * 可以用過Gateway::bindUid($client_id, $uid)把自己系統的id與client_id綁定 31 * @param $client_id 32 */ 33 public static function onConnect($client_id) 34 { 35 Gateway::sendToCurrentClient("Your client_id is $client_id"); 36 37 } 38 39 /** 40 * 當客戶端連接上gateway完成websocket握手時觸發的回調函數。 41 * 此回調只有gateway為websocket協議並且gateway沒有設置onWebSocketConnect時才有效。 42 * 無返回值,任何返回值都會被視為無效的 43 * $client_id client_id固定為20個字符的字符串,用來全局標記一個socket連接,每個客戶端連接都會被分配一個全局唯一的client_id。 44 * $data websocket握手時的http頭數據,包含get、server等變量 45 * @param $client_id 46 * @param $data 47 */ 48 public static function onWebSocketConnect($client_id, $data) 49 { 50 51 Gateway::sendToAll('歡迎' . $client_id); 52 // var_export($data); 53 // if(!isset($data['Authorization']) && !empty($data['Authorization'])) 54 // { 55 // Gateway::sendToClient($client_id,'not Authorization'); 56 //// Gateway::closeClient($client_id); 57 // } 58 59 } 60 61 /** 62 * 當客戶端發來數據(Gateway進程收到數據)后觸發的回調函數 63 * 無返回值,任何返回值都會被視為無效的 64 * @param $client_id 65 * @param $message 66 * @throws \Exception 67 */ 68 public static function onMessage($client_id, $message) 69 { 70 Gateway::sendToAll($message); 71 // 群聊,轉發請求給其它所有的客戶端 72 } 73 74 /** 75 * 客戶端與Gateway進程的連接斷開時觸發。不管是客戶端主動斷開還是服務端主動斷開,都會觸發這個回調。一般在這里做一些數據清理工作。 76 * @param $client_id 77 * @throws \Exception 78 */ 79 public static function onClose($client_id) 80 { 81 // 廣播 xxx logout 82 GateWay::sendToAll("client[$client_id] logout\n"); 83 } 84 }
5. 在你的業務代碼里使用 GatewayClient
1 <?php 2 3 4 namespace App\Http\Controllers\Api; 5 6 use App\Http\Controllers\Controller; 7 use GatewayClient\Gateway; 8 9 class IndexController extends Controller 10 { 11 public function __construct() 12 { 13 //這里填寫Register服務的ip和Register端口,注意端口不是gateway端口 也可以用外網IP 14 Gateway::$registerAddress = '127.0.0.1:1236'; 15 } 16 17 public function sendMessage() 18 { 19 Gateway::sendToAll('當前在線人數: ' . Gateway::getAllClientCount()); 20 Gateway::sendToClient('xxx', 'message'); 21 //TODO 更多方法 22 } 23 }
到這里代碼上的就處理的差不多了,接下來看部署
常見問題說明
- pcntl 配置問題
日志信息
Fatal error: Uncaught Error: Call to undefined function pcntl_signal()
因為默認這個沒有啟用,解決方法:
docker-php-ext-install pcntl
- 其他依賴問題
安裝event 需要sockets,安裝sockets 需要openssl
我本地使用的是laradock 服務器使用的 dnmp
本地 ---
需要在 docker-composer.yml php容器 (隨意)部分 添加端口映射 將你需要用到的都添加進去 例如
1 ports: 2 - "${WORKSPACE_SSH_PORT}:22" 3 - "1119:1119" 4 - "2000:2000" 5 - "12360:12360" 6 - "1236:1236" 7 - "2001:2001" 8 - "2002:2002" 9 - "2003:2003"
然后重啟對應的容器 docker-composer restart laradock_php_1

服務器 --- dnmp 目錄下的 .env
修改 .env ⽂件,在 PHP 擴展中新增 redis , pcntl
修改 docker-compose.yml ⽂件,在 php 模塊新增端⼝ 1119 提供 Websocket 服務
docker-composer restart php
nginx 設置 對應的conf 配置文件中 server塊外添加
upstream ws { server php:1119; keepalive 64; }
將ws 鏈接轉發到 php:1119
docker exec nginx nginx -t
docker exec nginx nginx -s reload
到這里就差不多啦
docker exec -it php bash cd /youApp/ php artisan GatewayWorker start 以debug 運行(或者 --daemon 守護進程方式運行)
雲服務器需要打開防火牆以及對應的安全組,或者策略 開放所需端口
測試:
1 <!DOCTYPE HTML> 2 <html> 3 <head> 4 <meta charset="utf-8"> 5 <title>ws 測試</title> 6 7 <script type="text/javascript"> 8 // 假設服務端ip為127.0.0.1 9 ws = new WebSocket("ws://x.x.x.x:1119"); 10 ws.onopen = function() { 11 alert("連接成功"); 12 ws.send('tom'); 13 alert("給服務端發送一個字符串:tom"); 14 }; 15 ws.onmessage = function(e) { 16 alert("收到服務端的消息:" + e.data); 17 }; 18 </script> 19 20 </head> 21 <body> 22 23 </body> 24 </html>
