Lumen/Laravel 集成 GatewayWorker (Workerman),实现简单的聊天系统.
GatewayWorker 与 Workerman的关系
Workerman相比GatewayWorker也更底层,需要开发者有一定的多进程编程经验。因为绝大多数开发者的目标是基于Workerman开发TCP长连接应用,而长连接应用服务端有很多共同之处,例如它们有相同的进程模型以及单发、群发、广播等接口需求。所以才有了GatewayWorker框架,GatewayWorker是基于Workerman开发的一个TCP长连接框架,实现了单发、群送、广播等长连接必用的接口。GatewayWorker框架实现了Gateway Worker进程模型,天然支持分布式多服务器部署,扩容缩容非常方便,能够应对海量并发连接。可以说GatewayWorker是基于Workerman实现的一个更完善的专门用于实现TCP长连接的项目框架。项目是长连接并且需要客户端与客户端之间通讯,建议使用GatewayWorker。
Step1.引入包
- 在
composer.json
件的require引入这三行"workerman/gateway-worker": "^3.0", "workerman/gatewayclient": "^3.0", "workerman/workerman": "^3.5"
- 终端的项目根目录输入
composer update
Step2. 创建一个Command
- 在
App\Console\Commands
目录创建GatewayWorkerServer.php
文件,作为服务端口的启动文件
<?php
namespace App\Console\Commands;
use GatewayWorker\BusinessWorker;
use Illuminate\Console\Command;
use Workerman\Worker;
use GatewayWorker\Gateway;
use GatewayWorker\Register;
class GatewayWorkerServer extends Command
{
/**
* The name and signature of the console command.
*
* @var string
*/
protected $signature = 'gateway-worker:server {action} {--daemon}';
/**
* The console command description.
*
* @var string
*/
protected $description = 'Start a GatewayWorker Server.';
/**
* constructor
*/
public function __construct()
{
parent::__construct();
}
/**
* Execute the console command.
*
* [@return](https://learnku.com/users/31554) mixed
*/
public function handle()
{
global $argv;
if (!in_array($action = $this->argument('action'), ['start', 'stop', 'restart'])) {
$this->error('Error Arguments');
exit;
}
$argv[0] = 'gateway-worker:server';
$argv[1] = $action;
$argv[2] = $this->option('daemon') ? '-d' : '';
$this->start();
}
private function start()
{
$this->startGateWay();
$this->startBusinessWorker();
$this->startRegister();
Worker::runAll();
}
private function startBusinessWorker()
{
$worker = new BusinessWorker();
$worker->name = 'BusinessWorker'; #设置BusinessWorker进程的名称
$worker->count = 1; #设置BusinessWorker进程的数量
$worker->registerAddress = '127.0.0.1:12360'; #注册服务地址
$worker->eventHandler = \App\GatewayWorker\Events::class; #设置使用哪个类来处理业务,业务类至少要实现onMessage静态方法,onConnect和onClose静态方法可以不用实现
}
private function startGateWay()
{
$gateway = new Gateway("websocket://0.0.0.0:23460");
$gateway->name = 'Gateway'; #设置Gateway进程的名称,方便status命令中查看统计
$gateway->count = 1; #进程的数量
$gateway->lanIp = '127.0.0.1'; #内网ip,多服务器分布式部署的时候需要填写真实的内网ip
$gateway->startPort = 2300; #监听本机端口的起始端口
$gateway->pingInterval = 30;
$gateway->pingNotResponseLimit = 0; #服务端主动发送心跳
$gateway->pingData = '{"mode":"heart"}';
$gateway->registerAddress = '127.0.0.1:12360'; #注册服务地址
}
private function startRegister()
{
new Register('text://0.0.0.0:12360');
}
}
//ws = new WebSocket("ws://127.0.0.1:23460");
//ws.onopen = function() {
// ws . send('{"mode":"say","order_id":"21",type:1,"content":"文字内容","user_id":21}');
// ws . send('{"mode":"chats","order_id":"97"}');
//};
//ws.onmessage = function(e) {
// console.log("收到服务端的消息:" + e.data);
//};
- 在
App\Console\Kernel
引入GatewayWorkerServer::class
Step3.创建监听文件
在app目录下创建 GatewayWorker/Events.php
文件
<?php
namespace App\GatewayWorker;
use App\Models\Home\Order;
use App\Models\Home\OrderChat;
use GatewayWorker\Lib\Gateway;
use Illuminate\Support\Facades\Log;
use mysql_xdevapi\Exception;
class Events
{
public static function onWorkerStart($businessWorker)
{
echo "BusinessWorker Start\n";
}
public static function onConnect($client_id)
{
Gateway::sendToClient($client_id, json_encode(['type' => 'init', 'client_id' => $client_id]));
}
public static function onWebSocketConnect($client_id, $data)
{
}
public static function onMessage($client_id, $message)
{
$response = ['errcode' => 0, 'msg' => 'ok', 'data' => []];
$message = json_decode($message);
if (!isset($message->mode)) {
$response['msg'] = 'missing parameter mode';
$response['errcode'] = ERROR_CHAT;
Gateway::sendToClient($client_id, json_encode($response));
return false;
}
switch ($message->mode) {
case 'say': #处理发送的聊天
if (self::authentication($message->order_id, $message->user_id)) {
OrderChat::store($message->order_id, $message->type, $message->content, $message->user_id);
} else {
$response['msg'] = 'Authentication failure';
$response['errcode'] = ERROR_CHAT;
}
break;
case 'chats': #获取聊天列表
$chats = OrderChat::where('order_id', $message->order_id)->get();
$response['data'] = ['chats' => $chats];
break;
default:
$response['errcode'] = ERROR_CHAT;
$response['msg'] = 'Undefined';
}
Gateway::sendToClient($client_id, json_encode($response));
}
public static function onClose($client_id)
{
Log::info('close connection' . $client_id);
}
private static function authentication($order_id, $user_id): bool
{
$order = Order::find($order_id);
if (is_null($order)) {
return false;
}
return in_array($user_id, [$order->user_id, $order->to_user_id]) ? true : false; #判断属不属于这个订单的两个人
}
}
注意 GatewayWorkerServer.php
文件中的 startBusinessWorker
方法中$worker->eventHandler
属性的路径是Event.php
的路径,namespace可以自己定义
最后执行一个 composer dump-autoload
Step4 测试
-
开启服务,在终端输入
php artisan gateway-worker:server start
-
在浏览器F12打开调试模式,在Console里输入
ws = new WebSocket("ws://127.0.0.1:23460");
ws = new WebSocket("ws://127.0.0.1:23460");
ws.onopen = function() {
ws . send('{"mode":"say","order_id":"375","type":1,"content":"你好","user_id":100036}');
ws . send('{"mode":"chats","order_id":"375"}');
};
ws.onmessage = function(e) {
console.log("收到服务端的消息:" + e.data);
};
简单实现订单里的买家和卖家交流、获取聊天记录,也可以把相关的信息存到缓存里,
在 public static function onMessage()
里修改自己的业务逻辑
http://doc2.workerman.net/ GatewayWorker2.x 3.x 手册
本作品采用《CC 协议》,转载必须注明作者和本文链接
怎么重启
你好,没做守护进程吗
@walt-white php artisan gateway-worker:server restart --daemon
我试了一下,报错
@walt-white 我刚加上去的 restart , 原本可以先 stop 再 start,
--daemon
是后台运行嗯嗯,谢谢你,公司目前打算做一款社交软件,我学习一下,谢谢你百忙之中的回复。
你好,请教你个问题,这个社交系统,需要消息队列处理吗
@walt-white 我这个没有用消息队列。 广播系统才需要用。
@summer-1994 明白了
根据你的步骤,然后我就卡住了,黑窗口执行完 启动命令后,就没任何反应了,网上的教程都照着做,就是没反应,也不给提示,崩溃中,,,workman 也下载了,根据另一篇文章做了一遍,又是这一步,开启,没反应,没提示,如图,这张图真的,所有的操作都是这张图,不给提示不给反应,求大神捎带看一眼
@wuwei18811707025 不支持Windows环境
日常收藏
@wuwei18811707025 显然监听端口没有开启,你根据gateway官网文档的内容,把start.php改成一个win_start.bat启动文件
跑通了~收藏 :kissing_heart:
@wuwei18811707025 windows 上配置php环境变量
从头开始一步一步跟着做的 最后执行最后执行一个 composer dump-autoload 出现错误 无法下一步;十万火急,在线跪等,感恩
@YooFishs
为什么我用数据库的时候报错了,找不到驱动could not find driver
为什么我收不到服务端消息
很nice的文章,点赞
哎,贴个图吧
你好 请问一下 怎么关闭服务端主动的心跳 而是接受客户端的心跳呢
@zx_6017 看下官方文档,里面好像有。
定制协议的那个处理分包粘包的文件要放在哪里啊
好的 感谢
问下 引入这个包的作用是什么 "workerman/gatewayclient": "^3.0"
启动成功了,感谢分享
完美
关闭时会产生两个僵尸进程 ,请问作者是怎么解决的
如果要弄两个不一样的php artisan gateway-worker:server,怎么设置