Nodejs chat源码解读(一)
Nodejs chat源码解读(一)
作为追随者们的饭前开胃菜,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的设计思想,敬请期待。
Node.js Chat 源码解读(一)
在本篇中,我们将深入分析Node.js Chat的源码,探讨其核心组件的实现方式。假设我们需要构建一个简单的Web聊天室,我们可以采用多种不同的技术方案。然而,Node.js为我们提供了一种简洁高效的方法。
1. 客户端实现
客户端可以使用JavaScript通过WebSocket或AJAX轮询的方式实现与服务器的实时通信。为了简化讨论,我们暂时忽略这部分内容,重点放在服务器端的实现上。
2. Session 管理
在Node.js Chat中,会话管理非常简单。首先,我们从server.js
文件中可以看到以下代码:
var sessions = {};
function createSession(nick) {
/** 新建session,新加入用户时调用 */
var sessionId = generateUniqueID(); // 假设存在一个生成唯一ID的函数
sessions[sessionId] = {
nick: nick,
timestamp: new Date()
};
return sessionId;
}
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(); // 这里假设session有一个destroy方法
delete sessions[id];
}
}
}, 1000);
这段代码展示了如何通过定时器每秒检查一次所有会话,如果某个会话的timestamp
超过了设定的超时时间,则将其销毁。这里的SESSION_TIMEOUT
是一个常量,用于定义会话的有效时间。
3. 消息管理
Node.js Chat通过一个channel
对象来维护一个固定大小的消息队列。以下是channel
对象的部分代码:
var channel = new function() {
var messages = [],
callbacks = [];
this.appendMessage = function(nick, type, text) {
/** 消息压入, 发言时调用 */
messages.push({ nick: nick, type: type, text: text, timestamp: new Date() });
/** 队列满,头上的被踢出 */
while (messages.length > MESSAGE_BACKLOG)
messages.shift();
/** 触发回调 */
for (var i = 0; i < callbacks.length; i++) {
callbacks[i](messages);
}
};
this.query = function(since, callback) {
/** 消息查询, 按照发言时间先后顺序列出 */
var result = [];
for (var i = 0; i < messages.length; i++) {
if (messages[i].timestamp > since)
result.push(messages[i]);
}
callback(result);
};
};
在这个例子中,appendMessage
方法用于添加新消息到队列中,并在消息队列超过预定义的大小时移除最旧的消息。同时,该方法还会触发所有注册的回调函数,以便更新前端展示。
4. Web Server 实现
在Node.js中,服务器可以直接处理HTTP请求,而无需依赖外部的Web服务器软件。以下是一个简单的示例代码:
const http = require('http');
http.createServer((req, res) => {
if (req.url === '/who') {
const nicks = [];
for (var id in sessions) {
if (!sessions.hasOwnProperty(id)) continue;
var session = sessions[id];
nicks.push(session.nick);
}
res.writeHead(200, {'Content-Type': 'application/json'});
res.end(JSON.stringify({ nicks: nicks }));
}
}).listen(PORT, HOST, () => {
console.log(`Server running at http://${HOST}:${PORT}/`);
});
这段代码展示了如何创建一个HTTP服务器,并处理特定的URL请求(如/who
),返回当前在线用户的列表。
总结
通过以上分析,我们可以看出Node.js Chat的实现与传统的Web应用有很大的不同。主要体现在以下几个方面:
- 会话管理和消息管理:Node.js Chat将所有的会话和消息存储在内存中,避免了磁盘或网络I/O操作,提高了性能。
- Web服务器集成:Node.js本身就是一个完整的Web服务器,不需要额外配置或依赖其他软件。
希望本文能帮助你更好地理解Node.js Chat的实现原理。在接下来的文章中,我们将进一步探讨前端的实现细节,特别是消息队列中的回调函数队列。
写的深入浅出
介绍的很不错~
感谢你的分享,收获很多
您好,能提供一下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
Nodejs Chat 源码解读(一)
前言
Node.js 提供了一个简单的 Web 聊天应用源码,通过解读该源码,我们可以更好地了解 Node.js 的实现特点和优势。本篇主要关注 session 管理、消息管理和 Web Server 的实现。
Session 管理
在 server.js
中,session 管理是通过一个全局对象 sessions
来实现的:
var sessions = {};
function createSession(nick) {
// 创建新的 session,新用户加入时调用
var id = Math.random().toString(36).substring(7);
sessions[id] = {
nick: nick,
timestamp: new Date()
};
return id;
}
为了清理过期的 session,Node.js 使用了 setInterval
定时器:
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) {
destroySession(id);
}
}
}, 1000);
function destroySession(id) {
delete sessions[id];
// 广播用户离开的消息
broadcast({ action: 'leave', nick: sessions[id].nick });
}
消息管理
消息管理则通过一个 channel
对象来实现,该对象维护一个固定大小的消息队列 messages
和一个回调函数队列 callbacks
:
var channel = new function() {
var messages = [],
callbacks = [];
this.appendMessage = function(nick, type, text) {
messages.push({ nick: nick, type: type, text: text });
while (messages.length > MESSAGE_BACKLOG) {
messages.shift();
}
// 触发所有等待的消息回调
for (var i = 0; i < callbacks.length; i++) {
callbacks[i](messages.slice());
}
};
this.query = function(since, callback) {
var result = messages.filter(function(msg) {
return msg.timestamp > since;
});
callback(result);
};
};
Web Server 实现
Node.js 本身就实现了 Web Server 的功能,例如处理 /who
请求,返回当前在线用户的列表:
fu.listen(PORT, HOST);
fu.get('/who', function(req, res) {
var nicks = [];
for (var id in sessions) {
if (!sessions.hasOwnProperty(id)) continue;
nicks.push(sessions[id].nick);
}
res.simpleJSON(200, { nicks: nicks });
});
小结
- Session 和消息管理:Node.js 在本地内存中管理 session 和消息,避免了额外的存储操作。
- Web Server:Node.js 本身就是 Web Server,简化了部署流程。
通过以上分析,我们可以看到 Node.js 在实现 Web 聊天应用时,通过利用其非阻塞 I/O 特性,有效地减少了对外部资源的依赖,提升了系统的性能和响应速度。