WebSockets with WCF

This post demonstrates an elementary chat service constructed using WCF and WebSockets. A custom binding that leverages WebSocket support in httpTransport is used. JSON is serialized and deserialized using byteStreamMessageEncoding encoding. Use httpsTransport for secure transport.

Service interface

The service interface is used to receive connection requests and messages from clients. It has only one method, as shown below.

using System.ServiceModel;
using System.ServiceModel.Channels;
using System.Threading.Tasks;

namespace ChatService
{
    [ServiceContract(CallbackContract = typeof(IChatServiceCallback))]
    interface IChatService
    {
        [OperationContract(IsOneWay = true, Action = "*")]
        Task SendMessage(Message message);
    }
}

Callback interface

The callback interface is used to send messages back to the clients.

using System.ServiceModel;
using System.ServiceModel.Channels;
using System.Threading.Tasks;

namespace ChatService
{
    [ServiceContract]
    interface IChatServiceCallback
    {
        [OperationContract(IsOneWay = true, Action = "*")]
        Task ReceiveMessage(Message message);
    }
}

Service implementation

The service implementation receives messages from clients, and fires them off to other clients, using their respective callback interface. Messages are sent to clients who have sent messages to a chat room, and are still connected.

using System;
using System.Collections.Concurrent;
using System.IO;
using System.Net.WebSockets;
using System.Runtime.Serialization.Json;
using System.ServiceModel;
using System.ServiceModel.Channels;
using System.Threading.Tasks;

namespace ChatService
{
    [ServiceBehavior(InstanceContextMode = InstanceContextMode.Single)]
    public class ChatServiceImplementation : IChatService
    {
        static ConcurrentDictionary<string, ConcurrentDictionary<string, Chatter>> rooms = 
            new ConcurrentDictionary<string, ConcurrentDictionary<string, Chatter>>();

        public async Task SendMessage(Message message)
        {
            if (message.IsEmpty) return;

            byte[] body = message.GetBody<byte[]>();
            MemoryStream stream = new MemoryStream(body);
            DataContractJsonSerializer ser = new DataContractJsonSerializer(typeof(Chatter));
            Chatter chatter = (Chatter)ser.ReadObject(stream);
            IChatServiceCallback callback = OperationContext.Current.GetCallbackChannel<IChatServiceCallback>();
            chatter.Callback = callback;

            IChannel channel = (IChannel)callback;
            channel.Faulted += channel_Faulted;
            channel.Closed += channel_Closed;

            ConcurrentDictionary<string, Chatter> room;
            if (!rooms.TryGetValue(chatter.Room, out room))
            {
                room = new ConcurrentDictionary<string, Chatter>();
                rooms.TryAdd(chatter.Room, room);
            }
            Chatter existingChatter;
            if (!room.TryGetValue(chatter.Nickname, out existingChatter))
            {
                room.TryAdd(chatter.Nickname, chatter);
            }
            else if (existingChatter.Callback != chatter.Callback)
            {
                existingChatter.Callback = chatter.Callback;
            }
            foreach (Chatter c in room.Values)
            {
                if (((IChannel)c.Callback).State == CommunicationState.Opened)
                    await c.Callback.ReceiveMessage(CreateMessage(body));
            }
        }

        private void channel_Closed(object sender, EventArgs e)
        {
            // Clean up
        }

        private void channel_Faulted(object sender, EventArgs e)
        {
            // Clean up
        }

        private Message CreateMessage(byte[] message)
        {
            Message channelMessage = ByteStreamMessage.CreateMessage(new ArraySegment<byte>(message));

            channelMessage.Properties["WebSocketMessageProperty"] =
                new WebSocketMessageProperty { MessageType = WebSocketMessageType.Text };

            return channelMessage;
        }
    }
}

Here’s the Chatter class, used to store state.

using System.Runtime.Serialization;

namespace ChatService
{
    [DataContract()]
    class Chatter : IExtensibleDataObject
    {
        [DataMember(Name = "nickname", IsRequired = true)]
        public string Nickname { get; set; }
        [DataMember(Name = "room", IsRequired = true)]
        public string Room { get; set; }
        [DataMember(Name = "message", IsRequired = true)]
        public string Message { get; set; }
        public IChatServiceCallback Callback { get; set; }

        public ExtensionDataObject ExtensionData { get; set; }
    }
}

App.config

App.config below creates a customBinding and associates it with the chat service.

<?xml version="1.0" encoding="utf-8"?>
<configuration>
  <system.serviceModel>
    <bindings>
      <customBinding>
        <binding name="webSocketHttpBinding">
          <byteStreamMessageEncoding/>
          <httpTransport>
            <webSocketSettings transportUsage="Always" createNotificationOnConnection="true"/>
          </httpTransport>
        </binding>
      </customBinding>
    </bindings>
    <services>
      <service name="ChatService.ChatServiceImplementation">
        <endpoint address="http://localhost:8004/chatservice" binding="customBinding" bindingConfiguration="webSocketHttpBinding" contract="ChatService.IChatService"/>
      </service>
    </services>
  </system.serviceModel>
  <startup>
    <supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.5"/>
  </startup>
</configuration>

Self hosted console app

A console app that hosts the service is shown below.

using System;
using System.ServiceModel;

namespace ChatServiceHost
{
    class Program
    {
        static void Main(string[] args)
        {
            ServiceHost host = new ServiceHost(typeof(ChatService.ChatServiceImplementation));
            host.Open();

            Console.WriteLine("Hit Enter to quit.");
            Console.ReadLine();
        }
    }
}

Chat web page

The following web page uses jQuery and WebSocket to send/receive messages to/from chat rooms.

<!DOCTYPE html>

<html lang="en" xmlns="http://www.w3.org/1999/xhtml">
<head>
    <meta charset="utf-8" />
    <title>Chat</title>
    <script type="text/javascript"
            src="https://code.jquery.com/jquery-2.1.4.min.js">
    </script>
</head>
<body>
    <form>
        Nickname <br/>
        <input id="nickname" type="text" value="nickname" /><br/>
        Room <br />
        <input id="room" type="text" value="room" /><br/>
        Message <br />
        <input id="message" type="text" value="message" /><br/>
        <input id="send" type="button" value="Send" />
    </form>

    <p>
        Messages
        <div id="messages">

        </div>
    </p>
    <img src="image/HTML5_Logo_64.png" />

    <script type="text/javascript">
        var url = 'ws://localhost:8004/chatservice'; // base url
        var connection = null;
        $(document).ready(documentReady);
        function documentReady() {
            $("#send").click(sendClick);
        }
        function sendClick() {
            if (connection == null) {
                connection = new WebSocket(url);
            } else {
                sendMessage();
                return;
            }
            connection.onopen = sendMessage;
            connection.onmessage = receiveMessage;
            connection.onerror = function (e) {
                alert('error ' + e);
                connection = null;
            };
        }
        
        function sendMessage() {
            var chatter = new Object();
            chatter.nickname = $('#nickname').val();
            chatter.room = $('#room').val();
            chatter.message = $('#message').val();
            connection.send(JSON.stringify(chatter));
        }
        function receiveMessage(e) {
            var chatter = JSON.parse(e.data);
            var message = chatter.nickname + '@' + chatter.room + ' said ' + chatter.message + '<br/>';
            $('#messages').prepend(message);
        }
    </script></body>
</html>

Testing

Open the HTML file in a modern web browser, and you’ll see the chat page. Open the same page in additional tabs. Once you send a message to one or more chat rooms in a tab, messages posted from other tabs using different nicknames to the same chat rooms, should appear in the Messages area in reverse chronological order.

One really pesky problem is the following exception you get, when you try to send the same Message instance to multiple callbacks
A property with the name 'TransactionFlowProperty' already exists..

I have also experimented with testing scalability by hitting the service using thor, and have noted that InstanceContextMode.Single behavior is the most responsive and reliable.

The source code is available from GitHub. I’d like to acknowledge Zhuyun Dai’s article at CodeProject for giving useful insights with regards to using byteStreamMessageEncoding.

A note on Hungarian notation

Back in the old days of Visual Basic, I got into the habit of using the Hungarian notation. That habit stuck with me through the Java days, but I’ve kicked it off almost completely now. My conclusion is that variable names should be clear, free of obscure prefixes, and explain their purpose. If I want to know the scope or type of the variable, I’ll just look it up using an IDE or grep.

Please read the post I’ve linked to above, to understand that there are at least two kinds of Hungarian notation. One comes from Charles Simonyi, the other popularized by Charles Petzold.

Generating a unique ID for a Windows PC

Software licensing usually works by tying a product to a single PC. Identifying the PC in a unique manner usually requires generating some kind of unique ID.

Here’s a log of certain attributes of a Windows PC (a Parallels virtual machine) read using the Windows Management Instrumentation API of .NET. It was generated using the WMI Query utility available at GitHub.

Win32_BaseBoard.Manufacturer="Parallels Software International Inc."
Win32_BaseBoard.Name="Base Board"
Win32_BaseBoard.SerialNumber="None"
Win32_BIOS.Manufacturer="Parallels Software International Inc."
Win32_BIOS.SMBIOSBIOSVersion="10.2.1 (29006) rev 0"
Win32_BIOS.SerialNumber="Parallels-FC C8 89 B7 D4 BF 4E 13 9D 53 D8 BC C1 9B 90 A8"
Win32_BIOS.ReleaseDate="20150520000000.000000+000"
Win32_BIOS.Version="PRLS   - 1"
Win32_Processor.ProcessorId="BFEBFBFF000306A9"
Win32_Processor.Name="Intel(R) Core(TM) i7-3840QM CPU @ 2.80GHz"
Win32_Processor.Manufacturer="GenuineIntel"
Win32_Processor.MaxClockSpeed="2800"
Win32_OperatingSystem.Name="Microsoft Windows 8 Pro|C:\WINDOWS|\Device\Harddisk0\Partition2"
Win32_OperatingSystem.OSArchitecture="64-bit"
Win32_OperatingSystem.SerialNumber="00330-80000-00000-AA279"

The challenge in generating a unique ID is deciding which information to use. The ID itself can be generated fairly easily by hashing the information using a cryptographic hash function.

Use too much of the information above and you risk the ID changing frequently. Updating Parallels may result in changes to Win32_BIOS.SMBIOSBIOSVersion and Win32_BIOS.ReleaseDate. Upgrading Windows may result in changes to Win32_OperatingSystem.Name, Win32_OperatingSystem.OSArchitecture, and Win32_OperatingSystem.SerialNumber.

I’ve found Win32_BIOS.SerialNumber and Win32_Processor.ProcessorId to be fairly stable. They’ll only change if the PC’s motherboard is changed or its CPU. Not likely to happen that frequently with laptops.

git stash

git stash can be a useful mechanism to set aside work you’re not ready to commit yet.

To stash staged changes simply run

git stash

Files in working directory not added to index are not affected, unless you add the -u option to stash those as well.

To view a list of stashes

git stash list

To inspect details of latest stash

git stash show stash@{0}

0 is the id shown by stash list. Use of stash@{0} is optional for latest stash entry.

To apply changes in latest stash to working directory

git stash apply

To eliminate the latest stash (use pop to apply and drop)

git stash drop

If stash apply or pop fails due to conflicts, but you still want to stage the changes, try

git checkout stash -- .

-- indicates current branch. The single dot indicates path beginning at current directory, but may be several different path specs. stash may be replaced with stash@{0}, use a different id to work with older stashes.

git tag

This post summarizes some useful tag related commands. All commands assume a tag with value v1.0.0, a version number. To create an annotated tag

git tag -a v1.0.0 -m "a comment"

To list all tags

git tag

To view details and diff for a tag

git show v1.0.0

To delete a tag

git tag -d v1.0.0

To replace a tag when it already exists

git tag -a v1.0.0 -f -m "a comment"

To create a tag pointing to the same commit as another tag

git tag -a v1.0.0 v1.0.0-rc -m "a comment"

To create a tag pointing to a specific commit

git tag -a v1.0.0 commit -m "a comment"

To checkout working tree at tag (add option -b to create branch at that point in the tree)

git checkout v1.0.0

To view commit log with tags

git log --decorate=full

To push tags to origin (add -f to force update if tag exists on remote)

git push origin --tags

To push tag deletes to origin

git push origin :refs/tags/v1.0.0

For other commands and options use man git or man git-tag.

USB bulk data transfer

USB bulk data transfers is how most data transfer occurs between a USB host such as a PC and a peripheral. This post shows, with help from a Total Phase Beagle USB 480, how the data transfer looks like. Here’s data captured using Data Center software and a Total Phase Beagle USB 480. According to the USB specification, all bits in a byte are written to the bus in little-endian fashion, and multi-byte values are also written in little-endian order. While the latter is still valid with the Beagle USB 480, single byte values are represented in their normal big-endian order.

bulk transfer

Each USB bulk data transfer is broken into smaller data transfers. Each of the smaller data transfers is initiated by a token packet, followed by a data packet, and an ACK packet.

Incoming transfers begin with a token packet, that has a PID (packet identifier) of IN (0x69) in the first byte. Actually, the PID is contained in the last 4 bits (0x9), the first 4 bits are a one’s complement of that value (0x6). The second and third bytes form a single word in little endian byte order, containing a 5-bit CRC, followed by a 4-bit endpoint ID, followed by a 7-bit device ID. Thus ASCII hex sequence 81 58 in the figure above, should be interpreted as 0x5881, which results in a CRC of 0x0B, an endpoint ID of 0x1, and a device ID of 0x01.

The token packet is followed by a data packet. The PID of the data packet may be DATA0 (0xC3) or DATA1 (0x4B) for full-speed, and also DATA2 (0x87) for high-speed bulk transfers. The data packet has a maximum payload size determined by the USB specification, and is described by wMaxPacketSize attribute in the endpoint descriptor. It may be 8, 16, 32, or 64 bytes for full-speed bulk endpoints (USB 1.1), and also 512 bytes for high-speed (USB 2.0) bulk endpoints. It is 64 bytes in the capture shown above. The payload is followed by a 2-byte CRC.

The ACK handshake packet indicates the end of data transfer. A bulk data transfer is composed of several such data transfers in sequence. The end of a bulk data transfer is signalled by a data packet that has less than wMaxPacketSize bytes. If the last, or only data packet, carries wMaxPacketSize, a data transfer containing data packet with payload size of 0 bytes is required.

Outgoing transfers are quite similar, except that the token packet uses a PID of OUT (0xE1, or 0x1 to be more accurate). Outgoing bulk data transfers can appear jumbled up with incoming bulk data transfers. Applications that leverage Beagle API need to take this into consideration, when parsing data captured on the bus.