代码之家  ›  专栏  ›  技术社区  ›  yuriks

C++设计——网络包和序列化

  •  4
  • yuriks  · 技术社区  · 16 年前

    然后,我希望有从数据包派生的类,例如:StatePacket、PauseRequestPacket等。这些子类中的每一个子类都将实现虚拟函数Handle(),当接收到其中一个数据包时,网络引擎将调用该函数,以便它能够完成任务,几个get/set函数,用于读取和设置数据数组中的字段。

    所以我有两个问题:

    1. (抽象)数据包类需要是可复制和可分配的,但没有切片,保留派生类的所有字段。派生类甚至可能没有额外的字段,只有函数,它将与基类上的数组一起工作。我怎样才能做到这一点?

    如果有人想澄清,尽管问。

    --多谢各位


    我对此不太满意,但这正是我所能做到的:

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


    是的,我知道工厂模式,但是如何编写它来构造每个类呢?一个巨大的转变声明?这也会复制每个类的ID(在工厂中一次,在序列化程序中一次),这是我想要避免的。

    4 回复  |  直到 13 年前
        1
  •  9
  •   Johannes Schaub - litb    16 年前

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

    virtual Packet * clone() const = 0;
    

    每个数据包实现实现如下所示:

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

    例如statepack。数据包类应该是不可变的。一旦收到一个数据包,它的数据要么被复制出来,要么被丢弃。因此,不需要赋值运算符。将赋值操作符设置为私有,而不定义它,这将有效地禁止赋值包。

    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是否真的已知等等。这只是个粗略的想法。

        2
  •  0
  •   Loki Astari    16 年前

        3
  •  0
  •   coryan    16 年前

    要使一个工厂类事先不知道所有类型,您需要提供一个每个类都注册自己的单例。我总是把定义模板类的静态成员的语法弄错了,所以不要只剪切&粘贴以下内容:

    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 */; }
    };
    
        4
  •  0
  •   Mr.Ree    16 年前

    为什么我们,包括我自己,总是把这么简单的问题弄得这么复杂?


    也许我在这里落伍了。但我不得不怀疑:这真的是满足你需求的最佳设计吗?


    看起来数据包的每个子类都需要自己的封送和解封代码。可能是继承数据包的编组/解编代码?也许是扩展它?所有这些都位于handle()的顶部,以及所需的任何其他内容。

    虽然实质上更麻烦,但它可能会更短&更快地将数据包的数据实现为数据包类的struct/union属性。

    编组和解编将集中进行。

    是一种容易出现错误的技术 . 但是写初稿通常是一种非常快速的方式。稍后,如果时间允许,您可以将其替换为基于每个属性类型的单独编组/解编组代码。

    也:


    转换 子类id类型上的某个地方的语句。factory技术(其本身非常强大和有用)为您进行切换,查找必要的clone()或copy()方法/对象。

    你可以自己打包做。您可以使用以下简单的方法:

    (getHandlerPointer(id))(此)


    除了快速的开发时间外,这种笨拙的方法(函数指针)的另一个优点是,您不需要不断地为每个数据包分配和删除一个新对象。您可以反复使用单个数据包对象。或者,如果要对数据包进行排队,则使用数据包向量。(请注意,在再次调用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 */
    

    例如。:

    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