9

With the Net Stream Object with TCP works great (as presetend in the node.js introduction video), but how should I do this in HTTP?

Is there a way to access sockets/clients within an http.createServer() ? Or what's the method to do it? I tried to figure out the solution from the official node chat demos souce code, but I don' really understand.

I understand the client side js, but what's happening after I(as a client) send a message trough AJAX, to the server side js? How can I send to the other clients who's on the server too?

Please note that I wan't to learn the logic of the process, so I don't want to use socket.io or any other frameworks, libraries, modules.

Thanks very much for any help!

Adam Halasz
  • 57,421
  • 66
  • 149
  • 213

3 Answers3

10

Ideally you just use WebSockets but the alternative is ajax long polling.

You can use a technique known as long polling to do the chat. This means you make an (ajax) request to the server and the server keeps hold of this request until it has some data left to send.

So the clients ends up periodically polling the server, if the server has no new messsages, it just keeps hold of your request. If it has a message it sends it back to the client and the client will poll the server again.

[[Pseudo Code]]

// 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!");

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

A better push technology would be Server-side events. See an example of it here. This does require browser support though (Chrome and opera I think).

Raynos
  • 166,823
  • 56
  • 351
  • 396
  • 1
    First WoW, thanks very much! This is a working way of doing it, but long polling has performance issues with a huge number of clients, so a comet server would be a nice solution, but I don't know how to implement it with node.js, look at http://www.ape-project.org/ajax-push.html it's similar to what I want but it's not node :) – Adam Halasz May 19 '11 at 00:03
  • 1
    @CIRK if you want something similar to APE then install socket.io. If you want to write it from scratch then read the socket.io source. For the record I have no clue how to implement server push in node :( – Raynos May 19 '11 at 00:04
  • I don't want APE nor socket.io :P, I would like to know how they work, looking at socket.io's source is evil for me :O, because I don't know the logic of it :S – Adam Halasz May 19 '11 at 00:06
  • @CIRK actually added the long polling logic now. Looking into server push spec. Btw the above example is Comet. Long polling and comet are the same. APE does support serverside events though. – Raynos May 19 '11 at 00:13
2

One way of doing it involves clients "subscribing" to a channel that acts as a distributor for messages. Once subscribed, a client then receives a copy of each message sent to the channel.

Many node chat services rely on redis' pubsub feature to handle this distribution of messages from one to any number of clients. If you wanted to "roll your own", understanding how redis solves this problem would be a great start.

Rob Raisch
  • 17,040
  • 4
  • 48
  • 58
  • 1
    but the question is how the messsage is sent to the client? thanks for redis, this looks interesting, however doesn't relates to `node.js` :), but as a PHP programmer this looks interesting – Adam Halasz May 18 '11 at 23:34
  • Well, simply put, once a client subscribes to the service, a queue is created to contain any messages sent by other clients. When a client sends a message it is added to each of the other subscribers' queues. Then the client requests any pending messages that have been added to its own queue. – Rob Raisch May 18 '11 at 23:40
  • 2
    Websockets (a feature of HTML5) allows a client to open a bi-directional socket to a web server which the server can use to "push" messages to the client. In the absence of this feature, clients poll the server (via AJAX) for any pending messages. The server doesn't push msgs since (absent websockets) it has no native facility to do so. This is why socket.io is so useful. If the client supports websockets, it uses them. If not, it tries a number of other means falling back to long-polling. – Rob Raisch May 18 '11 at 23:42
0

If you want to know the basic principles of long polling then try to look at this article. I summarized there certain parts of my own long poll server, how I implemented them and the article also contains links to other resources. It should give you at least the bigger picture of how long polling works.

If you want to learn the logic in order to have some coding fun with node.js, and not to use existing solutions, then I would recommend to go step by step from the most simple and basic implementation to more complex stuff. Don't try to build the entire thing from the first shot because it's one of the most surest way how to fail.

yojimbo87
  • 65,684
  • 25
  • 123
  • 131