Peter McGrattan’s Weblog

Silverlight, WCF, ASP.NET, AJAX, Graphics, RIA

Archive for the ‘Comet’ Category

Silverlight 2 WCF Polling Duplex Support – Part 1: Architecture

Posted by petermcg on September 3, 2008

Edited on 20/October/2008 : Code download updated to support Silverlight 2 RTW

(Code download)

Introduction

This is the first in a series of posts on the support for duplex communication with a WCF service added to Silverlight 2 Beta 2.  I should point out that this technology in its current form has been deemed by Microsoft as not yet fit for production applications and is therefore currently for evaluation only.  In this post I take a look at the general architecture of the technology and it’s approach to solving the problem of bi-directional communication between a server and a browser.  Parts two and three introduce the code for a client and server sample application that demonstrates the technology in practice.

The Assemblies

Support for bi-directional communication between a WCF Service and a Silverlight 2 Client over HTTP (port 80 by default) was introduced as part of the Silverlight 2 Beta 2 SDK. This is different to the Sockets infrastructure also supported by Silverlight 2 that requires a custom port within a specific range to be opened.  Two assemblies both named System.ServiceModel.PollingDuplex.dll provide this duplex support, one for the client, one for the server.  It’s clear to see which is which from the location each assembly is extracted to by the SDK installer:

%ProgramFiles%\Microsoft SDKs\Silverlight\v2.0\Libraries\Client\System.ServiceModel.PollingDuplex.dll

%ProgramFiles%\Microsoft SDKs\Silverlight\v2.0\Libraries\Server\Evaluation\System.ServiceModel.PollingDuplex.dll

Location aside, it’s possible to discern between the two based on the public key token of mscorlib.dll the assembly was built against (thanks to this post).  The client assembly is built against the Silverlight version, the server assembly against the .NET Framework version:

Client Assembly Manifest

Server Assembly Manifest

Both assemblies host types in two main namespaces familiar to WCF developers: System.ServiceModel and System.ServiceModel.Channels.  The content of the two assemblies in terms of types in these namespaces is similar but not identical.  The server side assembly has around nine additional internal types (PartialTrustHelpers, ContextQueue, IOThreadTimer, PollingDuplexChannelListener, RequestContextData, SafeNativeMethods, ServicePollingDuplexDispatcher, ServicePollingDuplexSessionChannel and UnsafeNativeMethods) with names that suggest they provide functionality outside of what can be achieved in the client sandbox.  The server assembly is therefore slightly larger than the client (101KB vs. 81KB) but from an API perspective they are identical in that each exposes only two public types: System.ServiceModel.PollingDuplexHttpBinding and System.ServiceModel.Channels.PollingDuplexBindingElement.  A little more exploration into these two types reveals that an instance of the latter is encapsulated by the former.  By this simple process of elimination it’s clear to see that creating an instance of the PollingDuplexHttpBinding type from both the client and server assemblies is the starting point to enabling WCF duplex communication in Silverlight 2 Beta 2.

The Binding

There are many different types of WCF Bindings, you can think of each one as a unique package of decisions with regard to the transport, message encoding, security and protocol details that will be employed to transfer a communication on the wire between a client and server or vice versa.  With these decisions encapsulated by the binding, enabling congruent communication between the client and server is simply a matter of instantiating the same binding type on both sides.  The PollingDuplexHttpBinding binding inherits from the standard WCF BasicHttpBinding class and specializes it to produce a new WCF binding specifically designed for communication with a Silverlight 2 client application.  The decisions packaged in the binding by default are to use HTTP as the transport, UTF-8 text as the message encoding, BasicHttpSecurityMode.None as the security setting (configurable in the constructor) and in addition to HTTP, Net Duplex and WS-Make Connection (WS-MC) as the protocols.  These protocols in turn rely on the XML and SOAP specifications, the Net Duplex Protocol is implemented to establish the Silverlight 2 client session with the server and WS-MC is used to establish a back channel using polling so that server initiated messages can be delivered to the client.  These protocols are specifically designed to be used in place of more traditional duplex protocols like TCP and cater for the fact that a Silverlight application running in a browser cannot accept new incoming connections unless a custom port is opened and agreed upon.  Another way to look at this is that without opening a custom port a Silverlight client application is not addressable by protocols such as TCP by default because the browser environment does not allow the creation of a listener. Consequently, with polling duplex communication the server must be addressable and the Silverlight application must initiate the connection to it.  In order to create the illusion of duplex communication (where either party can initiate a message exchange) in such a scenario, Microsoft have chosen to have the client poll the server for return messages after initiating the connection.

Polling and Timeouts

Once connected and until a fault or timeout occurs the client asynchronously polls the server more or less constantly give or take a few milliseconds between poll timeouts.  A poll returns when either there is a message to convey to the client or a poll-timeout occurs.  The default poll-timeout is currently around 60,000 milliseconds (60 seconds rather than 90 as you may have read elsewhere) but this can be altered by setting the PollTimeout property of an instance of PollingDuplexHttpBinding which will be forwarded to the identically named property of the binding’s encapsulated instance of PollingDuplexBindingElement.  When a PollTimeout occurs an exception is not raised unless either the client or server only has aborted the session during that time in which case the following exception is raised:

PollTimeout Exception

When the session has not been aborted and is still active, the occurrence of a PollTimeout does not raise an exception, an empty response (HTTP/1.1 200 OK with Content-Length:  0) is returned and a new poll originates on the network layer.  In the few milliseconds after a poll timeout occurs and before a new poll occurs, a queue of any messages to be sent to the client is maintained on the server ready to be flushed on the next poll.  There is another type of timeout that does raise an exception every time it occurs.  InactivityTimeout by default is set to 10 minutes (again configurable via the binding) and so will occur after 10 poll timeouts in a sequence, that is – after a period of 10 minutes during which no bytes have been sent or received by either party.  The following image shows the exception raised after an InactivityTimeout:

InActivityTimeout Exception

Anatomy of a Bi-Directional Message Exchange

In order to illustrate what I’ve covered so far and progress further a familiar context needs to be established in the form of a sample application.  The second and third posts in this series introduce the server and client parts of the sample application respectively and I encourage you to download the Visual Studio 2008 Solution, familiarize yourself with how it works and have a look at the code and comments.  The solution represents a simple client and server application that illustrates polling duplex communication in both directions.  The client displays stock prices in a grid and animates updates received from the server.  The user of the client application can also enter notes against a stock and have those notes propagate to all other connected clients via the server using polling duplex communication in the other direction.  Here is a screenshot of the sample application running with the server and two connected clients, the same stock price updates are being displayed by both clients and the notes entered in Internet Explorer have been pushed to another instance of the client running in Firefox:

Sample Application Running  

An excerpt of the most significant bi-directional conversation between one client and server assuming no communication problems would include the following events:

A. Client Connects To Server
B: Server Send Client An Empty Response
C. Client Polls Server For A Response
D. Server Sends Response Per Polling Client
E. Client Sends User Notes To Server
F. Server Sends Response Per Polling Client Including Notes

Tracing these events on the wire in practice results in the following traffic:

A. Client Connects To Server
POST /StockService HTTP/1.1
Accept: */*
Content-Length: 394
Content-Type: text/xml; charset=utf-8
SOAPAction: "Silverlight/IStockService/Register"
UA-CPU: x86
Accept-Encoding: gzip, deflate
User-Agent: Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 6.0; SLCC1; .NET CLR 2.0.50727; Media Center PC 5.0; InfoPath.2; .NET CLR 3.5.21022; .NET CLR 3.5.30729; .NET CLR 3.0.30618)
Host: 127.0.0.1:10201
Connection: Keep-Alive
Pragma: no-cache

<s:Envelope xmlns:s="http://schemas.xmlsoap.org/soap/envelope/">
  <s:Header>
    <netdx:Duplex xmlns:netdx="http://schemas.microsoft.com/2008/04/netduplex">
      <netdx:Address>http://docs.oasis-open.org/ws-rx/wsmc/200702/anoynmous?id=d640645c-4650-4889-8fd0-e7bf52156bcc</netdx:Address>
      <netdx:SessionId>e694f6c9-bab4-46a4-b81c-e5488609b48d</netdx:SessionId>
    </netdx:Duplex>
  </s:Header>
  <s:Body />
</s:Envelope>

This is a HTTP POST of the Net Duplex request from the client to the server to establish a unique session.  Note the presence of Connection: Keep-Alive and the SOAPAction in the HTTP request specifying the WCF service method to call (see part two for the server code).  The Net Duplex information is present as part of the SOAP header and includes two separate GUID identifiers in the Address and Session elements.  The Address element appears to specify an identifier for use by WS-MC (note the URI part of the identifier is the WS-MC anonymous URI although anonymous is spelt incorrectly) and the SessionId element appears to uniquely identify the current Net Duplex session; the SOAP body is empty.

B: Server Send Client An Empty Response
HTTP/1.1 200 OK
Content-Length: 0
Server: Microsoft-HTTPAPI/2.0
Date: Mon, 01 Sep 2008 11:27:43 GMT

The server sends back an empty response (Content-Length: 0) indicating that the connection was successful but no messages destined for this unique session’s client are available at this time.

C. Client Polls Server For A Response
POST /StockService HTTP/1.1
Accept: */*
Content-Length: 318
Content-Type: text/xml; charset=utf-8
SOAPAction: "http://docs.oasis-open.org/ws-rx/wsmc/200702/MakeConnection"
UA-CPU: x86
Accept-Encoding: gzip, deflate
User-Agent: Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 6.0; SLCC1; .NET CLR 2.0.50727; Media Center PC 5.0; InfoPath.2; .NET CLR 3.5.21022; .NET CLR 3.5.30729; .NET CLR 3.0.30618)
Host: 127.0.0.1:10201
Connection: Keep-Alive
Pragma: no-cache

<s:Envelope xmlns:s="http://schemas.xmlsoap.org/soap/envelope/">
  <s:Body>
    <wsmc:MakeConnection xmlns:wsmc="http://docs.oasis-open.org/ws-rx/wsmc/200702">
      <wsmc:Address>http://docs.oasis-open.org/ws-rx/wsmc/200702/anoynmous?id=d640645c-4650-4889-8fd0-e7bf52156bcc</wsmc:Address>
    </wsmc:MakeConnection>
  </s:Body>
</s:Envelope>

The above is a second HTTP POST to the server from the client a few milliseconds after the initial Net Duplex request.  This time the SOAPAction identifies it as a WS-MC MakeConnection request.  The SOAP envelope has no header but the body contains the same URI and GIUD identifier established in the Address element of the previous Net Duplex request.

D. Server Sends Response Per Polling Client
HTTP/1.1 200 OK
Content-Length: 578
Content-Type: text/xml; charset=utf-8
Server: Microsoft-HTTPAPI/2.0
Date: Mon, 01 Sep 2008 11:27:45 GMT

<s:Envelope xmlns:s="http://schemas.xmlsoap.org/soap/envelope/">
  <s:Header>
    <netdx:Duplex xmlns:netdx="http://schemas.microsoft.com/2008/04/netduplex">
      <netdx:Address>http://docs.oasis-open.org/ws-rx/wsmc/200702/anoynmous?id=d640645c-4650-4889-8fd0-e7bf52156bcc</netdx:Address>
      <netdx:SessionId>e694f6c9-bab4-46a4-b81c-e5488609b48d</netdx:SessionId>
    </netdx:Duplex>
  </s:Header>
  <s:Body>
    <Stock xmlns="urn:petermcg.wordpress.com" xmlns:i="http://www.w3.org/2001/XMLSchema-instance">
      <Ask>256.7</Ask>
      <Bid>256.02</Bid>
      <Notes i:nil="true"/>
      <Symbol>AAHH</Symbol>
    </Stock>
  </s:Body>
</s:Envelope>

The above is the first real response sent back along the HTTP response channel or "back-channel" (a channel capable of carrying a SOAP message, without initiating a new connection) in reaction to the client’s connection request and polling.  This time the server has queued a message destined for this unique session’s client in the form of a serialized stock object with the symbol AAHH.  The content-length is therefore greater than zero this time as the response consists of a SOAP envelope with the Net Duplex information in the header and the serialized Stock object in the body. See the server code in part two for how the Stock objects are serialized using the DataContractSerializer.

E. Client Sends User Notes To Server
POST /StockService HTTP/1.1
Accept: */*
Content-Length: 607
Content-Type: text/xml; charset=utf-8
SOAPAction: "Silverlight/IStockService/Sync"
UA-CPU: x86
Accept-Encoding: gzip, deflate
User-Agent: Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 6.0; SLCC1; .NET CLR 2.0.50727; Media Center PC 5.0; InfoPath.2; .NET CLR 3.5.21022; .NET CLR 3.5.30729; .NET CLR 3.0.30618)
Host: 127.0.0.1:10201
Connection: Keep-Alive
Pragma: no-cache

<s:Envelope xmlns:s="http://schemas.xmlsoap.org/soap/envelope/">
  <s:Header>
    <netdx:Duplex xmlns:netdx="http://schemas.microsoft.com/2008/04/netduplex">
      <netdx:Address>http://docs.oasis-open.org/ws-rx/wsmc/200702/anoynmous?id=d640645c-4650-4889-8fd0-e7bf52156bcc</netdx:Address>
      <netdx:SessionId>e694f6c9-bab4-46a4-b81c-e5488609b48d</netdx:SessionId>
    </netdx:Duplex>
  </s:Header>
  <s:Body>
    <Stock xmlns:i="http://www.w3.org/2001/XMLSchema-instance" xmlns="urn:petermcg.wordpress.com">
      <Ask>227.69</Ask>
      <Bid>226.74</Bid>
      <Notes>Notes added from Internet Explorer</Notes>
      <Symbol>CZLP</Symbol>
    </Stock>
  </s:Body>
</s:Envelope>

This time we see a similar SOAP envelope to the previous response except travelling in the opposite direction.  The SOAPAction in the HTTP POST request is specifying that the Sync method of the WCF service should be called (again see part two for the server code).  The SOAP envelope again contains the identifying Net Duplex information in the header and the body contains a single serialized stock instance, this time with some notes entered by the user.

F. Server Sends Response Per Polling Client Including Notes
HTTP/1.1 200 OK
Content-Length: 607
Content-Type: text/xml; charset=utf-8
Server: Microsoft-HTTPAPI/2.0
Date: Mon, 01 Sep 2008 11:28:17 GMT

<s:Envelope xmlns:s="http://schemas.xmlsoap.org/soap/envelope/">
  <s:Header>
    <netdx:Duplex xmlns:netdx="http://schemas.microsoft.com/2008/04/netduplex">
      <netdx:Address>http://docs.oasis-open.org/ws-rx/wsmc/200702/anoynmous?id=d640645c-4650-4889-8fd0-e7bf52156bcc</netdx:Address>
      <netdx:SessionId>e694f6c9-bab4-46a4-b81c-e5488609b48d</netdx:SessionId>
    </netdx:Duplex>
  </s:Header>
  <s:Body>
    <Stock xmlns="urn:petermcg.wordpress.com" xmlns:i="http://www.w3.org/2001/XMLSchema-instance">
      <Ask>227.69</Ask>
      <Bid>226.74</Bid>
      <Notes>Notes added from Internet Explorer</Notes>
      <Symbol>CZLP</Symbol>
    </Stock>
  </s:Body>
</s:Envelope>

On receipt of the notes post from the client, if there are no messages pending the server sends back an empty response as shown earlier.  A few milliseconds later when a client polls again or if there are other clients polling  as shown previously, the received update is echoed back in the response above.  To see the entire client and server code involved in generating these transmissions, download the sample Visual Studio 2008 solution attached to parts two or three.

Comet

Like all good problems, server to browser communication has prompted many slightly different solution implementations over the years.  The term Comet was first defined here (the technologies covered by the umbrella existed long before) as an umbrella term used to discuss these different implementations as a group when necessary (although it seems at one point it was nearly used as the codename for ASP.NET AJAX instead of Atlas).  More commonly the term is used in context with browser-native technologies such as JavaScript but I have seen it used to include proprietary plugins such as Silverlight.  The browser-native implementations apply methods of maintaining long lived connections between a browser and a server commonly using streaming or long-polling to enable partial page updates from JavaScript based on server initiated events.  If done correctly, this can result in near real time updates to a web-page without it ever explicitly requesting those updates.  Just some examples of applications that already use this kind of technology include:

Google Talk

Google Maps

Lightstreamer

Liberator

Kaazing

BlazeDS

Orbiter

Historically, browser-native Comet implementations have had to overcome several obstacles often based around the fact that the underlying technologies were not specifically designed to work in the way they were being manipulated.  Examples include browser compatibility, firewalls terminating long running connections and problems with incorrectly configured proxies.  Another obstacle is that keeping a long-lived connection open to a particular server uses up one of the simultaneous connections allowed by the HTTP 1.1 specification (have a look at the sixth paragraph under heading 8.1.4 here).  This can lead to browser usability problems but is often overcome by using a sub-domain on the same server as the initiator of the update events only.  It is very interesting to note that there are some changes around this restriction due in Internet Explorer 8 for broadband connections.  From a Silverlight perspective, as the implementation runs inside a plug-in the cross-browser compatibility obstacle is alleviated as long as the user has the plug-in installed.  These obstacles and their many different solutions by the different implementations of Comet in use today imply that no one solution is yet perfect across all browsers in all scenarios.  Work is underway to attempt to do something about this by standardising native support for server-sent events, event streaming and Web sockets in the HTML 5 specification, the latest Editor’s Draft at the time of writing is as up-to-date as a few days ago on 29 August 2008.  Native browser support for such communication in the future prompts musings on whether Microsoft could add not only polling but streaming duplex support to ASP.NET applications in the future using SOAP/JSON, the ASP.NET AJAX client framework, and an ASP.NET server control or WCF; and whether the messages would be compatible with this Silverlight framework on the wire and over the HTML bridge.

Posted in Comet, PollingDuplex, Silverlight, WCF | Tagged: , , , , , , , , , | 13 Comments »

 
Follow

Get every new post delivered to your Inbox.