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的设计思想,敬请期待。


9 回复

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应用有很大的不同。主要体现在以下几个方面:

  1. 会话管理和消息管理:Node.js Chat将所有的会话和消息存储在内存中,避免了磁盘或网络I/O操作,提高了性能。
  2. 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 特性,有效地减少了对外部资源的依赖,提升了系统的性能和响应速度。

回到顶部