GatewayWorker 与 Workerman的关系

Workerman可以看做是一个纯粹的socket类库,可以开发几乎所有的网络应用,不管是TCP的还是UDP的,长连接的还是短连接的。Workerman代码精简,功能强大,使用灵活,能够快速开发出各种网络应用。同时Workerman相比GatewayWorker也更底层,需要开发者有一定的多进程编程经验。

因为绝大多数开发者的目标是基于Workerman开发TCP长连接应用,而长连接应用服务端有很多共同之处,例如它们有相同的进程模型以及单发、群发、广播等接口需求。所以才有了GatewayWorker框架,GatewayWorker是基于Workerman开发的一个TCP长连接框架,实现了单发、群送、广播等长连接必用的接口。GatewayWorker框架实现了Gateway Worker进程模型,天然支持分布式多服务器部署,扩容缩容非常方便,能够应对海量并发连接。可以说GatewayWorker是基于Workerman实现的一个更完善的专门用于实现TCP长连接的项目框架。

用GatewayWorker还是Workerman?

如果你的项目是长连接并且需要客户端与客户端之间通讯,建议使用GatewayWorker。
短连接或者不需要客户端与客户端之间通讯的项目建议使用Workerman。
GatewayWorker不支持UDP监听,所以UDP服务请选择Workerman。
如果你是一个有多进程socket编程经验的人,喜欢定制自己的进程模型,可以选择Workerman。

一、在 composer.json 文件的 require 引入包
    "workerman/gateway-worker": "^3.0",
    "workerman/gatewayclient": "^3.0",
    "workerman/workerman": "^3.5"

在这里插入图片描述
终端的项目根目录输入 composer install,也可以composer update不过不建议

二、在 App\Console\Commands 目录创建 GatewayWorkerServer.php 文件,作为服务端口的启动文件
<?php

namespace App\Console;

use App\GatewayWorker\Events;
use GatewayWorker\BusinessWorker;
use GatewayWorker\Gateway;
use GatewayWorker\Register;
use Illuminate\Console\Command;
use Workerman\Worker;

class GatewayWorkerServer extends Command
{
	// 命令行的名称及签名。
    protected $signature = "gateway-worker:server {action} {--daemon}";
	// 命令行的描述
    protected $description = "Start a GatewayWorker Server.";

    public function __construct()
    {
        parent::__construct();
    }

    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' : '';//该参数是以daemon(守护进程)方式启动
        $this->start();
    }

    public function start()
    {
        $this->startGateWay();
        $this->startBusinessWorker();
        $this->startRegister();
        Worker::runAll();
    }

    public function startBusinessWorker()
    {
        $work = new BusinessWorker();
        $work->name = 'BusinessWorker';#设置BusinessWorker进程的名称
        $work->count = 2;#设置BusinessWorker进程的数量
        $work->registerAddress = '127.0.0.1:9501';#注册服务地址
        $work->eventHandler = Events::class;#设置使用哪个类来处理业务,业务类至少要实现onMessage静态方法,onConnect和onClose静态方法可以不用实现
    }

    public function startGateWay()
    {
        $gateway = new Gateway("websocket://0.0.0.0:9502");
        $gateway->name = 'Gateway';#设置Gateway进程的名称,方便status命令中查看统计
        $gateway->count = 2;#进程的数量
        $gateway->lanIp = '127.0.0.1';#内网ip,多服务器分布式部署的时候需要填写真实的内网ip
        $gateway->startPort = 9000;#监听本机端口的起始端口
        $gateway->pingInterval = 30;
        $gateway->pingNotResponseLimit = 0;#服务端主动发送心跳
        $gateway->pingData = '{"mode":"heart"}';
        $gateway->registerAddress = '127.0.0.1:9501';#注册服务地址
    }

    public function startRegister()
    {
        new Register('text://127.0.0.1:9501');
    }

	//ws = new WebSocket("ws://127.0.0.1:9502");
	//ws.onopen = function() {
	//    ws . send('{"mode":"say","from_uid":1,to_uid:2,"content":"文字内容"}');
	//    ws . send('{"mode":"chats","from_uid":1}');
	//};
	//ws.onmessage = function(e) {
	//    console.log("收到服务端的消息:" + e.data);
	//};
}

注意事项:
(1)$gateway->startPort监听本机端口的起始端口要小与其它端口
(2)进程数为CPU核数的1-2倍左右即可,当然如果你是32核的你也可以设置进程数为8,看实际情况而定

三、在 App\Console\Kernel 引入 GatewayWorkerServer::class
<?php

namespace App\Console;

use Illuminate\Console\Scheduling\Schedule;
use Illuminate\Foundation\Console\Kernel as ConsoleKernel;

class Kernel extends ConsoleKernel
{
    /**
     * The Artisan commands provided by your application.
     *
     * @var array
     */
    protected $commands = [
        //
        GatewayWorkerServer::class
    ];

    /**
     * Define the application's command schedule.
     *
     * @param  \Illuminate\Console\Scheduling\Schedule  $schedule
     * @return void
     */
    protected function schedule(Schedule $schedule)
    {
        // $schedule->command('inspire')
        //          ->hourly();
    }

    /**
     * Register the commands for the application.
     *
     * @return void
     */
    // protected function commands()
    // {
        // $this->load(__DIR__.'/Commands');

        // require base_path('routes/console.php');
    // }
}

也可加上面的 protected function commands ,然后在routes/consele.php里加以下代码

Artisan::command('inspire', function () {
    $this->comment(Inspiring::quote());
})->describe('Display an inspiring quote');

在这里插入图片描述

四、在 app 目录下创建 GatewayWorker/Events.php 监听文件
<?php

namespace App\GatewayWorker;

use App\Models\Order;
use App\Models\Chat;
use GatewayWorker\Lib\Gateway;
use Illuminate\Support\Facades\Log;

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 = ['status' => 200, 'msg' => 'ok', 'data' => $message];
        $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;
        }
        $from_uid = $message->from_uid;
        $to_uid = $message->to_uid;;
        switch ($message->mode) {
            case 'say':   #处理发送的聊天
                $chats = Chats::create(['from_id' => $from_uid, 'to_id' => $to_uid, 'content' => $content, 'no' => $count + 1]);
                $chats = Chats::select(['from_id', 'to_id', 'content'])->find($chats->id);
                $response['data'] = $chats;
                if (Gateway::isUidOnline($to_uid)) {
                    $response['msg'] = '已发送至uid为:' . $to_uid;
                    Gateway::sendToUid($to_uid, json_encode($response));
                } else {
                    $response['msg'] = '对方不在线';
                }
                break;
            case 'chats':  #获取聊天列表
                $chats = Chat::where('from_id', $message->from_id)->get();
                $response['data'] = ['chats' => $chats];
                break;
            default:
                $response['status'] = 201;
                $response['msg'] = 'Undefined';
        }

        Gateway::sendToClient($client_id, json_encode($response));
    }

    public static function onClose($client_id)
    {
        Log::info('close connection' . $client_id);
    }
}

注意:每次修改events.php后,记得重启,记得重启,记得重启

五、开启服务终端,命令行输入

php artisan gateway-worker:server start
在这里插入图片描述
其它命令

以debug(调试)方式启动:php artisan gateway-worker:server start

以daemon(守护进程)方式启动:php artisan gateway-worker:server start --daemon

停止:php artisan gateway-worker:server stop

重启:php artisan gateway-worker:server restart

平滑重启:php artisan gateway-worker:server reload

查看状态:php artisan gateway-worker:server status
.

测试:在浏览器 F12 打开调试模式,在 Console 里输入

ws = new WebSocket("ws://127.0.0.1:9502");
ws.onopen = function() {
	ws . send('{"mode":"say","from_uid":1,to_uid:2,"content":"文字内容"}');
	ws . send('{"mode":"chats","from_uid":1}');
};
ws.onmessage = function(e) {
	console.log("收到服务端的消息:" + e.data);
};

就能打印出对应的信息了

参考:
Workerman官方手册、GatewayWorker官方手册、Laravel官方手册、
https://learnku.com/articles/25208

Logo

权威|前沿|技术|干货|国内首个API全生命周期开发者社区

更多推荐