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

来源:https://www.cnblogs.com/a609251438/archive/2019/12/19/12069807.html
-Advertisement-
Play Games

推薦閱讀:Laravel 中使用 swoole 項目實戰開發案例一 (建立 swoole 和前端通信)​ 需求分析 我們假設有一個需求,我在後端點擊按鈕 1,首頁彈出 “後端觸發了按鈕 1”。後端點了按鈕 2,列表頁彈出 “後端觸發了按鈕 2”。做到根據不同場景推送到不同頁面。 代碼思路 Swool ...


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

 

需求分析

我們假設有一個需求,我在後端點擊按鈕 1,首頁彈出 “後端觸發了按鈕 1”。後端點了按鈕 2,列表頁彈出 “後端觸發了按鈕 2”。做到根據不同場景推送到不同頁面。

代碼思路


  • 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>

 

首頁

 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  

您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 庫名稱簡介 Chardet 字元編碼探測器,可以自動檢測文本、網頁、xml的編碼。colorama 主要用來給文本添加各種顏色,並且非常簡單易用。Prettytable 主要用於在終端或瀏覽器端構建格式化的輸出。difflib,[Python]標準庫,計算文本差異Levenshtein,快速計算字元 ...
  • 環境描述 idea java 8 1. POM文件 <?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/ ...
  • 簡單類對象的實例化過程 1、在方法區載入類; 2、在棧記憶體申請空間,聲明變數P; 3、在堆記憶體中開闢空間,分配對象地址; 4、在對象空間中,對對象的屬性進行預設初始化,類成員變數顯示初始化; 5、構造方法進棧,進行初始化; 6、初始化完成後,將堆記憶體中的地址賦給引用變數,構造方法出棧; 子類對象的實 ...
  • 題目描述: 在一個二維數組中(每個一維數組的長度相同),每一行都按照從左到右遞增的順序排序,每一列都按照從上到下遞增的順序排序。請完成一個函數,輸入這樣的一個二維數組和一個整數,判斷數組中是否含有該整數。 題目分析: 根據二維數組的特點可知,二維數組相當於一個矩陣; 根據題意可知該數組是有序的,因此 ...
  • 前言本文的文字及圖片來源於網路,僅供學習、交流使用,不具有任何商業用途,版權歸原作者所有,如有問題請及時聯繫我們以作處理。作者:夢想橡皮擦 CentOS環境安裝-簡介你好,當你打開這個文檔的時候,我知道,你想要的是什麼! Python爬蟲,如何快速的學會Python爬蟲,是你最期待的事情,可是這個事 ...
  • 這篇文章主要介紹瞭如何通過Java如何生成驗證碼並驗證。驗證碼的作用我想必大家都知道,話不多說開始實施! 首先創建一個springboot項目以下是項目結構,內有utli工具類、存放生成圖片驗證碼方法、controller存放一些攔截請求方法。 接下來 在utli中創建一個Class類,進行生成隨機 ...
  • 一、Spring Cloud核心組件:Eureka Netflix Eureka Eureka詳解 1、服務提供者 2、服務消費者 3、服務註冊中心 二、Spring Cloud核心組件:Ribbon 三、Spring Cloud核心組件:Feign 四、Spring Cloud核心組件:Hystr ...
  • 第一步:安裝vsftpd提供ftp服務 https://www.cnblogs.com/lyq159/p/12070791.html 第二步:安裝Nginx提供http服務 1.安裝準備:安裝Nginx環境 a) gcc 安裝nginx需要先將官網下載的源碼進行編譯,編譯依賴gcc環境,如果沒有gc ...
一周排行
    -Advertisement-
    Play Games
  • 示例項目結構 在 Visual Studio 中創建一個 WinForms 應用程式後,項目結構如下所示: MyWinFormsApp/ │ ├───Properties/ │ └───Settings.settings │ ├───bin/ │ ├───Debug/ │ └───Release/ ...
  • [STAThread] 特性用於需要與 COM 組件交互的應用程式,尤其是依賴單線程模型(如 Windows Forms 應用程式)的組件。在 STA 模式下,線程擁有自己的消息迴圈,這對於處理用戶界面和某些 COM 組件是必要的。 [STAThread] static void Main(stri ...
  • 在WinForm中使用全局異常捕獲處理 在WinForm應用程式中,全局異常捕獲是確保程式穩定性的關鍵。通過在Program類的Main方法中設置全局異常處理,可以有效地捕獲並處理未預見的異常,從而避免程式崩潰。 註冊全局異常事件 [STAThread] static void Main() { / ...
  • 前言 給大家推薦一款開源的 Winform 控制項庫,可以幫助我們開發更加美觀、漂亮的 WinForm 界面。 項目介紹 SunnyUI.NET 是一個基於 .NET Framework 4.0+、.NET 6、.NET 7 和 .NET 8 的 WinForm 開源控制項庫,同時也提供了工具類庫、擴展 ...
  • 說明 該文章是屬於OverallAuth2.0系列文章,每周更新一篇該系列文章(從0到1完成系統開發)。 該系統文章,我會儘量說的非常詳細,做到不管新手、老手都能看懂。 說明:OverallAuth2.0 是一個簡單、易懂、功能強大的許可權+可視化流程管理系統。 有興趣的朋友,請關註我吧(*^▽^*) ...
  • 一、下載安裝 1.下載git 必須先下載並安裝git,再TortoiseGit下載安裝 git安裝參考教程:https://blog.csdn.net/mukes/article/details/115693833 2.TortoiseGit下載與安裝 TortoiseGit,Git客戶端,32/6 ...
  • 前言 在項目開發過程中,理解數據結構和演算法如同掌握蓋房子的秘訣。演算法不僅能幫助我們編寫高效、優質的代碼,還能解決項目中遇到的各種難題。 給大家推薦一個支持C#的開源免費、新手友好的數據結構與演算法入門教程:Hello演算法。 項目介紹 《Hello Algo》是一本開源免費、新手友好的數據結構與演算法入門 ...
  • 1.生成單個Proto.bat內容 @rem Copyright 2016, Google Inc. @rem All rights reserved. @rem @rem Redistribution and use in source and binary forms, with or with ...
  • 一:背景 1. 講故事 前段時間有位朋友找到我,說他的窗體程式在客戶這邊出現了卡死,讓我幫忙看下怎麼回事?dump也生成了,既然有dump了那就上 windbg 分析吧。 二:WinDbg 分析 1. 為什麼會卡死 窗體程式的卡死,入口門檻很低,後續往下分析就不一定了,不管怎麼說先用 !clrsta ...
  • 前言 人工智慧時代,人臉識別技術已成為安全驗證、身份識別和用戶交互的關鍵工具。 給大家推薦一款.NET 開源提供了強大的人臉識別 API,工具不僅易於集成,還具備高效處理能力。 本文將介紹一款如何利用這些API,為我們的項目添加智能識別的亮點。 項目介紹 GitHub 上擁有 1.2k 星標的 C# ...