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

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

回到顶部