Laravel 中使用 swoole 項目實戰開發案例二 (後端主動分場景給界面推送消息)

推薦閱讀:Laravel 中使用 swoole 項目實戰開發案例一 (創建 swoole 和前端通訊)php

 

需求分析

咱們假設有一個需求,我在後端點擊按鈕 1,首頁彈出 「後端觸發了按鈕 1」。後端點了按鈕 2,列表頁彈出 「後端觸發了按鈕 2」。作到根據不一樣場景推送到不一樣頁面。html

代碼思路


  • Swoole fd
    客戶端瀏覽器打開或者刷新界面,在 swoole 服務會生成一個進程句柄 fd ,每次瀏覽器頁面有打開連接 websocket 的 js 代碼,便會生成,每次刷新的時候,會關閉以前打開的 fd,從新生成一個新的,關閉界面的時候會生成一個新的。swoole 的 fd 生成規則是從 1 開始遞增。

  • Redis Hash 存儲 fd
    咱們創建一個 key 爲 swoole:fds redis 哈希類型數據,fd 爲 hash 的字段,每一個字段的值咱們存儲前端 websocket 請求的 url 參數信息 (根據業務複雜度本身靈活變通,我在項目中會在 url 帶上 sessionId)。每次連接打開 swoole 服務的時候咱們存儲其信息,每次關閉頁面時候咱們清除其字段。在 redis 存儲以下

 


    • 觸發分場景推送
      在界面上當進行了觸發操做的時候,經過後臺 curl 請求 swoole http 服務,swoole http 服務根據你向我傳遞的參數分發給對應的邏輯處理。如 curl 請求 127.0.0.1:9502page=back&func=pushHomeLogic&token=123456 咱們能夠根據傳入的 func 參數,在後臺分發給對應邏輯處理。如分發給 pushHomeLogic 方法。在其裏面實現本身的邏輯。爲防止過多的 ifelse 以及 foreach 操做,咱們採用的是閉包,call_user_func 等方法實現以下

 

 1 public function onRequest($request,$response)
 2 {
 3     if ($this->checkAccess("", $request)) {
 4         $param = $request->get;
 5         // 分發處理請求邏輯
 6         if (isset($param['func'])) {
 7             if (method_exists($this,$param['func'])) {
 8                 call_user_func([$this,$param['func']],$request);
 9             }
10         }
11     }
12 }// 往首頁推送邏輯處理
13 public function pushHomeLogic($request)
14 {
15     $callback = function (array $aContent,int $fd,SwooleDemo $oSwoole)use($request) {
16         if ($aContent && $aContent['page'] == "home") {
17             $aRes['message'] = "後端按了按鈕1";
18             $aRes['code'] = "200";
19             $oSwoole::$server->push($fd,xss_json($aRes));
20         }
21     };
22     $this->eachFdLogic($callback);
23 }

 

完整代碼

swool 腳本代碼邏輯前端

  1 <?php
  2 
  3 namespace App\Console\Commands;
  4 
  5 use Closure;
  6 use Illuminate\Console\Command;
  7 use Illuminate\Support\Facades\Redis;
  8 
  9 class SwooleDemo extends Command
 10 {
 11     // 命令名稱
 12     protected $signature = 'swoole:demo';
 13     // 命令說明
 14     protected $description = '這是關於swoole websocket的一個測試demo';
 15     // swoole websocket服務
 16     private static $server = null;
 17 
 18     public function __construct()
 19     {
 20         parent::__construct();
 21     }
 22 
 23     // 入口
 24     public function handle()
 25     {
 26         $this->redis = Redis::connection('websocket');
 27         $server = self::getWebSocketServer();
 28         $server->on('open',[$this,'onOpen']);
 29         $server->on('message', [$this, 'onMessage']);
 30         $server->on('close', [$this, 'onClose']);
 31         $server->on('request', [$this, 'onRequest']);
 32         $this->line("swoole服務啓動成功 ...");
 33         $server->start();
 34     }
 35 
 36     // 獲取服務
 37     public static function getWebSocketServer()
 38     {
 39         if (!(self::$server instanceof \swoole_websocket_server)) {
 40             self::setWebSocketServer();
 41         }
 42         return self::$server;
 43     }
 44     // 服務處始設置
 45     protected static function setWebSocketServer():void
 46     {
 47         self::$server  = new \swoole_websocket_server("0.0.0.0", 9502);
 48         self::$server->set([
 49             'worker_num' => 1,
 50             'heartbeat_check_interval' => 60,    // 60秒檢測一次
 51             'heartbeat_idle_time' => 121,        // 121秒沒活動的
 52         ]);
 53     }
 54 
 55     // 打開swoole websocket服務回調代碼
 56     public function onOpen($server, $request)
 57     {
 58         if ($this->checkAccess($server, $request)) {
 59             self::$server->push($request->fd,xss_json(["code"=>200,"message"=>"打開swoole服務成功"]));
 60         }
 61     }
 62     // 給swoole websocket 發送消息回調代碼
 63     public function onMessage($server, $frame)
 64     {
 65 
 66     }
 67     // http請求swoole websocket 回調代碼
 68     public function onRequest($request,$response)
 69     {
 70         if ($this->checkAccess("", $request)) {
 71             $param = $request->get;
 72             // 分發處理請求邏輯
 73             if (isset($param['func'])) {
 74                 if (method_exists($this,$param['func'])) {
 75                     call_user_func([$this,$param['func']],$request);
 76                 }
 77             }
 78         }
 79     }
 80 
 81     // websocket 關閉回調代碼
 82     public function onClose($serv,$fd)
 83     {
 84         $this->redis->hdel('swoole:fds', $fd);
 85         $this->line("客戶端 {$fd} 關閉");
 86     }
 87 
 88     // 校驗客戶端鏈接的合法性,無效的鏈接不容許鏈接
 89     public function checkAccess($server, $request):bool
 90     {
 91         $bRes = true;
 92         if (!isset($request->get) || !isset($request->get['token'])) {
 93             self::$server->close($request->fd);
 94             $this->line("接口驗證字段不全");
 95             $bRes = false;
 96         } else if ($request->get['token'] != 123456) {
 97             $this->line("接口驗證錯誤");
 98             $bRes = false;
 99         }
100         $this->storeUrlParamToRedis($request);
101         return $bRes;
102     }
103 
104     // 將每一個界面打開websocket的url 存儲起來
105     public function storeUrlParamToRedis($request):void
106     {
107         // 存儲請求url帶的信息
108         $sContent = json_encode(
109             [
110                 'page' => $request->get['page'],
111                 'fd' => $request->fd,
112             ], true);
113         $this->redis->hset("swoole:fds", $request->fd, $sContent);
114     }
115 
116     /**
117      * @param $request
118      * @see 循環邏輯處理
119      */
120     public function eachFdLogic(Closure $callback = null)
121     {
122         foreach (self::$server->connections as $fd) {
123             if (self::$server->isEstablished($fd)) {
124                 $aContent = json_decode($this->redis->hget("swoole:fds",$fd),true);
125                 $callback($aContent,$fd,$this);
126             } else {
127                 $this->redis->hdel("swoole:fds",$fd);
128             }
129         }
130     }
131     // 往首頁推送邏輯處理
132     public function pushHomeLogic($request)
133     {
134         $callback = function (array $aContent,int $fd,SwooleDemo $oSwoole)use($request) {
135             if ($aContent && $aContent['page'] == "home") {
136                 $aRes['message'] = "後端按了按鈕1";
137                 $aRes['code'] = "200";
138                 $oSwoole::$server->push($fd,xss_json($aRes));
139             }
140         };
141         $this->eachFdLogic($callback);
142     }
143     // 往列表頁推送邏輯處理
144     public function pushListLogic($request)
145     {
146         $callback = function (array $aContent,int $fd,SwooleDemo $oSwoole)use($request) {
147             if ($aContent && $aContent['page'] == "list") {
148                 $aRes['message'] = "後端按了按鈕2";
149                 $aRes['code'] = "200";
150                 $oSwoole::$server->push($fd,xss_json($aRes));
151             }
152         };
153         $this->eachFdLogic($callback);
154     }
155 
156     // 啓動websocket服務
157     public function start()
158     {
159         self::$server->start();
160     }
161 }
162 控制器代碼
163 
164 <?php
165 
166 namespace App\Http\Controllers;
167 
168 use Illuminate\Http\Request;
169 use Illuminate\Support\Facades\Redis;
170 class TestController extends Controller
171 {
172     // 首頁
173     public function home()
174     {
175         return view("home");
176     }
177     // 列表
178     public function list()
179     {
180         return view("list");
181     }
182     // 後端控制
183     public function back()
184     {
185         if (request()->method() == 'POST') {
186            $this->curl_get($this->getUrl());
187            return json_encode(['code'=>200,"message"=>"成功"]);
188         } else {
189             return view("back");
190         }
191 
192     }
193     // 獲取要請求swoole websocet服務地址
194     public function getUrl():string
195     {
196         // 域名 端口 請求swoole服務的方法
197         $sBase = request()->server('HTTP_HOST');
198         $iPort = 9502;
199         $sFunc = request()->post('func');
200         $sPage = "back";
201         return $sBase.":".$iPort."?func=".$sFunc."&token=123456&page=".$sPage;
202     }
203     // curl 推送
204     public function curl_get(string $url):string
205     {
206         $ch_curl = curl_init();
207         curl_setopt ($ch_curl, CURLOPT_TIMEOUT_MS, 3000);
208         curl_setopt($ch_curl, CURLOPT_SSL_VERIFYPEER, 0);
209         curl_setopt ($ch_curl, CURLOPT_HEADER,false);
210         curl_setopt($ch_curl, CURLOPT_HTTPGET, 1);
211         curl_setopt($ch_curl, CURLOPT_RETURNTRANSFER,true);
212         curl_setopt ($ch_curl, CURLOPT_URL,$url);
213         $str  = curl_exec($ch_curl);
214         curl_close($ch_curl);
215         return $str;
216     }
217 }

 

頁面 js 代碼


  • 後端控制頁
 1 <!DOCTYPE html>
 2 <html lang="en">
 3 <head>
 4 <meta charset="UTF-8">
 5 <title>後端界面</title>
 6 <meta name=viewport content="width=device-width,initial-scale=1,maximum-scale=1,user-scalable=no">
 7 </head>
 8 <body>
 9 <button class="push" data-func="pushHomeLogic">按鈕1</button>
10 <button class="push" data-func="pushListLogic">按鈕2</button>
11 </body>
12 <script src="{{ asset("/vendor/tw/global/jQuery/jquery-2.2.3.min.js")}} "></script>
13 <script>
14 $(function () {
15     $(".push").on('click',function(){
16         var func = $(this).attr('data-func').trim();
17         ajaxGet(func)
18     })
19     function ajaxGet(func) {
20         url = "{{route('back')}}";
21         token = "{{csrf_token()}}";
22         $.ajax({
23             url: url,
24             type: 'post',
25             dataType: "json",
26             data:{func:func,_token:token},
27             error: function (data) {
28                 alert("服務器繁忙, 請聯繫管理員!");
29                 return;
30             },
31             success: function (result) {
32 
33             },
34         })
35     }
36 
37 })
38 </script>
39 </html>

 

首頁node

 1 <!DOCTYPE html>
 2 <html lang="en">
 3 <head>
 4 <meta charset="UTF-8">
 5 <title>swoole首頁</title>
 6 <meta name=viewport content="width=device-width,initial-scale=1,maximum-scale=1,user-scalable=no">
 7 </head>
 8 <body>
 9 <h1>這是首頁</h1>
10 </body>
11 <script>
12 var ws;//websocket實例
13 var lockReconnect = false;//避免重複鏈接
14 var wsUrl = 'ws://{{$_SERVER["HTTP_HOST"]}}:9502?page=home&token=123456';
15 
16 function initEventHandle() {
17     ws.onclose = function () {
18         reconnect(wsUrl);
19     };
20     ws.onerror = function () {
21         reconnect(wsUrl);
22     };
23     ws.onopen = function () {
24         //心跳檢測重置
25         heartCheck.reset().start();
26     };
27     ws.onmessage = function (event) {
28         //若是獲取到消息,心跳檢測重置
29         //拿到任何消息都說明當前鏈接是正常的
30         var data = JSON.parse(event.data);
31         if (data.code == 200) {
32             console.log(data.message)
33         }
34         heartCheck.reset().start();
35     }
36 }
37 createWebSocket(wsUrl);
38 /**
39  * 建立連接
40  * @param url
41  */
42 function createWebSocket(url) {
43     try {
44         ws = new WebSocket(url);
45         initEventHandle();
46     } catch (e) {
47         reconnect(url);
48     }
49 }
50 function reconnect(url) {
51     if(lockReconnect) return;
52     lockReconnect = true;
53     //沒鏈接上會一直重連,設置延遲避免請求過多
54     setTimeout(function () {
55         createWebSocket(url);
56         lockReconnect = false;
57     }, 2000);
58 }
59 //心跳檢測
60 var heartCheck = {
61     timeout: 60000,//60秒
62     timeoutObj: null,
63     serverTimeoutObj: null,
64     reset: function(){
65         clearTimeout(this.timeoutObj);
66         clearTimeout(this.serverTimeoutObj);
67         return this;
68     },
69     start: function(){
70         var self = this;
71         this.timeoutObj = setTimeout(function(){
72             //這裏發送一個心跳,後端收到後,返回一個心跳消息,
73             //onmessage拿到返回的心跳就說明鏈接正常
74             ws.send("heartbeat");
75             self.serverTimeoutObj = setTimeout(function(){//若是超過必定時間還沒重置,說明後端主動斷開了
76                 ws.close();//若是onclose會執行reconnect,咱們執行ws.close()就好了.若是直接執行reconnect 會觸發onclose致使重連兩次
77             }, self.timeout);
78         }, this.timeout);
79     },
80     header:function(url) {
81         window.location.href=url
82     }
83 
84 }
85 </script>
86 </html>

 

列表頁面

 1 <!DOCTYPE html>
 2 <html lang="en">
 3 <head>
 4 <meta charset="UTF-8">
 5 <title>swoole列表頁</title>
 6 <meta name=viewport content="width=device-width,initial-scale=1,maximum-scale=1,user-scalable=no">
 7 </head>
 8 <body>
 9 <h1>swoole列表頁</h1>
10 </body>
11 <script>
12 var ws;//websocket實例
13 var lockReconnect = false;//避免重複鏈接
14 var wsUrl = 'ws://{{$_SERVER["HTTP_HOST"]}}:9502?page=list&token=123456';
15 
16 function initEventHandle() {
17     ws.onclose = function () {
18         reconnect(wsUrl);
19     };
20     ws.onerror = function () {
21         reconnect(wsUrl);
22     };
23     ws.onopen = function () {
24         //心跳檢測重置
25         heartCheck.reset().start();
26     };
27     ws.onmessage = function (event) {
28         //若是獲取到消息,心跳檢測重置
29         //拿到任何消息都說明當前鏈接是正常的
30         var data = JSON.parse(event.data);
31         if (data.code == 200) {
32             console.log(data.message)
33         }
34         heartCheck.reset().start();
35     }
36 }
37 createWebSocket(wsUrl);
38 /**
39  * 建立連接
40  * @param url
41  */
42 function createWebSocket(url) {
43     try {
44         ws = new WebSocket(url);
45         initEventHandle();
46     } catch (e) {
47         reconnect(url);
48     }
49 }
50 function reconnect(url) {
51     if(lockReconnect) return;
52     lockReconnect = true;
53     //沒鏈接上會一直重連,設置延遲避免請求過多
54     setTimeout(function () {
55         createWebSocket(url);
56         lockReconnect = false;
57     }, 2000);
58 }
59 //心跳檢測
60 var heartCheck = {
61     timeout: 60000,//60秒
62     timeoutObj: null,
63     serverTimeoutObj: null,
64     reset: function(){
65         clearTimeout(this.timeoutObj);
66         clearTimeout(this.serverTimeoutObj);
67         return this;
68     },
69     start: function(){
70         var self = this;
71         this.timeoutObj = setTimeout(function(){
72             //這裏發送一個心跳,後端收到後,返回一個心跳消息,
73             //onmessage拿到返回的心跳就說明鏈接正常
74             ws.send("heartbeat");
75             self.serverTimeoutObj = setTimeout(function(){//若是超過必定時間還沒重置,說明後端主動斷開了
76                 ws.close();//若是onclose會執行reconnect,咱們執行ws.close()就好了.若是直接執行reconnect 會觸發onclose致使重連兩次
77             }, self.timeout);
78         }, this.timeout);
79     },
80     header:function(url) {
81         window.location.href=url
82     }
83 
84 }
85 </script>
86 </html>

 

界面效果

後臺控制點擊按鈕 1

 

 

 

後端界面點擊按鈕 2

 

相關文章
相關標籤/搜索