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 協議》,轉載必須註明作者和本文連結