2014-07-10 5 views
6

Ich arbeite mit einem einfachen, binären Protokoll. Jedes Paket besteht aus 10 Bytes. Das erste Byte gibt den Pakettyp an. Es werden viele (~ 50) Pakettypen verwendet.Protokoll-Parser und Handler in Java trennen

Ich möchte einen allgemeinen Parser für dieses Protokoll schreiben, das unabhängig von der Behandlung von Paketen ist. Daher sollte der Parser den Pakettyp erkennen und die Daten in eine Instanz der entsprechenden Paketklasse einfügen, die die Protokolldaten enthält. Z. B. unter Berücksichtigung der folgenden Klassen: Wenn der Parser den Pakettyp 1 -> neuen Typ 1() erkennt und Rohbytes liest und Temperatur und Feuchtigkeit einstellt. Ähnlich für Pakettyp 2 und alle anderen Pakettypen.

class Packet { 
    byte[] raw; 
} 

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

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

Da es so viele Pakettypen sind aber jede Anwendung verwendet nur sehr wenige, sollte es möglich sein, für bestimmte Arten zu registrieren, bevor beginnt das Parsen. Alle anderen Pakettypen werden ignoriert.

Ich plane, einen PacketParser für jeden Pakettyp zu haben. Wahrscheinlich brauche ich auch eine Handler-Klasse für jeden Typ. Z.B .:

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

Wie Parser und Handler verbinden? Über einem naiven Ansatz: Das Programm muss Type1Handler implementieren und die statische Variable Type1Parser.type1Handler festlegen.

Dann kann der Haupt-Parser wie folgt aussehen:

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; 
     ... 
    } 
    } 
} 

Dies scheint jedoch 1) zu sein, viele sehr ähnliche Zeilen Code 2) eine Menge Aufwand, da alle Paketparser instanziiert und Für jedes Paket wird parse() aufgerufen, auch wenn kein Handler registriert ist.

Haben Sie Ideen, wie Sie diesen Code verbessern können?

Hinweis: Das Parsing sollte für das Programm transparent sein. Parsing-Code sollte in der "Parsing-Bibliothek" bleiben. Im Idealfall "kennt" das Programm also nur die Klassen TypeXHandler und TypeX.

+0

"für jedes Paket wird parse() aufgerufen, auch wenn kein Handler registriert ist." - Es scheint notwendig zu sein, mindestens einen Parser aufzurufen, um die Bytes der Pakete im Eingangsstrom zu überspringen. Sie können den Pakettyp lesen und den Rest des Parsings überspringen, indem Sie einfach die Paketlänge überspringen (ich nahm an, dass jeder Pakettyp eine feste Länge hat). –

+0

Handler ist der Teil des Codes, an den Sie die Paketinformation weitergeben möchten? Was soll es tun? – NESPowerGlove

+0

Um * einige * Wiederholungen in 'Parse 'loszuwerden, können Sie' PacketParser Parser' nicht verwenden. Verwenden Sie den Schalter, um den Parser zu bestimmen und zu setzen, und wechseln Sie dann den Schalter 'parser.parse (packet);'? Ich weiß, dass es nicht tiefgründig ist, aber es reduziert das Wort. – ChiefTwoPencils

Antwort

0

Nun, fast wie torquestomp Antwort, hier kommt mein 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(); 
     } 
    } 
} 

Und hier ist, wie Sie es verwenden können:

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

Es gibt keine perfekte Antwort auf diese Designfrage, und ich möchte nicht so tun, als ob es meine ist, aber hoffentlich bringt meine instinktive Annäherung an dieses Problem Ihnen Dinge bei, die Sie noch nicht kennen! Die Haupt fehlende Komponente aus dem Code, den ich sehe, ist Generics:

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

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

Auf diese Weise können Sie faul statische Initialisierung verwenden, um die Pakettypen Sie sind sich dessen bewusst zu verwalten. Ich werde den Code nicht vollständig hier Fleisch, aber Ihnen eine Idee geben:

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

===

Dinge, die ich ausgelassen: Qualifikation, niedrigere Level-Implementierung, Fehlerprüfung, Synchronisation, Hauptmethode usw. Abhängig von Ihrer Konfiguration ist die statische Initialisierung möglicherweise nicht der richtige Weg, um TypeRegistry.register aufzurufen. Sie könnten also stattdessen eine Eigenschaftendatei in Erwägung ziehen, die die Klassen auflistet (ugh, hat aber ihre Vorzüge) oder eine fest codierte Sequenz von ruft Ihre Hauptmethode auf.

Da Parser und Handler funktionale Schnittstellen sind, vergessen Sie nicht, dass Sie sie mit Lambdas implementieren können! Sie können Tonnen von Codezeilen auf diese Weise speichern.

1

Sie hatten Recht, als Sie sagten, dass Bedarf eine abstrakte Klasse für Parsing von Daten.

package parser; 

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

    } 

Dann für jeden Pakettyp (Sie haben gesagt, Sie 50 haben, aber wenn das erste Byte den Typ des Pakets dann 256 deferent Typen sind möglich angibt), können Sie Klasse erstellen, wie Sie für bestimmte Art zB benötigen. . Type1Parser für Typ-1-Type122Parser für Typ 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) {} 
    } 

Dann können Sie eine Klasse, die die Haupt-Parser für alle darstellt. Wenn Sie für jedes Einkommenspaket ein Objekt für die spätere Verwendung benötigen, können Sie es in Vektor halten.

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

} 
Verwandte Themen