当前位置: 首页 > news >正文

laraval+node.js实现websocket

本文是使用laraval的event与node.js作为websocket服务器,与页面实现长连接;

基本知识

  • Laravel Event
  • Redis
  • Socket.io
  • Node.js

配置

  1. Laravel 中使用 Redis 你需用通过 Composer 来安装 predis/predis 包文件。

  2. Redis 在应用中的配置文件存储在 config/database.php,在这个文件中,你可以看到一个包含了 Redis 服务信息的 redis 数组:

    'redis' => [
      'cluster' => false,
    
      'default' => [
        'host' => '127.0.0.1',
        'port' => 6379,
        'database' => 0,
      ],
    ]
    复制代码
  3. Laravel Event

    Laravel 通过广播事件来共享事件到的服务端和客户端的 JavaScript 框架。

    所有的事件广播配置选项都被存储在 config/broadcasting.php 配置文件中。Laravel 附带了几种可用的驱动如 Pusher,Redis,和 Log,我们将使用 Redis 作为广播驱动,这里需要依赖 predis/predis 类库。

    由于默认的广播驱动使用的是 pusher,所以我们需要在 .env 文件中设置 BROADCAST_DRIVER=redis。

    下面是一个事件类的例子

    <?php
    
    namespace App\Events;
    
    use Illuminate\Broadcasting\Channel;
    use Illuminate\Queue\SerializesModels;
    use Illuminate\Broadcasting\PrivateChannel;
    use Illuminate\Broadcasting\PresenceChannel;
    use Illuminate\Foundation\Events\Dispatchable;
    use Illuminate\Broadcasting\InteractsWithSockets;
    use Illuminate\Contracts\Broadcasting\ShouldBroadcast;
    
    class MessageEvent implements ShouldBroadcast
    {
        use Dispatchable, InteractsWithSockets, SerializesModels;
    
        public $messages;
        protected $channel;
    
        /**
         * Create a new event instance.
         *
         * @return void
         */
        public function __construct($channel, $messages)
        {
            $this->channel = $channel;
            $this->messages = $messages;
        }
    
        /**
         * Get the channels the event should broadcast on.
         *
         * @return Channel|array
         */
        public function broadcastOn()
        {
            return [$this->channel];
        }
    
        public function broadcastAs()
        {
    
            return 'OnPushMessage';
    
        }
    
    
        public function broadcastWith()
        {
    
            return $this->messages;
    
        }
    
    }
    复制代码

注意

  • broadcastOn方法应返回一个数组,它表示所需广播的频道

  • broadcastAs返回的是一个字符串,它表示广播所触发的事件

  • broadcastWith 返回的是一个数组,它表示要给给定频道发布过去的数据

接下来是触发事件:

```
event(new MessageEvent(\$message->channel_id, $send_message));
```
复制代码

这个操作会自动的触发事件的执行并将信息广播出去。该广播操作底层借助了 redis 的订阅和发布机制。RedisBroadcaster 会将事件中的允许公开访问的数据通过给定的频道发布出去。

Node.js 和 Socket.io

对于发布出去的信息,我们需要一个服务来对接,让其能对 redis 的发布能够进行订阅,并且能把信息以 WebSocket 协议转发出去,这里我们可以借用 Node.js 和 socket.io 来非常方便的构建这个服务:

let app = require('http').createServer((req, res) => {
    res.writeHead(200);
    res.end('');
});
let io = require('socket.io')(app);

//redis可以配置
let Redis = require('ioredis');
var evn = 'test';
if(evn == 'local'){
    var host = '127.0.0.1';
    var port = '6379';
    var password = null;
} else if(evn == 'test'){
    var host = '186.5.562.2';
    var port = '6379';
    var password = '151554145';
}

let redis = new Redis({
    'host':host,
    'port':port,
    'password':password
});

//存储用户
var userList = [];

app.listen(6001, () => console.log('Server is running!'));

io.on('connection', (socket) => {

    socket.on('login', (user) => {

        if (userList[user.channel] === undefined) userList[user.channel] = [];

        if(user.openid.indexOf("admin") == -1){
            userList[user.channel].push(user.openid);
            io.emit(user.channel + ':UserChange', userList[user.channel]);
            let userCount = countUser();
            io.emit('user.count', userCount);
        }

        console.log('[L] openid:' + user.openid + ' Login!');

    });


    socket.on('disconnect', (res) => {

        let user = cookieToObject(socket.request.headers.cookie.split('; '));

        let openid = user['openid'] + "";

        if(openid.indexOf("admin") == -1){
            let index = userList[user['channel']].indexOf(user['openid'] + "");

            userList[user['channel']].splice(index, 1);

            io.emit(user.channel + ':UserChange', userList[user.channel]);

            let userCount = countUser();

            io.emit('user.count', userCount);
        }

        console.log('[L] openid:' + user['openid'] + ' Logout!');

    });

    socket.on('user.count', () => {

        let userCount = countUser();

        io.emit('user.count', userCount);

    });

});


redis.psubscribe('*', (err, count) => {

});

redis.on('pmessage', (subscrbed, channel, message) => {
    message = JSON.parse(message);
    console.log('[M]'.channel + ' Message :' + message.event, message.data);
    io.emit(channel + ':' + message.event, message.data);
});


function countUser() {

    let userCount = [];


    for (var i in userList) {
        userCount[i] = userList[i].length;

    }
    return userCount;
}

function cookieToObject(data) {

    let new_Object = [];

    for (let i in data) {

        data[i] = data[i].split('=');

        new_Object[data[i][0]] = data[i][1];

    }

    return new_Object;

}
复制代码

这里我们使用 Node.js 引入 socket.io 服务端并监听 6001 端口,借用 redis 的 psubscribe 指令使用通配符来快速的批量订阅,接着在消息触发时将消息通过 WebSocket 转发出去。

Socket.io 客户端

在 web 前端,我们需要引入 Socket.io 客户端开启与服务端 6001 端口的通讯,并订阅频道事件:

 $.cookie('channel', channel_id, {path: "/"});
 $.cookie('openid', user, {path: "/"});

 var socket = io(':6001');
 socket.on('connect', function () {
      socket.emit('login', {channel: channel_id, openid: user});
  });
socket.on(channel_id + ':removeMessage', function (res) {
      $(".message-list .message-item[data-id='"+res.id + "']").remove();
  });
复制代码

最后在使用node在后台启动server.js

至此整个通讯闭环结束,开发流程看起来就是这样的:

  • 在 Laravel 中构建一个支持广播通知的事件
  • 设置需要进行广播的频道及事件名称
  • 将广播设置为使用 redis 驱动
  • 提供一个持续的服务用于订阅 redis 的发布,及将发布内容通过 WebSocket 协议推送到客户端 -客户端打开服务端 WebSocket 隧道,并对事件进行订阅,根据指定事件的推送进行响应

相关文章:

  • PowerShell 脚本执行策略
  • MSDN SmartCast更改下载步骤
  • QT之二维绘图:场景,图元,视图
  • DockOne微信分享( 八十八):PPTV聚力传媒的Docker与DevOps
  • VM中ubuntu虚拟机共享文件夹,mnt下面没有hgfs
  • 软件測试的类型、方法以及策略------一张图搞定
  • 高亮必填字段
  • golang语法学习(一):变量,常量以及数据类型
  • Unity实现刺客信条灯光的思路探究
  • 【webstrom】webstrom打开多个项目,webstrom常用快捷键
  • CentOS 英文版安装中文环境与中文输入法
  • SSH连接linux时,长时间不操作就断开的解决方案(增强版)
  • 代码整洁之有意义的命名
  • 在阿里云k8s服务中玩转最新版原生dashboard
  • Linux netstat常用命令
  • ES10 特性的完整指南
  • JavaScript 无符号位移运算符 三个大于号 的使用方法
  • magento 货币换算
  • vue-router的history模式发布配置
  • 关于Android中设置闹钟的相对比较完善的解决方案
  • 观察者模式实现非直接耦合
  • 开发基于以太坊智能合约的DApp
  • 前嗅ForeSpider教程:创建模板
  • 设计模式(12)迭代器模式(讲解+应用)
  • 移动端唤起键盘时取消position:fixed定位
  • 容器镜像
  • 数据可视化之下发图实践
  • ​二进制运算符:(与运算)、|(或运算)、~(取反运算)、^(异或运算)、位移运算符​
  • #1015 : KMP算法
  • (八)Flask之app.route装饰器函数的参数
  • (附源码)ssm捐赠救助系统 毕业设计 060945
  • (六)什么是Vite——热更新时vite、webpack做了什么
  • (十)c52学习之旅-定时器实验
  • (转)创业的注意事项
  • .NET 5种线程安全集合
  • .NET 的静态构造函数是否线程安全?答案是肯定的!
  • .Net通用分页类(存储过程分页版,可以选择页码的显示样式,且有中英选择)
  • .net专家(张羿专栏)
  • ::前边啥也没有
  • [ C++ ] STL_list 使用及其模拟实现
  • [2016.7.test1] T2 偷天换日 [codevs 1163 访问艺术馆(类似)]
  • [8481302]博弈论 斯坦福game theory stanford week 1
  • [android] 练习PopupWindow实现对话框
  • [C# 开发技巧]实现属于自己的截图工具
  • [C#][opencvsharp]opencvsharp sift和surf特征点匹配
  • [C#]OpenCvSharp结合yolov8-face实现L2CS-Net眼睛注视方向估计或者人脸朝向估计
  • [C++]指针与结构体
  • [COGS 622] [NOIP2011] 玛雅游戏 模拟
  • [C语言]——内存函数
  • [Flex][问题笔记]TextArea滚动条问题
  • [LeetCode] Max Points on a Line
  • [Linux]进程间通信(进程间通信介绍 | 匿名管道 | 命名管道)
  • [MFC] MFC消息机制的补充
  • [NKCTF 2024]web解析
  • [No000016]为什么假期计划总是做不到?