websocket 导致大量apache进程_Swoole 服务端主动向websocket推送消息

7092c096dce3d9c7c1df20b3f3034a52.png

在之前的博文中,我们已经学完了如果使用swoole搭建websocket长连接,也学会了swoole的多进程数据共享操作。

但在一个完整的websocket长连接日常操作链中,服务端往往会主动给在线的用户单独推送消息,会群发一些消息。

在Swoole-websocket中给我们提供了一个onRequest事件,该事件用于监听外部请求。

也就是我们可以通过http请求向websocket中调取数据,进而发送消息。

onRequest的示例代码如下:

官网文档是:https://wiki.swoole.com/wiki/page/397.html

 $this->_ws->on('request', function ($request, $response) {//var_dump($request);# 如果你是get的,就改成get,可以用dump看看$request$param = $request->post;$data  = [];$data['code'] = 3;$data['user_nice'] = '系统通知';$data['content'] = $param['content'];# 下面我们来广播消息if (empty($param['user_id'])) {# 群发$this->broadcast($this->_ws, $this->json($data));# 返回消息$this->endRequest('200', '发送成功', $request, $response);} else {# 单发if (empty($this->_ws->user[$param['user_id']]['fd'])) {# 返回消息$this->endRequest('500', '客户不存在', $request, $response);} else {$user = $this->_ws->user[$param['user_id']];if ($user['status'] == 0) {# 返回消息$this->endRequest('500', '客户已下线', $request, $response);} else {$this->_ws->push($user['fd'], $this->json($data));# 返回消息$this->endRequest('200', '发送成功', $request, $response);}}}
});
其中最重要的是endRequest这个方法的代码,我们接着往下看:/*** request事件返回值
*/
private function endRequest($code, $msg, $request, $response) {$json = ['code' => "$code",'msg'  => "$msg",];# 输出响应$return = json_encode($json, JSON_UNESCAPED_UNICODE);# 需要end事件,否注会报500错误,并无结果返回# 不知道为啥,CLI模式下这个事件一次请求会有2次监听,但发现最后一次其中的server->request_uri会有个/favicon.ico参数# 所以凭借这个参数,我们可以做判断,放弃掉第一次监听返回# 还有,如果我们直接在onRequest中过滤掉第一次监听,那第二次监听就不会执行,也会报500错误# 所以我们只能在返回的时候做下手脚//if($request->server['request_uri'] == '/favicon.ico') {$response->end($return);//}# 而且我发现经过这样处理,onRequest事件那边也只会有一次请求了,特别奇怪。# 而且这样返回之后,浏览器直接请求还是报500错误。# 熟悉Swoole的朋友可以在下方留言,指教下我的疑惑。
}

从上面的注释中我们可以看出,endRequest的输出值很奇怪,它支持CLI模式下运行,但该模式下的会有2次endRequest监听,需要使用server->request_uri/favicon.ico参数进行拦截输出返回值,否注将报错。

而通过CURL或浏览器发包的方式则不能拦截,同时这种请求方式只有1次endRequest监听,所以不能拦截返回值。

同时需要注意,endRequest同步输出返回值,不能直接使用echo,而是需要把返回内容放在$response->end()中。

下面我们来看看完整的server.php端代码:

<?php
// +----------------------------------------------------------------------
// 小黄牛blog - Swoole 即时通讯交互处理
// +----------------------------------------------------------------------
// Copyright (c) 2018 https://xiuxian.junphp.com All rights reserved.
// +----------------------------------------------------------------------
// Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
// +----------------------------------------------------------------------
// Author: 小黄牛 <1731223728@qq.com>
// +----------------------------------------------------------------------
class Server{/*** 客户端身份存储器*/private $_table = []; /*** WS的启动实例*/private $_ws;/*** host-IP,0.0.0.0表示允许接收所有请求*/private $_host = '0.0.0.0';/*** 端口号*/private $_port = '9502';/*** 最大服务端心跳重连次数*/private $_max  = 3;/*** 强制心跳重连启动状态*/private $_status = true;/*** 这是启动服务端的入口*/public function run() { $this->start_service(); $this->start_table(); $this->request();$this->start_handshake();$this->start_message();$this->end();}/*** ①启动websocker服务*/private function start_service() {# 创建websocket服务器对象,监听0.0.0.0:9502端口$this->_ws = new swoole_websocket_server($this->_host, $this->_port);$this->_ws->set(['worker_num' => 4,// 开4个工作进程]);}/*** ①创建Table服务*/private function start_table() {# 创建最大只能存储1024个用户的数据$this->_table = new swoole_table(1024);# 创建字段$this->_table->column('fd', swoole_table::TYPE_INT, 8); // FD$this->_table->column('status', swoole_table::TYPE_INT, 8); // 离线状态$this->_table->column('heartbeat', swoole_table::TYPE_INT, 8); // 心跳重连数$this->_table->column('user_id', swoole_table::TYPE_STRING, 32); // 会员ID$this->_table->column('user_nice', swoole_table::TYPE_STRING, 32); // 会员名称$this->_table->create();# 将表附加到ws实例里,方便后续使用$this->_ws->user = $this->_table;}/*** ②监听WebSocket握手申请*/private function start_handshake() {# 监听WebSocket连接打开事件$this->_ws->on('open', function ($ws, $request){# 这里可以做些鉴权验证之类的});}/*** ③监听客户端消息发送请求*/private function start_message() {# 监听WebSocket消息事件$this->_ws->on('message', function ($ws, $frame) {$data    = json_decode($frame->data, true);$user_id = $data['user_id'];# 加入存储器$this->_ws->user->set($user_id, ['fd'        => $frame->fd, # FD'status'    => 1, # 设置上线状态'heartbeat' => 0, # 重置心跳重连数'user_id' => $data['user_id'], # 用户ID'user_nice' => $data['user_nice'], # 用户昵称]);# 登录广播处理if ($data['code'] == 1) {# 发送广播上线消息$data['content'] = '【'.$data['user_nice'].'】骑着小黄牛上线啦~!';$this->broadcast($ws, $this->json($data), $user_id);# 心跳重连检测} else if ($data['code'] == 4) {$this->broadcast($ws, $frame->data, $user_id);$this->timer();# 其他请求} else {# 广播消息$this->broadcast($ws, $frame->data, $user_id);}});} /*** ④监听客户端退出事件*/private function end() {# 这里加入了unset,清除open存储器,防止存储器无限增大# 监听WebSocket连接关闭事件$this->_ws->on('close', function ($ws, $fd) {$user = null;foreach ($this->_ws->user as $k=>$v) {if ($v['fd'] == $fd) {$user = $v;}}# 如果没用用户就跳过if (!$user) {return false;}# 获取用户ID$user_id = $user['user_id'];# 获取用户nice$user_nice = $user['user_nice'];# 设置离线状态$this->_ws->user->set($user_id, ['status'    => 0, # 设置离线状态]);$data = ['code' => 2,'user_id' => $user_id,'user_nice' => $user_nice,'content' => '【'.$user_nice.'】骑着小扫帚灰溜溜的走了~~!'];# 广播消息$this->broadcast($ws, $this->json($data));});$this->_ws->start();}/*** ④监听外部请求推送事件*/private function request() {$this->_ws->on('request', function ($request, $response) {//var_dump($request);# 如果你是get的,就改成get,可以用dump看看$request$param = $request->post;$data  = [];$data['code'] = 3;$data['user_nice'] = '系统通知';$data['content'] = $param['content'];# 下面我们来广播消息if (empty($param['user_id'])) {# 群发$this->broadcast($this->_ws, $this->json($data));# 返回消息$this->endRequest('200', '发送成功', $request, $response);} else {# 单发if (empty($this->_ws->user[$param['user_id']]['fd'])) {# 返回消息$this->endRequest('500', '客户不存在', $request, $response);} else {$user = $this->_ws->user[$param['user_id']];if ($user['status'] == 0) {# 返回消息$this->endRequest('500', '客户已下线', $request, $response);} else {$this->_ws->push($user['fd'], $this->json($data));# 返回消息$this->endRequest('200', '发送成功', $request, $response);}}}});}/*** request事件返回值*/private function endRequest($code, $msg, $request, $response) {$json = ['code' => "$code",'msg'  => "$msg",];# 输出响应$return = json_encode($json, JSON_UNESCAPED_UNICODE);# 需要end事件,否注会报500错误,并无结果返回# 不知道为啥,CLI模式下这个事件一次请求会有2次监听,但发现最后一次其中的server->request_uri会有个/favicon.ico参数# 所以凭借这个参数,我们可以做判断,放弃掉第一次监听返回# 还有,如果我们直接在onRequest中过滤掉第一次监听,那第二次监听就不会执行,也会报500错误# 所以我们只能在返回的时候做下手脚//if($request->server['request_uri'] == '/favicon.ico') {$response->end($return);//}# 而且我发现经过这样处理,onRequest事件那边也只会有一次请求了,特别奇怪。# 而且这样返回之后,浏览器直接请求还是报500错误。# 熟悉Swoole的朋友可以在下方留言,指教下我的疑惑。}/*** 广播消息* @todo 无* @author 小黄牛* @version v1.0.0.1 + 2018.11.12* @deprecated 暂不弃用* @global 无* @param object $wx 实例* @param string $content 广播内容* @param string $id 用户的userid*  @param bool $status 是否做心跳限制 * @return void*/private function broadcast($ws, $content, $id=null, $status=false) {# 向所有人广播foreach ($this->_ws->user as $k=>$v) {# 不向自己广播,并且要在线的# 注意,这里一定要有上线状态的限制,否则假设用户已经退出,但你的进程还开着,实际上已经关闭,这时候push就会报错# 只有正常在线的用户才能接收到广播# 加入心跳检测限制if ($k != $id && $v['status'] == 1 && $status == true) {$ws->push($v['fd'], $content);} else if ($v['user_id'] != $id && $v['status'] == 1 && $v['heartbeat'] == 0) {$ws->push($v['fd'], $content);}}}/*** 数组转json* @todo 无* @author 小黄牛* @version v1.0.0.1 + 2018.11.08* @deprecated 暂不弃用* @global 无* @param array $array 数组* @return json*/private function json($array) {return json_encode($array, JSON_UNESCAPED_UNICODE);}/*** 服务端定时强制心跳检测* @todo 无* @author 小黄牛* @version v1.0.0.1 + 2018.11.08* @deprecated 暂不弃用* @global 无* @return void*/private function timer() {# 注意强制心跳触发器不能放在open事件里,因为那时候用户还没有提交登录请求,是还没有userID的# 还有,强制心跳定时器只能触发一次,否则会出现生成多个定时器的情况if ($this->_status) {$this->_status = false;/*** ⑤服务端强制心跳检测* 每隔1分钟发送1次,如果连续3次强制心跳检测未通过,服务端将强制断开连接*/$obj = $this;swoole_timer_tick(60000, function ($timer_id) use (&$obj) {# 广播消息$obj->broadcast($obj->_ws, $obj->json(['code' => 5]), null, true);# 所有人的心跳次数+1foreach ($this->_ws->user as $k=>$v) {if (empty($v['heartbeat'])) {# 重置心跳次数$this->_ws->user->set($v['user_id'], ['heartbeat' => 0,]);}# 心跳次数累加$this->_ws->user->set($v['user_id'], ['heartbeat' => $v['heartbeat']+1]);# 心跳次数大于等于_max && 在线的 的连接关闭if ($v['heartbeat'] >= $obj->_max && $v['status'] == 1) {$data = $v;# 发送强制掉线广播$data['code'] = 6;$data['content'] = '【'.$data['user_nice'].'】已被服务端强制下线!';$obj->broadcast($obj->_ws, $obj->json($data), null, true);# 这里不需要unset连接,因为在close事件中,已经将这个连接设置为离线了# 主动关闭连接k$obj->_ws->close($v['fd']);}}});}}
}
$socketServer = new Server();
$socketServer->run();

然后再看如何给websocket主动推送的client.php端代码:

<?php
// +----------------------------------------------------------------------
// 小黄牛blog - websocket - http发包给TCP
// +----------------------------------------------------------------------
// Copyright (c) 2018 https://xiuxian.junphp.com All rights reserved.
// +----------------------------------------------------------------------
// Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
// +----------------------------------------------------------------------
// Author: 小黄牛 <1731223728@qq.com>
// +----------------------------------------------------------------------
function https_request($url, $data = null){# 初始化一个cURL会话$curl = curl_init();  //设置请求选项, 包括具体的urlcurl_setopt($curl, CURLOPT_URL, $url);curl_setopt($curl, CURLOPT_SSL_VERIFYPEER, FALSE);  //禁用后cURL将终止从服务端进行验证curl_setopt($curl, CURLOPT_SSL_VERIFYHOST, FALSE);if (!empty($data)){curl_setopt($curl, CURLOPT_POSTFIELDS, $data);  //设置具体的post数据}curl_setopt($curl, CURLOPT_RETURNTRANSFER, 1);        $response = curl_exec($curl);  //执行一个cURL会话并且获取相关回复//$httpCode = curl_getinfo($curl,CURLINFO_HTTP_CODE); //echo $httpCode;curl_close($curl);  //释放cURL句柄,关闭一个cURL会话return $response;
}
var_dump(https_request('http://IP:端口', ['user_id' => '用户ID',// 为空群发'content'=> '测试内容'
]));

我们只需要通过访问client.php,就能给指定用户推送消息拉。

同时我们需要注意,在真正开发中,我们还需要对onRequest事件的请求进行加密跟鉴权处理,否注很容易被竞争对手恶意攻击。

而且服务端还可以通过onRequest事件拉取到所有的在线用户消息,更多相关的功能都可自行扩展。


最后推荐大家可以用下我开源的一个基于Swoole4.5+研发的PHP框架。该框架基于注解实现了很多好玩的功能,很适合新人快速上手Swoole扩展。

SW-X框架-专注高性能便捷开发而生的PHP-SwooleX框架​www.sw-x.cn
604f8e745dacdb12498ada925ea17262.png

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/498976.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

python因子分析法_python——因子分析

因子分析用Python做的一个典型例子 一、实验目的 采用合适的数据分析方法对下面的题进行解答二、实验要求 采用因子分析方法&#xff0c;根据48位应聘者的15项指标得分&#xff0c;选出6名最优秀的应聘者。 三、代码 importpandas aspd importnumpy asnp importmath asmath imp…

polycom安卓手机客户端_三款免费「游戏串流」APP,在手机/电视上玩PC游戏

以前想把电脑游戏输出到电视机上玩&#xff0c;得用HDMI线输出&#xff0c;如果电脑主机和电视机离太远&#xff0c;HDMI线不够长&#xff0c;还得抱电脑主机到电视机旁边&#xff0c;非常不方便。随着技术的发展&#xff0c;现在已经有「串流」功能&#xff0c;只要电视机和电…

java框架谁搭建_从零开始搭建一个开发框架(Java + Hibernate + Spring + Oracle)

框架使用的系统技术以及数据库如下&#xff1a;技术&#xff1a;Java/Hibernate/Hibernate Annotation/Spring数据库&#xff1a;Oracle 10g整个框架的搭建步骤大致分为以下三步&#xff1a;1、创建数据库相关信息2、配置Hibernate框架(导入包、编写hibernate.cfg.xml配置文件、…

python获取返回值_python如何获取函数的返回值

函数需要先定义后调用&#xff0c;函数体中 return 语句的结果就是返回值。如果一个函数没有 reutrn 语句&#xff0c;其实它有一个隐含的 return 语句&#xff0c;返回值是 None&#xff0c;类型也是 NoneType。return 语句的作用&#xff1a;结束函数调用、返回值 指定返回值…

java模拟器百度_Java模拟实现百度文档在线浏览

这个思路是我参考网上而来&#xff0c;代码是我实现。采用Apache下面的OpenOffice将资源文件转化为pdf文件&#xff0c;然后将pdf文件转化为swf文件&#xff0c;用FlexPaper浏览。ok&#xff0c;A、下载OpenOffice (转换资源文件)B、下载JodConverter(调用OpenOffice)C、下载Sw…

python去掉标点、特殊符号_python3去掉string中的标点符号方法

网上看到的python去掉字符串中的标点符号的方法&#xff0c;大多是基于python2的&#xff0c;不适用python3&#xff0c;调整后代码如下&#xff1a; 代码 lower_case_documents [Hello, how are you!,Win money, win from home.,Call me now.,Hello, Call hello you tomorrow…

java第一阶段知识_第一阶段 Java语言(下)

本课程是Android入门教程之Java核心技术阶段&#xff0c;是Java开发高级部分&#xff0c;安卓开发中的基础知识&#xff0c;讲解以下内容&#xff1a;(一)反射与内省讲解了什么是反射&#xff0c;反射在应用中的作用&#xff0c;相关反射的API&#xff0c;如Class类&#xff0c…

body click js 委托_JS 事件循环

进程 线程CPU 分配资源的最小单位是进程&#xff0c;同一个时间内单个 CPU 只能运行一个进程&#xff0c;单个 CPU 一次只能运行一个任务CPU 调度的最小单位是线程&#xff0c;一个进程里面包含多个线程。可以看看阮老师的这篇文章&#xff0c;进程与线程的一个简单解释浏览器的…

java格式_JAVA语言格式

3.1 常量 3.1.1 常量概述 – 在程序执行的过程中&#xff0c;其值不可以发生改变的量 3.1.2 常量分类 – 字符串常量用双引号括起来的内容(“HelloWorld”) – 整数常量 所有整数(12,-23) – 小数常量 所有小数(12.34) – 字符常量 用单引号括起来的内容(‘a’,’A’,’0’) –…

django jsonresponse_利用 Django 动态展示 Pyecharts 图表数据的几种方法

本文将介绍如何在 web 框架 Django 中使用可视化工具 Pyecharts, 看完本教程你将掌握几种动态展示可视化数据的方法!Django 模板渲染1. 新建一个 Django 项目命令行中输入以下命令django-admin startproject pyecharts_django_demo创建一个应用程序python manage.py startapp d…

python网页登录验证码不显示_进网页需要验证码?不好意思,Python从来不惧各种验证码!...

今天要来说说滑动验证码了大家应该都很熟悉点击滑块然后移动到图片缺口进行验证现在越来越多的网站使用这样的验证方式为的是增加验证码识别的难度那么&#xff0c;对于这种验证码应该怎么破呢接下来就是见证神奇的时刻打开 b 站的登录页面可以看到登录的时候需要进行滑块验证按…

怎么调用新建模型里文章的内容_优雅地进行Tensorflow Lite模型转换

初涉知乎江湖&#xff0c;知道大佬很多&#xff0c;请温柔以待&#xff01;&#xff01;&#xff01;七日凌晨&#xff0c;谷歌连夜发布了有关于Tensorflow最新成果和技术&#xff0c;这应该是贾扬清离开脸书后另一个深度学习界令人惊呼的事件了吧&#xff01;&#xff08;旁白…

elastic java_ElasticSearch和Java环境变量

当我下载并解压缩elasticsearch并运行/bin/service.bat或/bin/elasticsearch.bat文件时&#xff0c;我进入了终端&#xff1a;JAVA_HOME environment variable must be set! Press any key to continue . . .当我这样做时&#xff0c;终端关闭。我已经卸载并重新安装&#xff0…

git add后取消_Git常用命令-总结

创建git用户$ git config --global user.name "Your Name"$ git config --global user.email "emailexample.com"初始化一个Git仓库&#xff0c;使用git init命令。添加文件到Git仓库&#xff0c;分两步&#xff1a;使用命令git add &#xff0c;注意&…

java数组图片_在JAVA中定义图片数组

为实现此意图需要分三段来实现&#xff1a;1。 初始化&#xff1a; Image[] imgnew Image[n];for(int i0;iimg[i]Toolkit.getDefaultToolkit().createImage(....)//装入图片位置}2. 启动变换图片与刷新界面线程run(){//线程入口while(flag){index;index%n;(或写成if(index>n…

查看csv编码_[小O地图-数据] - 坐标转地址文字(逆地理编码)

小O地图是一款基于互联网地图进行地理数据处理、分析、图表的软件。致力为广大科研人员提供专业地图数据&#xff0c;用于科研及学习。小&#xff2f;地图提供将【经纬度坐标转换为地址】的功能&#xff0c;例如&#xff1a;将“116.359861, 39.917225” 转换为 “北京市西城区…

c# mysql 封装_C#简单通用的数据库连接封装

usingSystem;usingSystem.Collections.Generic;usingSystem.Linq;usingSystem.Text;usingSystem.Threading.Tasks;usingSystem.Data;usingSystem.Data.SqlClient;namespaceDbLib{/// ///数据库类/// public classDatabase : IDisposable{/// ///受保护的数据库连接/// protecte…

python自动化办公源码_python自动化办公:文件篇(自动整理文件,一键完成)

import os list_all[]#初始化一个空列表 for root ,dirs,files in os.walk(rC:UsersShineionDesktop新建文件夹): for name in files: file_pathos.path.join(root,name)#包含路径的文件 file_nameos.path.split(file_path)[-1] list_all.append(file_name) print(list_all)如果…

java中no1_Java程序设计实验(NO.1).doc

Java程序设计实验(NO.1)1、实验目的&#xff1a;使用Java的String类操作字符串和子串。写一个程序可以对两个字符串进行测试&#xff0c;判断第一个字符串是否包含在第二个字符串中&#xff0c;例如字符串“op”包含在字符串“interoperabilityop”中。当第一个字符串包含在第二…

ocr中文数据集_CNOCR:测试集准确率最高98%,自带识别模型的中文OCR包

今天 Gitee 为大家介绍的是一款中文 OCR 包。大家都知道&#xff0c;训练模型是一件非常费时费力的事情&#xff0c;但今天这款项目已经自带训练好的识别模型&#xff0c;我们只需要下载下来使用即可&#xff0c;可以说是非常方便了&#xff0c;那么下面我们就去看看这个项目的…