[Node.js] 基于Socket.IO 的私聊

原文地址:http://www.moye.me/2015/01/02/node_socket-io/

引子

最近听到这么一个问题:Socket.IO 怎么实现私聊?换个提法:怎么定位到人(端),或者说怎么标识到连接,而不是依赖每个连接的socket.id。好问题。

在 Socket.IO Real-Time Web Application Development的指引下,形成了如下思路:

  1. 服务端在每个用户初次进入系统时,产生session_id
  2. 服务端强制用户输入昵称,与session_id对应
  3. 服务端的Socket.IO在连接时,可以拿到socket.request.headers.cookie,从这个cookie中解析出session_id,将socket 连接与 Web框架的context中的session_id 对应上
  4. 在服务端使用一个数组来保存如上三者产生的对应关系:[{name, session_id, socket} , ...]
  5. 有了对应关系的数组,就能定位到人并分清 [我] 和 [其他人],也便能够利用保存的socket 进行私聊

有了思路,就可以动手实践了:

Server端

ES6 的生成器太好用,做 Node Web 就从 koa 开始吧。那么,我的 package.json 看起来就会有这些依赖的库:

"co": "^4.0",
"koa": "^0.14.0", 
"koa-mount": "*",
"koa-ejs": "*",
"koa-static": "*",
"koa-router": "*",
"koa-session": "*",
"co-body": "*"

用户列表

思路中提到的用户列表,就是一个简单的数组:[{name, session_id, socket} , ...],围绕它的操作也特别简单(socketHandler:

//暴露给Web的接口
module.exports.addUser = addUser;
module.exports.otherUsers = otherUsers;

var users = [];

function findInUsers(session_id) {//通过session_id查找
    var index = -1;
    for (var j = 0, len = users.length; j < len; j++) {
        if (users[j].session_id === session_id)
            index = j;
    }
    return index;
}
function addUser(name, session_id) {
    var index = findInUsers(session_id);
    if (index === -1) //不存在则重新添加
        users.push({name: name, session_id: session_id,
            socket: null});
    else { //只更新昵称
        if (users[index].name !== name)
            users[index].name = name;
    }
}
function setUserSocket(session_id, socket){//更新用户socket
    var index = findInUsers(session_id);
    if (index !== -1){
        users[index].socket = socket;
    }
}
function findUser(session_id) {
    var index = findInUsers(session_id);
    return index > -1 ? users[index] : null;
}
function otherUsers(session_id){//其他人
    var results = [];
    for (var j = 0, len = users.length; j < len; j++) {
        if (users[j].session_id !== session_id)
            results.push({session_id: users[j].session_id,
                name: users[j].name});
    }
    return results;
}

Session存储

koa-session 这个库提供了 session 存储功能,它的使用非常简单:

var koa = require(‘koa‘);
var session = require(‘koa-session‘);

var app = koa();
app.keys = [config.SECRET];
app.use(session(app));

此外,koa-session会在Web request的cookie中会附上一个 koa:sess的session_id 标识串,那么,在 socket.io 的事件侦听中,我们可以这么用它:

io.on(‘connection‘, function (socket) {
    var sessionId = getSessionId(socket.request.headers.cookie,
        ‘koa:sess‘);
    if(sessionId){
         setUserSocket(sessionId, socket);
     }
 });

function getSessionId(cookieString, cookieName) {
    var matches = new RegExp(cookieName +
         ‘=([^;]+);‘, ‘gmi‘).exec(cookieString);
    return matches[1] ? matches[1] : null;
}

用户登录

所谓的登录,就是让用户输入一个昵称,将它与session_id对应上,并存储到前述用户数组中。假设我们的路由路径为 /chat,登录action路径为/chat/login,那么这个路由看起来是这样:

var Router = require(‘koa-router‘),
    router = new Router();
var parse = require(‘co-body‘);
var socketHandler = require(‘../../middlewares/socketHandler‘);

// GET /chat
router.get(‘/‘, function *() {
    var session_id = this.cookies.get(‘koa:sess‘);
    var name = this.session.name;
    if(session_id && name) {//添加到用户列表
        socketHandler.addUser(name, session_id);
        yield this.render(‘../www/views/chat‘); //使用ejs
    } else {
        this.redirect(‘/chat/login‘);
    }
});

// GET /chat/login 使用ejs模板
router.get(‘/login‘, function*(){
    yield this.render(‘../www/views/login‘)
});
// POST /chat/login 接收form提交: <input name=‘name‘>
router.post(‘/login‘, function*(){
    var body = yield parse(this);
    this.session.name = body.name || ‘guest‘;
    this.redirect(‘/chat‘)
});

module.exports = router;

广播和私聊消息处理

io.on(‘connection‘, function (socket) {
    socket.on(‘broadcast‘, function (data) {
        //广播
        var fromUser = findUser(sessionId);
        if(fromUser) {
            socket.broadcast.emit(‘broadcast‘, {
                name: fromUser.name,
                msg: data.msg
            });
        }
    });

    socket.on(‘private‘, function (data) {
        //私聊 {to_session_id, msg}
        var fromUser = findUser(sessionId);
        if(fromUser) {
            var toUser = findUser(data.to_session_id);
            if (toUser)
                toUser.socket.emit(‘private‘, {
                    name: fromUser.name,
                    msg: data.msg
                });
        }
    });
});

客户端

在连接到服务端后,客户端会定时拉取其他人的列表:

//定时获取其他人列表
function updateOthers() {
    $.post(‘/chat/others‘, function (others) {
         //...若干丑陋的UI DOM操作代码
         setTimeout(updateOthers, 1000);
    });
}
setTimeout(updateOthers, 1000);

对应的,服务端会有一个这样的接口:

// POST /chat/others 其他人列表
router.post(‘/others‘, function*(){
    var session_id = this.cookies.get(‘koa:sess‘);
    var name = this.session.name;
    if(session_id && name) {
        this.type = ‘application/json‘;
        this.body = socketHandler.otherUsers(session_id);
    } else {
        this.status = 404;
    }
});

运行效果

在三个不同的浏览器中跑起来,宛如上世纪90年代火得不行的聊天室 :)

源码

完整的源码放在我的Github上:https://github.com/rockdragon/socketchat,想让它跑起来,你需要把 Node 升到 0.11.14(因为用到了 Co V4 ),当然,README.MD里有详细的设置说明。

更多文章请移步我的blog新地址: http://www.moye.me/

时间: 2024-12-26 16:54:47

[Node.js] 基于Socket.IO 的私聊的相关文章

使用node.js和socket.io实现多人聊天室

刚学node.js,想着做点东西练练手.网上的东西多而杂,走了不少弯路,花了一天时间在调代码上.参考网上的一篇文章,重写了部分代码,原来的是基于基于node-websocket-server框架的,我没用框架,单单是socket.io. 一.基本功能 1.用户随意输入一个昵称即可登录2.登录成功后1) 对正在登录用户来说,罗列所有在线用户列表,罗列最近的历史聊天记录2) 对已登录的用户来说,通知有新用户进入房间,更新在线用户列表3.退出登录1)支持直接退出2) 当有用户退出,其他所有在线用户会收

Node.js学习(1):Node.js 和Socket.IO 实现chat

使用 Node.js 和 Socket.IO 构建简单的聊天程序 在node.js根目录下创建文件夹chat,里面添加两个文件:app.js和index.html app.js var fs = require('fs') , http = require('http') , socketio = require('socket.io'); var server = http.createServer(function(req, res) { res.writeHead(200, { 'Cont

基于Node.js的socket.io机制的陷阱,仅针对于客户端继承socket.io的问题的解决方案

Socket.IO enables real-time bidirectional event-based communication.It works on every platform, browser or device, focusing equally on reliability and speed. 众所周时  Socket.IO 确实是一套不错的即时数据传输的解决方案,由于其存在的良好的向下兼容性 (web socket Comet  xhr)使得其在任何平台上不存在任何的差异性

node.js之socket.io模块

socket.io模块是一个基于Node.js的项目,其作用主要是将WebSocket协议应用到所有的浏览器.该模块主要应用于实时的长连接多请求项目中,例如在线联网游戏.实时聊天.实时股票查看.二维码扫描登录等.——Node.js开发实战详解 安装和配置的方法和一般的NPM模块安装配置一致: # npm install soctet.io 应该是因为我的本地服务器是win7操作系统,所以在安装的时候会有一堆错误提示,但是貌似不影响使用socket.io模块,没有太在意这个.不知道在linux上是

使用Node.js的socket.io模块开发实时web程序

首发:个人博客,更新&纠错&回复 今天的思维漫游如下:从.net的windows程序开发,摸到nodejs的桌面程序开发,又熟悉了一下nodejs,对“异步”的理解有了上上周对操作系统的学习而更能理解.然后发现了Node.js中的socket.io这个模块,又觉得跟前几天用.net做客户端的socket游戏了.技术世界,兜兜转转,相逢一笑,疑是故人. socket.io用来做实时web程序,解决之前的B/S程序只有无状态连接,特定需求还需要用长连接这种“奇技淫巧”的问题.当然,这是html

node.js和socket.io纯js实现的即时通讯实例分享

在这个例子中,其实node.js并没有真正起到服务器的作用,因为我们这里可以直接运行client.html文件,而不用输入url请求,当 然,要想输入url请求页面内容还需要加入请求静态文件的代码.这个实例中node.js最重要的作用就是将服务端迁移到了js,实现了客户端和服务端语 言上的统一,我们只要在浏览器上同时运行两个client.html客户端页面,即可进行简单的即是通讯了,socket.io才是我们真正用来实现即时 通讯的消息的收发. var server = http.createS

node.js中socket.io的使用(node.js开发实战详解一个案例分析)

啊,又是这本书.好像里面有个交互图,呃...那个消息响应的顺序好像与我的程序不同. 其实问题也不大,操作实例,控制台运行该文件app.js开启服务: var io=require('socket.io').listen(8080,{log:false}); io.sockets.on('connection',function (socket){ socket.on('msg',function(data){ console.log(data); if(data.state){ if(data.

在线白板,基于socket.io的多人在线协作工具

首发:个人博客,更新&纠错&回复 是昨天这篇博文留的尾巴,socket.io库的使用练习,成品地址在这里. 代码已经上传到github,传送门.可以开俩浏览器看效果. 现实意义是俩人在线交流时说不明白,这时有个白板就好了,两人都能在上面写写画画,帮助沟通. 把github的readme搬过来—— 此项目用于socket.io技术的使用演示 1.安装node.js 2.在某个文件夹下执行npm install socket.io 3.将源码拷贝到该文件夹下 4.执行node main.js

深入理解Node.js基于事件驱动的回调

回调和异步调用的关系 首先明确一点,回调并非是异步调用,回调是一种解决异步函数执行结果的处理方法.在异步调用,如果我们希望将执行的结果返回并且处理时,可以通过回调的方法解决.为了能够更好的区分回调和异步回调的区别,我们来看一个简单的例子,代码如下: function waitFive(name, function_name){ var pus = 0; var currentDate = new Date(); while(pus < 5000){ var now = new Date(); p