在开发项目的过程中,我们常涉及到这样的需求,从pc后台推送消息到各个不同的终端(移动端,浏览器端或者pc端),当然实现方式有很多,现如今各大平台都有自己的实现通讯的框架,比如signalr,还有第些第三方提供的推送接口,像百度推送,谷歌推送等。。但有些时候需要自定义一些交互规则或协议,我们需要自己实现推送过程。那么在nodejs这个平台下其实就能很容易实现这样的推送服务。
思路:通过socket创建长连接,利用redis的pub/sub(消息/订阅)为每个登录用户订阅一个频道(subscribe)(规则由用户标识加协议构成),用户登出取消该订阅频道,把后台发布的(publish)消息存入redis消息列队(key的规则有用户标识和协议构成),在用户连接上推送服务器的时候读取用户匹配的列队相信,如果有消息,利用sokect的emit事件把消息发送给连接上推送服务器的匹配的客户客户端。
服务端
关于nodejs,redis安装配置这里就省略了。
下面是代码中要安装的包socket.io redis和koa(该处以koa做案列测试)
npm install socket.io
npm install redis
npm install koa
创建server.js并加入下面代码
var redis = require('redis');
var users = require('./users');
var server = (function () {
function server() {
var koa = require('koa');
this.app = new koa();
this.userOnline = new userOnlineManger();
this.server = require('http').createServer(this.app.callback());
this.io = require('socket.io')(this.server);
this.port = process.env.PORT || 3000;
var self = this;
//this.app.use(this.httpServerDebugErrorHandler);
this.io.on('connection', function (socket) {
socket.userOnline = self.userOnline;
self.onConnection(socket);
socket.on('requestLongConnect', function (data) {
self.onRequestLongConnect(self, socket, data);
});
socket.on('disconnect', function () {
self.onDisconnect(socket);
});
});
}
server.prototype.httpServerDebugErrorHandler = function (req, res, next) {
res.status(err.status || 500);
res.render('error', {
message: err.message,
error: err
});
}
server.prototype.onConnection = function (socket) {
}
server.prototype.onRequestLongConnect = function (self, socket, requestData) {
try {
// 可以在该处自定义可以的规则
socket.userInfo = requestData;
if (!(socket.subscribeRedisClient == null || socket.subscribeRedisClient == undefined)) {
socket.subscribeRedisClient.unsubscribe();
socket.subscribeRedisClient.end();
socket.subscribeRedisClient.quit();
}
if (!(socket.redisClient == null || socket.redisClient == undefined)) {
socket.redisClient.quit();
}
socket.subscribeRedisClient = redis.createClient();
socket.redisClient = redis.createClient();
socket.redisClient.get(requestData.Token, function (err, value) {
if (value == null || value == undefined) {
return;
}
socket.pubkey = value;
socket.userOnline.addOnlineUser(requestData.Token, socket);
self.publishMessage(socket);
socket.subscribeRedisClient.subscribe(requestData.Token, function (channel, count) {
});
socket.subscribeRedisClient.on('message', function (channel, message) {
self.publishMessage(socket);
});
})
} catch (ex) {
console.log(ex);
}
}
server.prototype.publishMessage = function (socket) {
socket.redisClient.lrange(socket.pubkey, 0, -1, function (arg, value) {
var multi = socket.redisClient.multi();
try {
socket.emit('rmooi', value);
for (var i = 0; i < value.length; i++) {
multi.lpop(socket.pubkey, function (err, arg) { });
}
multi.exec(function (err, replies) {
});
} catch (ex) {
multi.discard();
console.log(ex);
}
});
}
server.prototype.onDisconnect = function (socket) {
try {
if (socket.userInfo != null) {
socket.userOnline.removeOnlineUser(socket.userInfo.Token);
socket.redisClient.quit();
socket.subscribeRedisClient.unsubscribe();
socket.subscribeRedisClient.end(true);
socket.subscribeRedisClient.quit();
}
} catch (ex) {
console.log(ex);
}
}
server.prototype.start = function () {
this.app.use(require('koa-static')(__dirname + '/public'))
var port = this.port;
this.server.listen(port, function () {
console.log('监听端口 %d', port);
});
}
return server;
})();
module.exports = server;
创建users.js并加入下面代码,users.js主要是记录当前连接上推送服务的人数。
var users = (function() {
function users() {
this.OnlineUsers = {};
this.userOnlineCount = 0;
}
users.prototype.addOnlineUser = function(token, socket) {
if (this.OnlineUsers.hasOwnProperty(token)) {
return;
}
this.OnlineUsers[token] = socket;
this.userOnlineCount++;
}
users.prototype.removeOnlineUser = function(token) {
if (!this.OnlineUsers.hasOwnProperty(token)) {
return;
}
delete this.OnlineUsers[token];
this.userOnlineCount--;
}
return users;
})();
module.exports = users;
添加app.js 服务启动文件 加入代码
var server = require('./server);
var s = new server();
s.start();
客户端
浏览器端
var socket = io();
//监听rmooi消息
socket.on('rmooi', function(data) {
$('#resultDiv').append("<p>" + data + "</p>");
});
//连接长连接
$('#connectButton').click(function() {
var data = {};
data["Token"] = $('#tokenTextBox').val();
socket.emit('requestLongConnect', data);
});
测试结果
至此一个消息推送服务就完成了。