在文章的开头,我要解释一下,为什么不直接使用web Socket实现即时通讯,因为一部分浏览器并不兼容web Socket,导致即时通讯在这些浏览器上无法正常使用,所以才需要用到nodeJs里封装好webSocket协议的socket.io包模块来提供不兼容websocket浏览器的解决方案并接管客户与服务器端交互的IO流。(一般处理不兼容websocket的浏览器的方法就是考虑 commit 方式,或者用 Flash sockect.)
这个即时通讯的demo包括最基本的聊天室功能和私聊功能,其他附加的功能就是发送图片和表情及改变字体颜色,实现最基本的对话聊天功能。需要其他功能的可以懂得原理后自行添加模块进来。话不多说,直接用代码说话。(其中将会使用到express和socket.io两个包模块,一定要先安装好。PS: express是node.js中管理路由响应请求的模块,根据请求的URL返回相应的HTML页面。这里我们使用一个事先写好的静态页面返回给客户端,只需使用express指定要返回的页面的路径即可。如果不用这个包,我们需要将HTML代码与后台JavaScript代码写在一起进行请求的响应,不太方便。socket.io是Node.js中使用socket的一个包。使用它可以很方便地建立服务器到客户端的sockets连接,发送事件与接收特定事件。)
首先,我们来看服务器端代码:
var express = require('express'),
app = express(),
server = require('http').createServer(app),
io = require('socket.io').listen(server);
//指定静态HTML文件的位置
app.use('/', express.static(__dirname + '/www'));
server.listen(3000);//监听端口是否有来自客户端的请求
var onlineUsers = {};//在线用户列表
var socketList = {};//每个用户所持有的与服务器交互的socket列表
var onlineCount = 0;//在线人数
//处理socket事件
io.sockets.on('connection', function(socket) {
//新用户登陆
socket.on('login', function(obj) {
if (onlineUsers.hasOwnProperty(obj.userid)) {
socket.emit('userExisted');
} else {
socket.name = obj.userid;
socketList[obj.userid] = socket;
//检查在线用户列表,如果不存在,则将该用户加入在线用户表
if(!onlineUsers.hasOwnProperty(obj.userid)) {
onlineUsers[obj.userid] = obj.nickname;
//在线人数+1
onlineCount++;
}
socket.emit('loginSuccess',{onlineUsers:onlineUsers, onlineCount:onlineCount, user:obj});
io.sockets.emit('system', obj, onlineCount, 'login');
};
});
//用户离线
socket.on('disconnect', function() {
//将退出的用户从在线列表中删除
if(onlineUsers.hasOwnProperty(socket.name)) {
//退出用户的信息
var obj = {userid:socket.name, nickname:onlineUsers[socket.name]};
//删除
delete onlineUsers[socket.name];
delete socketList[socket.name];
//在线人数-1
onlineCount--;
//向所有客户端广播用户退出
socket.broadcast.emit('system', obj, onlineCount, 'logout');
}
});
//接收新信息
socket.on('postMsg', function(msg, color) {
socket.broadcast.emit('newMsg', onlineUsers[socket.name], msg, color);
});
//接收新私信(P2P)
socket.on('privateMsg', function(msg, color, userid) {
socketList[userid].emit('newMsg', onlineUsers[socket.name], msg, color);
});
//接收新图片
socket.on('img', function(imgData, color) {
socket.broadcast.emit('newImg', onlineUsers[socket.name], imgData, color);
});
//接收新私人图片(P2P)
socket.on('privateimg', function(imgData, color, userid) {
socketList[userid].emit('newImg', onlineUsers[socket.name], imgData, color);
});
});
学过JS的小伙伴们肯定都能看得懂大概的意思吧,如果不明白socket.io中的服务器推技术是怎么实现的,可以百度一下相关文档,这里就不多加赘述了。现在我们来看客户端的代码实现:
window.onload = function() {
var rdChat = new RdChat();
rdChat.init();
};
var RdChat = function() {
this.socket = null;
};
RdChat.prototype = {
init: function() {
var that = this;
var userList = {};//用户列表
var userCount = null;//用户数
this.socket = io.connect();
this.socket.on('connect', function() {
//用户登录
document.getElementById('info').textContent = 'get yourself a nickname :)';
document.getElementById('nickWrapper').style.display = 'block';
document.getElementById('nicknameInput').focus();
});
this.socket.on('nickExisted', function() {
document.getElementById('info').textContent = '!nickname is taken, choose another pls';
});
this.socket.on('loginSuccess', function(o) {
document.title = 'RdChat | ' + document.getElementById('nicknameInput').value;
this.userList = o.onlineUsers;
that._initUserList(o.onlineUsers);
document.getElementById('loginWrapper').style.display = 'none';
document.getElementById('messageInput').focus();
});
this.socket.on('error', function(err) {
if (document.getElementById('loginWrapper').style.display == 'none') {
document.getElementById('status').textContent = '!fail to connect :(';
} else {
document.getElementById('info').textContent = '!fail to connect :(';
}
});
this.socket.on('system', function(obj, userCount, type) {
var msg = obj.nickname + (type == 'login' ? ' joined' : ' left');
that._displayNewMsg('system ', msg, 'red');
if(type == 'login' && !this.userList.hasOwnProperty(obj.userid)){
that._updateUserList(obj);
}
if(document.getElementById('userlist').value == ""){
document.getElementById('status').textContent = userCount + (userCount > 1 ? ' users' : ' user') + ' online';
}else{
if(document.getElementById('userlist').value == obj.userid){
document.getElementById('status').textContent = obj.nickname + " " + type;
}
}
});
this.socket.on('newMsg', function(user, msg, color) {
that._displayNewMsg(user, msg, color);
});
this.socket.on('newImg', function(user, img, color) {
that._displayImage(user, img, color);
});
document.getElementById('loginBtn').addEventListener('click', function() {
//监听登录按钮的click事件
var nickName = document.getElementById('nicknameInput').value;
var userid = that._getUid();
if (nickName.trim().length != 0) {
that.socket.emit('login', {userid:userid, nickname:nickName});
} else {
document.getElementById('nicknameInput').focus();
};
}, false);
document.getElementById('nicknameInput').addEventListener('keyup', function(e) {
//监听回车键事件
if (e.keyCode == 13) {
var nickName = document.getElementById('nicknameInput').value;
var userid = that._getUid();
if (nickName.trim().length != 0) {