我正在读取包含一系列可变长度描述符的字节流,我在代码中将其表示为各种结构/类。每个描述符都有一个与所有其他描述符相同的固定长度头,用于标识其类型。
是否有适当的模型或模式可以用来最好地解析和表示每个描述符,然后根据它的类型执行适当的操作?
答案 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 Method或Abstract 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 } }
使用这种方法:
答案 5 :(得分:0)
如果您希望它是一个好的OO,您可以在对象层次结构中使用访问者模式。我是如何做到这一点的(用于识别从网络捕获的数据包,几乎与您可能需要的相同):
庞大的对象层次结构,包含一个父类
每个类都有一个静态构造函数,它注册了它的父级,所以父级知道它的直接子级(这是c ++,我认为在具有良好反射支持的语言中不需要这一步)
< / LI>每个类都有一个静态构造函数方法,它获取了字节流的剩余部分,并根据它,决定是否有责任处理该数据
当一个数据包进来时,我只是简单地将它传递给主父类(称为Packet)的静态构造方法,而后者又检查了它的所有子节点是否有责任处理该数据包,并且这是递归的,直到层次结构底部的一个类返回实例化的类。
每个静态“构造函数”方法都从字节流中剪切自己的标头,并仅将有效负载传递给其子节点。
这种方法的优点是,您可以在对象层次结构中的任何位置添加新类型 WITHOUT 需要查看/更改 ANY 其他类。它非常好用于数据包;它是这样的:
我希望你能看到这个想法。