Lumen/Laravel 整合 GatewayWorker (Workerman),實現簡單的聊天系統.

summer-1994發表於2019-03-11

GatewayWorker 與 Workerman的關係
Workerman相比GatewayWorker也更底層,需要開發者有一定的多程式程式設計經驗。因為絕大多數開發者的目標是基於Workerman開發TCP長連線應用,而長連線應用服務端有很多共同之處,例如它們有相同的程式模型以及單發、群發、廣播等介面需求。所以才有了GatewayWorker框架,GatewayWorker是基於Workerman開發的一個TCP長連線框架,實現了單發、群送、廣播等長連線必用的介面。GatewayWorker框架實現了Gateway Worker程式模型,天然支援分散式多伺服器部署,擴容縮容非常方便,能夠應對海量併發連線。可以說GatewayWorker是基於Workerman實現的一個更完善的專門用於實現TCP長連線的專案框架。專案是長連線並且需要客戶端與客戶端之間通訊,建議使用GatewayWorker。

Step1.引入包

  1. composer.json件的require引入這三行
        "workerman/gateway-worker": "^3.0",
        "workerman/gatewayclient": "^3.0",
        "workerman/workerman": "^3.5"

    file

  2. 終端的專案根目錄輸入 composer update

Step2. 建立一個Command

  1. 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);
//};
  1. App\Console\Kernel引入 GatewayWorkerServer::class
    file

Step3.建立監聽檔案

在app目錄下建立 GatewayWorker/Events.php檔案
file

<?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 測試

  1. 開啟服務,在終端輸入
    php artisan gateway-worker:server start
    file

  2. 在瀏覽器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);
};

file

簡單實現訂單裡的買家和賣家交流、獲取聊天記錄,也可以把相關的資訊存到快取裡,
public static function onMessage() 裡修改自己的業務邏輯

http://doc2.workerman.net/ GatewayWorker2.x 3.x 手冊

本作品採用《CC 協議》,轉載必須註明作者和本文連結

相關文章