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

Hosting Wcf Services on a Shared Hosting Plan

Hosting Wcf Services on a windows shared hosting plan has one issue that people should be aware of: You can only add one address per scheme to a service host. But what does this mean?

Shared hosting plans often run with the default IIS set up, which means that there are two addresses to your site: http://www.yourdomain.com and http://yourdomain.com. Wcf only allows one address of any scheme (ie http://) to be used as a Service host, but by default, it tries to add the two. If you would try to run a web service using this markup,

<%@ ServiceHost Language="C#" Debug="true" Service="SomeService" %>

You would receive the following error:

This collection already contains an address with scheme http.  There can be at most one address per scheme in this collection.

To get around this, you'll have to make a custom ServiceHostFactory that will select only one address, and have your markup use this custom factory with the 'Factory' attribute. Which address you prefer is up to you, just remember to use it in your Endpoint configurations!

.svc file:

<%@ ServiceHost Language="C#" Debug="true" Service="SomeService" Factory="SomeNamspace.AddressSelectorHostFactory" %>

   
 

AddressSelectorHostFactory.cs file:

public class AddressSelectorHostFactory : ServiceHostFactory
{
    protected override ServiceHost CreateServiceHost(Type serviceType, Uri[] baseAddresses)
    {
        // When hosting on a shared hosting plan, the default IIS configuration passes 2 addresses
        // to CreateServiceHost: http://www.yoururl.com and http://yoururl.com.
        //
        // You can only add ONE address of a certain scheme (ie: http://), so we just take the www address.

        
if (baseAddresses.Length > 1)
        {
            Uri address = (from u in baseAddresses where u.AbsoluteUri.StartsWith("http://www.") select u).First();
            return new ServiceHost(serviceType, address);
        }
        else
        
{
            return new ServiceHost(serviceType, baseAddresses[0]);
        }
    }
}

 
 

More about this can be read here.

Sunday, December 7, 2008

Red, White, and Blue


It can be found here.

I feel comfortable with Silverlight now and will be releasing a few "How To" articles for people interested in the technology. That said, the actual game didn't turn out as well as I had hoped, but I do think the map control, the one that features the zooming of the States, came out pretty slick.

So do I continue on this project, or move on to another one? Normal d-roc fashion would have me bury yet another failed project, and tackle something new. What will happen this time?

Wednesday, December 3, 2008

Regarding Coding Tutorials on Blogs

I was reading an article about the Model-View-ViewModel design pattern when I noticed a comment which sparked my rage against an all-too-common pattern I've observed in recent years:

If you're doing WPF development, you really need to check out Dan Crevier's series on DataModel-View-ViewModel. Right now there is no easy way to read through all his posts on the subject without navigating through them using the calendar control on his blog.

With the prevalence of increasingly easier blog services like Blogger or Wordpress, anyone can be a publisher.  Unfortunately some have used these services (with little additional customization) for purposes they are not well-suited for.  While we may likely contradict this post at some time in the future, I'd like to express my increasing annoyance with blogs that post coding tutorials.  A blog is a much-less-than-ideal medium for hosting coding tutorials. 

Why?

For one, when I'm looking for coding tutorials, I want them categorized.  I want to be able to easily sort through XNA Game Studio SpriteBatch tutorials or WPF TabControl tutorials.  With blogs, your posts are time-based.  They are generally consumed in much the same way a newspaper or magazine is; you don't often reach for an old issue of a magazine or a newspaper from a few months or years ago.  You use them to get recent information about topics of interest.  Blogs are designed around this assumption as well.

Now admittedly, a blog is orders of magnitude better for retrieving archived information, but the filtering mechanism is still subpar.  The best that you can reasonably hope is that the blog author applied an extensive and thorough use of tags, which certainly isn't something you can count on, and is often completely arbitrary.  (I can write an entire article about the problems with the hacky "tags" system so prevalent these days.  In fact, I'm struggling to come up with an adequate set of tags for this post.)

Instead, developers should separate their tutorials and their blog posts.  Perhaps services like Blogger should (if they don't already) easily facilitate categorized articles.  Google seems to be on to something with their Knol system, but I imagine there's a better way.  Speaking of Google, it seems that currently most tutorials are found by searching for a particular topic, and trusting that relevant archived developer blog entries will appear in the search results.  Thankfully, Google tends to pick up the slack where the blogging system falls short.  But it shouldn't have to be this way.

Tuesday, November 25, 2008

Coding Vacation '08: Bad Timing

Several weeks ago I took the initiative and committed to something that went against my conventional patterns of behavior.  I decided that I would spend some vacation time devoted primarily toward developing software -- my personal projects, of course.  In the back of my head, I heard voices telling me that this was not an acceptable justification to take vacation time.  Vacations are for trips or visiting family, they said.  But, much like the thought processes that steered me toward the video game industry rather than business apps, the thoughts compelling me to take this vacation won in the end.  I announced my plans confidently to my friends and family, as if asserting it this way would protect against any criticism.  And, well, I didn't receive any.  In fact, I only received support.

My girlfriend, of course, saw this as an opportunity to spend more time with me every day.  She is currently unemployed, so she has a lot of free time.  It's very difficult to make the argument that I need to spend so much time doing things alone on my computer when I have a rare opportunity to spend extra time together, but she seemed to understand.  Of course, that didn't stop her from coming over often, inevitably leading to a lot of time spent away from coding.  Much of this time was spent with her playing the newly released World of Warcraft expansion: Wrath of the Lich King.

I can't blame her for this; I always want to play, and I have to keep myself at least somewhat disciplined.  It just makes it so much easier to play when I have someone to play with.  Between my uber druid and my hopelessly gay mage, there's so much fun to be had; so many hours to lose.

image image

For the past week it's taken all my energy to avoid getting completely sucked into the grind.  I have to reach level 80.  I have to purchase an epic flying mount.  I have to obtain uber gear, etc.

To make matters worse, I'm leaving to visit my brother for Thanksgiving tomorrow.  Don't get me wrong, I love visiting him, but it's more time spent away from coding.  Compounding this is the fact that he plays World of Warcraft as well, and I'm giving him the new expansion pack as a gift.  Nevertheless, I'm bringing my Mac Mini and an LCD monitor with me tomorrow.  Yes, I'm a little bit crazy.

Despite all of this, I have made significant progress on my app, but there's much to be done.  I will talk more about it later when I feel less reserved about it.

Thursday, November 20, 2008

Red, White, And Blue Logo


Some say, it's amatuer. Screw those guys.



Tuesday, November 18, 2008

Oct 31st == Nov 30th && Shared plans == win!

So much for that beta by Oct 31st. I've had a lot of petty life things distract me from what's important: Red, White, And Blue! It is coming together though and I have high hopes. There are some interesting obstacles involved with keeping many clients in sync (quasi sync at least), and all the bugs that can occur when you have a client that loses sync with the db.

I have a really annoying issue where a browser's cache will prevent users from downloading a new client, so should I rename my client on every update just to trigger a download? Sounds clumsy. I don't think I would like old silverlight clients pinging my services either, so will I have to check a version string on every login? Seems so.

I am relying on the fact that all of my WCF sessions will exist in the same AppDomain, so I can use a single static to keep track of users. Risky and clumsy, I know, but I'm already stretching the capabilities of a shared hosting plan about as far as possible. I don't know many websites on shared hosting plans using WCF services, Ajax style PollingDuplex binding, Linq2Sql , hosting silverlight. This stuff came out last night (pretty much), so I've been happy with how much I can get away with on a shared hosting plan.

It really is a tribute to the computer industry when you can get so much power for a measly $100 / year hosting plan. This is our one up on any other industry: the low barrier of entry. Can you even imagine something similar in the auto industry? Investment banking? The factories, connections, and brown-nosing needed to do the most trivial things in other industries makes me love my field that much more.

See you next Sunday and have a happy thanksgiving!