簡體   English   中英

封裝網絡協議

[英]Encapsulating a network protocol

我正在開發一個應用程序,該應用程序接收原始的二進制消息(非常簡單,第一個字節是消息類型,其余是有效負載),然后對其進行處理。 我要完成的事情是確保將網絡服務從應用程序的其余部分中抽象出來,以允許不時修改協議,而又不會對應用程序的其余部分造成太大影響。 該應用程序的上下文是一個非常簡單的客戶端-服務器游戲,我現在正在為此做客戶端工作。

我現在有點掙扎。 我需要找到一種優雅的方法來將連接放入某種翻譯器/適配器服務,該服務會返回漂亮的對象(我認為)。 這些對象將被放入隊列中,以等待其余的應用程序使用。 我面臨的問題或多或少是這種結構(偽代碼):

假設每個消息是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語句。 我真的想不出將其切成小塊的正確方法,這將導致產生巨大的,容易出錯的方法。 我想知道是否有任何模式可以簡化此過程,因為我找不到任何模式。

感謝您的預先輸入!

我有一些執行此操作的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,查找我需要在messageMap實例化的Message類,然后使用反射來創建該類的實例。

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

您可能有一個由50個函數指針(即C#委托)組成的數組,您可以使用第一個字節的值對其進行索引,也可以擁有代表其鍵為第一個字節的值的代表字典。 不過,這只是編寫switch語句的另一種方式。

不過,它分布更廣:例如,當您創建新的消息類型(可能是新的源文件中的新類)時,您可以調用一個將新委托添加到委托的靜態集合的現有方法。

雖然不是完全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模塊)

我不知道這對您是否有用,只是想讓您知道我們如何處理此類問題。

好吧,當然有很多方法。 標准的一種是將功能存儲在字典中。 在功能語言中,您可能會寫類似

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

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM