2009-06-19 7 views
3

Je travaille sur une application qui reçoit un message binaire brut (très simple, le premier octet est le type de message, reste est une charge utile), puis fait quelque chose avec elle. Ce que j'essaie de faire, c'est de m'assurer que le service de réseautage est éloigné du reste de l'application, pour permettre de modifier le protocole de temps en temps sans trop affecter le reste de l'application. Le contexte de l'application est un jeu client-serveur très simple, pour lequel je fais le travail du client maintenant.Encapsulation d'un protocole réseau

Je me bats un peu maintenant. Je dois trouver une manière élégante de simplement jeter une connexion dans une sorte de service de traducteur/adaptateur, qui renvoie de jolis objets (je pense). Ces objets seront jetés dans une file d'attente en attente de consommation par le reste de l'application. Le problème que je suis face est plus ou moins cette construction (pseudo code):

Supposons que chaque message est 20 octets, donc je peux faire face à appeler cette fonction pour chaque 20 octets:

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

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

Évidemment, je vais utiliser une énumération ou des constantes pour l'affaire, mais ce n'est pas ce qui me dérange. Voici la chose. Je pense que j'ai environ 50 types de message, ce qui signifie que je vais obtenir une déclaration de commutateur avec 50 cas. Je ne peux pas vraiment penser à une manière appropriée de découper ceci en morceaux plus petits, qui résulteront en une méthode énorme, sujette aux erreurs. Je me demandais s'il y avait des motifs pour rendre cela plus facile, car je n'en trouvais pas.

Merci pour la contribution à l'avance!

Répondre

1

Eh bien, il y a certainement plusieurs façons. La norme est de stocker des fonctions dans un dictionnaire. Dans les langages fonctionnels vous écrire quelque chose comme

import MyProtocol 

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

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

Je ne sais pas quelle est votre version de C/C++/C#. Si vous ne pouvez pas y mettre des fonctions, placez des pointeurs sur les fonctions. Si certaines de vos fonctions sont très petites, dans certaines langues, vous pouvez mettre alors là avec lambda:

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

Il y a plus d'optimisations que je ferais. Voir, pour chaque message, vous avez une constante et un nom de fonction. Vous ne devez pas répéter, si:

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

Mais si vous voulez être philosophiquement correcte, vous pouvez avoir des classes séparées pour les gestionnaires:

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

C'est l'option que j'ai choisie, y compris les classes de gestionnaire séparées. Semble être le meilleur moyen de séparer le peu de logique qui traite chaque message séparé. –

+0

Ok! Que, comme dirait Ned Flanders, vous faites tout par le livre. –

1

Vous pouvez avoir un tableau de 50 pointeurs de fonction (c'est-à-dire délégués C#) que vous indexez en utilisant la valeur du premier octet ou un dictionnaire de délégués dont la clé est la valeur du premier octet. C'est simplement une autre façon d'écrire une déclaration de commutateur. Il est plus distribué: par exemple, lorsque vous créez un nouveau type de message (qui peut être une nouvelle classe dans un nouveau fichier source), alors au lieu d'éditer le code source pour ajouter un nouveau cas à une grosse instruction switch, vous pouvez appeler une méthode existante pour ajouter un nouveau délégué à la collection statique de délégués.

2

J'ai un code Java qui fait cela. J'espère que vous pouvez facilement traduire en C#. Essentiellement, j'ai une collection messageMap:

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

Ceci est une carte d'ID de message à leurs Message classes correspondant. J'appelle addMessage une fois pour chaque type de message:

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

Puis, quand un message arrive je l'ai lu l'ID de message hors du fil, regardez la classe Message je dois instancier dans messageMap, et ensuite utiliser la réflexion pour créer un instance de cette classe.

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

Ici newInstance() appelle le constructeur par défaut.

J'ai utilisé ce code générique de gestion des messages sur plusieurs applications avec des messages différents. Chacun a juste un joli bloc de code simple enregistrement des différents messages comme ceci:

// 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

Bien que pas exactement une solution C#, je l'ai traité une situation similaire récemment. Ma solution était d'utiliser F # ce qui le rend beaucoup plus facile.

Par exemple, mon code ressemble à ceci

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) 

Ce n'est pas la solution parfaite, mais ressemble beaucoup mieux que l'instruction switch en C#. Donc toute notre application est écrite en csharp, mais l'analyseur de message est écrit en fsharp.

Pour votre information: Nous avons plusieurs interfaces:

IDataTransportServer - Responsable de la réception de données via RS232 ou TCP/IP

IDataProcessor - Responsable de l'analyse des données binaires et de le transformer en instances de la classe de message

IMessageProcessor - Responsable du traitement des messages (c'est le module fsharp)

Je ne sais pas si cela est utile pour vous, mais je voulais juste t vous savez comment nous traitons ce genre de problème.

+0

Ceci est simplement une façon moins prolixe pour écrire le interrupteur, non? Belle option cependant! –

+0

Hey, dans ce cas, c'est, mais c'est en réalité beaucoup plus puissant. Et on m'a dit que je pourrais utiliser des syndicats discriminants pour le rendre encore meilleur. Quoi qu'il en soit laissez-moi voir si je peux trouver un exemple – TimothyP

+0

Eh bien l'exemple que j'ai eu a été retiré de la pastebin, mais fondamentalement, vous pouvez "basculer" sur plusieurs variables en même temps, ce qui est beaucoup plus puissant que le commutateur. Vous pouvez même avoir des restrictions. Comme | m quand m> 10 && m < 20 ->() – TimothyP

Questions connexes