Node.JS:如何创建HTTP聊天服务器?

9
使用TCP的Net Stream对象非常好(正如在node.js介绍视频中所示),但是在HTTP中应该如何做呢?
是否有一种方法可以在http.createServer()中访问套接字/客户端?或者有什么方法可以做到这一点吗?我试图从官方Node聊天演示源代码中找出解决方案,但我真的不理解。
我了解客户端JavaScript,但在我(作为客户端)通过AJAX发送消息到服务器端JavaScript后会发生什么?如何向其他也在服务器上的客户端发送消息?
请注意,我想了解这个过程的逻辑,因此我不想使用socket.io或任何其他框架、库或模块。
非常感谢您的帮助!
3个回答

10
理想情况下,您可以使用 WebSockets,但备选方案是使用长轮询 ajax。
您可以使用长轮询技术进行聊天。这意味着您向服务器发送一个(ajax)请求,并且服务器将保持这个请求,直到有一些数据需要发送。
因此,客户端定期轮询服务器,如果服务器没有新消息,则保持您的请求。如果它有消息,则将其发送回客户端,客户端将再次轮询服务器。 [[伪代码]] // Client.js
var Socket = function(ip, port, name) {
    this.ip = ip;
    this.port = port;
    this.name = name;
    this._cbs = [];
    this._poll();
};

// Call the server periodically for data.
Socket.prototype._poll = function() {
    var that = this;
    // if the server does not return then call it again
    var timer = setTimeout(function() {
         this._poll();
    }, 5000);
    $.ajax({
         type: "GET",
         timeout: 5000, 
         data: {
             name: this.name
         },
         url: this.ip + ":" + this.port,
         success: function(data) {
             // server returned, kill the timer.
             clearTimeout(timer);
             // send the message to the callback.
             for (var i = 0; i < that._cbs.length; i++) {
                 that._cbs[i](data);
             }
             // call the server again
             that._poll();
         }
    });
};

// Add a callback for a message event
Socket.prototype.on = function(event, cb) {
    if (event === "message") {
        this._cbs.push(cb);
    }
};

// Send a message to the server
Socket.prototype.send = function(message) {
    $.ajax({
         data: {
              message: message,
              name: this.name
         },
         type: "GET",
         url: this.ip + ":" + this.port
    });
};

var socket = new Socket('192.168.1.1', '8081', "Raynos");
socket.on("message", function(data) {
    console.log(data);
});
socket.send("Hello world!");

// 服务器文件

var url = require("url");
var events = require("events");
// store messages for clients
var clients = {};

var emitter = new events.EventEmitter();

http.createServer(function(req, res) {
    // get query string data
    var data = url.parse(req.url, true).query;
    // if client is not initialized then initialize it.
    if (data.name && !clients[data.name]) {
         clients[data.name] = [];
    }
    // if you posted a message then add it to all arrays
    if (data.message) {
         for (var k in clients) {
              clients[k].push(data.name + " : " + data.message);
         }
         // tell long pollers to flush new data.
         emitter.emit("new-data");
    } else if (clients[data.name].length > 0) {
         // else empty the clients array down the stream
         for (var i = 0; i < clients[data.name].length; i++) {
              res.write(clients[data.name].shift());
         };
         res.end();
    // long polling magic.
    } else {
         var cb = function() {
              for (var i = 0; i < clients[data.name].length; i++) {
                   res.write(clients[data.name].shift());
              };
              res.end();
              // kill that timer for the response timing out.
              clearTimeout(timer);
         }
         // when we get data flush it to client
         emitter.once("new-data", cb);
         var timer = setTimeout(function() {
              // too long has passed so remove listener and end response.
              emitter.removeListener(cb);
              res.end();
         }, 4500);
    }
}).listen(8081);

更好的推送技术是使用服务器端事件。在这里可以看到一个示例。不过这需要浏览器支持(我认为是Chrome和Opera)。


1
首先,非常感谢!这是一种可行的方法,但是长轮询在大量客户端时存在性能问题,因此使用Comet服务器将是一个不错的解决方案,但我不知道如何在node.js中实现它,请参考http://www.ape-project.org/ajax-push.html,它与我想要的类似,但不是node :) - Adam Halasz
1
如果你想要类似于APE的东西,那么安装socket.io。如果你想从头开始编写,那么阅读socket.io源代码。就记录而言,我不知道如何在node中实现服务器推送 :( - Raynos
我不想要 APE 也不想要 socket.io :P,我想知道它们是如何工作的,看 socket.io 的源代码对我来说很麻烦 :O,因为我不了解其中的逻辑 :S。 - Adam Halasz
@CIRK 现在已经添加了长轮询逻辑。正在研究服务器推送规范。顺便说一下,上面的例子是Comet。长轮询和Comet是相同的。APE确实支持服务器端事件。 - Raynos

2

其中一种方法是客户端“订阅”一个充当消息分发器的频道。一旦订阅,客户端就会接收到发送到该频道的每条消息的副本。

许多节点聊天服务依赖于redis的发布/订阅功能来处理从一个客户端到任意数量客户端的消息分发。如果您想要“自己动手”,了解redis如何解决这个问题将是一个很好的开始。


1
但问题是消息如何发送到客户端?感谢 Redis,这看起来很有趣,但与 node.js 无关 : ),但作为 PHP 程序员,这看起来很有趣。 - Adam Halasz
简单来说,一旦客户端订阅了服务,就会创建一个队列来包含其他客户端发送的任何消息。当客户端发送消息时,它将被添加到其他订阅者队列中。然后客户端请求获取已添加到自己队列中的任何待处理消息。 - Rob Raisch
2
Websockets(HTML5的一项功能)允许客户端打开一个双向套接字到Web服务器,服务器可以使用该套接字将消息“推送”到客户端。在没有此功能的情况下,客户端通过AJAX轮询服务器以获取任何待处理消息。服务器不推送消息,因为(除了Websockets外)它没有本地设施来这样做。这就是socket.io如此有用的原因。如果客户端支持Websockets,则使用它们。如果没有,则尝试多种其他方式,最终回退到长轮询。 - Rob Raisch

0

如果你想了解长轮询的基本原理,可以看看this article。我在那里总结了我自己的长轮询服务器的某些部分,以及我如何实现它们,文章还包含其他资源的链接。这应该至少能让你了解长轮询的工作原理。

如果你想学习逻辑,以便使用node.js进行编码乐趣,并且不使用现有的解决方案,那么我建议从最简单和基本的实现开始逐步进行,然后再转向更复杂的内容。不要试图一次性构建整个系统,因为这是失败的最可靠方法之一。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接