作为追随者们的饭前开胃菜,nodejs在它的网站上给出了一个不那么复杂的web系统——node chat,并且提供了源码下载。我们不妨从这道菜开始,品一品nodejs的葱姜蒜粉。
为避免成了事后诸葛亮,假设我们要做一个基于WEB的单机聊天室系统,我们可能用哪些方法?
- 客户端可能用JS定期轮巡查询最新的聊天信息,或者采用类似comet的“服务推”技术来实现聊天信息的及时展现;
- 后端呢,我假设你不会选择用C/C++等从底层开始写一个很强大的聊天室服务端;
- 那么我们用Java/PHP等作为服务端的动态语言,聊天信息的存储可能得用一个小型的数据库了,或者消息队列;session管理就用原生的本地文件存储;
- 在动态语言前端,我们还可能需要web server如nginx、apache等。
差不多就这些了。我假设你只用了不到一天的时间就搞定这些工作了,现在我们歇一会,来看看nodejs大人的做法和我们的方案有什么不一样。
客户端的实现暂时搁置一下,我们先看看后端的代码。
session管理
node chat的session管理比较简单,我们先从这里入手。在server.js中,有下列代码:
var sessions = {};
function createSession (nick) {
/** 新建session,新加入用户时调用 /
…
}
不难理解,node chat把session也放在一个变量名为sessions的对象列表中。每个session元素除了包含session id、用户nick之外,还有一个重要的属性timestamp来作为session过期的判断依据。
setInterval(function () {
var now = new Date();
for (var id in sessions) {
if (!sessions.hasOwnProperty(id)) continue;
var session = sessions[id];
if (now - session.timestamp > SESSION_TIMEOUT) {
session.destroy();
}
}
}, 1000);
我们看到node chat用一个定时器每隔1秒钟(1000 ms)遍历sessions列表,如果某个session元素的timestamp超过了session过期时间,则把它destroy掉(除了从sessions列表中把相应元素delete掉之外,destroy方法还负责向聊天室里广播一条“离开”的系统消息)。
消息管理
与我们采用小型数据库来存储聊天信息相比,node chat在server中通过channel对象维护了一个固定大小(MESSAGE_BACKLOG = 200)的消息队列messages:
var channel = new function () {
var messages = [],
callbacks = [];
this.appendMessage = function (nick, type, text) {
/* 消息压入,发言时调用 /
…
/* 队列满,头上的被踢出 /
while (messages.length > MESSAGE_BACKLOG)
messages.shift();
};
this.query = function (since, callback) {
/* 消息查询,按照发言时间先后顺序列出 */
…
};
};
看到这里,我们绝对不会漏掉另外一个不在我们设想中的队列——callbacks。直觉告诉我们,这个callbacks(回调函数队列)应该能够充分体现nodejs“事件机制”的核心思想。至于其中的究竟,我们在下一篇文章中结合前端的实现机制一起来介绍。
web server
与我们之前的传统设计一样,web server这一块通常都是采用开源的第三方软件来实现。我们一般不会太多地考虑它们的实现机制,而是直接拿来使用,顶多做一些优化而已。
有趣的是,nodejs相信自己就是一个足够优秀的web server,你瞧瞧,在server.js里紧接着它就来了这么一段代码:
fu.listen(Number(process.env.PORT || PORT), HOST);
…
fu.get("/who", function (req, res) {
var nicks = [];
for (var id in sessions) {
if (!sessions.hasOwnProperty(id)) continue;
var session = sessions[id];
nicks.push(session.nick);
}
res.simpleJSON(200, { nicks: nicks
, rss: mem.rss
});
});
不难理解,它是监听(listen)了本地的一个预定义端口(PORT = 8001);对于前端来的who请求(controller的概念出来了),它从sessions列表里找出当前在线的用户,并以JSON方式输出给前端。
作为源码解读的第一部分,我们的目标大概是完成了。我们不妨先小结一下,nodejs的实现与我们最初的设计之间的异同:
- 无论是nodejs还是PHP或者Java,单机版的web聊天室要实现的核心功能是一致的,即session管理以及消息管理;这是产品本身的特点决定的,与实现方式关系不大;
- nodejs的实现上,session与消息都维护在本地内存,不存在本地或者远程的磁盘/网络IO;反观传统设计,session和消息都需要用额外的存储介质;
- 注意到了没,nodejs前端没有任何web server?也就是说,在nodejs的世界里,程序即server。
正如上边预告的,在下一篇文章里,我将结合前端的实现机制来阐述消息队列中回调函数队列callbacks的设计思想,敬请期待。