本文详细讲解如何在Node.js环境中利用Socket.io搭建具备私聊和公聊功能的聊天室。文章从Socket服务监听配置入手,解析连接事件处理,重点阐述通过全局对象映射Socket实例以实现服务器中转的私聊逻辑,并包含用户加入、消息发送及断开连接时的状态维护代码示例。
Node.js Socket.io聊天室实现概述
首先展示最终实现的界面效果。

在前文介绍使用Angular与Node.js结合socket.io构建基础聊天室及多人聊天功能的基础上,本文将继续深入探讨如何在Node.js中利用socket实现具体的私聊与公聊聊天室功能,详细内容如下。
在Node.js应用生态中,socket相关技术表现尤为突出。socket.io在GitHub上获得了数万星的关注,其流行程度不亚于Express框架。为了帮助开发者更好地理解socket.io的实际应用,下面进行具体说明。
示例项目的访问地址为http://chat.lovewebgames.com/。
完整源代码可下载至https://github.com/tianxiangbing/chat。
由于服务器和数据库均采用了国外免费资源,访问速度可能会受到一定影响。
Socket服务端口配置
首先阐述对socket.io的理解:WebSocket机制类似于开启一个端口服务来监控通信流量。因此,我们可以依赖当前站点的80端口启动socket服务,也可以将其部署在其他端口上,例如:
具体实现代码如下:
require('socket.io').listen(3000);
上述代码表示监听3000端口。由于使用的免费服务器没有权限开启其他端口,因此仍使用80端口。鉴于80端口已被Express占用,所以在启动Express服务时传入了服务器实例。
相关代码如下:
var server = http.createServer(app);
var socket = require(‘./socket/msg')(server);
随后,在msg.js文件中的实现方式如下:
代码片段如下:
var db = require('../db/mysql');
var sio = require('socket.io');
var IO = function(server) {
var io = sio.listen(server)
至此配置完成。其中db用于创建MySQL连接,不属于本节讨论范围,故省略。
连接事件与消息处理机制
在socket.io中,首先需要创建io通道的连接,然后监视其中的socket事件,因为Node.js是事件驱动模型。具体代码如下:
代码如下:
io.on('connection', function(socket) {
console.log('a user connected.');
socket.on('disconnect', function() {
console.log('user disconnected.');
});
})
当有用户连接时,代码进入connection回调。如果是公聊,可以直接使用以下方式进行广播:
代码如下:
io.emit('chat message', {});私聊功能的Socket映射与中转逻辑
但本例涉及私聊,因此需要将socket对象临时保存在全局变量中,以便私聊对象能通过服务器找到目标用户的socket。这并非完全的点对点通信,而是经过服务器中转:消息先传给服务器,服务器再查找目标用户的socket对象并发出。整个流程如下,这里使用类数组对象进行存储。
代码示例如下:
var users = {},
usocket = {};
socket.on('user join', function(data) {
users[username] = username;
usocket[username] = socket;
})
由于需要用户名登录,这里将用户名作为唯一标识(此为示例,未处理用户名重复情况)。使用类数组形式的优势在于无需循环即可快速查找。当向用户A发送私聊时,先在usocket中查找其socket,然后调用其emit方法。
function sendUserMsg(data) {
if (data.to in usocket) {
console.log('================')
console.log('to' + data.to, data);
usocket[data.to].emit('to' + data.to, data);
usocket[data.user].emit('to' + data.user, data);
console.log('================')
}
}
这里emit调用两次的原因是:在向对方发送消息的同时,自己也需接收该消息以显示在界面上。这样做有两个好处:一是接口统一,聊天内容均由服务器下发;二是确认发送成功。
在客户端监听时,使用“to+用户名”的事件名进行监听。
socket.on('to' + user, function(data) {
//console.log(data);
formatMsg(data);
})
这样,无论是发送还是接收的消息,都会进入该事件监听器。最后,在用户断开连接时,务必删除对应的对象。
socket.on('disconnect', function() {
console.log('disconnect')
if (username) {
counter--;
delete users[username];
delete usocket[username];
if (home.name == username) {
homeLeave(username);
}
sendmsg({
type: 0,
msg: "用户<b>" + username + "</b>离开聊天室",
counter: counter,
users: users
})
}
});
至此,功能实现完毕。