Laravel 中使用 swoole 项目实战开发案例二 (后端主动分场景给界面推送消息)
推荐阅读: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 等方法实现如下
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'] = "";
$oSwoole::$server->push($fd,xss_json($aRes));
}
};
$this->eachFdLogic($callback);
}
完整代码
swool 脚本代码逻辑
<?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", );
self::$server->set([
'worker_num' => ,
'heartbeat_check_interval' => , // 60秒检测一次
'heartbeat_idle_time' => , // 121秒没活动的
]);
} // 打开swoole websocket服务回调代码
public function onOpen($server, $request)
{
if ($this->checkAccess($server, $request)) {
self::$server->push($request->fd,xss_json(["code"=>,"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'] != ) {
$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'] = "";
$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'] = "";
$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'=>,"message"=>"成功"]);
} else {
return view("back");
} }
// 获取要请求swoole websocet服务地址
public function getUrl():string
{
// 域名 端口 请求swoole服务的方法
$sBase = request()->server('HTTP_HOST');
$iPort = ;
$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, );
curl_setopt($ch_curl, CURLOPT_SSL_VERIFYPEER, );
curl_setopt ($ch_curl, CURLOPT_HEADER,false);
curl_setopt($ch_curl, CURLOPT_HTTPGET, );
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;
}
}
页面 js 代码
- 后端控制页
<!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..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 == ) {
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;
}, );
}
//心跳检测
var heartCheck = {
timeout: ,//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 == ) {
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;
}, );
}
//心跳检测
var heartCheck = {
timeout: ,//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>
界面效果
后台控制点击按钮 1
后端界面点击按钮 2
Laravel 中使用 swoole 项目实战开发案例二 (后端主动分场景给界面推送消息)的更多相关文章
- Laravel 中使用 swoole 项目实战开发案例一 (建立 swoole 和前端通信)
1 开发需要环境 工欲善其事,必先利其器.在正式开发之前我们检查好需要安装的拓展,不要开发中发现这些问题,打断思路影响我们的开发效率. 安装 swoole 拓展包 安装 redis 拓展包 安装 la ...
- iOS开发,推送消息 steps
概述:推送过程简介 一.App启动过程中,使用UIApplication::registerForRemoteNotificationTypes函数与苹果的APNS服务器通信,发出注册远程推送的申请. ...
- 知识图谱实战开发案例剖析-番外篇(1)- Neo4j是否支持按照边权重加粗和大数量展示
一.前言 本文是<知识图谱实战开发案例完全剖析>系列文章和网易云视频课程的番外篇,主要记录学员在知识图谱等相关内容的学习 过程中,提出的共性问题进行展开讨论.该部分内容原始内容记录在网易云 ...
- Android高效率编码-第三方SDK详解系列(二)——Bmob后端云开发,实现登录注册,更改资料,修改密码,邮箱验证,上传,下载,推送消息,缩略图加载等功能
Android高效率编码-第三方SDK详解系列(二)--Bmob后端云开发,实现登录注册,更改资料,修改密码,邮箱验证,上传,下载,推送消息,缩略图加载等功能 我的本意是第二篇写Mob的shareSD ...
- python 全栈开发,Day131(向app推送消息,玩具端消息推送)
先下载github代码,下面的操作,都是基于这个版本来的! https://github.com/987334176/Intelligent_toy/archive/v1.4.zip 注意:由于涉及到 ...
- IOS中程序如何进行推送消息(本地推送,远程推送)
[1]-------------什么是推送消息? 我就以一张图解释------------ [2]-----------IOS程序中如何进行本地推送?----------- 2.1,先征求用户同意 1 ...
- phonegap创建的ios项目推送消息出现闪退现象
使用phonegap创建的ios项目,推送消息时,当程序在前台运行或者在后台运行状态下,推送消息过来,可以解析并且跳转: 但是在程序从后台退出的状态下,当消息推送过来的时候,点击通知栏,打开程序,程序 ...
- cocos2d-x中本地推送消息
作者:HU 转载请注明,原文链接:http://www.cnblogs.com/xioapingguo/p/4038277.html IOS下很简单: 添加一条推送 void PushNotific ...
- 《Node+MongoDB+React 项目实战开发》已出版
前言 从深圳回长沙已经快4个月了,除了把车开熟练了外,并没有什么值得一提的,长沙这边要么就是连续下一个月雨,要么就是连续一个月高温暴晒,上班更是没啥子意思,长沙这边的公司和深圳落差挺大的,薪资也是断崖 ...
随机推荐
- bash:双引号和单引号
单引号.双引号都能引用字符和字符串 单引号:'$i'仅仅是字符,没有变量的意思了 双以号:变量等能表示出来
- 1 数据 & 图表
瞎逼逼:虽然是统计专业,但学艺不精.大学受过的专业训练很少,妥妥学渣.因此工作后决定重新复习,阅读材料为贾俊平的<统计学>第7版.每周更新. 我不按照书里的逻辑顺序和所有知识点来写我的笔记 ...
- href=”javascript:void(0);
href=”javascript:void(0);”这个的含义是,让超链接去执行一个js函数,而不是去跳转到一个地址,而void(0)表示一个空的方法,也就是不执行js函数. 为什么要使用href=” ...
- 理解Redis单线程运行模式
本文首发于:https://mp.weixin.qq.com/s/je4nqCIq6ARhSV2V5Ymmtg 微信公众号:后端技术指南针 0.概述 通过本文将了解到以下内容: Redis服务器采用单 ...
- Handler+Looper+MessageQueue深入详解
概述:Android中的异步处理机制由四部分组成:Handler+Looper+MessageQueue+message,用于实现线程间的通信. 用到的概念: Handler: 主要作用是发送消息和处 ...
- [ASP.NET Core 3框架揭秘] 配置[1]:读取配置数据[上篇]
提到"配置"二字,我想绝大部分.NET开发人员脑海中会立即浮现出两个特殊文件的身影,那就是我们再熟悉不过的app.config和web.config,多年以来我们已经习惯了将结构化 ...
- 分布式远程调用SpringCloud-Feign的两种具体操作方式(精华)
一 前言 几大RPC框架介绍 1.支持多语言的RPC框架,google的gRPC,Apache(facebook)的Thrift 2.只支持特定语言的RPC框架,例如新浪的Motan 3.支持服务治理 ...
- Mybatis工作流程源码分析
1.简介 MyBatis 是一款优秀的持久层框架,它支持定制化 SQL.存储过程以及高级映射.MyBatis 避免了几乎所有的 JDBC 代码和手动设置参数以及获取结果集.MyBatis 可以使用简单 ...
- Jeewx-Boot 1.1 版本发布,基于SpringBoot的开源微信管家系统
项目介绍 JeewxBoot是一款基于SpringBoot的开源微信管家系统,采用SpringBoot2.1.3 + Mybatis + Velocity 框架技术.支持微信公众号.微信第三方平台(扫 ...
- python排序算法之一:冒泡排序(及其优化)
相信冒泡排序已经被大家所熟知,今天看了一篇文章,大致是说在面试时end在了冒泡排序上,主要原因是不能给出冒泡排序的优化. 所以,今天就写一下python的冒泡排序算法,以及给出一个相应的优化.OK,前 ...