C ++设计 - 网络数据包和序列化

时间:2008-12-01 02:06:14

标签: c++ inheritance serialization

对于我的游戏,我有一个Packet类,它代表网络数据包,基本上由一组数据和一些纯虚函数组成

然后我想要从Packet派生类,例如:StatePacket,PauseRequestPacket等。这些子类中的每一个都将实现虚拟函数Handle(),当网络引擎被调用时收到这些数据包以便它可以完成它的工作,有几个get / set函数可以读取和设置数据数组中的字段。

所以我有两个问题:

  1. (抽象)Packet类需要是可复制和可分配的,但是没有切片,保留派生类的所有字段。甚至可能派生类没有额外的字段,只有函数,它可以与基类上的数组一起使用。我怎样才能做到这一点?
  2. 序列化时,我会给每个子类一个唯一的数字ID,然后在子类自己的序列化之前将它写入流。但是对于反序列化,我如何将读取的ID映射到适当的子类以实现它?
  3. 如果有人想要任何澄清,请问。

    - 谢谢


    编辑:我对此并不满意,但这就是我管理的内容:

    Packet.h:http://pastebin.com/f512e52f1
    Packet.cpp:http://pastebin.com/f5d535d19
    PacketFactory.h:http://pastebin.com/f29b7d637
    PacketFactory.cpp:http://pastebin.com/f689edd9b
    PacketAcknowledge.h:http://pastebin.com/f50f13d6f
    PacketAcknowledge.cpp:http://pastebin.com/f62d34eef

    如果有人有时间查看并提出任何改进建议,我会感激不尽。


    是的,我知道工厂模式,但我如何编码它来构建每个类?一个巨大的转换声明?这也会重复每个类的ID(一次在工厂,一个在序列化器中),我想避免。

4 个答案:

答案 0 :(得分:8)

对于复制,您需要编写克隆函数,因为构造函数不能是虚拟的:

virtual Packet * clone() const = 0;

每个Packet实现如下所示:

virtual Packet * clone() const {
    return new StatePacket(*this);
}

例如StatePacket。数据包类应该是不可变的。收到数据包后,其数据可以被复制或丢弃。因此不需要赋值运算符。将赋值运算符设为私有,不要定义它,这将有效地禁止分配包。

对于反序列化,使用工厂模式:创建一个类,根据消息ID创建正确的消息类型。为此,您可以对已知消息ID使用switch语句,也可以使用以下映射:

struct MessageFactory {
    std::map<Packet::IdType, Packet (*)()> map;

    MessageFactory() {
        map[StatePacket::Id] = &StatePacket::createInstance;
        // ... all other
    }

    Packet * createInstance(Packet::IdType id) {
        return map[id](); 
    }
} globalMessageFactory;

确实,你应该添加一个检查,比如id是否真的已知并且这样的东西。这只是一个粗略的想法。

答案 1 :(得分:0)

您需要查找工厂模式。

工厂查看传入的数据并为您创建正确类的对象。

答案 2 :(得分:0)

要让Factory类提前不了解所有类型,您需要提供每个类自我注册的单例。我总是得到用于定义模板类的静态成员的语法错误,所以不要只剪切和粘贴它:

class Packet { ... };

typedef Packet* (*packet_creator)();

class Factory {
public:
  bool add_type(int id, packet_creator) {
    map_[id] = packet_creator; return true;
  }
};

template<typename T>
class register_with_factory {
public:
  static Packet * create() { return new T; }
  static bool registered;
};

template<typename T>
bool register_with_factory<T>::registered = Factory::add_type(T::id(), create);

class MyPacket : private register_with_factory<MyPacket>, public Packet {
//... your stuff here...

  static int id() { return /* some number that you decide */; }
};

答案 3 :(得分:0)

为什么我们(包括我自己)总是让这么简单的问题如此复杂?


也许我不在这里。但我不禁要问:这真的是最适合您需求的设计吗?

总的来说,通过函数/方法指针,聚合/委托以及传递数据对象,而不是通过多态,可以更好地实现仅函数继承。

多态性是一种非常强大且有用的工具。但它只是我们可用的众多工具之一。


看起来Packet的每个子类都需要自己的编组和解组编码。也许继承Packet的编组/解编码?也许延伸它?全部在handle()之上,还有其他所需的东西。

这是很多代码。

虽然更多的kludgey,它可能会更短&amp;更快地将Packet的数据实现为Packet类的struct / union属性。

然后集中编组和解组。

根据您的架构,它可以像写(和数据)一样简单。假设您的客户端/服务器系统之间没有大/小端问题,并且没有填充问题。 (例如,sizeof(data)在两个系统上都是相同的。)

写入(和数据)/读取(和数据)是一种容易出错的技术。但是编写初稿通常是一种非常快捷的方式。稍后,当时间允许时,您可以将其替换为基于每个属性类型的编组/解组编码。

另外:我已经将存储/接收的数据存储为结构。您可以使用operator =()按位复制结构,这有时非常有用!虽然在这种情况下可能没那么多。


最终,您将在该子类-id类型的某处获得 switch 语句。工厂技术(它本身非常强大和有用)可以为您切换,查找必要的clone()或copy()方法/对象。

你可以在Packet中自己做。你可以使用简单的东西:

(getHandlerPointer(id))(this)


除了快速开发时间之外,这种方法(函数指针)的另一个优点是,您不需要为每个数据包不断地分配和删除新对象。您可以反复重复使用单个数据包对象。或者如果要对数据包进行排队,则为数据包矢量。 (请注意,我会在再次调用read()之前清除Packet对象!只是为了安全......)

根据您游戏的网络流量密度,分配/取消分配可能会变得昂贵。然后,过早优化是所有邪恶的根源。你总是可以自己推出新的/删除操作符。 (然而更多的编码开销......)


您丢失的内容(使用函数指针)是每种数据包类型的清晰隔离。特别是能够在不改变预先存在的代码/文件的情况下添加新数据包类型。


示例代码:

class Packet
{
public:
  enum PACKET_TYPES
  {
    STATE_PACKET = 0,
    PAUSE_REQUEST_PACKET,

    MAXIMUM_PACKET_TYPES,
    FIRST_PACKET_TYPE = STATE_PACKET
  };

  typedef  bool ( * HandlerType ) ( const Packet & );

protected:
        /* Note: Initialize handlers to NULL when declared! */
  static HandlerType  handlers [ MAXIMUM_PACKET_TYPES ];

  static HandlerType  getHandler( int thePacketType )
    {   // My own assert macro...
      UASSERT( thePacketType, >=, FIRST_PACKET_TYPE    );
      UASSERT( thePacketType, <,  MAXIMUM_PACKET_TYPES );
      UASSERT( handlers [ thePacketType ], !=, HandlerType(NULL) );
      return handlers [ thePacketType ];
    }

protected:
   struct Data
   {
            // Common data to all packets.
     int  number;
     int  type;

     union
     {
       struct
       {
         int foo;
       } statePacket;

       struct
       {
         int bar;
       } pauseRequestPacket;

     } u;

   } data;


public:

  //...
  bool readFromSocket() { /*read(&data); */ }  // Unmarshal
  bool writeToSocket()  { /*write(&data);*/ }  // Marshal

  bool handle() { return ( getHandler( data.type ) ) ( * this ); }

}; /* class Packet */

PS:你可能会去谷歌并抓住cdecl / c ++ decl。它们是非常有用的程序。特别是在玩函数指针时。

E.g:

c++decl> declare foo as function(int) returning pointer to function returning void
void (*foo(int ))()
c++decl> explain void (* getHandler( int  ))( const int & );
declare getHandler as function (int) returning pointer to function (reference to const int) returning void