[英]C++ design - Network packets and serialization
對於我的游戲,我有一個Packet類,它代表網絡數據包,基本上由一組數據和一些純虛函數組成
然后我想要從Packet派生類,例如:StatePacket,PauseRequestPacket等。這些子類中的每一個都將實現虛擬函數Handle(),當其中一個數據包時,它將被網絡引擎調用。接收到它可以完成它的工作,幾個get / set函數可以讀取和設置數據數組中的字段。
所以我有兩個問題:
如果有人想要任何澄清,請問。
- 謝謝
編輯:我對此不太滿意,但這就是我所管理的:
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.