2009-06-19 196 views
3

我正在研究一个应用程序,它接收一个原始的二进制消息(非常简单,第一个字节是消息类型,其余是有效载荷),然后用它做些事情。我试图完成的事情是确保将网络服务从应用程序的其余部分抽象出来,以允许现在修改协议,而不会影响应用程序的其他部分。 该应用程序的上下文是一个非常简单的客户端 - 服务器游戏,为此我正在做客户端的工作。封装网络协议

虽然我现在有点挣扎。我需要找到一种优雅的方式来将连接引入某种翻译器/适配器服务,它会返回漂亮的对象(我认为)。这些对象将被抛出队列,等待应用程序的其余部分使用。我现在面临的问题是或多或少这个结构(伪代码):

假设每个消息是20个字节,这样我就可以处理调用这个函数为每个20个字节:

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

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

很显然,我会用一个枚举或常量来处理这个案例,但这不是那个让我烦恼的事情。 这是事情。我认为我有大约50种消息类型,这意味着我将得到一个包含50个案例的switch语句。我无法真正想到一种合适的方式将它分解成更小的部分,这将导致巨大的,容易出错的方法。我想知道是否有任何模式可以使这更容易,因为我找不到任何模式。

感谢您的提前输入!

回答

1

那么,肯定有很多方法。标准的一个是将函数存储在字典中。在功能语言中,你会写如

import MyProtocol 

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

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

我不确定C/C++/C#的版本是什么。如果你不能把函数放在那里,然后把指针指向函数。如果您的一些功能都非常小,在一些语言中,你可以把再向右那里lambda

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

有更多的优化,我会怎么做。对于每个消息,你都有一个常量和一个函数名。你不必再重复,但:

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:]) 
... 

但是,如果你想成为哲学上是正确的,你可以有单独的类的处理程序:

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:]) 
... 
1

您可以使用第一个字节的值进行索引的50个函数指针(即C#委托)的数组或第一个字节的值为关键字的委托字典。这只是编写switch语句的另一种方式。

虽然它的分布比较分散:例如,当您创建新的消息类型(可能是新源文件中的新类)时,则不是编辑源代码以将新的情况添加到大的switch语句,您可以调用现有方法将新委托添加到静态委托集合中。

2

我有一些这样做的Java代码。希望您可以轻松转换为C#。从本质上讲,我有一个messageMap集合:

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

这是从消息ID映射到其相应Message类。我称addMessage一次为每个不同的消息类型:

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

然后,当一个消息到达予读出导线中的消息ID,查找该Message I类需要在messageMap实例化,然后使用反射来创建一个该类的实例。

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

这里newInstance()调用默认构造函数。

我在不同消息的多个应用程序中使用了这个通用的消息处理代码。每个人只是有代码的一个不错的,简单的块注册不同的消息,像这样:

// 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); 
1

虽然不完全是一个C#的解决方案,我最近处理了类似的情况。 我的解决方案是使用F#,这使得它更容易。

例如,我的代码看起来像这样

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) 

这不是完美的解决方案,但看起来比C#中的switch语句好多了。 所以我们的整个应用程序都是用csharp写的,但是消息解析器是用fsharp写的。

供参考: 我们有几个接口:

IDataTransportServer - 负责通过RS232或TCP/IP

IDataProcessor接收数据 - 负责解析二进制数据,使之成为所述消息类的实例

IMessageProcessor - 负责处理消息(这是fsharp模块)

我不知道这是否对您有用,但只是想乐你知道 我们如何处理这类问题。

+0

这仅仅是写一个更简洁的方式开关,对吗?不错的选择,但! – 2009-06-21 22:52:58

+0

嘿,在这种情况下,它实际上更强大。而且我被告知可以使用歧视性工会让它变得更好。无论如何,让我看看我是否能找到一个例子 – TimothyP 2009-06-22 01:29:16