2009-10-23 27 views
2

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

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

回答

9

我写了很多这些类型的解析器。

我建议您阅读固定长度的头文件,然后使用简单的开关箱将正确的构造函数分派给您的结构,将固定头文件和流传递给该构造函数,以便它可以使用该变量的部分流。

+0

这个答案到底是什么问题?这是最好的! – 2009-10-24 13:28:39

+1

+1,简单易用。 – 2009-10-31 02:59:17

+0

同意,+1,这个简单而简洁。 – 2009-11-02 00:34:50

1

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

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

2

这是文件解析中的常见问题。通常情况下,读取已知的部分描述符(幸运的是在这种情况下为固定长度,但并不总是),并将其分支到那里。一般来说,我在这里使用的是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方法可能更合适 - 并且您可能不需要任何额外的接口,因为你可以直接注入适当的代码。

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的读缓冲区(可能是大有效载荷的问题)
+0

一个很好的建议输入流,将不允许随机访问像套接字和这样的 – 2009-11-03 08:02:48

2

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

+0

一个有用的建议 – 2009-11-04 11:02:30

0

如果你希望它是很好的OO,你可以在对象层次结构中使用访问者模式。如何我已经做到了像这样(为识别捕捉断的网络数据包,非常可能需要同样的事情):

  • 巨大的对象层次,一个父类

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

  • 每个类都有一个静态构造方法得到了字节流的剩余部分,并据此决定它是否是h是否负责处理该数据

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

  • 每个静态的“构造函数”方法都会从字节流中删除它自己的头文件,并仅将有效内容传递给其子节点。

这种方法的好处是,你可以在任何地方对象层次WITHOUT需要查看/修改任何其他类中添加新类型。它工作得非常好,很适合数据包。它是这样的:

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

我希望你能看到的想法。