tags:

views:

97

answers:

4

I'm working on an app which takes in a raw binary message (very simple, first byte is the message type, rest is payload), and then does something with it. The thing I'm trying to accomplish is making sure that the networking service is abstracted away from the rest of the app, to allow for modifying the protocol now and then without affecting the rest of the application too much. The context of the application is a very simple client-server game, for which I am doing the client work now.

I'm kinda struggling now though. I need to find an elegant way to just throw a connection into some sort of translator/adapter service, which returns pretty objects (I think). Those objects will be thrown in a queue awaiting consumption by the rest of the app. The problem I'm facing is more or less this construct (pseudo code):

Let's assume each message is the 20 bytes, so I can deal with calling this function for each 20 bytes:

public Message GetMessage(byte[] buffer)
{
  switch(buffer[0])
  {
     case 1:
       return Message1(...);
     case 2:
       return Message2(...);
     .....

     case n:
       return MessageN(...);
  }
}

Obviously, I'll use an enum or constants for the case, but that's not the thing that's bugging me. Here's the thing. I think I've got about 50 message types, which would mean I'll get a switch statement with 50 cases. I can't really think of a proper way to cut this up into smaller pieces, which will result in a huge, error prone method. I was wondering if there's any patterns to make this easier, as I couldn't find any.

Thanks for the input in advance!

+1  A: 

You could have an array of 50 function pointers (i.e. C# delegates) which you index using the value of first byte, or a dictionary of delegates whose key is the the value of first byte. That's merely another way of writing a switch statement though.

It's more distributed though: for example, when you create a new message type (which may be a new class in a new source file), then instead of editing source code to add a new case to a big switch statement, you can call an existing method to add a new delegate to the static collection of delegates.

ChrisW
+1  A: 

I have some Java code which does this. Hopefully you can easily translate to C#. Essentially, I have a messageMap collection:

private final Map<Byte, Class<? extends Message>> messageMap;

This is a map from message IDs to their corresponding Message classes. I call addMessage once for each different message type:

public void addMessage(int id, Class<? extends Message> messageClass) {
    messageMap.put((byte) id, messageClass);
}

Then when a message arrives I read the message ID off the wire, look up the Message class I need to instantiate in messageMap, and then use reflection to create an instance of that class.

Class<? extends Message> messageClass = messageMap.get(id);
Message                  message      = messageClass.newInstance();

Here newInstance() calls the default constructor.

I've used this generic message-handling code across multiple applications with different messages. Each one just has a nice, simple block of code registering the different messages like so:

// Messages that we can send to the client.
addOutgoingMessage(0, HeartbeatMessage.class);
addOutgoingMessage(1, BeginMessage    .class);
addOutgoingMessage(2, CancelMessage   .class);

// Messages that the client can send.
addIgnoredMessage (0, HeartbeatMessage.class);
addIncomingMessage(1, StatusMessage   .class, statusMessageHandler);
addIncomingMessage(2, ProgressMessage .class, progressMessageHandler);
addIncomingMessage(3, OutputMessage   .class, outputMessageHandler);
addIncomingMessage(4, FinishedMessage .class, finishedMessageHandler);
addIncomingMessage(5, CancelledMessage.class, cancelledMessageHandler);
addIncomingMessage(6, ErrorMessage    .class, errorMessageHandler);
John Kugelman
+1  A: 

While not exactly a C# solution, I have dealt with a similar situation recently. My solution was to use F# which makes it a lot easier.

For example, my code looks like this

member private this.processDefaultGroupMessage(m : Message) =
        try
            match m.Intro.MessageType with
            | (1us) -> this.listFirmwareVersions(m)                               //ListFirmwareVersions              0
            | (2us) -> this.startLoadingFirmwareVersion(m)                        //StartLoadingFirmwareVersion       1
            | (3us) -> this.loadFirmwareVersionBlock(m)                           //LoadFirmwareVersionBlock          2
            | (4us) -> this.removeFirmwareVersion(m)                              //RemoveFirmwareVersion             3
            | (5us) -> this.activateFirmwareVersion(m)                            //ActivateFirmwareVersion           3        
            | (12us) -> this.startLoadingBitmapLibrary(m)                         //StartLoadingBitmapLibrary         2
            | (13us) -> this.loadBitmapBlock(m)                                   //LoadBitmapLibraryBlock            2        
            | (21us) -> this.listFonts(m)                                         //ListFonts                         0
            | (22us) -> this.loadFont(m)                                          //LoadFont                          4
            | (23us) -> this.nakResponse(m, VPL_FRAMELENGTH)                      //RemoveFont                        3
            | (24us) -> this.nakResponse(m, VPL_FRAMELENGTH)                      //SetDefaultFont                    3         
            | (31us) -> this.nakResponse(m, VPL_FRAMELENGTH)                      //ListParameterSets                 0
            | (32us) -> this.nakResponse(m, VPL_FRAMELENGTH)                      //LoadParameterSets                 4
            | (33us) -> this.nakResponse(m, VPL_FRAMELENGTH)                      //RemoveParameterSet                3
            | (34us) -> this.nakResponse(m, VPL_FRAMELENGTH)                      //ActivateParameterSet              3
            | (35us) -> this.nakResponse(m, VPL_FRAMELENGTH)                      //GetParameterSet                   3        
            | (41us) -> this.nakResponse(m, VPL_FRAMELENGTH)                      //StartSelfTest                     0
            | (42us) -> this.ackResponse(m)                                       //GetStatus (reply with ACK)        0
            | (43us) -> this.getStatusDetail(m)                                   //GetStatusDetail                   0
            | (44us) -> this.resetStatus(m)                                       //ResetStatus                       5
            | (45us) -> this.setDateTime(m)                                       //SetDateTime                       6
            | (46us) -> this.nakResponse(m, VPL_FRAMELENGTH)                      //GetDateTime                       0
            | (71us) -> this.clearConfiguration(m)                                //ClearConfiguration                0
            | (72us) -> this.defineTextFields(m)                                  //DefineTextFields                  11
            | (74us) -> this.defineClockFields(m)                                 //DefineClockFields                 13
            | (80us) -> this.deleteFieldDefinitions(m)                            //DeleteFieldDefinitions            14
            | (91us) -> this.preloadTextFields(m)                                 //PreloadTextFields                 15
            | (94us) -> this.clearFields(m)                                       //ClearFields                       17
            | (95us) -> this.activate(m)                                          //Activate                          0
            | _ -> this.nakResponse(m, VPL_REQUESTNOTSUPPORTED)
        with 
            | _ -> this.nakResponse(m, VPL_INVALID)

It's not the perfect solution, but looks a lot better than the switch statement in c#. So our entire application is written in csharp, but the message parser is written in fsharp.

FYI: We have several interfaces:

IDataTransportServer - Responsible for receiving data via RS232 or TCP/IP

IDataProcessor - Responsible for parsing binary data and turning it into instances of the Message class

IMessageProcessor - Responsible for processing messages (this is the fsharp module)

I have no idea if this is useful for you, but just wanted to let you know how we deal with this kind of problem.

TimothyP
This is simply a less verbose way to write the switch, right? Nice option though!
Erik van Brakel
Hey, in this case it is, but it's actually much more powerfull. And I've been told I could use discriminating unions to make it even better. Anyway let me see if I can find an example
TimothyP
TimothyP
+1  A: 

Well, there are certainly many ways. The standard one is to store functions in a dictionary. In functional languages you would write something like

import MyProtocol

handler  = { mListFirmware :  listFirmwareVersions,                  
             mLoadFirmware :  startLoadingFirmwareVersion,
             mLoadFirmwareBl: loadFirmwareVersionBlock, 
             ...
}

...
try {
    handler[message[0]](message[1:])
} catch (NotInTheDictionary e) {
    # complain ...
}

I'm not sure what's your version of C/C++/C#. If you can't put functions there, then put pointers to functions. If some of your functions are very small, in some languages you can put then right there with lambda:

 ...
             mLoadFirmware :  (lambda (m): start_load(m[1:3]); do_threads()),
 ...

There are more optimizations that I would do. See, for every message you have a constant and a function name. You don't have to repeat, though:

 Messages = new Array()

 def listFirmwareVersions(m):
      ...
     Messages.add(Name_Of_This_Method(), This_Method) 
     # it's possible to get name of current function in Python or C#

 ... # how to send 
 send_message(Messages.lookup(listFirmwareVersions), ...)

 ... # how to receive
 try {  
     Messages[message[0]](message[1:])
 ...

But if you want to be philosophically correct, you can have separate classes for handlers:

 class MessageHandler:
      static int message_handlers = [] 
      int msg
      Array data
      void handler
      Message(a_handler):               
          msg = message_handlers.add(this)
          handler = a_handler
      write(Stream s):
          s.write(msg, data)

  listFirmwareVersions = new MessageHandler(do_firmware)
  startLoadingFirmwareVersion = new MessageHandler(load_firmware) 
  ...

 ... # how to send 
 listFirmwareVersions.send(...)

 ... # how to receive
 try {
      message_handlers[message[0]](message[1:])
 ...
ilya n.
This is the option I went for, including the separate handler classes. Seems to be the best way to separate the bit of logic that's dealing with each separate message.
Erik van Brakel
Ok! Than, as Ned Flanders would say, you're doing everything by the book.
ilya n.