【问题标题】:Separating protocol parser and handler in Java在 Java 中分离协议解析器和处理程序
【发布时间】:2014-07-11 06:43:01
【问题描述】:

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

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

class Packet {
  byte[] raw;
}

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

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

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

我计划为每种数据包类型配备一个 PacketParser。可能,我也需要每种类型的处理程序类。例如:

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() 都会被调用,即使没有注册处理程序。

任何想法如何改进此代码?

注意:解析对程序应该是透明的。解析代码应该留在“解析库”中。所以理想情况下,程序只“知道”类 TypeXHandler 和 TypeX。

【问题讨论】:

  • " 对于每个数据包 parse() 都被调用,即使没有注册处理程序。" - 至少,似乎有必要调用解析器来跳过输入流中的数据包字节。您可以通过跳过数据包长度来读取数据包类型并跳过其余的解析(我假设每种数据包类型都有固定长度)。
  • handler 是您想要传递数据包信息的代码部分吗?它应该做什么?
  • 要摆脱parse 中的一些 重复,你不能做PacketParser parser; 使用开关来确定和设置解析器,然后在开关外面做parser.parse(packet);?我知道这并不深刻,但确实减少了逐字记录。

标签: java parsing


【解决方案1】:

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

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 的正确方法,因此您可以考虑一个列出类的属性文件(呃,但有它的优点),或者你的 main 方法中的一个硬编码的调用序列。

由于Parser 和Handler 在这里是函数式接口,不要忘记您可以使用lambdas 来实现它们!这样可以节省大量代码。

【讨论】:

    【解决方案2】:

    您说的需要一个抽象类来解析数据数组是对的。

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

    然后对于每种数据包类型(您说可以有 50 个,但如果第一个字节指示数据包的类型,则可能有 256 个不同的类型),您可以根据需要为某些类型创建类,例如.. Type1Parser for type 1 个用于 122 类型的 Type122Parser。

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

    【讨论】:

      【解决方案3】:

      好吧,就像 Torrestomp 的答案一样,我的代码来了:

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

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2011-09-10
        • 1970-01-01
        • 1970-01-01
        • 2011-04-20
        • 1970-01-01
        相关资源
        最近更新 更多