65.9K
CodeProject 正在变化。 阅读更多。
Home

动手实践:使用 Node.js 构建聊天室 Web 应用 | 第三部分

emptyStarIconemptyStarIconemptyStarIconemptyStarIconemptyStarIcon

0/5 (0投票)

2015年5月19日

CPOL

8分钟阅读

viewsIcon

8824

在本期内容中,我将向您展示如何使用现有的基于 Express 的 Node.js 应用来创建一个支持 WebSocket 的聊天室后端。

本系列 Node.js 教程将帮助您构建一个完全部署在云端的、由 Node.js 驱动的实时聊天室 Web 应用。在本系列中,您将学习如何在 Windows 机器上设置 Node.js,如何使用 express 开发 Web 前端,如何将 node express 应用部署到 Azure,如何使用 socketio 添加实时层,以及如何将所有这些部署在一起。

级别:初学者到中级——您需要了解 HTML5 和 JavaScript

第一部分 - Node.js 入门

第二部分 - 欢迎使用 Node.js 和 Azure 的 Express

第三部分 - 使用 Node、Mongo 和 Socket.IO 构建后端

第四部分 – 使用 Bootstrap 构建聊天室 UI

第五部分 - 使用 WebSockets 连接聊天室

第六部分 – 最终章和调试远程 Node 应用

第三部分 – 使用 Node.js、Socket.IO 和 Mongo 构建聊天室后端

欢迎阅读动手实践 Node.js 教程系列的第二部分:构建一个 Node.js 驱动的聊天室 Web 应用。在本期内容中,我将向您展示如何使用现有的基于 Express 的 Node.js 应用来创建一个支持 WebSocket 的聊天室后端。

什么是 WebSockets?什么是 Socket.IO?

WebSocket 是一种协议,旨在允许 Web 应用程序在 Web 浏览器和 Web 服务器之间创建 TCP 上的全双工通道(即实现双向通信)。它与 HTTP 完全兼容,并使用 TCP 端口号 80。WebSocket 使 Web 应用程序能够实现实时通信,并支持客户端和服务器之间的高级交互。它支持包括 Internet Explorer、Google Chrome、Firefox、Safari 和 Opera 在内的多种浏览器。

Socket.IO 是一个 JavaScript 库和 Node.js 模块,可让您简单快速地创建实时双向事件驱动的通信应用程序。它极大地简化了使用 WebSockets 的过程。我们将使用 Socket.IO v1.0 来构建我们的聊天室应用。

将 Socket.IO 添加到 package.json

Package.json 是一个包含项目相关元数据的文件,包括其依赖项。NPM 可以使用此文件下载项目所需的模块。请查看对此文件进行交互式解释的 链接,了解其内容。

让我们将 Socket.IO 添加为项目的依赖项。有两种方法可以做到这一点。

1. 如果您一直遵循本教程系列,并且已设置了 Visual Studio 项目,请右键单击项目中的 NPM 部分,然后选择“安装新的 NPM 包…”

打开窗口后,搜索“socket.io”,选择第一个结果,然后勾选“添加到 package.json”复选框。单击“安装包”按钮。这将把 Socket.IO 安装到您的项目中,并将其添加到 package.json 文件中。

package.json

	{
  "name": "NodeChatroom",
  "version": "0.0.0",
  "description": "NodeChatroom",
  "main": "app.js",
  "author": {
    "name": "Rami Sayar",
    "email": ""
  },
  "dependencies": {
    "express": "3.4.4",
    "jade": "*",
    "socket.io": "^1.0.6",
    "stylus": "*"
  }
}

2. 如果您使用的是 OS X 或 Linux,您可以通过在项目文件夹的根目录下运行以下命令来达到与上述相同的效果。

npm install --save socket.io

将 Socket.IO 添加到 app.js

下一步是向 app.js 添加 Socket.IO。您可以通过替换以下代码来实现。

http.createServer(app).listen(app.get('port'), function(){
  console.log('Express server listening on port ' + app.get('port'));
});

待替换为

var serve = http.createServer(app);
var io = require('socket.io')(serve);

serve.listen(app.get('port'), function () {
    console.log('Express server listening on port ' + app.get('port'));
});

这将捕获 HTTP 服务器到一个名为 serve 的变量中,并将该 HTTP 服务器传递给 Socket.IO 模块进行附加。最后一个代码块获取 serve 变量并执行 listen 函数,该函数启动 HTTP 服务器。

记录用户加入和离开

理想情况下,我们希望记录用户加入聊天室。以下代码通过挂钩一个回调函数来处理到我们 HTTP 服务器的每一次 WebSocket“连接”事件,从而实现这一目的。在回调函数中,我们调用 console.log 来记录用户已连接。我们可以将此代码添加到调用 serve.listen 之后。

io.on('connection', function (socket) {
    console.log('a user connected');
});

要实现用户断开连接时的相同操作,我们需要为每个 socket 挂钩“disconnect”事件。在上一代码块的 console log 之后添加以下代码。

    socket.on('disconnect', function () {
        console.log('user disconnected');
    });

最后,代码将如下所示

io.on('connection', function (socket) {
    console.log('a user connected');
    socket.on('disconnect', function () {
        console.log('user disconnected');
    });
});

广播在聊天频道接收到的消息

Socket.IO 提供了一个名为 emit 的函数来发送事件。

在“chat”频道上接收到的任何消息都将通过在回调函数中使用 broadcast 标志调用 emit 来广播到此 socket 的所有其他连接。

socket.on('chat', function (msg) {
    socket.broadcast.emit('chat', msg);
});

最后,代码将如下所示

io.on('connection', function (socket) {
    console.log('a user connected');
    socket.on('disconnect', function () {
        console.log('user disconnected');
    });
 
    socket.on('chat', function (msg) {
        socket.broadcast.emit('chat', msg);
    });
});

将消息保存到 NoSQL 数据库

聊天室应该将聊天消息保存到一个简单的数据存储中。通常,在 Node.js 中保存到数据库有两种方法:您可以使用特定于数据库的驱动程序,或者使用 ORM。在本教程中,我将向您展示如何将消息保存到 MongoDB。当然,您也可以使用您喜欢的任何其他数据库,包括 PostgreSQL 或 MySQL 等 SQL 数据库。

您应该确保有一个 MongoDB 可供连接。您可以使用第三方服务来托管您的 MongoDB,例如 MongoHQ 或 MongoLab。请查看这篇 教程,了解如何在 Azure 中使用 MongoLab 附加组件创建 MongoDB。您可以一直阅读到“创建应用”部分,但请确保将 MONGOLAB_URI 保存到您可以稍后轻松访问的地方。

创建 MongoDB 并获得数据库的 MONGOLAB_URI 后 – 从您已复制到剪贴板的连接信息中 – 您需要确保该 URI 可供应用程序使用。将此类敏感信息添加到代码或源代码管理工具的配置文件中不是最佳实践。您可以将该值添加到 Azure Web 应用程序的配置菜单的连接字符串列表中(例如,在您使用的教程中),或者将其添加到应用设置列表中(名称为“CUSTOMCONNSTR_MONGOLAB_URI”)。在本地计算机上,您可以将其添加到环境变量中,名称为“CUSTOMCONNSTR_MONGOLAB_URI”,值为该 URI。

下一步是向我们的项目添加对 MongoDB 的支持。您可以通过将以下行添加到 package.json 的 dependencies 对象来实现。请确保保存对文件的更改。

"mongodb": "^1.4.10",

在解决方案资源管理器中右键单击项目中的 NPM 部分,以显示右键菜单。从内容菜单中单击“安装缺失的包”以安装 MongoDB 包,以便可以使用它作为模块。

我们需要导入该模块,以便在 app.js 中使用 MongoDB 客户端对象。您可以将以下代码行添加到第一个 require(‘’) 函数调用之后,例如第 11 行。

var mongo = require('mongodb').MongoClient;

我们希望使用 CUSTOMCONNSTR_MONGOLAB_URI 环境变量中的 URI 连接到数据库。连接成功后,我们希望插入在 socket 连接中接收到的聊天消息。

mongo.connect(process.env.CUSTOMCONNSTR_MONGOLAB_URI, function (err, db) {
    var collection = db.collection('chat messages');
    collection.insert({ content: msg }, function (err, o) {
        if (err) { console.warn(err.message); }
        else { console.log("chat message inserted into db: " + msg); }
    });
});

如上面的代码所示,我们使用 process.env 对象来获取环境变量的值。我们进入数据库中的一个集合,并调用 insert 函数,传入一个包含内容的 JSON 对象。

现在,每条消息都已保存到我们的 MongoDB 数据库中。

发送最后 10 条接收到的消息

当然,我们不希望用户在加入聊天室时感到迷茫,因此我们应该确保将最后 10 条接收到的消息发送到服务器,以便至少让他们获得一些上下文。为此,我们需要连接到 mongo。在这种情况下,我将避免将所有 socket 代码包装在一个数据库连接中,以便在服务器即使失去数据库连接的情况下也能正常工作。

我们还将对查询进行排序并限制到最后 10 条消息,我们将使用 MongoDB 生成的 _id,因为它包含一个时间戳(尽管在更具可扩展性的情况下,您将希望在聊天消息中创建专用的时间戳),然后调用 limit 函数将结果限制为仅 10 条消息。

我们将流式传输来自 MongoDB 的结果,以便一旦它们到达就可以尽快将它们发送到聊天室。

mongo.connect(process.env.CUSTOMCONNSTR_MONGOLAB_URI, function (err, db) {
    var collection = db.collection('chat messages')
    var stream = collection.find().sort({ _id : -1 }).limit(10).stream();
    stream.on('data', function (chat) { socket.emit('chat', chat.content); });
});

上面的代码完成了前面段落中解释的任务。

部署到 Azure

您可以通过遵循之前的教程(例如 第二部分)重新部署到 Azure。

结论

总之,我们拥有一个能够将通过 WebSockets 接收到的消息广播到所有其他已连接客户端的聊天系统。该系统将消息保存到数据库,并检索最后 10 条消息,为新加入聊天室的每个用户提供上下文。

敬请关注第四部分!

第四部分——使用 Bootstrap 构建聊天室 UI——即将发布到 CodeProject,并且也可以在我的博客 这里 找到。您可以通过关注我的 Twitter 帐户 @ramisayar 来及时了解此文章及其他文章。

更多关于 Azure 上的 Node 学习

欲了解更深入的 Node.js 学习,我的课程 可在 Microsoft Virtual Academy 这里 找到。

或者一些关于类似 Node 主题的短视频

本文是 Microsoft Web 开发技术系列的一部分。我们很高兴与您分享 Microsoft Edge 及其 新渲染引擎。请访问 modern.IE 获取免费虚拟机或在您的 Mac、iOS、Android 或 Windows 设备上进行远程测试。

© . All rights reserved.