node chat源码解读(一)
发布于 4年前 作者 pengchun 6211 次浏览 最后一次编辑是 3年前

作为追随者们的饭前开胃菜,nodejs在它的网站上给出了一个不那么复杂的web系统——node chat,并且提供了源码下载。我们不妨从这道菜开始,品一品nodejs的葱姜蒜粉。

为避免成了事后诸葛亮,假设我们要做一个基于WEB的单机聊天室系统,我们可能用哪些方法?



  1. 客户端可能用JS定期轮巡查询最新的聊天信息,或者采用类似comet的“服务推”技术来实现聊天信息的及时展现;

  2. 后端呢,我假设你不会选择用C/C++等从底层开始写一个很强大的聊天室服务端;

  3. 那么我们用Java/PHP等作为服务端的动态语言,聊天信息的存储可能得用一个小型的数据库了,或者消息队列;session管理就用原生的本地文件存储;

  4. 在动态语言前端,我们还可能需要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的实现与我们最初的设计之间的异同:


  1. 无论是nodejs还是PHP或者Java,单机版的web聊天室要实现的核心功能是一致的,即session管理以及消息管理;这是产品本身的特点决定的,与实现方式关系不大;

  2. nodejs的实现上,session与消息都维护在本地内存,不存在本地或者远程的磁盘/网络IO;反观传统设计,session和消息都需要用额外的存储介质;

  3. 注意到了没,nodejs前端没有任何web  server?也就是说,在nodejs的世界里,程序即server



正如上边预告的,在下一篇文章里,我将结合前端的实现机制来阐述消息队列中回调函数队列callbacks的设计思想,敬请期待。

7 回复

写的深入浅出

介绍的很不错~

感谢你的分享,收获很多

您好,能提供一下nodejs网站上,关于这个源码的下载地址吗?一直没有找到!谢谢!

请问关于静态资源的调用 fu.get('/’,staticHandler(‘index.html’)); fu.get方法返回的是’/’路径对应要执行的方法, 在fu.js,server.createServer里, var handler = getMap[url.parse(req.url).pathname] || notFound; handler其实就是staticHandler函数 然后在下面调用 handler(req,res) 然而staticHandler函数只有一个参数,并且是字符串类型的 所以我想问当静态文件加载的时候,get方法是怎么工作的

github上找不到源码了,能提供一份吗。TX

回到顶部