Laravel+swoole 实现websocket长链接
需要使用 swoole 扩展
我使用的是 swoole 5.x
start 方法启动服务 和 定时器
调整 listenQueue 定时器可以降低消息通讯延迟
定时器会自动推送队列里面的消息
testMessage 方法测试给指定用户推送消息
使用 laravel console 启动
<?phpnamespace App\Console\Commands;use App\Services\SocketService;
use Illuminate\Console\Command;class WsServer extends Command
{/*** The name and signature of the console command.** @var string*/protected $signature = 'app:wsServer';/*** The console command description.** @var string*/protected $description = 'Command description';/*** Execute the console command.*/public function handle(){$SocketService = new SocketService();$SocketService->start();}
}
socket 服务实现代码
<?phpnamespace App\Services;use Swoole\WebSocket\Server;
use Swoole\Timer;
use Illuminate\Support\Facades\Redis;
use RedisException;
use Swoole\Http\Request;class SocketService
{public $port = 9501;public $server;public $links;public $cmds = [];public function __construct (){$this->links = collect([]);$this->server = new Server("0.0.0.0", env('APP_SOCKET_PORT', $this->port ));$this->server->on( 'open', function (Server $server, Request $request){$this->open( $server, $request );} );$this->server->on( 'message', function (Server $server, $frame){$this->message( $server, $frame );} );$this->server->on( 'close', function (Server $server, $fd){$this->close( $server, $fd );} );}public function start(){$this->linkManage();$this->listenQueue();$this->server->start();}public function print( $message, $level = 'info' ){if( is_array($message) || is_object($message) ){$message = json_encode($message, 320);}print_r( "[". date("Y-m-d H:i:s") ."] " . $level . ' ' . $message . "\n" );}public function linkManage(){Timer::tick( 100, function (){//var_dump( "listenQueue while: " . json_encode($this->cmds, 320) );$cmd = array_shift( $this->cmds );if( $cmd ){switch ( $cmd['operate'] ){case 'open':// 活跃$this->links->push( [ "fd" => $cmd['fd'], "user_id" => intval($cmd['user_id']??0), 'updated_at' => date("Y-m-d H:i:s") ] );$this->print( "添加客户端:fd = " . json_encode($cmd, 320) );break;case 'close':$newLinks = [];foreach ( $this->links as $link ){if( $link['fd'] == $cmd['fd'] ){continue;}$newLinks[] = $link;}$this->links = collect( $newLinks );$this->print( "删除客户端:fd = " . json_encode($cmd, 320) );break;case 'heartbeat':$newLinks = [];foreach ( $this->links as $link ){if( $link['fd'] == $cmd['fd'] ){$link['updated_at'] = date("Y-m-d H:i:s");}$newLinks[] = $link;}$this->links = collect( $newLinks );break;}// $this->print( "连接数量是:" . $this->links->count() );// $this->print( "连接数量是:" . $this->links->toJson() );}$newLinks = [];foreach ( $this->links as $link ){if( strtotime( $link['updated_at'] ) < (time() - 60) ){$this->print( "长时间未心跳,删除客户端:fd = " . json_encode($link, 320) );if( $this->server->isEstablished( $link['fd'] ) ){$this->disconnect( $link['fd'], '未进行心跳' );}continue;}$newLinks[] = $link;}$this->links = collect( $newLinks );} );}public function listenQueue(){Timer::tick( 1000, function (){// Redis::rpush( "SocketService:listenQueue", serialize(["hahah"]) )try{$element = Redis::lpop('SocketService:listenQueue');if( $element ){$this->print( "listenQueue 有新的信息哦:" . $element );$data = unserialize($element);if( ! empty( $data['user_id']) ){$links = $this->links->where( "user_id", $data['user_id'] )->values()->all();if( empty($links) ){$this->print( "没有在线用户:user_id = " . json_encode($data, 320) );//var_export( $this->links );//var_export( $links );}foreach ( $links as $link ){if( ! $this->server->isEstablished( $link['fd'] ) ){array_push( $this->cmds, [ 'operate' => 'close', 'fd' => $link['fd'] ] );continue;}try{// 生成消息数据$message = $this->makeMessage( $data['data'], $data['type'], $data['message'] );// 开始推送$this->runPush( $link['fd'], $message );}catch (\Throwable $e){$this->print( "数据推送异常:" . json_encode([ $e->getMessage(),$e->getLine(), $e->getFile() ], 320) );}}}}}catch (RedisException $e){Redis::connect();}});}public function open( Server $server, Request $request ){$params = $request->get;if( empty( $params['user_id'] ) ){$this->disconnect( $request->fd, '缺少用户信息' );return true;}array_push( $this->cmds, [ 'operate' => 'open', 'fd' => $request->fd, 'user_id' => $params['user_id'] ] );// 生成消息数据$message = $this->makeMessage( [ 'fd' => $request->fd ], "connectionSuccessful", "连接成功" );// 开始推送$this->runPush( $request->fd, $message );$this->print( "server: handshake success with fd{$request->fd} " );}public function message( Server $server, $frame ){//$data = json_decode( $frame->data, true );if( is_array( $data ) ){if( $data['type'] == "ping" ){array_push( $this->cmds, [ 'operate' => 'heartbeat', 'fd' => $frame->fd ] );$this->server->push( $frame->fd, json_encode( [ "type" => "pong" ] , 320 ) );}else{$this->print( "receive from {$frame->fd}:{$frame->data},opcode:{$frame->opcode},fin:{$frame->finish} " );}}}public function close(Server $server, $fd){array_push( $this->cmds, [ 'operate' => 'close', 'fd' => $fd ] );$this->print( "client {$fd} closed " );}public function push( $fd, string $data ){$this->server->push($fd, $data);}public function disconnect(int $fd, string $reason = '', int $code = SWOOLE_WEBSOCKET_CLOSE_NORMAL){$this->server->disconnect($fd, $code, $reason);}public function makeMessage( array $data, $type = "", $message = "" ){return [ 'type' => $type, "message" => $message, "data" => $data ];}public function runPush( $fd, $message ){$this->print( "推送消息: {$fd} - " . json_encode( $message, 320 ) );$this->server->push( $fd, json_encode( $message , 320 ) );}/*** App\Services\SocketService::testMessage( 92 )* @param $user_id* @return void*/public static function testMessage( $user_id ){Redis::rpush( "SocketService:listenQueue", serialize(["user_id" => $user_id,"type" => "testMessage", "message" => "测试消息", "data" => ["hello world!"],]) );}相关文章:
Laravel+swoole 实现websocket长链接
需要使用 swoole 扩展 我使用的是 swoole 5.x start 方法启动服务 和 定时器 调整 listenQueue 定时器可以降低消息通讯延迟 定时器会自动推送队列里面的消息 testMessage 方法测试给指定用户推送消息 使用 laravel console 启动 <?phpnamespace App\Console\Comman…...
【C#】Array和List
C#中的List<T>和数组(T[])在某些方面是相似的,因为它们都是用来存储一系列元素的集合。然而,它们在功能和使用上有一些重要的区别: 数组(Array) 固定大小:数组的大小在声明时…...
SpringCloud网关的实现原理与使用指南
Spring Cloud网关是一个基于Spring Cloud的微服务网关,它是一个独立的项目,可以对外提供API接口服务,负责请求的转发和路由。本文将介绍Spring Cloud网关的实现原理和使用指南。 一、Spring Cloud网关的实现原理 Spring Cloud网关基于Spring…...
LabVIEW 与 PLC 通讯方式
在工业自动化中,LabVIEW 与 PLC(可编程逻辑控制器)的通信至关重要,常见的通信方式包括 OPC、Modbus、EtherNet/IP、Profibus/Profinet 和 Serial(RS232/RS485)。这些通信协议各有特点和应用场景,…...
数据结构初阶·排序算法(内排序)
目录 前言: 1 冒泡排序 2 选择排序 3 插入排序 4 希尔排序 5 快速排序 5.1 Hoare版本 5.2 挖坑法 5.3 前后指针法 5.4 非递归快排 6 归并排序 6.1递归版本归并 6.2 非递归版本归并 7 计数排序 8 排序总结 前言: 目前常见的排序算法有9种…...
PL/SQL oracle上多表关联的一些记录
1.记录自己在PL/SQL上写的几张表的关联条件没有跑出来的一些优化 1. join后面跟上筛选条件 left join on t1.id t2.id and --- 带上分区字段,如 t1.month 202405, 操作跑不出来的一些问题,可能是数据量过大,未做分区过滤 2. 创建…...
Java.Net.UnknownHostException:揭开网络迷雾,解锁异常处理秘籍
在Java编程的浩瀚宇宙中,java.net.UnknownHostException犹如一朵不时飘过的乌云,让开发者在追求网络畅通无阻的道路上遭遇小挫。但别担心,今天我们就来一场说走就走的探险,揭秘这个异常的真面目,并手把手教你几招应对之…...
第十课:telnet(远程登入)
如何远程管理网络设备? 只要保证PC和路由器的ip是互通的,那么PC就可以远程管理路由器(用telnet技术管理)。 我们搭建一个下面这样的简单的拓扑图进行介绍 首先我们点击云,把云打开,点击增加 我们绑定vmn…...
【概率论三】参数估计:点估计(矩估计、极大似然法)、区间估计
文章目录 一. 点估计1. 矩估计法2. 极大似然法2.1. 似然函数2.2. 极大似然估计法 3. 评价估计量的标准3.1. 无偏性3.2. 有效性3.3. 一致性 二. 区间估计1. 区间估计的概念2. 正态总体参数的区间估计 参数估计讲什么 由样本来确定未知参数参数估计分为点估计与区间估计 一. 点估…...
自动化产线 搭配数据采集监控平台 创新与突破
自动化产线在现在的各行各业中应用广泛,已经是现在的生产趋势,不同的自动化生产设备充斥在各行各业中,自动化的设备会产生很多的数据,这些数据如何更科学化的管理,更优质的利用,就需要数据采集监控平台来完…...
【Karapathy大神build-nanogpt】Take Away Notes
B站翻译LINK Personal Note Andrej rebuild gpt2 in pytorch. Take Away Points Before entereing serious training, he use Shakespear’s work as a small debugging datset to see if a model can overfit. Overfitging is a should thing.If we use TF32 or BF32, (by…...
MySQL学习记录 —— 이십이 MySQL服务器日志
文章目录 1、日志介绍2、一般、慢查询日志1、一般查询日志2、慢查询日志FILE格式TABLE格式 3、错误日志4、二进制日志5、日志维护 1、日志介绍 中继服务器的数据来源于集群中的主服务。每次做一些操作时,把操作保存到重做日志,这样崩溃时就可以从重做日志…...
HTTPS请求头缺少HttpOnly和Secure属性解决方案
问题描述: 建立Filter拦截器类 package com.ruoyi.framework.security.filter;import com.ruoyi.common.core.domain.model.LoginUser; import com.ruoyi.common.utils.SecurityUtils; import com.ruoyi.common.utils.StringUtils; import com.ruoyi.framework.…...
react基础样式控制
行内样式 <div style{{width:500px, height:300px,background:#ccc,margin:200px auto}}>文本</div> class类名 注意:在react中使用class类名必须使用className 在外部src下新建index.css文件写入你的样式 .fontcolor{color:red } 在用到的页面引入…...
【区块链 + 智慧政务】涉税行政事业性收费“e 链通”项目 | FISCO BCOS应用案例
国内很多城市目前划转至税务部门征收的非税收入项目已达 17 项,其征管方式为行政主管部门核定后交由税务 部门征收。涉税行政事业性收费受限于传统的管理模式,缴费人、业务主管部门、税务部门、财政部门四方处于 相对孤立的状态,信息的传递靠…...
Socket、WebSocket 和 MQTT 的区别
Socket 协议 定义:操作系统提供的网络通信接口,抽象了TCP/IP协议,支持TCP和UDP。特点: 通用性:不限于Web应用,适用于各种网络通信。协议级别:直接使用TCP/UDP,需要手动管理连接和数…...
企业网络实验(vmware虚拟机充当DHCP服务器)所有IP全部保留,只为已知mac分配固定IP
文章目录 需求实验修改dhcp虚拟机配置文件测试PC获取IP查看user-bind 需求 (vmware虚拟机充当DHCP服务器)所有IP全部保留,只为已知mac分配固定IP 实验 前期配置: https://blog.csdn.net/xzzteach/article/details/140406092 后续配置均在以上配置的前…...
HouseCrafter:平面草稿至3D室内场景的革新之旅
在室内设计、房地产展示和影视布景设计等领域,将平面草稿图快速转换为立体的3D场景一直是一个迫切的需求。HouseCrafter,一个创新的AI室内设计方案,正致力于解决这一挑战。本文将探索HouseCrafter如何将这一过程自动化并提升至新的高度。 一、定位:AI室内设计的革新者 Ho…...
C#统一委托Func与Action
C#在System命名空间下提供两个委托Action和Func,这两个委托最多提供16个参数,基本上可以满足所有自定义事件所需的委托类型。几乎所有的 事件 都可以使用这两个内置的委托Action和Func进行处理。 Action委托: Action定义提供0~16个参数&…...
MongoDB 基本查询语句
基本查询 查询所有文档: db.collection.find()示例: db.users.find()按条件查询文档: db.collection.find({ key: value })示例: db.users.find({ age: 25 })查询并格式化输出: db.collection.find().pretty()示例&…...
Docker 离线安装指南
参考文章 1、确认操作系统类型及内核版本 Docker依赖于Linux内核的一些特性,不同版本的Docker对内核版本有不同要求。例如,Docker 17.06及之后的版本通常需要Linux内核3.10及以上版本,Docker17.09及更高版本对应Linux内核4.9.x及更高版本。…...
装饰模式(Decorator Pattern)重构java邮件发奖系统实战
前言 现在我们有个如下的需求,设计一个邮件发奖的小系统, 需求 1.数据验证 → 2. 敏感信息加密 → 3. 日志记录 → 4. 实际发送邮件 装饰器模式(Decorator Pattern)允许向一个现有的对象添加新的功能,同时又不改变其…...
深入剖析AI大模型:大模型时代的 Prompt 工程全解析
今天聊的内容,我认为是AI开发里面非常重要的内容。它在AI开发里无处不在,当你对 AI 助手说 "用李白的风格写一首关于人工智能的诗",或者让翻译模型 "将这段合同翻译成商务日语" 时,输入的这句话就是 Prompt。…...
ssc377d修改flash分区大小
1、flash的分区默认分配16M、 / # df -h Filesystem Size Used Available Use% Mounted on /dev/root 1.9M 1.9M 0 100% / /dev/mtdblock4 3.0M...
Go 语言接口详解
Go 语言接口详解 核心概念 接口定义 在 Go 语言中,接口是一种抽象类型,它定义了一组方法的集合: // 定义接口 type Shape interface {Area() float64Perimeter() float64 } 接口实现 Go 接口的实现是隐式的: // 矩形结构体…...
Leetcode 3577. Count the Number of Computer Unlocking Permutations
Leetcode 3577. Count the Number of Computer Unlocking Permutations 1. 解题思路2. 代码实现 题目链接:3577. Count the Number of Computer Unlocking Permutations 1. 解题思路 这一题其实就是一个脑筋急转弯,要想要能够将所有的电脑解锁&#x…...
macOS多出来了:Google云端硬盘、YouTube、表格、幻灯片、Gmail、Google文档等应用
文章目录 问题现象问题原因解决办法 问题现象 macOS启动台(Launchpad)多出来了:Google云端硬盘、YouTube、表格、幻灯片、Gmail、Google文档等应用。 问题原因 很明显,都是Google家的办公全家桶。这些应用并不是通过独立安装的…...
unix/linux,sudo,其发展历程详细时间线、由来、历史背景
sudo 的诞生和演化,本身就是一部 Unix/Linux 系统管理哲学变迁的微缩史。来,让我们拨开时间的迷雾,一同探寻 sudo 那波澜壮阔(也颇为实用主义)的发展历程。 历史背景:su的时代与困境 ( 20 世纪 70 年代 - 80 年代初) 在 sudo 出现之前,Unix 系统管理员和需要特权操作的…...
鱼香ros docker配置镜像报错:https://registry-1.docker.io/v2/
使用鱼香ros一件安装docker时的https://registry-1.docker.io/v2/问题 一键安装指令 wget http://fishros.com/install -O fishros && . fishros出现问题:docker pull 失败 网络不同,需要使用镜像源 按照如下步骤操作 sudo vi /etc/docker/dae…...
UR 协作机器人「三剑客」:精密轻量担当(UR7e)、全能协作主力(UR12e)、重型任务专家(UR15)
UR协作机器人正以其卓越性能在现代制造业自动化中扮演重要角色。UR7e、UR12e和UR15通过创新技术和精准设计满足了不同行业的多样化需求。其中,UR15以其速度、精度及人工智能准备能力成为自动化领域的重要突破。UR7e和UR12e则在负载规格和市场定位上不断优化…...
