封装网络协议

发布于 2024-07-25 04:43:06 字数 779 浏览 2 评论 0 原文

我正在开发一个应用程序,它接受原始二进制消息(非常简单,第一个字节是消息类型,其余是有效负载),然后用它做一些事情。 我想要完成的事情是确保网络服务从应用程序的其余部分中抽象出来,以便允许时不时地修改协议,而不会过多影响应用程序的其余部分。 该应用程序的上下文是一个非常简单的客户端-服务器游戏,我现在正在为此做客户端工作。

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

让我们假设每条消息都是 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 语句。 我真的想不出一种正确的方法来将其切成更小的部分,这将导致一个巨大的、容易出错的方法。 我想知道是否有任何模式可以让这变得更容易,因为我找不到任何模式。

感谢您提前输入!

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!

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(4

榆西 2024-08-01 04:43:07

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

不过,它更加分布式:例如,当您创建新的消息类型(可能是新源文件中的新类)时,您可以调用现有方法将新委托添加到委托的静态集合中。

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.

谈下烟灰 2024-08-01 04:43:07

虽然不完全是 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 - 负责解析二进制数据并将其转换为 Message 类的实例

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

我不知道如果这对您有用,但只是想让您知道
我们如何处理此类问题。

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.

瞄了个咪的 2024-08-01 04:43:06

我有一些 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 类>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);

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);
雨的味道风的声音 2024-08-01 04:43:06

嗯,当然有很多方法。 标准的是将函数存储在字典中。 在函数式语言中,您可能会编写类似“

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

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:])
 ...
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文