workerman-gateway 构建 websocket 服务
前言
Workerman 是一个高性能的 PHP socket 服务器框架,支持 TCP、UDP、Unix 套接字、HTTP、Websocket 等多种协议。本文将介绍如何使用 workerman-gateway 构建一个 websocket 服务。并且在 Laravel 中使用。
与 Laravel 等 框架结合 逻辑
摘自 workerman
官方文档 传送门:workerman-gateway
使用GatewayWorker时开发者最关心的是如何与现有mvc框架(ThinkPHP Yii laravel等)整合,以下是官方推荐的整合方式。见示意图:
总体原则
现有
mvc
框架项目与GatewayWorker
独立部署互不干扰所有的业务逻辑都由网站页面
post
/get
到mvc
框架中完成GatewayWorker
不接受客户端发来的数据,即GatewayWorker
不处理任何业务逻辑,GatewayWorker
仅仅当做一个单向的推送通道仅当
mvc
框架需要向浏览器主动推送数据时才在mvc
框架中调用Gateway
的API
GatewayClient
完成推送。
安装
安装 workerman-gateway
1 | composer require 'workerman/workerman' -W |
创建 websocket 服务
在 Laravel 项目创建一个Command WorkermanCommand.php
文件,内容如下:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
namespace App\Console\Commands;
use GatewayWorker\BusinessWorker;
use GatewayWorker\Gateway;
use GatewayWorker\Register;
use Illuminate\Console\Command;
use Workerman\Worker;
class WorkermanCommand extends Command
{
/**
* The name and signature of the console command.
*
* @var string
*/
protected $signature = 'wk {action} {--d}';
/**
* The console command description.
*
* @var string
*/
protected $description = 'Start a Workerman server.';
private static int $businessWorkerPort = 2238;
private static int $gatewayPort = 21579;
/**
* Execute the console command.
*
*/
public function handle()
{
global $argv;
$action = $this->argument('action');
$argv[0] = 'wk';
$argv[1] = $action;
$argv[2] = $this->option('d') ? '-d' : '';
$this->start();
}
private function start()
{
$this->startGateWay();
$this->startBusinessWorker();
$this->startRegister();
Worker::runAll();
}
private function startBusinessWorker()
{
$worker = new BusinessWorker();
// worker名称
$worker->name = 'BusinessWorker';
// bussinessWorker进程数量
$worker->count = 1;
// 服务注册地址
$worker->registerAddress = '127.0.0.1:' . self::$businessWorkerPort;
// 设置处理业务的类,此处制定Events的命名空间
$worker->eventHandler = \App\Workerman\Events::class;
}
private function startGateWay()
{
// gateway 进程,这里使用Text协议,可以用telnet测试
$gateway = new Gateway("websocket://0.0.0.0:" . self::$gatewayPort);
// gateway名称,status方便查看
$gateway->name = 'Gateway';
// gateway进程数
$gateway->count = 1;
// 本机ip,分布式部署时使用内网ip
$gateway->lanIp = '127.0.0.1';
// 内部通讯起始端口,假如$gateway->count=4,起始端口为4000
$gateway->startPort = self::$gatewayPort - 1200;
// ping间隔
$gateway->pingInterval = 55;
// 要求客户端在$pingInterval时间内必须发送数据来维持链接,否则客户端会被断开
$gateway->pingNotResponseLimit = 1;
// 心跳数据
$gateway->pingData = '{"type":"Heart"}';
// 服务注册地址
$gateway->registerAddress = '127.0.0.1:' . self::$businessWorkerPort;
}
private function startRegister()
{
// register 进程
new Register('text://0.0.0.0:' . self::$businessWorkerPort);
}
}
启动命令
1 | php artisan wk start |
事件处理
在 app/Workerman/Events.php
文件中,内容如下:
这里的代码是一个示例,是以MVC和Workerman解耦的方式,websocket服务只作为单方向下发信息。
所有来自用户的信息都不直接发送到ws服务器,而是通过mvc服务器短链接的形式,然后通过gatewayclient
下发信息。
需要注意的是,通过 gatewayclient
下发信息是不会经过 onMessage
而是直接通过 gateway
进行下发。
你可以根据自己的需求来修改。
1 |
|
后端控制器
在 app/Http/Controllers/WebSocketController.php
文件中,内容如下:
利用 gatewayclient
可以实现后端控制器,比如用户注册,用户验证,用户上线,用户下线,用户发送消息等等。
1 |
|
后记
workerman
的使用场景非常广泛,比如聊天室,直播间,游戏等等。
并且 BusinessWorker
Gateway
Register
三个进程可以分布式部署,可以实现高可用。
其实目前还有几个问题
问题1
通过mvc的业务系统通过gatewayclient进行数据下发的,所以所有来自web前端的请求都是通过短链接形式上报,再下发。
那当群(group)里有人掉线或者下线(没有通过接口,即 没有通过 leaveGroup)的方式来离开。那我就无法给群组下发 xxx 离开群聊 的信息。
另外同样的,我给某个人发信息的时候,我可以在业务接口里先判断此人是否在线
下面是伪代码1
2
3
4
5
6
7
8
9
10public function say(int $uid, string $message)
{
if (GatewayClient::isUidOnline($uid)){
// 发送消息
GatewayClient::sendToUid($uid, $message);
}else{
// 存储离线消息
Redis::lpush('wss:user.' . $uid . ':offline.message', $message);
}
}
但这个依旧有问题,我为什么在发信息之前无法判断这人是否在线。