在Java中分离协议解析器和处理程序

时间:2014-07-10 20:09:41

标签: java parsing

我正在使用简单的二进制协议。每个数据包由10个字节组成。第一个字节指定数据包类型。使用了很多(约50种)数据包类型。

我想为此协议编写一个通用解析器,它与数据包的处理无关。因此,解析器应该检测数据包类型并将数据放入适当的数据包类的实例中,该实例包含协议数据。例如,考虑以下类:当解析器检测到包类型1时 - >新的Type1()并读取原始字节并设置温度和湿度。类似于数据包类型2和所有其他数据包类型。

class Packet {
  byte[] raw;
}

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

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

由于有很多数据包类型,但每个应用程序只使用很少的数据包,因此应该可以在解析开始之前注册某些类型。忽略所有其他数据包类型。

我打算为每种数据包类型配备一个PacketParser。可能我也需要每种类型的处理程序类。 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);
  }
}

如何连接解析器和处理程序?超越天真的方法: 程序需要实现Type1Handler并设置静态变量Type1Parser.type1Handler。

然后主解析器可能如下所示:

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

然而,这似乎是1)很多非常相似的代码行2)很多开销,因为所有数据包解析器都被实例化,并且每个数据包都会调用parse(),即使没有注册处理程序。 / p>

如何改进此代码?

注意:解析应该对程序透明。解析代码应保留在"解析库中#34;。因此,理想情况下,该计划只会知道"类TypeXHandler和TypeX。

3 个答案:

答案 0 :(得分:2)

这个设计问题没有完美的答案,我不想假装我的,但希望我本能地解决这个问题可以教给你一些你不知道的东西!我看到的代码中缺少的主要组件是泛型:

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

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

这样,您可以使用延迟静态初始化来管理您知道的数据包类型。我不会在这里完全充实代码,而是给你一个想法:

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

===

我省略的内容:限定符,低级实现,错误检查,同步,主方法等。根据您的设置,静态初始化可能不是调用TypeRegistry.register的正确方法,所以你可以而是考虑一个属性文件,列出类(呃,但有其优点),或主方法中的硬编码调用序列。

由于此处ParserHandler是功能接口,因此不要忘记您可以使用lambdas实现它们!您可以通过这种方式保存大量代码。

答案 1 :(得分:1)

当你说需要一个抽象类来解析数据数组时,你是对的。

    package parser;

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

    }   

然后对于每种数据包类型(你说你可以有50但如果第一个字节表示数据包的类型,则可能有256种不同的类型),你可以根据需要为某种类型创建类,例如.. Type1Parser for type 1 Type122Parser用于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) {}
    }

然后你可以有一个代表所有人的主解析器的类。如果您需要每个收入数据包都有一个对象供以后使用,那么您可以将其保存在向量中。

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

}

答案 2 :(得分:0)

好吧,几乎就像torquestomp的回答,这是我的代码:

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

以下是您可以使用它的方法:

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