1.Workerman是什么?(套用官網)
Workerman是一款純PHP開發的開源高性能的PHP socket 服務框架。
Workerman不是重復造輪子,它不是一個MVC框架,而是一個更底層更通用的socket服務框架,你可以用它開發tcp代理、梯子代理、做游戲服務器、郵件服務器、ftp服務器、甚至開發一個php版本的redis、php版本的數據庫、php版本的nginx、php版本的php-fpm等等。Workerman可以說是PHP領域的一次創新,讓開發者徹底擺脫了PHP只能做WEB的束縛。
實際上Workerman類似一個PHP版本的nginx,核心也是多進程+Epoll+非阻塞IO。Workerman每個進程能維持上萬並發連接。由於本身常住內存,不依賴Apache、nginx、php-fpm這些容器,擁有超高的性能。同時支持TCP、UDP、UNIXSOCKET,支持長連接,支持Websocket、HTTP、WSS、HTTPS等通訊協以及各種自定義協議。擁有定時器、異步socket客戶端、異步Mysql、異步Redis、異步Http、異步消息隊列等眾多高性能組件。
2. GatewayWorker是什么?(套用官網)
GatewayWorker基於Workerman開發的一個項目框架,用於快速開發TCP長連接應用,例如app推送服務端、即時IM服務端、游戲服務端、物聯網、智能家居等等
GatewayWorker使用經典的Gateway和Worker進程模型。Gateway進程負責維持客戶端連接,並轉發客戶端的數據給BusinessWorker進程處理,BusinessWorker進程負責處理實際的業務邏輯(默認調用Events.php處理業務),並將結果推送給對應的客戶端。Gateway服務和BusinessWorker服務可以分開部署在不同的服務器上,實現分布式集群。
3. Gatewayworker + thinkphp
數據交互模型:
流程:
- 客戶端(瀏覽器)發出socket請求與getwayworker建立連接
- 客戶端發出http請求(注意是發送http請求處理業務,所有業務都放在tp處理)
- tp處理業務邏輯(把client_id與uid綁定、客戶端分組、數據庫查詢等),然后調用gateway的接口把結果數據進行廣播
- 客戶端接收廣播的數據,進行視圖渲染
4. 環境搭建
1. workerman 與 gateway安裝
composer require workerman/workerman
composer require workerman/gateway-worker
官網實例 點擊下載
2. 配置
start_gateway.php文件配置(開啟gateway服務,並在注冊服務注冊)
<?php use \Workerman\Worker; use \Workerman\WebServer; use \GatewayWorker\Gateway; use \GatewayWorker\BusinessWorker; use \Workerman\Autoloader; // $context = array( 'ssl' => array( 'local_cert' => '/etc/pki/tls/certs/public.pem', // 或者crt文件 'local_pk' => '/etc/pki/tls/private/214498534070135.key', 'verify_peer' => false ) ); // gateway 進程,這里使用websocket協議,這里使用了443端口,所有要加載ssl的配置,websocket://0.0.0.0:443:允許所有任何客戶端使用wss協議訪問 $gateway = new Gateway("websocket://0.0.0.0:443",$context); $gateway->transport = 'ssl'; // gateway名稱,status方便查看 $gateway->name = 'YourAppGateway'; // gateway進程數 $gateway->count = 4; // 本機ip,分布式部署時使用內網ip $gateway->lanIp = '172.31.240.231'; // 內部通訊起始端口,假如$gateway->count=4,起始端口為4000 // 則一般會使用4000 4001 4002 4003 4個端口作為內部通訊端口 $gateway->startPort = 2900; // 服務注冊地址 $gateway->registerAddress = '172.31.240.231:1238'; // 心跳檢測 15秒一次 $gateway->pingInterval = 15; $gateway->pingNotResponseLimit = 1; // 當pingData為空,服務器將不會向客戶端發送心跳檢測(為了節省服務器資源,心跳檢測最好由客戶端發起) $gateway->pingData = '';
3. start_businessworker.php(開啟businessworker服務,並在注冊服務注冊)
<?php use \Workerman\Worker; use \Workerman\WebServer; use \GatewayWorker\Gateway; use \GatewayWorker\BusinessWorker; use \Workerman\Autoloader; // bussinessWorker 進程 $worker = new BusinessWorker(); // worker名稱 $worker->name = 'YourAppBusinessWorker'; // bussinessWorker進程數量 $worker->count = 4; // 服務注冊地址 $worker->registerAddress = '172.40.239.231:1238'; // 如果不是在根目錄啟動,則運行runAll方法 if(!defined('GLOBAL_START')) { Worker::runAll(); }
4. start_register.php(開啟注冊服務)
<?php use \Workerman\Worker; use \GatewayWorker\Register; // register 服務必須是text協議 $register = new Register('text://172.40.239.231:1238'); // 如果不是在根目錄啟動,則運行runAll方法 if(!defined('GLOBAL_START')) { Worker::runAll(); }
5. 啟動服務
啟動
以debug(調試)方式啟動
php start.php start
以daemon(守護進程)方式啟動
php start.php start -d
停止
php start.php stop
重啟
php start.php restart
平滑重啟
php start.php reload
查看狀態
php start.php status
5. 客戶端操作(這是我基於小程序接口,封裝的一個socket庫)
var socket = new Socket('wss://wss.xinyuruiyang.com'); socket.on("open",function (res) { console.log('WebSocket連接已打開!') _this.setData({ isOpenSocket: true }); });
6. gateway端操作
<?php use \GatewayWorker\Lib\Gateway; use \Workerman\Lib\Timer; class Events { public static $worker_id = null; /** * 當客戶端連接時觸發 * 如果業務不需此回調可以刪除onConnect * * @param int $client_id 連接id */ public static function onConnect($client_id) { // 向當前client_id發送數據(觸發客戶端的init時間) Gateway::sendToClient($client_id, json_encode(["init",["client_id"=>$client_id]])); } }
7. tp端操作
private function bind_user($room,$client_id,$uid=''){ if( empty($uid) ){ $sk = $this->checksession(); $openid = $this->get_openid($sk); $uid = D('User')->where(['openid'=>$openid])->getField('id'); } // client_id與uid綁定 Gateway::bindUid($client_id, $uid); // 加入某個群組(可調用多次加入多個群組) Gateway::joinGroup($client_id, $room);
// 設置session Gateway::setSession($client_id, ['uid'=>$uid]);
// 獲取客戶端分組人數 $number = Gateway::getClientCountByGroup($room); D('Match')->where(['rid'=>$room,'uid'=>$uid,'status'=>['in',['1','2']]])->save(['client_id'=>$client_id]); $message = D('Match')->where(['rid'=>$room,'status'=>['in',['1','2']]])->select(); foreach($message as &$v){ $v['user'] = D('User')->where(['id'=>$v['uid']])->find(); $v['user']['teamside'] = D('Match')->where(['rid'=>$room,'uid'=>$v['uid'],'status'=>['in',['1','2']]])->getField('teamside'); }
// 向某個房間廣播數據 Gateway::sendToGroup($room, json_encode(["room",$message])); }
}