├── .gitignore ├── public ├── images │ ├── demo.png │ ├── repeat.jpg │ └── icon-close.png ├── javascripts │ └── notify.js └── stylesheets │ └── style.css ├── views ├── error.ejs └── index.ejs ├── io ├── messageTpye.js ├── ioHelper.js └── io.js ├── routes ├── users.js └── index.js ├── package.json ├── app.js ├── utils └── redis.js ├── bin └── www └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | # Created by .ignore support plugin (hsz.mobi) 2 | .gitignore 3 | .idea/ 4 | node_modules/ 5 | -------------------------------------------------------------------------------- /public/images/demo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gytai/node-websocket-msg-sender/master/public/images/demo.png -------------------------------------------------------------------------------- /views/error.ejs: -------------------------------------------------------------------------------- 1 |
<%= error.stack %>4 | -------------------------------------------------------------------------------- /public/images/repeat.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gytai/node-websocket-msg-sender/master/public/images/repeat.jpg -------------------------------------------------------------------------------- /public/images/icon-close.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gytai/node-websocket-msg-sender/master/public/images/icon-close.png -------------------------------------------------------------------------------- /io/messageTpye.js: -------------------------------------------------------------------------------- 1 | const messageType= { 2 | 'public':'public', 3 | 'private':'private' 4 | }; 5 | 6 | exports.messageType = messageType; -------------------------------------------------------------------------------- /routes/users.js: -------------------------------------------------------------------------------- 1 | var express = require('express'); 2 | var router = express.Router(); 3 | 4 | /* GET users listing. */ 5 | router.get('/', function(req, res, next) { 6 | res.send('respond with a resource'); 7 | }); 8 | 9 | module.exports = router; 10 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "nodemessage", 3 | "version": "0.0.0", 4 | "private": true, 5 | "scripts": { 6 | "start": "node ./bin/www" 7 | }, 8 | "dependencies": { 9 | "body-parser": "~1.17.1", 10 | "cookie-parser": "~1.4.3", 11 | "debug": "~2.6.3", 12 | "ejs": "~2.5.6", 13 | "express": "~4.15.2", 14 | "morgan": "~1.8.1", 15 | "serve-favicon": "~2.4.2", 16 | "socket.io": "^2.0.3", 17 | "socket.io-client": "^2.0.3", 18 | "redis": "^2.8.0" 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /io/ioHelper.js: -------------------------------------------------------------------------------- 1 | var redis = require('../utils/redis'); 2 | 3 | var ioSvc = {}; 4 | ioSvc.io = null; 5 | 6 | //初始化实例 7 | ioSvc.setInstance = function (io) { 8 | this.io = io; 9 | }; 10 | 11 | ioSvc.getInstance =function () { 12 | return this.io; 13 | }; 14 | 15 | //服务器给所有客户端广播消息 16 | ioSvc.serverBroadcastMsg = function (data) { 17 | console.log('发送广播消息'); 18 | console.log(data); 19 | this.io.sockets.emit('message',data); 20 | }; 21 | 22 | //服务端给指定用户发消息 23 | ioSvc.serverToPrivateMsg = function (uid,data) { 24 | console.log('发送私人消息'); 25 | console.log(data); 26 | redis.get(uid,function (err,sid) { 27 | if(err){ 28 | console.error(err); 29 | } 30 | if(sid){ 31 | //给指定的客户端发送消息 32 | this.io.sockets.socket(sid).emit('message',data); 33 | } 34 | }); 35 | }; 36 | 37 | exports.ioSvc = ioSvc; -------------------------------------------------------------------------------- /routes/index.js: -------------------------------------------------------------------------------- 1 | var express = require('express'); 2 | var router = express.Router(); 3 | var ioSvc = require('../io/ioHelper').ioSvc; 4 | var msgType = require('../io/messageTpye').messageType; 5 | 6 | /* GET home page. */ 7 | router.get('/', function(req, res, next) { 8 | res.render('index', { title: 'Node-Msg-Sender' }); 9 | }); 10 | 11 | router.get('/sendMsg', function(req, res, next) { 12 | var type = req.query.type || msgType.public; 13 | var content = req.query.content || 'none'; 14 | var uid = req.query.uid; 15 | 16 | switch (type){ 17 | case msgType.public: 18 | ioSvc.serverBroadcastMsg(content); 19 | break; 20 | case msgType.private: 21 | if(!uid){ 22 | return res.send({code:400,msg:'uid参数必传'}); 23 | } 24 | ioSvc.serverToPrivateMsg(uid,content); 25 | break; 26 | } 27 | return res.send({code:200,msg:'发送成功'}); 28 | 29 | }); 30 | 31 | module.exports = router; 32 | -------------------------------------------------------------------------------- /app.js: -------------------------------------------------------------------------------- 1 | var express = require('express'); 2 | var path = require('path'); 3 | var favicon = require('serve-favicon'); 4 | var logger = require('morgan'); 5 | var cookieParser = require('cookie-parser'); 6 | var bodyParser = require('body-parser'); 7 | 8 | var app = express(); 9 | 10 | var index = require('./routes/index'); 11 | var users = require('./routes/users'); 12 | 13 | // view engine setup 14 | app.set('views', path.join(__dirname, 'views')); 15 | app.set('view engine', 'ejs'); 16 | 17 | // uncomment after placing your favicon in /public 18 | //app.use(favicon(path.join(__dirname, 'public', 'favicon.ico'))); 19 | app.use(logger('dev')); 20 | app.use(bodyParser.json()); 21 | app.use(bodyParser.urlencoded({ extended: false })); 22 | app.use(cookieParser()); 23 | app.use(express.static(path.join(__dirname, 'public'))); 24 | 25 | app.use('/', index); 26 | app.use('/users', users); 27 | 28 | // catch 404 and forward to error handler 29 | app.use(function(req, res, next) { 30 | var err = new Error('Not Found'); 31 | err.status = 404; 32 | next(err); 33 | }); 34 | 35 | // error handler 36 | app.use(function(err, req, res, next) { 37 | // set locals, only providing error in development 38 | res.locals.message = err.message; 39 | res.locals.error = req.app.get('env') === 'development' ? err : {}; 40 | 41 | // render the error page 42 | res.status(err.status || 500); 43 | res.render('error'); 44 | }); 45 | 46 | module.exports = app; 47 | -------------------------------------------------------------------------------- /utils/redis.js: -------------------------------------------------------------------------------- 1 | var redisSvc = {}; 2 | var redis = require("redis"); 3 | 4 | if(!client){ 5 | var client = redis.createClient(); 6 | } 7 | 8 | client.on("error", function (err) { 9 | console.log("Redis Error :" , err); 10 | client = null; 11 | }); 12 | 13 | client.on('connect', function(){ 14 | console.log('Redis连接成功.'); 15 | }); 16 | 17 | /** 18 | * 添加string类型的数据 19 | * @param key 键 20 | * @params value 值 21 | * @params expire (过期时间,单位秒;可为空,为空表示不过期) 22 | * @param callBack(err,result) 23 | */ 24 | redisSvc.set = function(key, value, expire, callback){ 25 | 26 | client.set(key, value, function(err, result){ 27 | 28 | if (err) { 29 | console.log(err); 30 | callback(err,null); 31 | return; 32 | } 33 | 34 | if (!isNaN(expire) && expire > 0) { 35 | client.expire(key, parseInt(expire)); 36 | } 37 | 38 | callback(null,result) 39 | }) 40 | }; 41 | 42 | /** 43 | * 查询string类型的数据 44 | * @param key 键 45 | * @param callBack(err,result) 46 | */ 47 | redisSvc.get = function(key, callback){ 48 | 49 | client.get(key, function(err,result){ 50 | 51 | if (err) { 52 | console.log(err); 53 | callback(err,null); 54 | return; 55 | } 56 | 57 | callback(null,result); 58 | }); 59 | }; 60 | 61 | /* 62 | *删除String 类型的key 63 | * @param key 键 64 | * @param callBack(err,result) 65 | */ 66 | redisSvc.del = function(key, callback){ 67 | 68 | client.del(key, function(err,result){ 69 | 70 | if (err) { 71 | console.log(err); 72 | callback(err,null); 73 | return; 74 | } 75 | 76 | callback(null,result); 77 | }); 78 | }; 79 | 80 | 81 | module.exports = redisSvc; -------------------------------------------------------------------------------- /bin/www: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | /** 4 | * Module dependencies. 5 | */ 6 | 7 | var app = require('../app'); 8 | var debug = require('debug')('nodemessage:server'); 9 | var http = require('http'); 10 | var ioSvc = require('../io/io'); 11 | /** 12 | * Get port from environment and store in Express. 13 | */ 14 | 15 | var port = normalizePort(process.env.PORT || '3000'); 16 | app.set('port', port); 17 | 18 | /** 19 | * Create HTTP server. 20 | */ 21 | 22 | var server = http.createServer(app); 23 | 24 | //??Socket.IO 25 | var io = require('socket.io')(server); 26 | ioSvc.ioServer(io); 27 | 28 | /** 29 | * Listen on provided port, on all network interfaces. 30 | */ 31 | 32 | server.listen(port); 33 | server.on('error', onError); 34 | server.on('listening', onListening); 35 | 36 | /** 37 | * Normalize a port into a number, string, or false. 38 | */ 39 | 40 | function normalizePort(val) { 41 | var port = parseInt(val, 10); 42 | 43 | if (isNaN(port)) { 44 | // named pipe 45 | return val; 46 | } 47 | 48 | if (port >= 0) { 49 | // port number 50 | return port; 51 | } 52 | 53 | return false; 54 | } 55 | 56 | /** 57 | * Event listener for HTTP server "error" event. 58 | */ 59 | 60 | function onError(error) { 61 | if (error.syscall !== 'listen') { 62 | throw error; 63 | } 64 | 65 | var bind = typeof port === 'string' 66 | ? 'Pipe ' + port 67 | : 'Port ' + port; 68 | 69 | // handle specific listen errors with friendly messages 70 | switch (error.code) { 71 | case 'EACCES': 72 | console.error(bind + ' requires elevated privileges'); 73 | process.exit(1); 74 | break; 75 | case 'EADDRINUSE': 76 | console.error(bind + ' is already in use'); 77 | process.exit(1); 78 | break; 79 | default: 80 | throw error; 81 | } 82 | } 83 | 84 | /** 85 | * Event listener for HTTP server "listening" event. 86 | */ 87 | 88 | function onListening() { 89 | var addr = server.address(); 90 | var bind = typeof addr === 'string' 91 | ? 'pipe ' + addr 92 | : 'port ' + addr.port; 93 | debug('Listening on ' + bind); 94 | } 95 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 基于Nodejs websocket socket.io的消息转发系统 message pusher written in nodejs based on socket.io 2 | ============== 3 | 4 | 消息实时推送,支持在线用户数实时统计。基于[Socket.IO](https://socket.io/)开发,使用websocket推送数据,当浏览器不支持websocket时自动切换comet推送数据。 5 | 6 | 支持Linux,mac,windows等环境部署。 7 | 8 | 9 | 10 | 效果截图 11 | ====== 12 |  13 | 14 | 线上demo 15 | ====== 16 | 17 | http://112.74.81.224:3000/ 18 | 19 | 可以通过url:http://112.74.81.224:3000/sendMsg/?type=private&uid=1504936989000&content=消息内容 向当前用户发送消息 20 | 21 | 可以通过url:http://112.74.81.224:3000/sendMsg/?type=public&content=消息内容 向所有在线用户推送消息 22 | 23 | uid为接收消息的uid,如果不传递则向所有人推送消息 24 | content 为消息内容 25 | 26 | 注:可以通过php或者其它语言的curl功能实现后台推送 27 | 28 | 下载安装 29 | ====== 30 | 1、git clone https://github.com/gytai/node-websocket-msg-sender.git 31 | 32 | 2、npm install 33 | 34 | 3、apt-get install redis-server 35 | 36 | 4、redis-server 37 | 38 | 后端服务启动停止,先安装PM2(Advanced Node.js process manager,http://pm2.keymetrics.io/) 39 | ====== 40 | ### 启动服务 41 | pm2 start bin/www --name msg-sender 42 | 43 | ### 停止服务 44 | pm2 stop msg-sender 45 | 46 | Web前端代码类似: 47 | ==== 48 | ```javascript 49 | // 引入前端文件 50 | 51 | 67 | ``` 68 | 69 | 其他客户端 70 | ==== 71 | 根据websocket协议即可。具体参考websocket协议。 72 | 73 | 74 | Nodejs后端调用api向任意用户推送数据 75 | ==== 76 | ```javascript 77 | var type = req.query.type || msgType.public; 78 | var content = req.query.content || 'none'; 79 | var uid = req.query.uid; 80 | 81 | switch (type){ 82 | case msgType.public: 83 | ioSvc.serverBroadcastMsg(content); 84 | break; 85 | case msgType.private: 86 | if(!uid){ 87 | return res.send({code:400,msg:'uid参数必传'}); 88 | } 89 | ioSvc.serverToPrivateMsg(uid,content); 90 | break; 91 | } 92 | ``` 93 | 94 | Http 发送数据,可以配置跨站发送(需要设置跨域放行)。例如安卓或者IOS等其他客户端也可以方便的发送消息。 95 | ==== 96 | 可以通过url:http://localhost:3000/sendMsg/?type=private&uid=1504936989000&content=消息内容 向当前用户发送消息 97 | 98 | 可以通过url:http://localhost:3000/sendMsg/?type=public&content=消息内容 向所有在线用户推送消息 99 | 100 | 101 | 102 | -------------------------------------------------------------------------------- /public/javascripts/notify.js: -------------------------------------------------------------------------------- 1 | (function ($) { 2 | $.fn.extend({ 3 | notify: function (options) { 4 | var settings = $.extend({ type: 'sticky', speed: 500, onDemandButtonHeight: 35 }, options); 5 | return this.each(function () { 6 | var wrapper = $(this); 7 | var ondemandBtn = $('.ondemand-button'); 8 | var dh = -35; 9 | var w = wrapper.outerWidth() - ondemandBtn.outerWidth(); 10 | ondemandBtn.css('left', w).css('margin-top', dh + "px" ); 11 | var h = -wrapper.outerHeight(); 12 | wrapper.addClass(settings.type).css('margin-top', h).addClass('visible').removeClass('hide'); 13 | if (settings.type != 'ondemand') { 14 | wrapper.stop(true, false).animate({ marginTop: 0 }, settings.speed); 15 | } 16 | else { 17 | ondemandBtn.stop(true, false).animate({ marginTop: 0 }, settings.speed); 18 | } 19 | 20 | var closeBtn = $('.close', wrapper); 21 | closeBtn.click(function () { 22 | if (settings.type == 'ondemand') { 23 | wrapper.stop(true, false).animate({ marginTop: h }, settings.speed, function () { 24 | wrapper.removeClass('visible').addClass('hide'); 25 | ondemandBtn.stop(true, false).animate({ marginTop: 0 }, settings.speed); 26 | }); 27 | } 28 | else { 29 | wrapper.stop(true, false).animate({ marginTop: h }, settings.speed, function () { 30 | wrapper.removeClass('visible').addClass('hide'); 31 | }); 32 | } 33 | }); 34 | if (settings.type == 'floated') { 35 | $(document).scroll(function (e) { 36 | wrapper.stop(true, false).animate({ top: $(document).scrollTop() }, settings.speed); 37 | }).resize(function (e) { 38 | wrapper.stop(true, false).animate({ top: $(document).scrollTop() }, settings.speed); 39 | }); 40 | } 41 | else if (settings.type == 'ondemand') { 42 | ondemandBtn.click(function () { 43 | $(this).animate({ marginTop: dh }, settings.speed, function () { 44 | wrapper.removeClass('hide').addClass('visible').animate({ marginTop: 0 }, settings.speed, function () { 45 | 46 | }); 47 | }) 48 | }); 49 | } 50 | 51 | }); 52 | 53 | } 54 | }); 55 | })(jQuery); 56 | -------------------------------------------------------------------------------- /views/index.ejs: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 |