node mysql分布式_nodejs使用socket.io实现分布式一对一发送消息

最近在用 nodejs 实现一个消息通知的功能,其中用到了 socket.io;由于初次使用 socket.io,在网上查找资料大都是聊天室广播的教程,没有找到一对一发送消息的具体实现方法,这里分享一下自己学习使用过程中踩到某些坑和最终实现方法。

要实现的功能:

server 端数据更新后,发送通知到对应客户端的用户;server 是用 nodejs 写的,并使用了分布式,同时开启了多个实例。

# 一、安装依赖包

npm install socket.io

复制代码

# 二、socket.io 的使用

1、index.js 文件内容

//index.js

var express = require('express')

var app = express()

var server = require('http').Server(app)

var io = require('socket.io')(server, {

path: '/notice' //修改客户端请求的路径,默认为/socket.io

})

new (require('./notice')).init(io) //传入io

复制代码

2、notice.js 文件内容

var Notice = null

var socketMap = {} //用户对应socket.id

//初始化socket连接

exports.init = function(io) {

//连接验证

io.use(function(socket, next) {

//console.log(socket.request.headers.cookie);

var token = socket.request._query.token || ''

if (validate(token)) {

socket.request.headers.user = { userId: userId }

return next()

} else {

return next(new Error('Authentication error'))

}

})

Notice = io.of('/notice').on('connection', function(socket) {

var user = socket.handshake.headers.user

var user_id = user && user.userId

if (user_id) {

socketMap[user_id] = socket.id

}

socket.on('disconnect', function() {

delete socketMap[user_id]

})

})

}

//其他模块调用,发送消息

exports.send = function(data) {

var user_id = data.accountID

var socket_id = socketMap[user_id]

Notice.to(socket_id).emit('notice', data)

}

复制代码

3、客户端文件

var socket = io('http://localhost?token=token');

socket.on('notice', function (data) {

console.log(data);

});

复制代码

在跨域请求的情况下socket.request.headers.cookie是无法获取到有效的 cookie 的,所以这里直接从客户端传 token 过来进行验证。验证通过后,将用户信息存到 headers 中,客户端连接成功后,取出用户信息,并将用户 id 与此 socket 连接对应 id 存储到全局socketMap变量中,然后外部模块通过调用 send 方法来发送消息。

这种情况在单实例情况下是没有问题的,但是如果 server 开启了多个实例就出问题了。

# 三、使用 nginx 实现分布式部署

首先需要安装nginx;安装完成后编写配置文件;

upstream socket_test {

ip_hash;

server 127.0.0.1:8013;

server 127.0.0.1:8014;

}

server {

listen 80;

server_name socket.test.com;

location / {

proxy_pass http://socket_test/;

proxy_set_header Upgrade $http_upgrade;

proxy_set_header Connection "upgrade";

proxy_http_version 1.1;

proxy_set_header Host $host;

proxy_set_header X-Real-IP $remote_addr;

proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;

}

}

复制代码

启动两个项目实例,分别监听 8013 和 8014 端口;然后启动 nginx

sudo nginx

复制代码

通过测试会发现,消息有时候能收到,有时候收不到。原因是因为客户端请求的时候,会不定的请求服务端两个实例中的一个,如果连接的时候是请求的 8013 端口的实例,二发送消息是 8014 端口的实例 ,就会在 8014 的实例中找不到建立连接的 socket。

# 四、分布式解决方法

socket.io 官网中提供了使用socket.io-redis

来解决此方法;socket.io-redis 中使用了 redis 的消息订阅与发布的功能,当有通知发送的时候,会触发 onmessage 事件,然后会调用 broadcast 广播。

接下来将 socket.io-redis 添加到 index.js 文件中

//index.js

var express = require('express')

var app = express()

var server = require('http').Server(app)

var io = require('socket.io')(server, {

path: '/notice' //修改客户端请求的路径,默认为/socket.io

})

new (require('./notice')).init(io) //传入io

var redis = require('socket.io-redis')

io.adapter(redis({ host: settings.REDIS_HOST, port: settings.REDIS_PORT }))

复制代码

然后重启后会发现还是不行,原因是因为在 socketMap 变量中有时候就找不到对应的 user_id;因为是分布式的,所有 socketMap 变量在两台实例中并不是共享的,所以这里使用 redis,在 socket 建立的时候将 user_id 对应的 socket_id 存储到 redis 中,发送消息的时候从 redis 中读取 socket_id 这样就可以了。

有时候一个用户可能会同时有多个 socket 连接,所以 user_id 对应的 socket_id 就可以是一个数组,发送消息的时候要循环发送,连接断开的时候要只删除对应的 socket_id,保留其他建立的连接。

另外服务器重启时 socket 会重新建立连接,所以在初始化的的时候要清空 redis 中所有存储的数据。 GitHub webhooks实现自动部署

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值