欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

Laravel 中使用 swoole 项目实战开发案例二 (后端主动分场景给界面推送消息)

程序员文章站 2023-03-26 20:10:45
推荐阅读: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 存储如下

Laravel 中使用 swoole 项目实战开发案例二 (后端主动分场景给界面推送消息)

 


    • 触发分场景推送
      在界面上当进行了触发操作的时候,通过后台 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     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

 

 

Laravel 中使用 swoole 项目实战开发案例二 (后端主动分场景给界面推送消息)

 

Laravel 中使用 swoole 项目实战开发案例二 (后端主动分场景给界面推送消息)

后端界面点击按钮 2

 

Laravel 中使用 swoole 项目实战开发案例二 (后端主动分场景给界面推送消息)