Sunday, January 4, 2009

Using the PollingDuplexHttpBinding for a Silverlight Group Chat

1) A Quick Intro to the PollingDuplexHttpBinding

Comet technologies, such as AJAX push and HTTP server push, allow web pages to have data pushed to them from the server, rather than always having the client pull information. It mimics this feature by having the browser poll the server at regular but short intervals (~1 second) to check for updates. Now web pages can be updated dynamically without any user input. Lucky for us, Silverlight supports this using the PollingDuplexHttpBinding Wcf Binding, which does most of the heavy lifting.

In this article, we hope to build a basic Silverlight group chat application by connecting a PollingDuplexHttpBinding to a Wcf Service hosted on a shared hosting plan.

There are excellent introductory articles to this technology that are pretty much required reading if unfamiliar with the PollingDuplexHttpBinding (Skim the first link if pressed for time):

  1. Basketball score server by Dan Wahlin
  2. Stock quote server by Peter McGrattan

The bulk of the PollingDuplexHttpBinding functionality is covered in the blogs listed above. From here on out, I will talk about some of the higher level details involved in making the simple chat program on top of the PushDataReceiver.

2) The static list of clients.

Every time a client connects to our webservice, we instantiate a new instance of the GameStreamClient class and keep it in a static List<ChatClient>. This does restrict us to only being able to run our chat server on one appdomain.

private static List<IGameStreamClient> clients = new List<IGameStreamClient>();

Our service gets instantiated on a Per Session basis as shown by the mark up below. In retrospect, I actually think it should have been a singleton, but that's for another time.

[ServiceBehavior(InstanceContextMode =
    InstanceContextMode.PerSession,ConcurrencyMode = ConcurrencyMode.Single,AutomaticSessionShutdown = true)]


3) The inactivity timeout and the stay alive ping.

Each Wcf connection has an inactivity timeout that defaults to around 10 minutes. We want our clients to be able to idle in the chat room, and not get booted for inactivity. Here we introduce a stay alive packet to reset this inactivity timeout. The end user will know nothing of it, and this way they can idle all they want.

Message gameDataMsg =
    Message.CreateMessage(MessageVersion.Soap11,"Silverlight/IGameStreamService/Receive","stayalive");

gameDataMsg.Properties.Add("Type", "StayAlive");

this.localClient.BeginReceive(gameDataMsg, EndSend, this.localClient);

4) I remove when I catch an exception when sending to a client.

With the PollingDuplexHttpBinding, the only way we will know if a client disconnects, is when the server fails to deliver a message. We cannot rely on the client to tell us when they are disconnecting, especially when considering that they are connecting from a browser; there's no real way to elegantly close out the client. Plus, they could just crash. As a result, there is no real disconnect, there is more of a send failure mechanism, that removes clients from the static list of ChatClients. In the proceeding ChatMessage broadcast, any clients that throw a CommunicationException/TimeoutException are removed from the server's client list. These timeouts could block the server however, so we must handle this asynchronously so as not to penalize connected clients.

foreach (IGameStreamClient client in clients)
{
    try
    {
        //Send data to the client
        if (client != null)
        {
            Message gameDataMsg =
                Message.CreateMessage(MessageVersion.Soap11,"Silverlight/IGameStreamService/Receive",data,this.serializer);

            gameDataMsg.Headers.Add(MessageHeader.CreateHeader("Type", "", "DataWrapper"));
            client.BeginReceive(gameDataMsg, EndSend, client);
        }
    }
    catch (Exception ex)
    {
        // Exception caught when trying to send message to client so remove them from client list.
        // Should probably catch a more specific exception but I'll leave that as an exercise for the reader.
        clientsToRemove.Add(client);
    }
}
foreach (IGameStreamClient client in clientsToRemove)
{
    clients.Remove(client);
}


5) Using DataContracts and DataContractSerializers to send complex types.

Sending strings back and forth is not really fun. Complex types such as structs would allow for much richer data transfer. So it's a good thing Wcf supports DataContracts and has a DataContractSerializer that makes this process seemless.

private readonly DataContractSerializer serializer = new DataContractSerializer(typeof(ChatData));
// Serialize
Message
gameDataMsg =
    Message.CreateMessage(MessageVersion.Soap11,"Silverlight/IGameStreamService/Receive",chatData,this.serializer);

// Deserialize
ChatData
chatData = receivedMessage.GetBody<ChatData>(this.serializer);


6) Adding Header information to the Message object so the Processor can serialize to the appropriate type.

When your application gorws in complexity, you will probably have multiple DataContracts, but your Wcf Callback contract will only have one message handler, and you will need to programmatically handle the Message Body. To know what that body is so you can deserialize it, the sample tags the outgoing message with type strings in the Headers. This acts as a form of metadata for the message and allows the caller to deserialize the DataContract to the message of the passed type, allowing proper complexity to the callback contract. This is a lot better than clumsily having a class with a body and a type, and dealing with deserialization logic yourself.

//Server creates the message and tags it with a type.
Message gameDataMsg =
    Message.CreateMessage(MessageVersion.Soap11,"Silverlight/IGameStreamService/Receive","stayalive");

gameDataMsg.Properties.Add("Type", "StayAlive");

// Receiver parses the message type and deserializes using the correct deserializer.
// Check message type
string type = string.Empty;
for (int i = 0; i < receivedMessage.Headers.Count; i++)
{
    if (receivedMessage.Headers[i].Name == "Type")
    {
        type = receivedMessage.Headers.GetHeader<string>(i);
        break;
    }
}
// Dispatch message based on type.
switch (type)
{
    case "StayAlive":
    break;
    case "DataWrapper":


7) Source code.

Source code

1 comment:

Christian Ruiz said...

Hi, I'm triing your example in silverlight 3 but it throws an exception.