nodejs socket redis的pub/sub实现消息推送服务

     在开发项目的过程中,我们常涉及到这样的需求,从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);
        });

测试结果

至此一个消息推送服务就完成了。

猜你喜欢

转载自blog.csdn.net/zYjmor/article/details/82941895