1、建立啟動命令檔案
在app/Console/Commands/目錄下建立WorkerMan.php
namespace App\Console\Commands;
use GatewayWorker\BusinessWorker;
use GatewayWorker\Gateway;
use GatewayWorker\Register;
use Illuminate\Console\Command;
use Workerman\Worker;
class WorkerMan extends Command
{
protected $signature = 'wk {action} {--d}';
protected $description = 'Start a Workerman server.';
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();
$workerPath = storage_path('workerman/');
if (!is_dir($workerPath))
mkdir($workerPath, 0755, true);
Worker::$pidFile = $workerPath . config('app.name') . '_workman.pid';
$logPath = $workerPath . date('Ym') . '/';
if (!is_dir($logPath))
mkdir($logPath, 0755, true);
Worker::$logFile = $logPath . date('d') . '.log';
Worker::runAll();
}
private function startBusinessWorker()
{
$worker = new BusinessWorker();
$worker->name = 'BusinessWorker';
$worker->count = 3;
$worker->registerAddress = '127.0.0.1:1236';
$worker->eventHandler = \App\Workerman\Events::class;
}
private function startGateWay()
{
//http://doc.workerman.net/faq/secure-websocket-server.html
// 證照最好是申請的證照
// $context = array(
// // 更多ssl選項請參考手冊 http://php.net/manual/zh/context.ssl.php
// 'ssl' => array(
// 請使用絕對路徑
// 'local_cert' => '/www/wwwroot/cattle_car.com/public/ssl/4035525_niuniu.micropig.cn.pem', // 也可以是crt檔案
//'local_pk' => '/www/wwwroot/cattle_car.com/public/ssl/4035525_niuniu.micropig.cn.key',
// 'local_cert' => '/www/server/panel/vhost/cert/cattle_car.com/fullchain.pem',
// 'local_pk' => '/www/server/panel/vhost/cert/cattle_car.com/privkey.pem',
// 'verify_peer' => false,
// 'verify_peer_name' => false,
// 'allow_self_signed' => true, //如果是自簽名證照需要開啟此選項
// )
// );
//$gateway = new Gateway("websocket://0.0.0.0:2346", $context);
$gateway = new Gateway("websocket://0.0.0.0:2346");
//$gateway->transport = 'ssl';
$gateway->name = 'Gateway';
$gateway->count = 1;
$gateway->lanIp = '127.0.0.1';
$gateway->startPort = 2300;
$gateway->pingInterval = 30;
$gateway->pingNotResponseLimit = 0;
$gateway->pingData = '{"type":"ping"}';
$gateway->registerAddress = '127.0.0.1:1236';
}
private function startRegister()
{
new Register('text://127.0.0.1:1236');
}
}
- 啟動命令
[root@local]# php artisan wk start
- 守護模式啟動
[root@local]# php artisan wk start --d
- 其它命令:
檢視狀態:status / 停止: stop / 過載: reload / 重啟: restart [–d]
2、建立windows環境啟動命令檔案
由於windows不支援批次啟動服務,所以每個服務需要單獨啟動
namespace App\Console\Commands;
use GatewayWorker\BusinessWorker;
use GatewayWorker\Gateway;
use GatewayWorker\Register;
use Illuminate\Console\Command;
use Workerman\Worker;
class WorkerManWin extends Command
{
//相容win
protected $signature = 'wk
{action : action}
{--start=all : start}
{--d : daemon mode}';
protected $description = 'Start a Workerman server.';
public function handle()
{
global $argv;
$action = $this->argument('action');
//針對 Windows 一次執行,無法註冊多個協議的特殊處理
if ($action === 'single') {
$start = $this->option('start');
if ($start === 'register') {
$this->startRegister();
} elseif ($start === 'gateway') {
$this->startGateWay();
} elseif ($start === 'worker') {
$this->startBusinessWorker();
}
Worker::runAll();
return;
}
$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->name = 'BusinessWorker';
$worker->count = 1;
$worker->registerAddress = '127.0.0.1:1236';
$worker->eventHandler = \App\Workerman\Events::class;
}
private function startGateWay()
{
$gateway = new Gateway("websocket://0.0.0.0:2346");
$gateway->name = 'Gateway';
$gateway->count = 1;
$gateway->lanIp = '127.0.0.1';
$gateway->startPort = 2300;
$gateway->pingInterval = 30;
$gateway->pingNotResponseLimit = 0;
$gateway->pingData = '{"type":"ping"}';
$gateway->registerAddress = '127.0.0.1:1236';
}
private function startRegister()
{
new Register('text://0.0.0.0:1236');
}
}
- 啟動命令
[root@local]# php artisan wk single --start=gateway [root@local]# php artisan wk single --start=worker [root@local]# php artisan wk single --start=register
3、定義事件類
在app/Workerman/目錄下建立Events.php
namespace App\Workerman;
use App\Helpers\Jwt;
use App\Models\UserModel;
use GatewayWorker\BusinessWorker;
use GatewayWorker\Lib\Gateway;
use Illuminate\Support\Facades\Log;
use Workerman\Lib\Timer;
class Events
{
/**
* 業務服務啟動事件
* @param BusinessWorker $businessWorker
* @return void
*/
public static function onWorkerStart(BusinessWorker $businessWorker)
{
self::log(__FUNCTION__, $businessWorker->workerId);
Timer::add(1, function () use ($businessWorker) {
$time_now = time();
foreach ($businessWorker->connections as $connection) {
// 有可能該connection還沒收到過訊息,則lastMessageTime設定為當前時間
if (empty($connection->lastMessageTime)) {
$connection->lastMessageTime = $time_now;
continue;
}
// 上次通訊時間間隔大於心跳間隔,則認為客戶端已經下線,關閉連線
if ($time_now - $connection->lastMessageTime > 30) {
if ($connection->id) {
//todo
}
//斷開後的回撥
echo "Client ip {$connection->getRemoteIp()} timeout!!!\n";
$connection->close();
}
}
});
}
/**
* 客戶端連線事件
* @param string $clientId
* @return void
*/
public static function onConnect(string $clientId)
{
self::log(__FUNCTION__, $clientId);
}
/**
* 客戶端websocket 連線事件
* @param string $clientId
* @param mixed $data
* @return void
*/
public static function onWebSocketConnect(string $clientId, $data)
{
self::log(__FUNCTION__, $clientId, $data);
}
/**
* 客戶端websocket訊息
* @param string $clientId
* @param string $messageJson
* @return void
*/
public static function onMessage(string $clientId, string $messageJson)
{
self::log(__FUNCTION__, $clientId, $messageJson);
$message = json_decode($messageJson);
if (empty($message->type)) {
self::sendMessage(500, '請配置type');
return;
}
switch ($message->type) {
case 'login':
// 登入業務
break;
case 'ping':
self::sendMessage(201, 'pong');
break;
default:
self::sendMessage(500, '訊息型別不支援');
}
}
/**
* 關閉客戶端websocket
* @param string $clientId
* @return void
*/
public static function onClose(string $clientId)
{
self::log(__FUNCTION__, $clientId);
Gateway::destoryClient($clientId);
}
/**
* 寫日誌
* @param string $title
* @param $data
* @return void
*/
protected static function log(string $title, ...$data): void
{
if (config('app.debug')) {
var_dump("========== {$title} ==========");
var_dump($data);
Log::info("{$title} | " . json_encode($data, 256));
}
}
/**
* 傳送客戶端訊息
* @param int $code
* @param mixed $message
* @param array|null $data
* @param string $clientId
* @return void
*/
protected static function sendMessage(int $code, $message, ?array $data = null, string $clientId = ''): void
{
$sendMessage = json_encode([
'code' => $code,
'msg' => $message,
'data' => $data,
]);
if ($clientId)
Gateway::sendToClient($clientId, $sendMessage);
else
Gateway::sendToCurrentClient($sendMessage);
}
}
- 測試可以在:www.websocket-test.com/ 連線服務並接收訊息
4、js連線websocket服務
let ws = new WebSocket('ws://192.168.0.100:2346');
// 獲取連線狀態
console.log('ws連線狀態:' + ws.readyState);
//監聽是否連線成功
ws.onopen = function () {
console.log('ws連線狀態:' + ws.readyState);
//連線成功則傳送登入請求
let message = {type: "login", token: "JWT授權碼"};
ws.send(JSON.stringify(message));
}
// 接聽伺服器發回的資訊並處理展示
ws.onmessage = function (data) {
console.log('接收到來自伺服器的訊息:');
console.log(data);
//完成通訊後關閉WebSocket連線
ws.close();
}
// 監聽連線關閉事件
ws.onclose = function () {
// 監聽整個過程中websocket的狀態
console.log('ws連線狀態:' + ws.readyState);
}
// 監聽並處理error事件
ws.onerror = function (error) {
console.log(error);
}
5、後端向客戶端傳送訊息
\GatewayClient\Gateway::$registerAddress = '127.0.0.1:1236';
Gateway::sendToUid(29, '{"type": "update", "data": {"name": "張三", "aratar": "..."}}');
注意:此處是單向api方式傳送訊息,registerAddress地址與啟動服務註冊的registerAddress地址保持對應,如本地可直接使用預設設定。內網呼叫時,服務與客戶端使用內網IP。
Gateway服務端與客戶端使用方法,請查閱官方手冊:www.workerman.net/doc/gateway-work...
本作品採用《CC 協議》,轉載必須註明作者和本文連結