如何在c ++中透明地处理不同的协议版本?

时间:2009-09-29 16:07:43

标签: c++ language-agnostic design-patterns protocols

这是一个通用的C ++设计问题。

我正在编写一个使用客户端/服务器模型的应用程序。现在我正在编写服务器端。许多客户已经存在(一些由我自己编写,另一些由第三方编写)。问题是这些现有客户端都使用不同的协议版本(多年来已经有2-3次协议更改)。

由于我正在重写服务器,我认为现在是设计我的代码的好时机,这样我就可以透明地处理许多不同的协议版本。在所有协议版本中,来自客户端的第一个通信包含协议版本,因此对于每个客户端连接,服务器都确切地知道它需要谈论哪个协议。

执行此操作的天真方法是使用以下语句来丢弃代码:

if (clientProtocolVersion == 1)
    // do something here
else if (clientProtocolVersion == 2)
    // do something else here
else if (clientProtocolVersion == 3)
    // do a third thing here...

由于以下原因,此解决方案非常糟糕:

  1. 当我添加新协议版本时,我必须在源树中找到使用这些if语句的所有位置,并修改它们以添加新功能。
  2. 如果出现新的协议版本,并且协议版本的某些部分与另一个版本相同,我需要修改if语句,以便他们阅读if (clientProtoVersion == 5 || clientProtoVersion == 6)
  3. 我确信有更多理由说明设计糟糕,但我现在想不出来。
  4. 我正在寻找的是一种使用C ++语言的功能智能地处理不同协议的方法。我已经考虑过使用模板类,可能使用模板参数指定协议版本,或者可能是类层次结构,每个不同的协议版本都有一个类......

    我确信这是一种非常常见的设计模式,所以很多人以前一定遇到过这个问题。

    修改

    你们中的许多人都建议继承heirarchy,最旧的协议版本在顶部,就像这样(请原谅我的ASCII艺术):

    IProtocol
        ^
        |
    CProtoVersion1
        ^
        |
    CProtoVersion2
        ^
        |
    CProtoVersion3
    

    ......就重复使用而言,这似乎是一件明智的事情。但是,当您需要扩展协议并添加基本的新消息类型时会发生什么?如果我在IProtocol中添加虚拟方法,并在CProtocolVersion4中实现这些新方法,那么在早期协议版本中如何处理这些新方法?我想我的选择是:

    • 使默认实现成为NO_OP(或者可能在某处记录消息)。
    • 抛出异常,虽然这似乎是一个坏主意,即使我正在输入它。
    • ......做点什么吗?

    EDIT2:

    除上述问题外,当较新的协议消息需要比旧版本更多的输入时会发生什么?例如:

    在protocl版本1中,我可能有:

    ByteArray getFooMessage(string param1, int param2)

    在协议版本2中,我可能想要:

    ByteArray getFooMessage(string param1, int param2, float param3)

    两个不同的协议版本现在有不同的方法签名,这很好,除了它强制我通过所有调用代码并将所有调用更改为2个参数到3个参数,具体取决于所使用的协议版本,这是我首先要避免的是什么!

    将协议版本信息与其他代码分开的最佳方法是什么,以便隐藏当前协议的具体内容?

6 个答案:

答案 0 :(得分:10)

由于您需要动态选择要使用的协议,因此使用不同的类(而不是模板参数)来选择协议版本似乎是正确的方法。基本上这是战略模式,但如果你想要真正精心设计,访客也是可能的。

由于这些是同一协议的所有不同版本,您可能在基类中有共同的东西,然后是子类的差异。另一种方法可能是使基类用于最旧版本的协议,然后让每个后续版本都具有继承自先前版本的类。这是一个有点不寻常的继承树,但它的好处是它保证为更高版本所做的更改不会影响旧版本。 (我假设旧版协议的类会很快稳定下来,然后很少会改变。

但是,如果您决定组织层次结构,则需要在知道协议版本后立即选择协议版本对象,然后将其传递给需要“协商”协议的各种事物。 / p>

答案 1 :(得分:4)

我已经使用(并听说其他人使用)模板来解决这个问题。我们的想法是将您的不同协议分解为基本的原子操作,然后使用boost::fusion::vector之类的东西来构建各个块之外的协议。

以下是一个非常粗糙(缺少很多部分)的例子:

// These are the kind of atomic operations that we can do:
struct read_string { /* ... */ };
struct write_string { /* ... */ };
struct read_int { /* ... */ };
struct write_int { /* ... */ };

// These are the different protocol versions
typedef vector<read_string, write_int> Protocol1;
typedef vector<read_int, read_string, write_int> Protocol2;
typedef vector<read_int, write_int, read_string, write_int> Protocol3;

// This type *does* the work for a given atomic operation
struct DoAtomicOp {
  void operator ()(read_string & rs) const { ... }
  void operator ()(write_string & ws) const { ... }
  void operator ()(read_int & ri) const { ... }
  void operator ()(write_int & wi) const { ... }
};

template <typename Protocol> void doProtWork ( ... ) {
  Protocl prot;
  for_each (prot, DoAtomicOp (...));
}

因为protocl版本是动态的,所以你需要一个顶级switch语句来确定使用哪个protocl。

void doWork (int protocol ...) {
  switch (protocol) {
  case PROTOCOL_1:
    doProtWork<Protocol1> (...);
    break;
  case PROTOCOL_2:
    doProtWork<Protocol2> (...);
    break;
  case PROTOCOL_3:
    doProtWork<Protocol3> (...);
    break;
  };
}

要添加新协议(使用现有类型),您只需要定义protocl序列:

typedef vector<read_int, write_int, write_int, write_int> Protocol4;

然后在switch语句中添加一个新条目。

答案 2 :(得分:0)

我倾向于使用不同的类来为同一接口的不同协议实现适配器。

根据协议和差异,您可能会使用TMP获取状态机或协议详细信息,但生成六组使用六种协议版本的任何代码可能都不值得;运行时多态性就足够了,对于大多数情况来说,TCP IO可能足够慢,不想硬编码所有内容。

答案 3 :(得分:0)

可能过于简单,但这听起来像是继承的工作? 一个基类IProtocol,它定义了协议的功能(可能还有一些常用的方法),然后为每个协议实现IProtocol的一个实现?

答案 4 :(得分:0)

您需要一个协议工厂,为适当的版本返回协议处理程序:

ProtocolHandler&  handler = ProtocolFactory::getProtocolHandler(clientProtocolVersion);

然后,您可以使用继承来仅更新版本之间已更改的协议部分。

请注意,ProtocolHandler(基类)基本上是一种模式。因此它不应该保持自己的状态(如果需要通过方法传递,并且stratergy将更新状态对象的属性)。

因为stratergy不维护状态我们可以在任意数量的线程之间共享ProtcolHandler,因此所有权不需要离开工厂对象。因此工厂只需要为它理解的每个协议版本创建一个处理程序对象(这甚至可以懒得完成)。因为工厂对象保留所有权,您可以返回协议处理程序的引用。

答案 5 :(得分:0)

我会同意Pete Kirkham的观点,我认为维护6个不同版本的类来支持不同的协议版本会非常不愉快。如果你能做到这一点,似乎最好让旧版本只实现适配器转换为最新协议,这样你只需要维护一个工作协议。你仍然可以使用如上所示的继承层次结构,但旧的协议实现只需要进行调整然后调用最新的协议。

相关问题