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

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

有帮助吗?

解决方案

我写了很多这类解析器。

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

其他提示

这是文件解析中的常见问题。通常,您阅读 已知的 描述符的一部分(幸运的是在这种情况下是固定长度的,但并非总是如此),并将其分支到那里。一般来说我用一个 策略模式 在这里,因为我通常期望系统具有广泛的灵活性 - 但直接交换机或工厂也可以工作。

另一个问题是:您控制并信任下游代码吗?意义:工厂/策略实施?如果这样做,那么您只需为它们提供流和您期望它们消耗的字节数(也许放置一些调试断言,以验证它们是否正确) 准确读取正确的数量)。

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

对此进行扩展;这里的一种策略设计可能类似于:

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

您可以根据预期标记构建此类序列化器的字典(如果数量很小,则只是一个列表),并解析您的序列化器,然后调用 Deserialize 方法。如果您不认识该标记,则(其中之一):

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

作为上述内容的旁注 - 如果系统是在运行时确定的,无论是通过反射还是通过运行时 DSL(等等),这种方法(策略)很有用。如果系统是 完全 在编译时可预测(因为它不会改变,或者因为您正在使用代码生成),然后直接 switch 方法可能更合适 - 并且您可能不需要任何额外的接口,因为您可以直接注入适当的代码。

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

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

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

我会建议:

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 的读取缓冲区上等待(对于大负载来说可能是一个问题)

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

  • 巨大的对象层次结构,只有一个父类

  • 每个类都有一个向其父类注册的静态构造函数,因此父类知道其直接子类(这是 C++,我认为在具有良好反射支持的语言中不需要此步骤)

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

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

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

这种方法的优点是您可以在对象层次结构中的任何位置添加新类型 没有 需要看到/改变 任何 其他类。对于数据包来说,它的效果非常好。事情是这样的:

  • 以太网数据包
  • IP包
  • UDP数据包、TCP数据包、ICMP数据包
  • ...

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

许可以下: CC-BY-SA归因
不隶属于 StackOverflow
scroll top