【问题标题】:Node.JS: How to create a HTTP Chat Server?Node.JS:如何创建 HTTP 聊天服务器?
【发布时间】:2011-08-28 11:22:11
【问题描述】:

使用 TCP 的 Net Stream Object 效果很好(作为 node.js introduction video 中的预设端),但我应该如何在 HTTP 中执行此操作?

有没有办法在 http.createServer() 中访问套接字/客户端?或者有什么方法可以做到?我试图从官方node chat demossouce code那里想办法解决,但我不太明白。

我了解客户端 js,但是在我(作为客户端)通过 AJAX 向服务器端 js 发送消息后会发生什么?如何发送给服务器上的其他客户端?

请注意,我不想学习流程的逻辑,所以我不想使用 socket.io 或任何其他框架、库、模块。

非常感谢您的帮助!

【问题讨论】:

    标签: javascript sockets node.js chat comet


    【解决方案1】:

    理想情况下,您只需使用 WebSockets,但替代方法是 ajax 长轮询。

    您可以使用一种称为长轮询的技术进行聊天。这意味着您向服务器发出(ajax)请求,并且服务器保持该请求,直到它有一些数据要发送。

    所以客户端最终会定期轮询服务器,如果服务器没有新消息,它只会保留您的请求。如果它有消息,它会将其发送回客户端,客户端将再次轮询服务器。

    [[伪代码]]

    // 客户端.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!");
    

    // server.js

    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);
    

    更好的推送技术是Server-side events。请参阅example of it here。不过,这确实需要浏览器支持(我认为是 Chrome 和 opera)。

    【讨论】:

    • 第一个哇,非常感谢!这是一种可行的方法,但是长轮询对大量客户端存在性能问题,因此彗星服务器将是一个不错的解决方案,但是我不知道如何使用 node.js 来实现它,请看@ 987654324@ 它与我想要的相似,但它不是节点 :)
    • @CIRK 如果你想要类似于 APE 的东西,然后安装 socket.io。如果您想从头开始编写它,请阅读 socket.io 源代码。作为记录,我不知道如何在节点中实现服务器推送:(
    • 我不想要 APE 和 socket.io :P,我想知道它们是如何工作的,查看 socket.io 的源代码对我来说是邪恶的 :O,因为我不知道它的逻辑:S
    • @CIRK 现在实际上添加了长轮询逻辑。查看服务器推送规范。顺便说一句,上面的例子是彗星。长轮询和彗星是一样的。不过 APE 确实支持服务器端事件。
    【解决方案2】:

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

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

    【讨论】:

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

    如果您想了解长轮询的基本原理,请尝试查看this article。我在那里总结了我自己的长轮询服务器的某些部分,我是如何实现它们的,文章还包含指向其他资源的链接。它至少应该让您更全面地了解轮询的工作时间。

    如果您想学习逻辑以便在使用 node.js 时获得一些编码乐趣,而不是使用现有的解决方案,那么我建议您从最简单和基本的实现逐步走向更复杂的东西。不要试图从一开始就构建整个事物,因为这是最可靠的失败方式之一。

    【讨论】:

      猜你喜欢
      • 2014-11-13
      • 2014-01-29
      • 2016-05-11
      • 2014-09-22
      • 2011-11-12
      • 2014-04-17
      • 2016-02-04
      • 2012-11-27
      • 1970-01-01
      相关资源
      最近更新 更多