原文地址: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/

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

  1. Node.js 和Socket.IO 实现chat WEBIM

    socket官方:   http://socket.io/  需求:实现WEB IM功能,数据从服务器PUSH  不是PULL  websocket是基于HTML5的新特性,不兼容IE6,7,8 .. ...

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

    首发:个人博客,更新&纠错&回复 今天的思维漫游如下:从.net的windows程序开发,摸到nodejs的桌面程序开发,又熟悉了一下nodejs,对“异步”的理解有了上上周对操作系统 ...

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

    在这个例子中,其实node.js并没有真正起到服务器的作用,因为我们这里可以直接运行client.html文件,而不用输入url请求,当 然,要想输入url请求页面内容还需要加入请求静态文件的代码.这 ...

  4. node.js和socket.io实现im

    im——Instant Messaging 即时通讯 基本技术原理 (1)通过IM服务器登陆或注销 (2)用户A通过列表找到B,用户B获得消息并与之交谈 (3)通过IM服务器指引建立与B单独的通讯通道 ...

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

    首发:个人博客,更新&纠错&回复 是昨天这篇博文留的尾巴,socket.io库的使用练习,成品地址在这里. 代码已经上传到github,传送门.可以开俩浏览器看效果. 现实意义是俩人在 ...

  6. 基于socket.io的实时在线选座系统

    基于socket.io的实时在线选座系统(demo) 前言 前段时间公司做一个关于剧院的项目,遇到了这样一种情况. 在高并发多用户同时选座的情况下,假设A用户进入选座页面,正在选择座位,此时还没有提交 ...

  7. Node.js + Web Socket 打造即时聊天程序嗨聊

    前端一直是一块充满惊喜的土地,不仅是那些富有创造性的页面,还有那些惊赞的效果及不断推出的新技术.像node.js这样的后端开拓者直接将前端人员的能力扩大到了后端.瞬间就有了一统天下的感觉,来往穿梭于前 ...

  8. Node+Express+MongoDB + Socket.io搭建实时聊天应用

    Node+Express+MongoDB + Socket.io搭建实时聊天应用 前言 本来开始写博客的时候只是想写一下关于MongoDB的使用总结的,后来觉得还不如干脆写一个node项目实战教程实战 ...

  9. Node+Express+MongoDB + Socket.io搭建实时聊天应用实战教程(二)--node解析与环境搭建

    前言 本来开始写博客的时候只是想写一下关于MongoDB的使用总结的,后来觉得还不如干脆写一个node项目实战教程实战.写教程一方面在自己写的过程中需要考虑更多的东西,另一方面希望能对node入门者有 ...

随机推荐

  1. [转] MySQL 查询表数据大小的总结

    一:关于mysql表数据大小 我们知道mysql存储数据文件一般使用表空间存储 当mysql使用innodb存储引擎的时候,mysql使用表存储数据分为共享表空间和独享表空间两种方式 ·共享表空间:I ...

  2. C++ 非阻塞套接字的使用 (1)

    在维护代码的过程中,发现软件运行的CPU占用率居高不下,在4核的电脑上占用了25%的CPU.查阅资料的得知,这是可能是由于软件中出现了死循环. 经过对软件的一些测试,最终确定了死循环出现的位置——通讯 ...

  3. 微信公共平台开发-(.net实现)1--成为开发者

    刚换了个新环境,哎这都快一个月了,还没适应过来,还是怀念老地方呀.老板让开发一个基于微信平台的开发项目,而且是用net实现.当时就蒙了,微信就用了一会个人赶脚不好,所以果断不用,现在让开发,而且查了一 ...

  4. NoSQL学习二:MongoDB基本管理命令

    MongoDB命令学习  一.MongoDB命令帮助 在安装MongoDB后,启动服务器进程(mongod),可以通过在客户端命令mongo实现对MongoDB的管理和监控: 这是MongoDB最上层 ...

  5. MQTT和WebSocket

    严格来说,MQTT跟WebSocket关系不大.他们不是在一个层级的.   MQTT和TCP.WebSocket的关系可以用下图一目了然:   参考资料: http://www.zhihu.com/q ...

  6. 继续送假期干货——响应式图片工具smartImg

    中午看<众妙之门>看到一个响应式图片处理工具(点此查看)的介绍,然后就心血来潮想着不妨自己写一个基于JQ的吧,于是就又有了这么一个干货给大家. smartImg 的全部文件可以从我的Git ...

  7. STC12C5A60S2笔记2(存储)

    STC12C5A60S2单片机分为4个物理上独立的存储区域: 1. 程序flash存储器(60KB) 程序Flash存储器用以存储用户程序及数据.单片机复位后默认从0000H单元开始执行指令. 1) ...

  8. Aoite 系列 目录

    介绍 本项目从2009年孵化(V->Sofire->Aoite),至今已度过5个年头.一直在优化,一直在重构,一直在商用.有十分完整的单元测试用例.可以放心使用. Aoite on 博客园 ...

  9. wamp虚拟主机配置

    1.首先打开apache的配置文件httpd.conf,并去掉#Include conf/extra/httpd-vhosts.conf前面的#,启用虚拟主机功能 2.先把localhost配置好,免 ...

  10. java中基本类型和包装类型实践经验

    至今,小菜用java快两年了,有些事,也该有个总结. 基本类型和包装类型的概念在本文不作赘述. 如果这两种类型直接使用,倒没什么值得讨论的,无非就是自动装箱拆箱,java可以让你感觉不到他们的存在,但 ...