Repository: gytai/node-websocket-msg-sender Branch: master Commit: 3256674041c2 Files: 14 Total size: 24.0 KB Directory structure: gitextract_2wts0mrp/ ├── README.md ├── app.js ├── bin/ │ └── www ├── io/ │ ├── io.js │ ├── ioHelper.js │ └── messageTpye.js ├── package.json ├── public/ │ ├── javascripts/ │ │ └── notify.js │ └── stylesheets/ │ └── style.css ├── routes/ │ ├── index.js │ └── users.js ├── utils/ │ └── redis.js └── views/ ├── error.ejs └── index.ejs ================================================ FILE CONTENTS ================================================ ================================================ FILE: README.md ================================================ 基于Nodejs websocket socket.io的消息转发系统 message pusher written in nodejs based on socket.io ============== 消息实时推送,支持在线用户数实时统计。基于[Socket.IO](https://socket.io/)开发,使用websocket推送数据,当浏览器不支持websocket时自动切换comet推送数据。 支持Linux,mac,windows等环境部署。 效果截图 ======  线上demo ====== http://112.74.81.224:3000/ 可以通过url:http://112.74.81.224:3000/sendMsg/?type=private&uid=1504936989000&content=消息内容 向当前用户发送消息 可以通过url:http://112.74.81.224:3000/sendMsg/?type=public&content=消息内容 向所有在线用户推送消息 uid为接收消息的uid,如果不传递则向所有人推送消息 content 为消息内容 注:可以通过php或者其它语言的curl功能实现后台推送 下载安装 ====== 1、git clone https://github.com/gytai/node-websocket-msg-sender.git 2、npm install 3、apt-get install redis-server 4、redis-server 后端服务启动停止,先安装PM2(Advanced Node.js process manager,http://pm2.keymetrics.io/) ====== ### 启动服务 pm2 start bin/www --name msg-sender ### 停止服务 pm2 stop msg-sender Web前端代码类似: ==== ```javascript // 引入前端文件 ``` 其他客户端 ==== 根据websocket协议即可。具体参考websocket协议。 Nodejs后端调用api向任意用户推送数据 ==== ```javascript var type = req.query.type || msgType.public; var content = req.query.content || 'none'; var uid = req.query.uid; switch (type){ case msgType.public: ioSvc.serverBroadcastMsg(content); break; case msgType.private: if(!uid){ return res.send({code:400,msg:'uid参数必传'}); } ioSvc.serverToPrivateMsg(uid,content); break; } ``` Http 发送数据,可以配置跨站发送(需要设置跨域放行)。例如安卓或者IOS等其他客户端也可以方便的发送消息。 ==== 可以通过url:http://localhost:3000/sendMsg/?type=private&uid=1504936989000&content=消息内容 向当前用户发送消息 可以通过url:http://localhost:3000/sendMsg/?type=public&content=消息内容 向所有在线用户推送消息 ================================================ FILE: app.js ================================================ var express = require('express'); var path = require('path'); var favicon = require('serve-favicon'); var logger = require('morgan'); var cookieParser = require('cookie-parser'); var bodyParser = require('body-parser'); var app = express(); var index = require('./routes/index'); var users = require('./routes/users'); // view engine setup app.set('views', path.join(__dirname, 'views')); app.set('view engine', 'ejs'); // uncomment after placing your favicon in /public //app.use(favicon(path.join(__dirname, 'public', 'favicon.ico'))); app.use(logger('dev')); app.use(bodyParser.json()); app.use(bodyParser.urlencoded({ extended: false })); app.use(cookieParser()); app.use(express.static(path.join(__dirname, 'public'))); app.use('/', index); app.use('/users', users); // catch 404 and forward to error handler app.use(function(req, res, next) { var err = new Error('Not Found'); err.status = 404; next(err); }); // error handler app.use(function(err, req, res, next) { // set locals, only providing error in development res.locals.message = err.message; res.locals.error = req.app.get('env') === 'development' ? err : {}; // render the error page res.status(err.status || 500); res.render('error'); }); module.exports = app; ================================================ FILE: bin/www ================================================ #!/usr/bin/env node /** * Module dependencies. */ var app = require('../app'); var debug = require('debug')('nodemessage:server'); var http = require('http'); var ioSvc = require('../io/io'); /** * Get port from environment and store in Express. */ var port = normalizePort(process.env.PORT || '3000'); app.set('port', port); /** * Create HTTP server. */ var server = http.createServer(app); //??Socket.IO var io = require('socket.io')(server); ioSvc.ioServer(io); /** * Listen on provided port, on all network interfaces. */ server.listen(port); server.on('error', onError); server.on('listening', onListening); /** * Normalize a port into a number, string, or false. */ function normalizePort(val) { var port = parseInt(val, 10); if (isNaN(port)) { // named pipe return val; } if (port >= 0) { // port number return port; } return false; } /** * Event listener for HTTP server "error" event. */ function onError(error) { if (error.syscall !== 'listen') { throw error; } var bind = typeof port === 'string' ? 'Pipe ' + port : 'Port ' + port; // handle specific listen errors with friendly messages switch (error.code) { case 'EACCES': console.error(bind + ' requires elevated privileges'); process.exit(1); break; case 'EADDRINUSE': console.error(bind + ' is already in use'); process.exit(1); break; default: throw error; } } /** * Event listener for HTTP server "listening" event. */ function onListening() { var addr = server.address(); var bind = typeof addr === 'string' ? 'pipe ' + addr : 'port ' + addr.port; debug('Listening on ' + bind); } ================================================ FILE: io/io.js ================================================ /* *介绍:socket.io 功能封装 *作者:TaiGuangYin *时间:2017-09-09 * */ var redis = require('../utils/redis'); var msgType = require('./messageTpye'); var ioSvc = require('./ioHelper').ioSvc; //服务端连接 function ioServer(io) { var _self = this; ioSvc.setInstance(io); //初始化连接人数 redis.set('online_count',0,null,function (err,ret) { if(err){ console.error(err); } }); io.on('connection', function (socket) { console.log('SocketIO有新的连接!'); _self.updateOnlieCount(true); //用户与Socket进行绑定 socket.on('login', function (uid) { console.log(uid+'登录成功'); redis.set(uid,socket.id,null,function (err,ret) { if(err){ console.error(err); } }); redis.set(socket.id,uid,null,function (err,ret) { if(err){ console.error(err); } }); }); //断开事件 socket.on('disconnect', function() { console.log("与服务其断开"); _self.updateOnlieCount(false); redis.get(socket.id,function (err,val) { if(err){ console.error(err); } redis.del(socket.id,function (err,ret) { if(err){ console.error(err); } }); redis.del(val,function (err,ret) { if(err){ console.error(err); } }); }); }); //重连事件 socket.on('reconnect', function() { console.log("重新连接到服务器"); }); //监听客户端发送的信息,实现消息转发到各个其他客户端 socket.on('message',function(msg){ if(msg.type == msgType.messageType.public){ socket.broadcast.emit("message",msg.content); }else if(msg.type == msgType.messageType.private){ var uid = msg.uid; redis.get(uid,function (err,sid) { if(err){ console.error(err); } if(sid){ //给指定的客户端发送消息 io.sockets.socket(sid).emit('message', msg.content); } }); } }); }); this.updateOnlieCount = function (isConnect) { //记录在线客户连接数 redis.get('online_count',function (err,val) { if(err){ console.error(err); } if(!val){ val = 0; } if(typeof val == 'string'){ val = parseInt(val); } if(isConnect){ val += 1; }else{ val -= 1; if(val<=0){ val = 0; } } console.log('当前在线人数:'+val); io.sockets.emit('update_online_count', { online_count: val }); redis.set('online_count',val,null,function (err,ret) { if(err){ console.error(err); } }); }); }; } //模块导出 exports.ioServer = ioServer; ================================================ FILE: io/ioHelper.js ================================================ var redis = require('../utils/redis'); var ioSvc = {}; ioSvc.io = null; //初始化实例 ioSvc.setInstance = function (io) { this.io = io; }; ioSvc.getInstance =function () { return this.io; }; //服务器给所有客户端广播消息 ioSvc.serverBroadcastMsg = function (data) { console.log('发送广播消息'); console.log(data); this.io.sockets.emit('message',data); }; //服务端给指定用户发消息 ioSvc.serverToPrivateMsg = function (uid,data) { console.log('发送私人消息'); console.log(data); redis.get(uid,function (err,sid) { if(err){ console.error(err); } if(sid){ //给指定的客户端发送消息 this.io.sockets.socket(sid).emit('message',data); } }); }; exports.ioSvc = ioSvc; ================================================ FILE: io/messageTpye.js ================================================ const messageType= { 'public':'public', 'private':'private' }; exports.messageType = messageType; ================================================ FILE: package.json ================================================ { "name": "nodemessage", "version": "0.0.0", "private": true, "scripts": { "start": "node ./bin/www" }, "dependencies": { "body-parser": "~1.17.1", "cookie-parser": "~1.4.3", "debug": "~2.6.3", "ejs": "~2.5.6", "express": "~4.15.2", "morgan": "~1.8.1", "serve-favicon": "~2.4.2", "socket.io": "^2.0.3", "socket.io-client": "^2.0.3", "redis": "^2.8.0" } } ================================================ FILE: public/javascripts/notify.js ================================================ (function ($) { $.fn.extend({ notify: function (options) { var settings = $.extend({ type: 'sticky', speed: 500, onDemandButtonHeight: 35 }, options); return this.each(function () { var wrapper = $(this); var ondemandBtn = $('.ondemand-button'); var dh = -35; var w = wrapper.outerWidth() - ondemandBtn.outerWidth(); ondemandBtn.css('left', w).css('margin-top', dh + "px" ); var h = -wrapper.outerHeight(); wrapper.addClass(settings.type).css('margin-top', h).addClass('visible').removeClass('hide'); if (settings.type != 'ondemand') { wrapper.stop(true, false).animate({ marginTop: 0 }, settings.speed); } else { ondemandBtn.stop(true, false).animate({ marginTop: 0 }, settings.speed); } var closeBtn = $('.close', wrapper); closeBtn.click(function () { if (settings.type == 'ondemand') { wrapper.stop(true, false).animate({ marginTop: h }, settings.speed, function () { wrapper.removeClass('visible').addClass('hide'); ondemandBtn.stop(true, false).animate({ marginTop: 0 }, settings.speed); }); } else { wrapper.stop(true, false).animate({ marginTop: h }, settings.speed, function () { wrapper.removeClass('visible').addClass('hide'); }); } }); if (settings.type == 'floated') { $(document).scroll(function (e) { wrapper.stop(true, false).animate({ top: $(document).scrollTop() }, settings.speed); }).resize(function (e) { wrapper.stop(true, false).animate({ top: $(document).scrollTop() }, settings.speed); }); } else if (settings.type == 'ondemand') { ondemandBtn.click(function () { $(this).animate({ marginTop: dh }, settings.speed, function () { wrapper.removeClass('hide').addClass('visible').animate({ marginTop: 0 }, settings.speed, function () { }); }) }); } }); } }); })(jQuery); ================================================ FILE: public/stylesheets/style.css ================================================ @charset "utf-8"; body { margin:0px; padding:0px; font-family: Arial, Helvetica, sans-serif; background:url(/images/repeat.jpg); font-size:15px; color:#000; } ul{list-style:none; margin:0px; padding:0px; margin-top:20px;} li{padding-bottom:20px;} .sticky p, .floated p, .fixed p, .ondemand p{ float:left; padding:0px; margin:0px; margin-left:10px; line-height:45px; color:#fff; font-size:12px;} .sticky a, .floated a, .fixed a, .ondemand a{ float:right; margin:13px 10px 0px 0px; } img{border:0px;} .wrapper{padding:20px;} .sticky { position:fixed; top:0; left:0; z-index:1000; width:100%; border-bottom:3px solid #fff !important; background: #91BD09; /* Old browsers */ background: -moz-linear-gradient(top, #91BD09 0%, #91BD09 100%); /* FF3.6+ */ /* FireFox 3.6 */ /* Safari4+, Chrome */ -ms-filter: "progid:DXImageTransform.Microsoft.gradient(startColorstr='#91BD09', endColorstr='#91BD09')"; -pie-background: linear-gradient(#91BD09, #91BD09 100%); behavior: url(PIE.htc); -moz-box-shadow: 1px 1px 7px #676767; -webkit-box-shadow: 1px 1px 7px #676767; box-shadow: 1px 1px 7px #676767; height: 45px; background-image: -webkit-gradient(linear,left bottom,left top,color-stop(0, #91BD09),color-stop(1, #91BD09));/* IE6,IE7 */ /* IE8 */ /* Firefox F3.5+ */ /* Safari3.0+, Chrome */ } .floated { position:absolute; top:0; left:0; z-index:1000; width:100%; border-bottom:3px solid #fff !important; background: #0e59ae; /* Old browsers */ background: -moz-linear-gradient(top, #0e59ae 0%, #0e59ae 100%); /* FF3.6+ */ -ms-filter: "progid:DXImageTransform.Microsoft.gradient(startColorstr='#0E59AE', endColorstr='#0E59AE')"; -moz-box-shadow: 1px 1px 7px #676767; -webkit-box-shadow: 1px 1px 7px #676767; box-shadow: 1px 1px 7px #676767; height: 45px; background-image: -webkit-gradient(linear,left bottom,left top,color-stop(0, #0E59AE),color-stop(1, #0E59AE));/* IE6,IE7 */ -pie-background: linear-gradient(#0E59AE, #0E59AE 100%); behavior: url(PIE.htc); } .fixed { position:absolute; top:0; left:0; width:100%; border-bottom:3px solid #fff !important; background: #660099; /* Old browsers */ background: -moz-linear-gradient(top, #660099 0%, #660099 100%); /* FF3.6+ */ /* FireFox 3.6 */ /* Safari4+, Chrome */ -ms-filter: "progid:DXImageTransform.Microsoft.gradient(startColorstr='#660099', endColorstr='#660099')"; -pie-background: linear-gradient(#660099, #660099 100%); behavior: url(PIE.htc); -moz-box-shadow: 1px 1px 7px #676767; -webkit-box-shadow: 1px 1px 7px #676767; box-shadow: 1px 1px 7px #676767; height: 45px; background-image: -webkit-gradient(linear,left bottom,left top,color-stop(0, #660099),color-stop(1, #660099));/* IE6,IE7 */ /* IE8 */ /* Firefox F3.5+ */ /* Safari3.0+, Chrome */ } .ondemand { width:100%; border-bottom:3px solid #fff !important; position:absolute; top:0; left:0; z-index:1000; background: #CC0000; /* Old browsers */ background: -moz-linear-gradient(top, #CC0000 0%, #CC0000 100%); /* FF3.6+ */ /* FireFox 3.6 */ /* Safari4+, Chrome */ -ms-filter: "progid:DXImageTransform.Microsoft.gradient(startColorstr='#CC0000', endColorstr='#CC0000')"; -pie-background: linear-gradient(#CC0000, #CC0000 100%); behavior: url(PIE.htc); -moz-box-shadow: 1px 1px 7px #676767; -webkit-box-shadow: 1px 1px 7px #676767; box-shadow: 1px 1px 7px #676767; height: 45px; background-image: -webkit-gradient(linear,left bottom,left top,color-stop(0, #CC0000),color-stop(1, #CC0000));/* IE6,IE7 */ /* IE8 */ /* Firefox F3.5+ */ /* Safari3.0+, Chrome */ } .ondemand-button { width:40px !important; height:40px; float:right !important; z-index:999; position:absolute; margin-right:100px!important; } #footer{width:100%; margin:0 auto; font-size:12px; color:#0E59AE; height:30px; margin-top:200px;border-top:1px solid #CCC;padding:18px;} .hide{display:none;} /* Buttons */ .round.button { -moz-border-radius: 15px; -webkit-border-radius: 15px; border-radius: 15px; background-image: url(button-images/round-button-overlay.png); border: 1px solid rgba(0, 0, 0, 0.25); font-size: 13px; padding: 0; } .button { -moz-border-radius: 5px; -webkit-border-radius: 5px; border-radius: 5px; -moz-box-shadow: 0 1px 3px rgba(0, 0, 0, 0.50); -webkit-box-shadow: 0 1px 3px rgba(0, 0, 0, 0.50); box-shadow: 0 1px 3px rgba(0, 0, 0, 0.50); background: #222; border: 1px solid rgba(0, 0, 0, 0.25); color: white !important; cursor: pointer; display: inline-block; font-size: 13px; font-weight: bold; line-height: 1; overflow: visible; padding: 5px 15px 6px; position: relative; text-decoration: none; text-shadow: 0 -1px 1px rgba(0, 0, 0, 0.25); width: auto; text-align: center; } .round.button span { -moz-border-radius: 14px; -webkit-border-radius: 14px; border-radius: 14px; display: block; line-height: 1; padding: 4px 15px 6px; } .green.button { background-color:#91BD09; } .green.button:hover { background-color:#749A02; } .green.button:active { background-color:#a4d50b; } .blue.button { background-color:#0E59AE; } .blue.button:hover { background-color:#063468; } .blue.button:active { background-color:#1169cc; } .purple.button { background-color:#660099; } .purple.button:hover { background-color:#330066; } .purple.button:active { background-color:#7f02bd; } .red.button { background-color:#CC0000; } .red.button:hover { background-color:#990000; } .red.button:active { background-color:#ea0202; } .close {} .show{ background: #CC0000; /* Old browsers */ background: -moz-linear-gradient(top, #CC0000 0%, #CC0000 100%); /* FF3.6+ */ /* FireFox 3.6 */ /* Safari4+, Chrome */ -ms-filter: "progid:DXImageTransform.Microsoft.gradient(startColorstr='#CC0000', endColorstr='#CC0000')"; -pie-background: linear-gradient(#CC0000, #CC0000 100%); behavior: url(PIE.htc); -moz-box-shadow: 1px 1px 7px #676767; -webkit-box-shadow: 1px 1px 7px #676767; box-shadow: 1px 1px 7px #676767; height: 35px; float: right; width: 30px; overflow:hidden; /*margin-top: 0px !important;*/ margin-right: 10px !important; text-align: center; background-image: -webkit-gradient(linear,left bottom,left top,color-stop(0, #CC0000),color-stop(1, #CC0000));/* IE6,IE7 */ /* IE8 */ /* Firefox F3.5+ */ /* Safari3.0+, Chrome */ /* Opera 10.5, IE 9.0 */ } .show img{margin-top:10px;} ================================================ FILE: routes/index.js ================================================ var express = require('express'); var router = express.Router(); var ioSvc = require('../io/ioHelper').ioSvc; var msgType = require('../io/messageTpye').messageType; /* GET home page. */ router.get('/', function(req, res, next) { res.render('index', { title: 'Node-Msg-Sender' }); }); router.get('/sendMsg', function(req, res, next) { var type = req.query.type || msgType.public; var content = req.query.content || 'none'; var uid = req.query.uid; switch (type){ case msgType.public: ioSvc.serverBroadcastMsg(content); break; case msgType.private: if(!uid){ return res.send({code:400,msg:'uid参数必传'}); } ioSvc.serverToPrivateMsg(uid,content); break; } return res.send({code:200,msg:'发送成功'}); }); module.exports = router; ================================================ FILE: routes/users.js ================================================ var express = require('express'); var router = express.Router(); /* GET users listing. */ router.get('/', function(req, res, next) { res.send('respond with a resource'); }); module.exports = router; ================================================ FILE: utils/redis.js ================================================ var redisSvc = {}; var redis = require("redis"); if(!client){ var client = redis.createClient(); } client.on("error", function (err) { console.log("Redis Error :" , err); client = null; }); client.on('connect', function(){ console.log('Redis连接成功.'); }); /** * 添加string类型的数据 * @param key 键 * @params value 值 * @params expire (过期时间,单位秒;可为空,为空表示不过期) * @param callBack(err,result) */ redisSvc.set = function(key, value, expire, callback){ client.set(key, value, function(err, result){ if (err) { console.log(err); callback(err,null); return; } if (!isNaN(expire) && expire > 0) { client.expire(key, parseInt(expire)); } callback(null,result) }) }; /** * 查询string类型的数据 * @param key 键 * @param callBack(err,result) */ redisSvc.get = function(key, callback){ client.get(key, function(err,result){ if (err) { console.log(err); callback(err,null); return; } callback(null,result); }); }; /* *删除String 类型的key * @param key 键 * @param callBack(err,result) */ redisSvc.del = function(key, callback){ client.del(key, function(err,result){ if (err) { console.log(err); callback(err,null); return; } callback(null,result); }); }; module.exports = redisSvc; ================================================ FILE: views/error.ejs ================================================
<%= error.stack %>================================================ FILE: views/index.ejs ================================================