最近使用 swoole 作了項目,裏面設計推送信息給界面前端,和無登錄用戶的狀態監控,如下是本人從中獲取的一點心得,有改進的地方請留言評論。php
咱們假設有一個需求,我在後端點擊按鈕1,首頁彈出「後端觸發了按鈕1」。後端點了按鈕2,列表頁彈出「後端觸發了按鈕2」。作到根據不一樣場景推送到不一樣頁面。html
客戶端瀏覽器打開或者刷新界面,在swoole服務會生成一個進程句柄 fd ,每次瀏覽器頁面有打開連接websocket的js代碼,便會生成,每次刷新的時候,會關閉以前打開的 fd,從新生成一個新的,關閉界面的時候會生成一個新的。swoole的 fd生成規則是從1開始遞增。前端
咱們創建一個key爲swoole:fds redis哈希類型數據,fd 爲hash的字段,每一個字段的值咱們存儲前端websocket請求的url參數信息(根據業務複雜度本身靈活變通,我在項目中會在url帶上sessionId)。每次連接打開swoole服務的時候咱們存儲其信息,每次關閉頁面時候咱們清除其字段。在redis存儲以下jquery
在界面上當進行了觸發操做的時候,經過後臺curl請求swoole http服務,swoole http服務根據你向我傳遞的參數分發給對應的邏輯處理。如curl請求127.0.0.1:9502page=back&func=pushHomeLogic&token=123456
咱們能夠根據傳入的func參數,在後臺分發給對應邏輯處理。如分發給pushHomeLogic方法。在其裏面實現本身的邏輯。爲防止過多的if else 以及 foreach 操做,咱們採用的是閉包,call_user_func等方法實現以下web
public function onRequest($request,$response) { if ($this->checkAccess("", $request)) { $param = $request->get; // 分發處理請求邏輯 if (isset($param['func'])) { if (method_exists($this,$param['func'])) { call_user_func([$this,$param['func']],$request); } } } }// 往首頁推送邏輯處理 public function pushHomeLogic($request) { $callback = function (array $aContent,int $fd,SwooleDemo $oSwoole)use($request) { if ($aContent && $aContent['page'] == "home") { $aRes['message'] = "後端按了按鈕1"; $aRes['code'] = "200"; $oSwoole::$server->push($fd,xss_json($aRes)); } }; $this->eachFdLogic($callback); }
<?php namespace App\Console\Commands; use Closure; use Illuminate\Console\Command; use Illuminate\Support\Facades\Redis; class SwooleDemo extends Command { // 命令名稱 protected $signature = 'swoole:demo'; // 命令說明 protected $description = '這是關於swoole websocket的一個測試demo'; // swoole websocket服務 private static $server = null; public function __construct() { parent::__construct(); } // 入口 public function handle() { $this->redis = Redis::connection('websocket'); $server = self::getWebSocketServer(); $server->on('open',[$this,'onOpen']); $server->on('message', [$this, 'onMessage']); $server->on('close', [$this, 'onClose']); $server->on('request', [$this, 'onRequest']); $this->line("swoole服務啓動成功 ..."); $server->start(); } // 獲取服務 public static function getWebSocketServer() { if (!(self::$server instanceof \swoole_websocket_server)) { self::setWebSocketServer(); } return self::$server; } // 服務處始設置 protected static function setWebSocketServer():void { self::$server = new \swoole_websocket_server("0.0.0.0", 9502); self::$server->set([ 'worker_num' => 1, 'heartbeat_check_interval' => 60, // 60秒檢測一次 'heartbeat_idle_time' => 121, // 121秒沒活動的 ]); } // 打開swoole websocket服務回調代碼 public function onOpen($server, $request) { if ($this->checkAccess($server, $request)) { self::$server->push($request->fd,xss_json(["code"=>200,"message"=>"打開swoole服務成功"])); } } // 給swoole websocket 發送消息回調代碼 public function onMessage($server, $frame) { } // http請求swoole websocket 回調代碼 public function onRequest($request,$response) { if ($this->checkAccess("", $request)) { $param = $request->get; // 分發處理請求邏輯 if (isset($param['func'])) { if (method_exists($this,$param['func'])) { call_user_func([$this,$param['func']],$request); } } } } // websocket 關閉回調代碼 public function onClose($serv,$fd) { $this->redis->hdel('swoole:fds', $fd); $this->line("客戶端 {$fd} 關閉"); } // 校驗客戶端鏈接的合法性,無效的鏈接不容許鏈接 public function checkAccess($server, $request):bool { $bRes = true; if (!isset($request->get) || !isset($request->get['token'])) { self::$server->close($request->fd); $this->line("接口驗證字段不全"); $bRes = false; } else if ($request->get['token'] != 123456) { $this->line("接口驗證錯誤"); $bRes = false; } $this->storeUrlParamToRedis($request); return $bRes; } // 將每一個界面打開websocket的url 存儲起來 public function storeUrlParamToRedis($request):void { // 存儲請求url帶的信息 $sContent = json_encode( [ 'page' => $request->get['page'], 'fd' => $request->fd, ], true); $this->redis->hset("swoole:fds", $request->fd, $sContent); } /** * @param $request * @see 循環邏輯處理 */ public function eachFdLogic(Closure $callback = null) { foreach (self::$server->connections as $fd) { if (self::$server->isEstablished($fd)) { $aContent = json_decode($this->redis->hget("swoole:fds",$fd),true); $callback($aContent,$fd,$this); } else { $this->redis->hdel("swoole:fds",$fd); } } } // 往首頁推送邏輯處理 public function pushHomeLogic($request) { $callback = function (array $aContent,int $fd,SwooleDemo $oSwoole)use($request) { if ($aContent && $aContent['page'] == "home") { $aRes['message'] = "後端按了按鈕1"; $aRes['code'] = "200"; $oSwoole::$server->push($fd,xss_json($aRes)); } }; $this->eachFdLogic($callback); } // 往列表頁推送邏輯處理 public function pushListLogic($request) { $callback = function (array $aContent,int $fd,SwooleDemo $oSwoole)use($request) { if ($aContent && $aContent['page'] == "list") { $aRes['message'] = "後端按了按鈕2"; $aRes['code'] = "200"; $oSwoole::$server->push($fd,xss_json($aRes)); } }; $this->eachFdLogic($callback); } // 啓動websocket服務 public function start() { self::$server->start(); } }
<?php namespace App\Http\Controllers; use Illuminate\Http\Request; use Illuminate\Support\Facades\Redis; class TestController extends Controller { // 首頁 public function home() { return view("home"); } // 列表 public function list() { return view("list"); } // 後端控制 public function back() { if (request()->method() == 'POST') { $this->curl_get($this->getUrl()); return json_encode(['code'=>200,"message"=>"成功"]); } else { return view("back"); } } // 獲取要請求swoole websocet服務地址 public function getUrl():string { // 域名 端口 請求swoole服務的方法 $sBase = request()->server('HTTP_HOST'); $iPort = 9502; $sFunc = request()->post('func'); $sPage = "back"; return $sBase.":".$iPort."?func=".$sFunc."&token=123456&page=".$sPage; } // curl 推送 public function curl_get(string $url):string { $ch_curl = curl_init(); curl_setopt ($ch_curl, CURLOPT_TIMEOUT_MS, 3000); curl_setopt($ch_curl, CURLOPT_SSL_VERIFYPEER, 0); curl_setopt ($ch_curl, CURLOPT_HEADER,false); curl_setopt($ch_curl, CURLOPT_HTTPGET, 1); curl_setopt($ch_curl, CURLOPT_RETURNTRANSFER,true); curl_setopt ($ch_curl, CURLOPT_URL,$url); $str = curl_exec($ch_curl); curl_close($ch_curl); return $str; } }
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>後端界面</title> <meta name=viewport content="width=device-width,initial-scale=1,maximum-scale=1,user-scalable=no"> </head> <body> <button class="push" data-func="pushHomeLogic">按鈕1</button> <button class="push" data-func="pushListLogic">按鈕2</button> </body> <script src="{{ asset("/vendor/tw/global/jQuery/jquery-2.2.3.min.js")}} "></script> <script> $(function () { $(".push").on('click',function(){ var func = $(this).attr('data-func').trim(); ajaxGet(func) }) function ajaxGet(func) { url = "{{route('back')}}"; token = "{{csrf_token()}}"; $.ajax({ url: url, type: 'post', dataType: "json", data:{func:func,_token:token}, error: function (data) { alert("服務器繁忙, 請聯繫管理員!"); return; }, success: function (result) { }, }) } }) </script> </html>
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>swoole首頁</title> <meta name=viewport content="width=device-width,initial-scale=1,maximum-scale=1,user-scalable=no"> </head> <body> <h1>這是首頁</h1> </body> <script> var ws;//websocket實例 var lockReconnect = false;//避免重複鏈接 var wsUrl = 'ws://{{$_SERVER["HTTP_HOST"]}}:9502?page=home&token=123456'; function initEventHandle() { ws.onclose = function () { reconnect(wsUrl); }; ws.onerror = function () { reconnect(wsUrl); }; ws.onopen = function () { //心跳檢測重置 heartCheck.reset().start(); }; ws.onmessage = function (event) { //若是獲取到消息,心跳檢測重置 //拿到任何消息都說明當前鏈接是正常的 var data = JSON.parse(event.data); if (data.code == 200) { console.log(data.message) } heartCheck.reset().start(); } } createWebSocket(wsUrl); /** * 建立連接 * @param url */ function createWebSocket(url) { try { ws = new WebSocket(url); initEventHandle(); } catch (e) { reconnect(url); } } function reconnect(url) { if(lockReconnect) return; lockReconnect = true; //沒鏈接上會一直重連,設置延遲避免請求過多 setTimeout(function () { createWebSocket(url); lockReconnect = false; }, 2000); } //心跳檢測 var heartCheck = { timeout: 60000,//60秒 timeoutObj: null, serverTimeoutObj: null, reset: function(){ clearTimeout(this.timeoutObj); clearTimeout(this.serverTimeoutObj); return this; }, start: function(){ var self = this; this.timeoutObj = setTimeout(function(){ //這裏發送一個心跳,後端收到後,返回一個心跳消息, //onmessage拿到返回的心跳就說明鏈接正常 ws.send("heartbeat"); self.serverTimeoutObj = setTimeout(function(){//若是超過必定時間還沒重置,說明後端主動斷開了 ws.close();//若是onclose會執行reconnect,咱們執行ws.close()就好了.若是直接執行reconnect 會觸發onclose致使重連兩次 }, self.timeout); }, this.timeout); }, header:function(url) { window.location.href=url } } </script> </html>
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>swoole列表頁</title> <meta name=viewport content="width=device-width,initial-scale=1,maximum-scale=1,user-scalable=no"> </head> <body> <h1>swoole列表頁</h1> </body> <script> var ws;//websocket實例 var lockReconnect = false;//避免重複鏈接 var wsUrl = 'ws://{{$_SERVER["HTTP_HOST"]}}:9502?page=list&token=123456'; function initEventHandle() { ws.onclose = function () { reconnect(wsUrl); }; ws.onerror = function () { reconnect(wsUrl); }; ws.onopen = function () { //心跳檢測重置 heartCheck.reset().start(); }; ws.onmessage = function (event) { //若是獲取到消息,心跳檢測重置 //拿到任何消息都說明當前鏈接是正常的 var data = JSON.parse(event.data); if (data.code == 200) { console.log(data.message) } heartCheck.reset().start(); } } createWebSocket(wsUrl); /** * 建立連接 * @param url */ function createWebSocket(url) { try { ws = new WebSocket(url); initEventHandle(); } catch (e) { reconnect(url); } } function reconnect(url) { if(lockReconnect) return; lockReconnect = true; //沒鏈接上會一直重連,設置延遲避免請求過多 setTimeout(function () { createWebSocket(url); lockReconnect = false; }, 2000); } //心跳檢測 var heartCheck = { timeout: 60000,//60秒 timeoutObj: null, serverTimeoutObj: null, reset: function(){ clearTimeout(this.timeoutObj); clearTimeout(this.serverTimeoutObj); return this; }, start: function(){ var self = this; this.timeoutObj = setTimeout(function(){ //這裏發送一個心跳,後端收到後,返回一個心跳消息, //onmessage拿到返回的心跳就說明鏈接正常 ws.send("heartbeat"); self.serverTimeoutObj = setTimeout(function(){//若是超過必定時間還沒重置,說明後端主動斷開了 ws.close();//若是onclose會執行reconnect,咱們執行ws.close()就好了.若是直接執行reconnect 會觸發onclose致使重連兩次 }, self.timeout); }, this.timeout); }, header:function(url) { window.location.href=url } } </script> </html>