这篇的重点内容是讲解算法,但是在正式开始之前,我强烈建议你再回顾一下上一篇的内容,因为要理解这个算法,就需要意识到协议的具体细节不重要,重要的是这种协议是否可以用一种树形结构来表达,而且在这个结构中,每一个节点(即field)要么是一个必要节点,要么是一个可选节点,且它最多只依赖于一个之前的(如果我们以文件目录树来想像这棵树的话,一个节点更靠前就意味着这个节点更靠近上方的位置)节点,即,不能出现一个节点依赖于之前的多个节点——除非这些节点可以合并为一个复合节点(即子协议field)——或之后的某个节点,但是多个节点都依赖于之前的同一个节点的情况是允许的(通常,我们也可以把这些节点合并为一个复合节点)。通俗的讲,就是我们接下来要讨论的这个算法,与这棵协议树长得什么样子无关(对于同一个协议,不同的人可能将它描述成不同的样子,这种情况是可能的,也是允许的),而只与每个节点的性质有关。正是这个特性,使得这个算法具有极强的通用性。这样,我们只要保证我们描述出的协议树与协议文档是一致的(检查其正确性也要比分析一堆的if else和位运算容易得多),那么就可以期待我们能够得到一个正确的结果。
听起来,这个算法好像很复杂,其实不然,它意外的非常简单,但需要你有较好的想像力。是不是有点迫不及待了呢?好,现在我们先从一个特例开始:所有节点都是必要的节点,没有任何两个节点有依赖关系(即所有节点都是独立的)。对于这种情况,我们想像一下把这棵树“拍平”,只留下叶子节点(但先后顺序不变),那么这个叶子节点组成的节点序列,不就是我们要的field序列了吗(因为每个复合节点在bit流中的起始位置,就是这个复合节点的第一个叶子节点在bit流中的起始位置,而大小就是所有叶子节点的大小的和,然后想象将bit流映射到这个field序列上)?而要得到一个“拍平”的树,我们只需要执行一个深度优先的遍历算法就可以,是不是很简单呢?(事实上这个特例有更简单的处理方法,就是按协议写一个大大的嵌套式的struct,然后直接对buffer指针做一个强制类型转换即可,这个例子提醒我们,程序设计是一门非常灵活的艺术,我们不能教条的去看待它,这也是我本人不欣赏《设计模式》一书的原因)
那么当我们引入了可选节点后,是否情况就不一样了呢?其实是一样的,还是想像我们把一棵树“拍平”,只不过现在,有些叶子节点我们需要删除,删除的原则就是,先一个一个的检查这个叶子是否是必要节点,如果是,就保留这个节点,同时修正bit流的访问偏移量(即指向下一个field的起始位置);而如果不是,就向(已生成的)序列的前方找到它的条件节点,然后看看它的条件节点的内容是不是允许这个可选节点出现(即计算出这个可选节点的个数),如果允许,就保留(并修正bit流的访问偏移量),否则,就删除。看到了吗?差别只有一点点,是不是也很简单?(现在你应该明白,为什么上一篇文章中,我们的协议描述接口要设计成那样了吧?同时,正因为大部分的协议都是这种情况,所以我们不能象上一种情况那样,用一个静态的struct去处理它们)