Somebody recently asked me about duplex communications with Silverlight clients (i.e. pushing data out to the client application) and it made me realise that I needed to brush up on some of the basic detail. So I put together a little learning project, and it's something I'd like to share.
Note this has been built with the RC of VS 2010 and SL 4.0.
Network Communications in Silverlight
Mike Taulty has put together a great series of Channel 9 videos where he does a whistle-stop tour of the various network options available within SL 4. Simply put, the main ones are:
- WCF over HTTP
- WCF over TCP
- TCP sockets
- UDP sockets
- WebRequest over HTTP
My application will use the two WCF options to implement a real-time chat application, although currently only the duplex HTTP binding is implemented. Here is a screenshot of it in action, showing two browser windows communicating in real-time:
Architecture of my Sample Application
The Silverlight portion of the application is pretty simple and uses the NavigationFrame (introduced in SL 3) and if you've played with the Silverlight Navigation Application template then you should be fairly familiar with the project layout. The important view is PollingDuplexHttpClient.xaml.
When using the MVVM pattern (which I am) there are a number of ways of hooking the ViewModel into the view (reference). I'm using a simple XAML-based approach to set the DataContext of the Page to the ViewModel:
2: <app:DuplexHttpClientViewModel />
The ViewModel class itself derives from an abstract base class DuplexChatClientViewModelBase. The sole purpose of the concrete ViewModel classes is to specify the actual WCF binding to use (net.tcp or http). And the primary purpose of the base ViewModel class is to co-ordinate the services which do the real work.
There’s a couple of aspects to the application which I think are interesting. I hope to be posting some further detail on these in future.
Doing the Duplex Communication
The WCF communication is handled in the asynchronous manner typical of Silverlight, and is contained in the PushDataReceiver class. There are four separate chains of execution in this class:
- EnsureStarted –> OnOpenCompleteFactory –> CompleteOpenFactory –> OnOpenCompleteChannel –> CompleteOpenChannel
- Send –> OnSendComplete –> CompleteSend
- Receive –> OnReceiveComplete –> CompleteReceive
- Close –> OnCloseComplete –> CompleteClose
Obviously the first and last of these are only called once, whereas the Send and Receive chains are more frequently executed. The sending operation does not need much explanation:
1: public void Send(Message message)
3: IAsyncResult resultChannel = channel.BeginSend(message, new AsyncCallback(OnSend), channel);
5: if (resultChannel.CompletedSynchronously)
9: private void OnSend(IAsyncResult result)
11: if (result.CompletedSynchronously) return;
15: private void CompleteOnSend(IAsyncResult result)
17: var sendingChannel = (IDuplexSessionChannel) result.AsyncState;
The receiving operation is similar in structure, with the exception that it is recursive and it needs to actually do something with the incoming message. It is first initiated by the CompleteOpenChannel method
1: private void ReceiveLoop(IDuplexSessionChannel receivingChannel)
3: if (receivingChannel.State == CommunicationState.Opened)
5: IAsyncResult result = receivingChannel.BeginReceive(new AsyncCallback(OnReceiveComplete), receivingChannel);
6: if (result.CompletedSynchronously) CompleteReceive(result);
10: private void OnReceiveComplete(IAsyncResult result)
12: if (result.CompletedSynchronously) return;
16: private void CompleteReceive(IAsyncResult result)
18: var receivingChannel = (IDuplexSessionChannel) result.AsyncState;
20: Message receivedMessage = receivingChannel.EndReceive(result);
22: if (receivedMessage != null)
23: uiThread.Post(ServiceLocator.Processor.ProcessData, receivedMessage); // Run on UI Thread.
Line 33 in the above code snippet is where the recursion happens to ensure that we always wait for incoming data. Please note that exception handling code has been omitted here for the sake of clarity … it is present in the code download! ;-)
Now all this seems very clear and straightforward, except for the fact that the server has no way of knowing that we are actually listening. And this is why the service exposes an InitiateDuplex method. From the CompleteOpenChannel method, we call Send against this service method. It is this method which obtains a reference to the back channel to the SilverLight client.
I plan to run some deeper analysis into what’s actually happening on the wire. Check back here soon for some details.
The WCF Service
There’s some surprising stuff going on in the service implementation here - a timer to keep channels alive, recording of IP addresses and timestamps, etc – but the most important bits are in the service contract (which is attributed to have a CallbackContract) and the InitiateDuplex method. Here the code is building up a list of clients that will be sent any incoming message:
1: public void InitiateDuplex(Message receivedMessage)
3: lock (clients)
5: localClient = OperationContext.Current.GetCallbackChannel<IChatRoomClient>();
The code which uses the list of back channels is the DispatchToClientsmethod, which gets called whenever a client pushes a message to the SendMessage service method:
1: private void DispatchToClients(ChatData data)
3: var clientsToRemove = new List<IChatRoomClient>();
4: lock (clients)
6: foreach (IChatRoomClient client in clients)
10: //Send data to the client
11: if (client != null)
13: Message chatMsg = Message.CreateMessage(MessageVersion.Soap12WSAddressing10,
18: chatMsg.Headers.Add(MessageHeader.CreateHeader("Type", "", "DataWrapper"));
19: client.BeginReceive(chatMsg, EndSend, client);
22: catch (Exception)
24: // Exception caught when trying to send message to client so remove them from client list.
29: foreach (IChatRoomClient client in clientsToRemove)
FYI a lot of this code comes fairly directly from Dan Wahlin’s post about duplex communications.
Caveat: the server delivers message to the listed clients one-by-one. This means that delivery will slow down with the number of connected clients.
If you want to get this running on your machine you will need Visual Studio 2010 RC. The steps you need to follow are pretty simple to enable HTTP communications:
- Download the code
- Build everything in Visual Studio and hit F5 to start the SilverlightTestBed.Web project and the WCF service.
I plan to post a few more articles about this application:
- Analysing HTTP traffic and communication patterns
- Enabling net.tcp communication
- Analysing what that looks like on the wire
- Refactoring the code to use Rx (Reactive Extensions for .Net)