【发布时间】:2009-10-23 11:47:02
【问题描述】:
我正在从包含一系列可变长度描述符的字节流中读取数据,这些描述符在我的代码中表示为各种结构/类。每个描述符都有一个与所有其他描述符相同的固定长度标头,用于标识其类型。
是否有合适的模型或模式可以用来最好地解析和表示每个描述符,然后根据它的类型执行适当的操作?
【问题讨论】:
标签: parsing streaming header structure descriptor
我正在从包含一系列可变长度描述符的字节流中读取数据,这些描述符在我的代码中表示为各种结构/类。每个描述符都有一个与所有其他描述符相同的固定长度标头,用于标识其类型。
是否有合适的模型或模式可以用来最好地解析和表示每个描述符,然后根据它的类型执行适当的操作?
【问题讨论】:
标签: parsing streaming header structure descriptor
我写过很多这样的解析器。
我建议您阅读固定长度的标头,然后使用简单的 switch-case 将正确的构造函数分派给您的结构,将固定的标头和流传递给该构造函数,以便它可以使用流的可变部分.
【讨论】:
这是文件解析中的常见问题。通常,您读取描述符的 known 部分(幸运的是,在这种情况下它是固定长度的,但并非总是如此),然后将其分支到那里。通常我在这里使用strategy pattern,因为我通常希望系统具有广泛的灵活性——但直接开关或工厂也可以工作。
另一个问题是:您是否控制并信任下游代码?含义:工厂/战略实施?如果你这样做了,那么你可以只给他们流和你期望他们消耗的字节数(也许放置一些调试断言,以验证他们确实读取了正确的数量)。
如果您不能信任工厂/策略实现(也许您允许用户代码使用自定义反序列化器),那么我将在流顶部构造一个包装器 (example: SubStream from protobuf-net ),只允许消耗预期的字节数(之后报告 EOF),并且不允许在此块之外进行查找/等操作。我还会进行运行时检查(即使在发布版本中)是否已经消耗了足够的数据——但在这种情况下,我可能只会读取任何未读数据——即,如果我们期望下游代码消耗 20 个字节,但它只读取 12 ,然后跳过接下来的 8 个并读取我们的下一个描述符。
对此进行扩展;这里的一种策略设计可能类似于:
interface ISerializer {
object Deserialize(Stream source, int bytes);
void Serialize(Stream destination, object value);
}
您可以为每个预期的标记构建此类序列化程序的字典(如果数量很少,则只是一个列表),并解析您的序列化程序,然后调用 Deserialize 方法。如果您不认识标记,则(其中之一):
作为上述的旁注 - 如果系统是在运行时确定的,无论是通过反射还是通过运行时 DSL(等),这种方法(策略)很有用。如果系统在编译时完全是可预测的(因为它没有改变,或者因为您正在使用代码生成),那么直接的 switch 方法可能更合适 - 你可能不需要任何额外的接口,因为您可以直接注入适当的代码。
【讨论】:
要记住的关键一件事是,如果您正在从流中读取并且未检测到有效的标头/消息,请在重试之前仅丢弃第一个字节。很多时候我看到整个数据包或消息被丢弃,这可能导致有效数据丢失。
【讨论】:
这听起来可能是Factory Method 或Abstract Factory 的工作。根据标题,您可以选择调用哪个工厂方法,并返回相关类型的对象。
这是否比简单地将构造函数添加到 switch 语句更好取决于您正在创建的对象的复杂性和一致性。
【讨论】:
我建议:
fifo = FIFO.new 而(fd 可读){ 从 fd 中读取所有内容并将其粘贴到 fifo if (fifo 的前面有一个有效的头部并且 fifo 对于有效载荷足够大){ 调度构造函数,从 fifo 中删除字节 } }用这个方法:
【讨论】:
如果您希望它是好的 OO,您可以在对象层次结构中使用访问者模式。我是这样做的(用于识别从网络捕获的数据包,几乎与您可能需要的相同):
庞大的对象层次结构,只有一个父类
每个类都有一个向其父类注册的静态构造函数,因此父类知道它的直接子类(这是 c++,我认为在具有良好反射支持的语言中不需要此步骤)
每个类都有一个静态构造方法来获取字节流的剩余部分,并据此决定是否由他负责处理该数据
每个静态“构造函数”方法都从字节流中切出自己的标头,并仅将有效负载传递给其子级。
这种方法的好处是您可以在对象层次结构中的任何位置添加新类型无需需要查看/更改ANY其他类。它对数据包非常好用;它是这样的:
我希望你能看到这个想法。
【讨论】: