2014-07-10 3 views
6

Je travaille avec un protocole binaire simple. Chaque paquet est composé de 10 octets. Le premier octet spécifie le type de paquet. Il y a beaucoup (environ 50) types de paquets utilisés.Séparer l'analyseur de protocole et le gestionnaire en Java

Je veux écrire un analyseur général pour ce protocole qui est indépendant de la gestion des paquets. L'analyseur doit donc détecter le type de paquet et placer les données dans une instance de la classe de paquets appropriée, qui contient les données de protocole. Par exemple, en considérant les classes ci-dessous: Lorsque l'analyseur détecte le type de paquet 1 -> new Type1() et lit les octets bruts et définit la température et l'humidité. De même pour le type de paquet 2 et tous les autres types de paquets.

class Packet { 
    byte[] raw; 
} 

class Type1 extends Packet { 
    int temperature; 
    int humidity; 
} 

class Type2 extends Packet { 
    DateTime sunrise; 
    DateTime sunset; 
} 

Comme il y a tant de types de paquets, mais chaque application utilise que très peu, il devrait être possible d'enregistrer pour certains types avant l'analyse commence. Tous les autres types de paquets sont ignorés.

Je prévois d'avoir un PacketParser pour chaque type de paquet. Probablement, j'ai besoin d'un cours de handler pour chaque type aussi. E.g .:

abstract class Type1Parser { 
    abstract void handle(Type1 packet); 
} 

class Type1Parser extends PacketParser { 
    //how to use/set handler? how to pass packet to handler? 
    static public Type1Handler type1Handler = null; 

    @override 
    void parse(Packet input) { 
    if(type1Handler == null) 
     return; 
    Type1 packet = new Type1(input); 
    packet.temperature = byteToInt(input.raw, 0, 3); 
    packet.humidity = byteToInt(input.raw, 4, 7); 

    type1Handler.handle(packet); 
    } 
} 

Comment se connecter parser et handler? Au-dessus d'une approche naïve: Le programme doit implémenter Type1Handler et définir la variable statique Type1Parser.type1Handler.

Ensuite, l'analyseur principal peut ressembler à ceci:

class MainParser { 
    Type1Parser type1 = new Type1Parser(); 
    Type2Parser type2 = new Type2Parser(); 
    ... 
    void parse(byte[] packet) { 
    switch(packet[0]) { 
     case 1: type1.parse(packet); break; 
     case 2: type2.parse(packet); break; 
     ... 
    } 
    } 
} 

Cependant, cela semble être 1) un grand nombre de lignes très similaires de code 2) beaucoup de frais généraux, puisque tout analyseur de paquets sont instanciés et pour chaque paquet parse() est appelé, même si aucun gestionnaire n'est enregistré.

Des idées pour améliorer ce code?

Remarque: L'analyse doit être transparente pour le programme. Le code d'analyse doit rester dans la "bibliothèque d'analyse". Donc idéalement, le programme ne "connaît" que les classes TypeXHandler et TypeX.

+0

"pour chaque paquet analysé() est appelé, même si aucun gestionnaire n'est enregistré." - semble être nécessaire d'appeler un analyseur, au moins, pour sauter les octets des paquets dans le flux d'entrée. Vous pouvez lire le type de paquet et ignorer le reste de l'analyse en sautant simplement la longueur du paquet (j'ai supposé que chaque type de paquet avait une longueur fixe). –

+0

Est-ce que le gestionnaire de la partie de code que vous souhaitez transmettre les informations de paquet à? Qu'est-ce que c'est supposé faire? – NESPowerGlove

+0

Pour se débarrasser de la répétition de * some * dans 'parse', ne pouvez-vous pas faire l'analyseur' PacketParser '? Utilisez le commutateur pour déterminer et définir l'analyseur, puis sur le commutateur 'parser.parse (paquet);'? Je sais que ce n'est pas profond mais réduit le verbatim. – ChiefTwoPencils

Répondre

0

Eh bien, presque comme réponse torquestomp, voici mon code:

interface Packet { 
} 
interface PacketParser<T extends Packet> { 
    Class<T> getPacketClass(); 
    int getPacketId(); 
    int getPacketLength(); 
    Packet parse(byte[] raw, int offset); 
} 
interface PacketListener<T extends Packet> { 
    Class<T> getPacketClass(); 
    void onPacket(T packet); 
} 
interface PacketParsersRegistry { 
    <T extends Packet> void registerPacketParser(PacketParser<T> packetParser); 
    <T extends Packet> void registerPacketListener(PacketListener<T> packetListener); 
} 
class PacketHandlers<T extends Packet> { 
    final PacketParser<T> parser; 
    PacketListener<T> listener; 

    PacketHandlers(PacketParser<T> parser) { 
     this.parser = parser; 
    } 

    void setListener(PacketListener<T> listener) { 
     this.listener = listener; 
    } 
} 
class MainParser implements PacketParsersRegistry { 
    private final HashMap<Class<?>, PacketHandlers<?>> handlers = new HashMap<>(); 
    private final HashMap<Integer, PacketParser> parsers = new HashMap<>(); 

    @Override 
    public <T extends Packet> void registerPacketParser(PacketParser<T> packetParser) { 
     parsers.put(packetParser.getPacketId(), packetParser); 

     Class<T> packetClass = packetParser.getPacketClass(); 
     handlers.put(packetClass, new PacketHandlers<>(packetParser)); 
    } 

    @Override 
    public <T extends Packet> void registerPacketListener(PacketListener<T> packetListener) { 
     //noinspection unchecked 
     PacketHandlers<T> handlers = (PacketHandlers<T>) this.handlers.get(packetListener.getPacketClass()); 
     if (handlers != null) { 
      handlers.setListener(packetListener); 
     } 
    } 

    void parse(byte[] stream, int offset) { 
     while (offset < stream.length) { 
      int type = stream[offset]; 
      PacketParser parser = parsers.get(type); 
      // parser m.b. != null here 
      PacketListener listener = (PacketListener) handlers.get(parser.getPacketClass()); 
      if (listener != null) { 
       Packet packet = parser.parse(stream, offset); 
       //noinspection unchecked 
       listener.onPacket(packet); 
      } 
      offset += parser.getPacketLength(); 
     } 
    } 
} 

Et voici comment vous pouvez l'utiliser:

class HumidityPacket implements Packet {} 

public class Main { 
    public static void main(String[] args) { 
     MainParser parser = new MainParser(); 
     //... 
     parser.registerPacketListener(new PacketListener<HumidityPacket>() { 
      @Override 
      public Class<HumidityPacket> getPacketClass() { 
       return HumidityPacket.class; 
      } 

      @Override 
      public void onPacket(HumidityPacket packet) { 
       // todo 
      } 
     }); 
    } 
} 
2

Il n'y a pas de réponse parfaite à cette question de conception, et je ne veux pas prétendre que la mienne est, mais j'espère que mon approche instinctive de ce problème vous apprendra des choses que vous ne saviez pas déjà! La principale composante manquante de votre code que je vois est Génériques:

public interface Parser<T extends Packet> { 
    T parse(Packet packet); 
} 

public interface Handler<T extends Packet> { 
    void handle(T packet); 
} 

De cette façon, vous pouvez utiliser l'initialisation statique paresseux pour gérer les types de paquets que vous êtes au courant. Je ne la chair le code tout à fait, mais pour vous donner une idée:

public class TypeRegistry { 
    private static Map<Integer, TypeHandlerBundle<?>> typeHandlerBundles; 

    static <T> register(int typeNum, Class<T> clazz, Parser<T> parser, Handler<T> handler) { 
    // Make bundle, add to map 
    } 

    ... void parse(Packet packet) { 
    if (typeHandlerBundles.containsKey((int) packet[0])) { 
     TypeHandlerBundle<?> bundle = typeHandlerBundles.get((int) packet[0]); 
     bundle.parseAndHandle(packet); 
    } 
    } 
} 

public class TypeHandlerBundle<T extends Packet> { 
    ... 
    private final Parser<T> parser; 
    private final Handler<T> handler; 

    ... void parseAndHandle(Packet packet) { 
    T parsedPacket = parser.parse(packet); 
    handler.handle(parsedPacket); 
    } 
} 

... 

public class Type1Processor { 
    static { 
    TypeRegistry.register(1, Type1.class, TYPE1_PARSER, TYPE1_HANDLER); 
    } 

    // Definition of constants, implementation, etc. 
    // ... 
} 

===

choses que j'omis: Qualifiers, faible mise en œuvre de niveau, Vérification des erreurs, la synchronisation, la méthode principale En fonction de votre configuration, l'initialisation statique peut ne pas être la bonne façon d'appeler TypeRegistry.register. Vous pouvez donc envisager un fichier de propriétés qui répertorie les classes (mais a ses mérites) ou une séquence codée en dur de appelle dans votre méthode principale. Comme Parser et Handler sont des interfaces fonctionnelles ici, n'oubliez pas que vous pouvez les implémenter avec lambdas! Vous pouvez enregistrer des tonnes de lignes de code de cette façon.

1

Vous aviez raison quand vous avez dit que le besoin d'une classe abstraite pour analyse du tableau de données.

package parser; 

    public abstract class TypeParser { 
     public abstract void parse(byte[] arr); 

    } 

Ensuite, pour chaque type de paquet (vous avez dit que vous pouvez avoir 50 mais si le premier octet indique le type de paquet, puis 256 types déférents sont possibles), vous pouvez créer la classe que vous avez besoin pour certains, par exemple de type. . Type1Parser pour le type 1 Type122Parser pour le type 122.

package parser.type; 

import parser.TypeParser; 

public class Type1Parser extends TypeParser{  

    public void parse(byte[] array){ 
       // do with the bytes of array what you want 
      } 
} 

package parser.type; 

import parser.TypeParser; 

public class Type122Parser extends TypeParser { 
    public void parse(byte[] arr) {} 
    } 

alors vous pouvez avoir une classe qui représente l'analyseur principal pour tous. Si vous avez besoin de chaque paquet de revenu pour avoir un objet pour une utilisation ultérieure, vous pouvez le garder en vecteur.

package parser; 

import java.util.Vector; 

public class MainParser { 

    private Vector<TypeParser> vecTypeParse=new Vector<TypeParser>(); 

    public void parsePacket(byte[] array){ 
     if(array==null || array.length<1) return; // or throw some exception   
     int typePacket=array[0]&0xff; 
     String s="parser.type.Type"+String.valueOf(typePacket)+"Parser"; 
     TypeParser type=null; 
     try { 
     type=(TypeParser)Class.forName(s).newInstance(); //here you create class that you need 
     } catch(InstantiationException e) {e.printStackTrace(); 
     } catch(IllegalAccessException e) {e.printStackTrace(); 
     } catch(ClassNotFoundException e) {e.printStackTrace();} 

     // you can do something with the exceptons 
     if(type==null) return; // or throw some exception 
     type.parse(array); // here parse data for class you just created. 
     this.vecTypeParse.addElement(type);  
     } 

} 
Questions connexes