本文是使用laraval的event与node.js作为websocket服务器,与页面实现长连接;
基本知识
- Laravel Event
- Redis
- Socket.io
- Node.js
配置
-
Laravel 中使用 Redis 你需用通过 Composer 来安装 predis/predis 包文件。
-
Redis 在应用中的配置文件存储在 config/database.php,在这个文件中,你可以看到一个包含了 Redis 服务信息的 redis 数组:
'redis' => [ 'cluster' => false, 'default' => [ 'host' => '127.0.0.1', 'port' => 6379, 'database' => 0, ], ] 复制代码
-
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 隧道,并对事件进行订阅,根据指定事件的推送进行响应