前言
之前公司需要一個內部的通訊軟體,就叫我做一個。通訊軟體嘛,就離不開通訊了,然後我就想到了長連線。這裡本人用的是GatewayWorker框架。
什麼是GatewayWorker框架?
GatewayWorker是基於Workerman開發的一套TCP長連線的應用框架,實現了單發、群發、廣播等介面,內建了mysql類庫,GatewayWorker分為Gateway程序和Worker程序,支援分散式部署,能夠支援大量的連線數。
GatewayWorker的工作原理
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 ,開啟守護程序,如果出現一下頁面即成功開啟
如想關閉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)
}
}
上述為之前給公司做內部通訊軟體時個人整理內容,水平有限,如有錯誤之處,望各位園友不吝賜教!如果覺得不錯,請點選推薦和關注!謝謝~๑•́₃•̀๑ [鮮花][鮮花][鮮花]