从字节流中解析可变长度描述符并对其类型进行操作

时间:2009-10-23 11:47:02

标签: parsing streaming header structure descriptor

我正在读取包含一系列可变长度描述符的字节流,我在代码中将其表示为各种结构/类。每个描述符都有一个与所有其他描述符相同的固定长度头,用于标识其类型。

是否有适当的模型或模式可以用来最好地解析和表示每个描述符,然后根据它的类型执行适当的操作?

6 个答案:

答案 0 :(得分:9)

我写了很多这类解析器。

我建议你读取固定长度的头文件,然后使用一个简单的switch-case调度到结构的正确构造函数,将固定的头部和流传递给该构造函数,以便它可以使用流的可变部分

答案 1 :(得分:2)

这是文件解析中的常见问题。通常,您阅读描述符的已知部分(在这种情况下幸运地是固定长度,但并非总是如此),并将其分支到那里。通常我在这里使用strategy pattern,因为我通常希望系统具有广泛的灵活性 - 但是直接开关或工厂也可以使用。

另一个问题是:您是否控制并信任下游代码?含义:工厂/战略实施?如果你这样做,那么你可以给它们流和你希望它们消耗的字节数(也许放置一些调试断言,以验证它们读取的数量恰好合适)。

如果你不能信任工厂/策略实现(也许你允许用户代码使用自定义反序列化器),那么我会在流的顶部构造一个包装器({{3这只允许消耗预期的字节数(之后报告EOF),并且不允许在该块之外进行搜索/等操作。我也有运行时检查(即使在发布版本中)已经消耗了足够的数据 - 但在这种情况下我可能只是读取任何未读数据 - 即如果我们期望下游代码消耗20个字节,但它只读取12 ,然后跳过下一个8并阅读我们的下一个描述符。

进一步扩展;这里的一个策略设计可能有:

interface ISerializer {
    object Deserialize(Stream source, int bytes);
    void Serialize(Stream destination, object value);
}

您可以为每个预期标记构建一个字典(或者只是一个列表,如果数字很小),并解析序列化程序,然后调用Deserialize方法。如果您无法识别标记,则(其中之一):

  • 跳过给定的字节数
  • 抛出错误
  • 将额外的字节存储在某个缓冲区中(允许意外数据的往返)

作为上述的附注 - 如果系统是在运行时通过反射或通过运行时DSL(等)确定的,则此方法(策略)非常有用。如果系统在编译时完全可预测(因为它没有改变,或者因为你正在使用代码生成),那么直接switch方法可能更合适 - 并且你可能不需要任何额外的接口,因为你可以直接注入适当的代码。

答案 2 :(得分:2)

要记住的一件事是,如果您正在从流中读取并且未检测到有效的标头/消息,请在再次尝试之前丢弃第一个字节。很多时候我看到整个数据包或消息被丢弃,这可能导致有效数据丢失。

答案 3 :(得分:1)

听起来这可能是Factory MethodAbstract Factory的工作。根据标题,您可以选择要调用的工厂方法,并返回相关类型的对象。

这是否比简单地将构造函数添加到switch语句更好取决于您正在创建的对象的复杂性和一致性。

答案 4 :(得分:0)

我建议:


fifo = Fifo.new

while(fd is readable) {
  read everything off the fd and stick it into fifo
  if (the front of the fifo is has a valid header and 
      the fifo is big enough for payload) {

      dispatch constructor, remove bytes from fifo
  }
}

使用这种方法:

  • 您可以对错误的有效负载进行一些错误检查,并可能丢弃不良数据
  • 数据没有等待fd的读缓冲区(可能是大型有效载荷的问题)

答案 5 :(得分:0)

如果您希望它是一个好的OO,您可以在对象层次结构中使用访问者模式。我是如何做到这一点的(用于识别从网络捕获的数据包,几乎与您可能需要的相同):

  • 庞大的对象层次结构,包含一个父类

  • 每个类都有一个静态构造函数,它注册了它的父级,所以父级知道它的直接子级(这是c ++,我认为在具有良好反射支持的语言中不需要这一步)

    < / LI>
  • 每个类都有一个静态构造函数方法,它获取了字节流的剩余部分,并根据它,决定是否有责任处理该数据

  • 当一个数据包进来时,我只是简单地将它传递给主父类(称为Packet)的静态构造方法,而后者又检查了它的所有子节点是否有责任处理该数据包,并且这是递归的,直到层次结构底部的一个类返回实例化的类。

  • 每个静态“构造函数”方法都从字节流中剪切自己的标头,并仅将有效负载传递给其子节点。

这种方法的优点是,您可以在对象层次结构中的任何位置添加新类型 WITHOUT 需要查看/更改 ANY 其他类。它非常好用于数据包;它是这样的:

  • 分组
  • EthernetPacket
  • IP分组
  • UDPPacket,TCPPacket,ICMPPacket
  • ...

我希望你能看到这个想法。