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>