簡體   English   中英

C ++設計 - 網絡數據包和序列化

[英]C++ design - Network packets and serialization

對於我的游戲,我有一個Packet類,它代表網絡數據包,基本上由一組數據和一些純虛函數組成

然后我想要從Packet派生類,例如:StatePacket,PauseRequestPacket等。這些子類中的每一個都將實現虛擬函數Handle(),當其中一個數據包時,它將被網絡引擎調用。接收到它可以完成它的工作,幾個get / set函數可以讀取和設置數據數組中的字段。

所以我有兩個問題:

  1. (抽象)Packet類需要是可復制和可分配的,但是沒有切片,保留派生類的所有字段。 甚至可能派生類沒有額外的字段,只有函數,它可以與基類上的數組一起使用。 我怎樣才能做到這一點?
  2. 在序列化時,我會給每個子類一個唯一的數字ID,然后在子類自己的序列化之前將它寫入流。 但是對於反序列化,我如何將讀取的ID映射到適當的子類以實現它?

如果有人想要任何澄清,請問。

- 謝謝


編輯:我對此不太滿意,但這就是我所管理的:

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(一次在工廠,一個在序列化器中),我想避免。

對於復制,您需要編寫克隆函數,因為構造函數不能是虛擬的:

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是否真的已知和這樣的東西。 這只是一個粗略的想法。

您需要查找工廠模式。

工廠查看輸入數據並為您創建正確類的對象。

要讓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 */; }
};

為什么我們(包括我自己)總是讓這么簡單的問題如此復雜?


也許我不在這里。 但我不禁要問:這真的是最適合您需求的設計嗎?

總的來說,通過函數/方法指針,聚合/委托以及數據對象的傳遞,而不是通過多態,可以更好地實現僅函數繼承。

多態性是一種非常強大且有用的工具。 但它只是我們可用的眾多工具之一。


看起來Packet的每個子類都需要自己的編組和解組編碼。 也許繼承Packet的編組/解編碼? 也許延伸它? 全部在handle()之上,還有其他所需的東西。

這是很多代碼。

雖然實際上更多的kludgey,實現Packet的數據作為Packet類的struct / union屬性可能更短更快。

然后集中編組和解組。

根據您的體系結構,它可以像寫(和數據)一樣簡單。 假設您的客戶端/服務器系統之間沒有大/小端問題,並且沒有填充問題。 (例如,sizeof(data)在兩個系統上都是相同的。)

寫(和數據)/讀(和數據) 是一種容易出錯的技術 但是編寫初稿通常是一種非常快捷的方式。 稍后,當時間允許時,您可以將其替換為基於每個屬性類型的編組/解組編碼。

另外:我已經將存儲/接收的數據存儲為結構。 您可以使用operator =()按位復制結構,這有時非常有用! 雖然在這種情況下可能不是那么多。


最終,您將在該子類-id類型的某處使用switch語句。 工廠技術(它本身非常強大和有用)為您進行此切換,查找必要的clone()或copy()方法/對象。

或者你可以在Packet中自己做。 你可以使用一些簡單的東西:

(getHandlerPointer(id))(this)


除了快速開發時間之外,這種kludgey(函數指針)方法的另一個優點是,您不需要為每個數據包不斷地分配和刪除新對象。 您可以反復重復使用單個數據包對象。 或者如果要對數據包進行排隊,則為數據包矢量。 (請注意,在再次調用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。 它們是非常有用的程序。 特別是在玩函數指針時。

例如:

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

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM