【workerman】uniapp+thinkPHP5使用GatewayWorker實現實時通訊

我恨bug發表於2024-07-05

前言

之前公司需要一個內部的通訊軟體,就叫我做一個。通訊軟體嘛,就離不開通訊了,然後我就想到了長連線。這裡本人用的是GatewayWorker框架。

什麼是GatewayWorker框架?

GatewayWorker是基於Workerman開發的一套TCP長連線的應用框架,實現了單發、群發、廣播等介面,內建了mysql類庫,GatewayWorker分為Gateway程序和Worker程序,支援分散式部署,能夠支援大量的連線數。

GatewayWorker的工作原理

image

1、啟動所有程序(GatewayWorker、business、register)

2、GatewayWorker和business程序啟動後向register請求註冊

3、register服務收到註冊請求後,把所有Gateway的通訊地址儲存在記憶體中同時把記憶體中所有的Gateway的通訊地址發給business

4、business程序得到所有的Gateway內部通訊地址後進行連線GatewayWorker

5、如果有新的GatewayWorker服務進行register,則將新的Gateway內部通訊地址列表將廣播給所有buiness並建立連線

6、如果有GatewayWorker下線,則Register服務會收到通知,會將該GatewayWorker內部通訊地址刪除,然後廣播新的內部通訊地址列表給所有business

7、此時GatewayWorker與buiness已經建立起長連線

8、客戶端的事件及接受的資料全部由GatewayWorker轉發給business進行處理。

目錄結構

├── Applications // 專案應用目錄
│   └── YourAppGateway  // 建立一個存放workman的目錄,名字隨意
│       ├── Events.php // 處理主邏輯業務的檔案,管理onConnect onMessage onClose 等方法
│       ├── start_gateway.php // gateway程序啟動指令碼、配置服務註冊地址、埠號、程序數等引數
│       ├── start_businessworker.php // 使用者程序的啟動指令碼
│       └── start_register.php // 註冊服務的啟動指令碼
│
├── start.php // 全域性啟動指令碼,此指令碼會依次載入Applications/YourAppGateway/start*.php對所有指令碼進行啟動
│
└── vendor    // GatewayWorker框架和Workerman框架原始碼目錄

GatewayWorker實現

以寶塔為例

1.安裝composer

登入SSH終端,使用以下命令下載Composer的安裝指令碼:

php -r "copy('https://getcomposer.org/installer', 'composer-setup.php');"

執行下面的命令來安裝Composer:

php composer-setup.php --install-dir=/usr/local/bin --filename=composer

檢查composer版本

composer -v //檢查composer版本

2.安裝workerman

在專案根目錄開啟寶塔終端,輸入以下命令安裝workman

composer require topthink/think-worker

3.安裝GatewayWorker

在專案根目錄開啟寶塔終端,輸入以下命令安裝GatewayWorker

composer require workerman/gateway-worker

4.實現程式碼

可以選擇官方提供的demo 連結:http://www.workerman.net/download/GatewayWorker.zip

或者使用我根據demo改編而來的

先在專案應用目錄(一般是Applications)下新建一個檔案儲存以下四個程序檔案

start_gateway.php

<?php 
use \Workerman\Worker;
use \Workerman\WebServer;
use \GatewayWorker\Gateway;
use \GatewayWorker\BusinessWorker;
use \Workerman\Autoloader;

// 自動載入類
require_once __DIR__ . '/../../vendor/autoload.php';

// gateway 程序,這裡使用Text協議,可以用telnet測試
$gateway = new Gateway("websocket://0.0.0.0:8283");
// gateway名稱,status方便檢視
$gateway->name = 'YourAppGateway';
// gateway程序數
$gateway->count = 200;
// 本機ip,分散式部署時使用內網ip
$gateway->lanIp = '127.0.0.1';
// 內部通訊起始埠,假如$gateway->count=4,起始埠為4000
// 則一般會使用4000 4001 4002 4003 4個埠作為內部通訊埠 
$gateway->startPort = 2900;
// 服務註冊地址、埠
$gateway->registerAddress = '127.0.0.1:1237';

// 心跳間隔
//$gateway->pingInterval = 10;
// 心跳資料
//$gateway->pingData = '{"type":"ping"}';

/* 
// 當客戶端連線上來時,設定連線的onWebSocketConnect,即在websocket握手時的回撥
$gateway->onConnect = function($connection)
{
    $connection->onWebSocketConnect = function($connection , $http_header)
    {
        // 可以在這裡判斷連線來源是否合法,不合法就關掉連線
        // $_SERVER['HTTP_ORIGIN']標識來自哪個站點的頁面發起的websocket連結
        if($_SERVER['HTTP_ORIGIN'] != 'http://kedou.workerman.net')
        {
            $connection->close();
        }
        // onWebSocketConnect 裡面$_GET $_SERVER是可用的
        // var_dump($_GET, $_SERVER);
    };
}; 
*/

// 如果不是在根目錄啟動,則執行runAll方法
if(!defined('GLOBAL_START'))
{
    Worker::runAll();
}

start_businessworker.php

<?php 
use Workerman\Worker;
use Workerman\WebServer;
use GatewayWorker\Gateway;
use GatewayWorker\BusinessWorker;
use Workerman\Autoloader;

// 自動載入類
require_once __DIR__ . '/../../vendor/autoload.php';

// bussinessWorker 程序
$worker = new BusinessWorker();
// worker名稱
$worker->name = 'YourAppBusinessWorker';
// bussinessWorker程序數量
$worker->count = 200;
// 服務註冊地址、埠
$worker->registerAddress = '127.0.0.1:1237';

// 如果不是在根目錄啟動,則執行runAll方法
if(!defined('GLOBAL_START'))
{
    Worker::runAll();
}

start_register.php

<?php 
use \Workerman\Worker;
use \GatewayWorker\Register;

// 自動載入類
require_once __DIR__ . '/../../vendor/autoload.php';

// register 必須是text協議 1237為埠
$register = new Register('text://0.0.0.0:1237');

// 如果不是在根目錄啟動,則執行runAll方法
if(!defined('GLOBAL_START'))
{
    Worker::runAll();
}

Events.php

<?php

use \GatewayWorker\Lib\Gateway;

/**
 * 主邏輯
 * 主要是處理 onConnect onMessage onClose 三個方法
 */
class Events
{
    /**
     * 當客戶端連線時觸發
     * 
     * @param int $client_id 連線id
     */
    public static function onConnect($client_id)
    {
        echo "【新的客戶端連結】:client_id:".$client_id.PHP_EOL;
        // 向當前client_id傳送資料 
        Gateway::sendToClient($client_id, "");
        
        // 向所有人傳送
        $data=[
            'client_id'=>$client_id,
            'message'=>'歡迎'.$client_id.'登入!',
            'data'=>[]
        ];
        Gateway::sendToAll(json_encode($data));
        // Gateway::sendToAll("$client_id login\r\n");
    }
    
   /**
    * 當客戶端發來訊息時觸發
    * @param int $client_id 連線id
    * @param mixed $message 具體訊息
    */
   public static function onMessage($client_id, $message){
       
       $data=[
            'client_id'=>$client_id,
            'message'=>$client_id.'說:'.$result['message'],
            'data'=>$message
        ];
        Gateway::sendToAll(json_encode($data));
        // 向所有人傳送 
        // Gateway::sendToAll("$client_id said $message\r\n");
   }
   
   /**
    * 當使用者斷開連線時觸發
    * @param int $client_id 連線id
    */
   public static function onClose($client_id)
   {
       // 向所有人傳送 
    //   GateWay::sendToAll("$client_id 退出了!\r\n");
   }
}

隨後在專案的根目錄下新建一個啟動檔案

start_all_workman.php

<?php

ini_set('display_errors', 'on');
use Workerman\Worker;

if(strpos(strtolower(PHP_OS), 'win') === 0)
{
    exit("start.php not support windows, please use start_for_win.bat\n");
}

// 檢查擴充套件
if(!extension_loaded('pcntl'))
{
    exit("Please install pcntl extension. See http://doc3.workerman.net/appendices/install-extension.html\n");
}

if(!extension_loaded('posix'))
{
    exit("Please install posix extension. See http://doc3.workerman.net/appendices/install-extension.html\n");
}

// 標記是全域性啟動
define('GLOBAL_START', 1);

require_once __DIR__ . '/vendor/autoload.php';

// 載入所有Applications/*/start.php,以便啟動所有服務
foreach(glob(__DIR__.'/application/此處請改成你自己命名存放workman的目錄名/start*.php') as $start_file)
{
    require_once $start_file;
}
// 執行所有服務
Worker::runAll();

注意開啟埠後記得去放行埠!!!除了寶塔放行以外,你的伺服器(阿里雲/騰訊雲等等)也記得要去放行!!!

啟動workman

在專案根目錄下開啟終端,輸入php start_all_workman.php start -d ,開啟守護程序,如果出現一下頁面即成功開啟

image

如想關閉workman程序則輸入php start_all_workman.php stop 進行關閉

GatewayWorker使用

如果你的網站使用的是Https協議的話,WebSocket必須使用wss協議
但是wss協議不支援IP:埠的形式,而是隻能寫域名+url
所以為了解決使用https協議而WebSocket不能連線的問題,可以使用Nginx進行反向代理
在網站配置檔案的server下加入以下程式碼

location /connectWorkman(名字隨你取,別跟其他反向代理重名就行)
  {
    proxy_pass http://127.0.0.1:8283;
    proxy_http_version 1.1;
    proxy_set_header Upgrade $http_upgrade;
    proxy_set_header Connection "Upgrade";
    proxy_set_header X-Real-IP $remote_addr;
   }

前端使用(uniapp)

init() {
	SocketTask = uni.connectSocket({
		url: 'wss://chat.gdpaimaihui.com/auction', //正式
		header: {
			'content-type': 'application/json'
		},
		success: function(res) {
			console.log('WebSocket連線建立', res);
		},
		fail: function(err) {
			uni.showToast({
				title: '網路異常!',
				icon: 'none'
			});
			console.log(err);
		}
	});

	//websocket監聽事件
	SocketTask.onOpen((res) => {
		socketOpen = true
		canReconnect = true
		console.log('監聽 WebSocket 連線開啟事件。', res);
		//websocket連線後可以啟動個定時器,每隔一段時間進行心跳一次,以防心跳停止斷開連線
		this.timer = setInterval(() => {
			SocketTask.send({
				data: '心跳',
				success() {
					// console.log('傳送心跳成功');
				}
			})
		}, 2000)
	});
	SocketTask.onError((onError) => {
		console.log('監聽 WebSocket 錯誤。錯誤資訊', onError);
		socketOpen = false;
		if (canReconnect) {
			this.reconnect()
			canReconnect = false
		}
	});

	SocketTask.onMessage((res) => {
		console.log('監聽WebSocket接受到伺服器的訊息事件。伺服器返回的訊息', res);
	});
},

//重新連線
reconnect() {
	if (!socketOpen) {
		let count = 0;
		reconnectInterval = setInterval(() => {
			console.log("正在嘗試重連")
			uni.showToast({
				title: '正在嘗試重連',
				icon: 'none'
			})
			this.init();
			count++
			console.log();
			//重連一定次數後就不再重連
			if (count >= reconnectTimes) {
				clearInterval(reconnectInterval)
				uni.showToast({
					title: '網路異常或伺服器錯誤',
					icon: 'none'
				})
			}
		}, reconnectDelay)
	}
}

上述為之前給公司做內部通訊軟體時個人整理內容,水平有限,如有錯誤之處,望各位園友不吝賜教!如果覺得不錯,請點選推薦和關注!謝謝~๑•́₃•̀๑ [鮮花][鮮花][鮮花]

相關文章