I was intrigued by this post from Tomek which has links to a prototype of an application built with Silverlight but using WebSockets.
It’s kind of interesting because running the application in IE9 gives me;
now, the only bit of that application that’s actually Silverlight is the highlighted blue square which is providing a client side implementation of WebSockets to the browser based code which is just HTML/JS. If I run in Chrome I see;
because Chrome has support for WebSockets already and so the sample switches out the Silverlight functionality.
If you’ve not read about WebSockets then there’s a starter here and info on the protocol up here.
If you’ve programmed with connected, TCP sockets then you know that the model is essentially;
- Server listens. Client connects.
- Connection stays open during lifetime of communication.
- Client and Server send stuff any time they like in a full-duplex manner.
So, traditional sockets are great in that they allow full duplex comms such as when the server wants to notify the client that something has happened but they’re not so great in that they require an open connection which tends to limit your server side scalability. They’re also not so great when it comes to crossing boundaries that only allow HTTP on port 80 or 443.
If you’ve programmed with HTTP then you know that the model is essentially;
- Server listens. Client connects.
- Client sends request.
- Server sends response.
- Client (generally) disconnects as soon as that response comes back.
and so HTTP is a great use of sockets in that it makes the model a lot more scalable by not requiring a permanent connection between the client and the server and server-side state but, because of that lack of connection, you can’t have the server notify the client of something because, generally, the client and server have no connection at any particular point in time.
Now, of course you can use HTTP in various ways to try and give the illusion that the server does have a connection to the client and that’s normally done by just having the client poll the server on some period essentially asking “Do you have anything for me right now?” with the expectation that the answer from the server will frequently be “no”.
So, a server wanting to “notify” a client simply has to put its notification data into a database and wait for the next time that client polls when it will be able to deliver the notification.
There are even special HTTP tricks around this such as the “long polling” trick where the client polls on some frequency but holds the HTTP connection open longer than it usually would in the hope that during the lifetime of the connection the server will find that it has something it wants to push to the client and will send it down the already open connection. Clearly, there’s a balance here between how frequently a client polls and how long it keeps that connection open when it does poll.
These sort of techniques are sometimes known as Comet and this idea of “long polling” is built into the WCF Polling Duplex implementation that can be used from Silverlight applications.
As an aside the place where I’ve seen interest around this kind of functionality is from developers building financial apps who need low latency on server-driven data change notifications.
WebSockets comes along (or is coming along) and again blurs the HTTP model and the raw TCP model. The essence of it is;
- Client makes HTTP connection over (e.g.) port 80/443.
- Client’s request uses a combination of Connection/Upgrade header to request “WebSockets”.
- If the server understands that (and it may well not today) then the client and server now can keep the socket open and send data back and forward to each other as they require.
- Data flows at UTF-8 with markers so that the client and server can denote “start” and “end” of each message which I guess makes life a lot easier for the programmer who has to handle those messages.
Naturally, there’s a whole tonne more complexity to it than that but that’s the essence.
Then there’s the API that a browser might expose around this stuff which is documented up here – browser support for this is “mixed” at the moment hence Tomek’s example using Silverlight to “bridge” that gap.
I ended up debugging the Javascript a little to see what’s going on in the sample – it’s not often that I can get enticed into debugging Javascript but this drew me in.
Firstly, the code determines whether the browser already has WebSocket support;
if (window.WebSocket == undefined) { $(document).ready(function () { var script = document.createElement("script"); document.body.appendChild(script); script.src = 'Silverlight.js'; var slhost = document.createElement("div"); document.body.insertBefore(slhost, document.body.firstChild); //document.body.appendChild(slhost); slhost.innerHTML = "<object/>"; // Snipped for brevity }); } else { $.slws._loaded = true; }
if it doesn’t then it injects an additional DIV onto the page and hosts a Silverlight XAP within that DIV. When that element is ready it then runs this code;
var slCtl = sender.getHost(); window.WebSocket = function (url) { this.slws = slCtl.Content.services.createObject("websocket"); this.slws.Url = url; this.readyState = this.slws.ReadyState; var thisWs = this; this.slws.OnOpen = function (sender, args) { thisWs.readyState = thisWs.slws.ReadyState; if (thisWs.onopen) thisWs.onopen(); }; this.slws.OnMessage = function (sender, args) { if (args.Message.charAt(0) == '"' && thisWs.onmessage) thisWs.onmessage({ data: args.Message }); }; this.slws.OnClose = function (sender, args) { thisWs.readyState = thisWs.slws.ReadyState; if (thisWs.onclose) thisWs.onclose(); }; this.slws.Open(); }; window.WebSocket.prototype.send = function (message) { if (message.charAt(0) != '"') message = '"' + message + '"'; this.slws.Send(message); }; window.WebSocket.prototype.close = function() { this.slws.Close(); };
That is – this code will only run if the browser doesn’t support WebSockets already and what it does is to wire up window.WebSocket to something that implements the WebSockets API. If the browser already support WebSockets then there’s no need for any of this.
Where does that implementation come from? The Silverlight code. You can see here from the Javascript that it is reaching into the Silverlight code and creating an object (i.e. a .NET object) that has registered itself under the name of “websocket” and from the Javascript here we can see that we expect that object to have events OnOpen, OnMessage, OnClose and methods called Open, Send, Close.
From there on in the rest of the Javascript code is abstracted from whether the WebSockets implementation is a native browser one or a Silverlight one.
Neat.
So, what does the code inside of the Silverlight app look like – I must admit, I took Reflector to it. At application startup it registers a .NET type so that it can be instantiated from Javascript with the name “websocket”;
and that type WebSocket (in the packaged System.ServiceModel.WebSockets.dll) is marked as being scriptable;
and offers the expected interface around Open/Close/Send and events OnOpen/OnMessage/OnClose and if you have a poke around in the SeventyFiveWebSocketProtocol class you’ll find it sending an HTTP GET with the relevant Connection and Upgrade headers and processing the server’s response.
Not only is this a nice example of WebSockets but it’s also a nice example of;
- Programming across the HTML bridge in Silverlight in order to use .NET types from Javascript, invoke functions on those types and handle events from them.
- Plugging a cross-browser incompatibility in a really slick way with some Silverlight code – relying on Microsoft to test that Silverlight does the right thing cross-browser and cross-platform rather than waiting until all the browsers implement some feature (in this case, WebSockets).
The only remaining “mystery” to me around this sample was what’s going on with the server-side as I’m not sure whether you could build a websockets server on IIS purely at the WCF layer.
My first thought would be that it’d require some IIS support to get that going so I’ll need to ponder on that and perhaps ask Tomek whether he can explore a little more around what’s happening on the server side.