Wednesday, January 11, 2012

Comet in a socket: let's talk real-time

I must admit, writing web applications which provide real-time functionality is a personal favorite of mine. It is still a novel thing among the mostly-static web pages - although the novelty is fading fast with more and more web pages providing real-time or pseudo real-time updates (like Twitter). It is also, to some degree, using (or abusing, as some would say) the HTTP protocol in a way it was not intended to. In short, a challenge! Don't all programmers like a little challenge now and then?

Unfortunately (or rather, fortunately) it is becoming less and less of a challenge every day, with new web technologies entering common use. Although there does not yet exist an agreed-on standard on how such a real-time communication should be done while still only using the HTTP protocol, the WebSockets proposal is gaining traction, with some major browsers already implementing it in their newest versions. And for those that don't - there is always Flash as a fallback.

But let us not get ahead of ourselves. Before we move on to WebSockets, it would be beneficial to quickly cover the other methods used for real-time communication in the past.

The Donkey way: server polling

The first and most naive way of providing real-time communication was by polling the web server repeatedly in hopes of getting some updates. As with most naive solutions, this is pretty simple to implement (just keep sending AJAX or - yuck! - reload requests in set time intervals) and like most of them, utterly bad.

  • It wastes network resources: imagine all these HTTP headers going back and forth while there is absolutely no new content to send.
  • It wastes server resources: the server needs to accommodate a lot of such requests in a short amount of time, wastes time negotiating the connections (HTTP/1.1 connection: keep-alive helps to some degree) and wastes even more time repeatedly checking if there are any updates.
  • It is always a compromise - you either poll very often and thus overload your server, or you poll infrequently and thus it is not really "real-time".
  • The latency is often bad - since the connection needs to be negotiated time and again, it takes longer for any updates to go through when compared with traditional desktop client/server apps.

In short: this is bad, don't do it, preferably not even in the simplest cases.

The Java/Flash way: use an applet or Flash

So, since the naive approach is a no-go, what can you do? You can always use Java applets or Flash, but they have their own drawbacks.

  • They require proprietary plugins - most (but not all) people have Flash installed on their desktops, but lots of mobile devices don't. This is even worse with Java.
  • They take time to start - after loading your page, you still need to wait for the plugin to launch. Again, Java is the biggest offender here.
  • They are not liked by firewalls - a problem most visible in corporate networks, where firewalls/proxies may only allow HTTP connections to go through.

Still not good enough, but at least there is some improvement. In many cases, you may even be able to live with these drawbacks and so using Java or Flash may be OK for you. If not, however, then you are still only limited to what the HTTP protocol has to offer. Therefore, another solution was devised: long-polling, also known as Comet.

The revised Donkey way: server long polling

Long-polling is an evolution over the naive idea I presented before. It can be implemented in almost the same way on the client side; the server, however, does not immediately close the connection if there is no new data available. Instead, the connection is left open for as long as there is nothing to send, or until the browser decides it has timed out. If this happens, any potentially available data is sent and then the connection is closed. The client interprets the result and then immediately opens another connection to the server. Although not ideal, this is somewhat better than the naive polling method.

  • No (or less) bandwidth is wasted if there is no new data to be sent.
  • If new data comes relatively infrequently (that is, if the client is able to open a new connection before new data becomes available), long-polling can have pretty good latency.
  • In case of data becoming available very frequently, the method may degenerate to becoming only marginally better than the naive approach.

A slight modification of this technique is to not close the connection even if there is new data; that data is sent but the connection is kept open. This can, however, provide problems with browsers buffering the response and not presenting it to the JavaScript code immediately, waiting for the connection to be closed first.

And thus, we finally get to...

The WebSockets way: becoming an early adopter

WebSockets is a new standard intended to solve headaches associated with all the techniques described before. It provides a way for client application to establish a two-way full duplex connection to the server and can be used in an HTML5-compliant browsers, thus eliminating the need to either use (long) polling, or depend on third-party browser plugins. Unfortunately, it is still a working draft and not yet supported in all the major browser, though even Internet Explorer team is currently planning to include it in the upcoming Internet Explorer 10. This leaves hope that the standard's adoption, once it is actually finished, will be relatively fast. Currently, the hybi-10 WebSockets draft is supported in IE10 developer preview, Firefox 7 and Google Chrome 14.

The major selling point for this standard, aside from it actually being a standard, is how straightforward it is to implement in JavaScript. You simply instantiate a new WebSocket object, providing a server URL as its parameter, and then register callbacks for up to four different events: onopen, onerror, onmessage and onclose.

var ws = new WebSocket("ws://myserver.com/chat");

ws.onopen = function() {
    ws.send("Hello!");
};
ws.onmessage = function(msg) {
    alert("Got " + msg);
}
ws.onerror = function() { ... }
ws.onclose = function() { ... }

And that's it! Now you can talk with your server all the time, any time you want.

But wait, there is more

This short example did not even touch on the subject of how to implement WebSockets on the server, nor what to do with the non-compliant browsers. You can find the practical part covered in the second part of the series.

2 comments:

  1. Thanks a lot for such a wonderful post, the stuff posted were really interesting and useful. The quality of the content was good and clear. Thanks for the post

    trash cans with lids

    ReplyDelete
  2. Java Developer's Scratchpad containing version number should not be permanent and should be phased out after some time, preferably redirecting clients to the current version (unless the resource is intentionally versioned, like for example a Wiki page). Also, there should exist a "canonical" permalink versionless URI for resources, that should always serve the most recent version


    CMS Design

    ReplyDelete