出處:http://www.cppblog.com/tx7do/archive/2011/05/07/145865.html
最簡單的TCP網絡封包解包(補充)-序列化
如若描述或者代碼當中有謬誤之處,還望指正。將數據能夠在TCP中進行傳輸的兩種方法
1.直接拷貝struct就可以了;
2.序列化。
拷貝Struct存在的問題
1.不能應付可變長類型的數據,比如STL中的那些容器,當然,STL的容器歸根到底就是一個class,他們的長度都是不確定的;
2.內存對齊的問題,Windows默認的對齊是4字節,如果不去刻意關閉掉對齊的話,那麼可能會多出不少沒必要的字節數,但是如果關閉了,內存拷貝又會慢一些。
序列化是怎麼序列化的?
其實很簡單,我們使用一個uint8類型的數組,假設我們這裏有一個uint16類型的數據,那麼我們就把它拷貝進去uint8的數組裏面,那麼它就佔了兩個元素。這是最基本的規則。具體請參考代碼裏面的ByteBuffer::append()方法。而那些class神馬的,我們只要按照自己設定的規則順序拷貝進去就可以了。這個在BytBuffer裏面默認支持了常用的STL容器,可以參看代碼。
類型定義
//
// Windows/Visual C++
//
typedef signed __int8 int8;
typedef unsigned __int8 uint8;
typedef signed __int16 int16;
typedef unsigned __int16 uint16;
typedef signed __int32 int32;
typedef unsigned __int32 uint32;
typedef signed __int64 int64;
typedef unsigned __int64 uint64;
#endif
ByteBuffer的代碼
/// 字節流緩衝類,可以進行序列化和解序列化操作,並且可以緩衝字節流數據。
//////////////////////////////////////////////////////////////////////////
class ByteBuffer
{
public:
const static size_t DEFAULT_SIZE = 0x1000;
ByteBuffer()
: mReadPos(0)
, mWritePos(0)
{
mStorage.reserve(DEFAULT_SIZE);
}
ByteBuffer(size_t res)
: mReadPos(0)
, mWritePos(0)
{
mStorage.reserve(res);
}
ByteBuffer(const ByteBuffer &buf)
: mReadPos(buf.mReadPos)
, mWritePos(buf.mWritePos)
, mStorage(buf.mStorage)
{}
//////////////////////////////////////////////////////////////////////////
public:
void clear()
{
mStorage.clear();
mReadPos = mWritePos = 0;
}
template <typename T>
void append(T value)
{
append((uint8*)&value, sizeof(value));
}
template <typename T>
void put(size_t pos, T value)
{
put(pos, (uint8*)&value, sizeof(value));
}
//////////////////////////////////////////////////////////////////////////
public:
ByteBuffer& operator<<(bool value)
{
append<char>((char)value);
return *this;
}
ByteBuffer& operator<<(uint8 value)
{
append<uint8>(value);
return *this;
}
ByteBuffer& operator<<(uint16 value)
{
append<uint16>(value);
return *this;
}
ByteBuffer& operator<<(uint32 value)
{
append<uint32>(value);
return *this;
}
ByteBuffer& operator<<(uint64 value)
{
append<uint64>(value);
return *this;
}
ByteBuffer& operator<<(int8 value)
{
append<int8>(value);
return *this;
}
ByteBuffer& operator<<(int16 value)
{
append<int16>(value);
return *this;
}
ByteBuffer& operator<<(int32 value)
{
append<int32>(value);
return *this;
}
ByteBuffer& operator<<(int64 value)
{
append<int64>(value);
return *this;
}
ByteBuffer& operator<<(float value)
{
append<float>(value);
return *this;
}
ByteBuffer& operator<<(double value)
{
append<double>(value);
return *this;
}
ByteBuffer& operator<<(time_t value)
{
append<time_t>(value);
return *this;
}
ByteBuffer& operator<<(const std::string& value)
{
append((uint8 const *)value.c_str(), value.length());
append((uint8)0);
return *this;
}
ByteBuffer& operator<<(const char* str)
{
append( (uint8 const *)str, str ? strlen(str) : 0);
append((uint8)0);
return *this;
}
//////////////////////////////////////////////////////////////////////////
public:
ByteBuffer& operator>>(bool& value)
{
value = read<char>() > 0 ? true : false;
return *this;
}
ByteBuffer& operator>>(uint8& value)
{
value = read<uint8>();
return *this;
}
ByteBuffer& operator>>(uint16& value)
{
value = read<uint16>();
return *this;
}
ByteBuffer& operator>>(uint32& value)
{
value = read<uint32>();
return *this;
}
ByteBuffer& operator>>(uint64& value)
{
value = read<uint64>();
return *this;
}
ByteBuffer& operator>>(int8& value)
{
value = read<int8>();
return *this;
}
ByteBuffer& operator>>(int16& value)
{
value = read<int16>();
return *this;
}
ByteBuffer& operator>>(int32& value)
{
value = read<int32>();
return *this;
}
ByteBuffer& operator>>(int64& value)
{
value = read<int64>();
return *this;
}
ByteBuffer& operator>>(float &value)
{
value = read<float>();
return *this;
}
ByteBuffer& operator>>(double &value)
{
value = read<double>();
return *this;
}
ByteBuffer& operator>>(time_t& value)
{
value = read<time_t>();
return *this;
}
ByteBuffer& operator>>(std::string& value)
{
value.clear();
while (rpos() < size())
{
char c = read<char>();
if (c == 0)
{
break;
}
value += c;
}
return *this;
}
ByteBuffer& operator>>(char value[])
{
std::string strValue;
strValue.clear();
while (rpos() < size())
{
char c = read<char>();
if (c == 0)
{
break;
}
strValue += c;
}
strncpy(value, strValue.c_str(), strValue.size());
return *this;
}
//////////////////////////////////////////////////////////////////////////
public:
uint8 operator[](size_t pos)
{
return read<uint8>(pos);
}
size_t rpos() const
{
return mReadPos;
};
size_t rpos(size_t rpos_)
{
mReadPos = rpos_;
return mReadPos;
};
size_t wpos() const
{
return mWritePos;
}
size_t wpos(size_t wpos_)
{
mWritePos = wpos_;
return mWritePos;
}
template <typename T> T read()
{
T r = read<T>(mReadPos);
mReadPos += sizeof(T);
return r;
};
template <typename T> T read(size_t pos) const
{
assert(pos + sizeof(T) <= size() || PrintPosError(false,pos,sizeof(T)));
return *((T const*)&mStorage[pos]);
}
void read(uint8 *dest, size_t len)
{
assert(mReadPos + len <= size() || PrintPosError(false, mReadPos,len));
memcpy(dest, &mStorage[mReadPos], len);
mReadPos += len;
}
const uint8* contents() const { return &mStorage[mReadPos]; }
size_t size() const { return mStorage.size(); }
bool empty() const { return mStorage.empty(); }
void resize(size_t _NewSize)
{
mStorage.resize(_NewSize);
mReadPos = 0;
mWritePos = size();
};
void reserve(size_t _Size)
{
if (_Size > size()) mStorage.reserve(_Size);
};
void append(const std::string& str)
{
append((uint8 const*)str.c_str(), str.size() + 1);
}
void append(const char *src, size_t cnt)
{
return append((const uint8 *)src, cnt);
}
void append(const uint8 *src, size_t cnt)
{
if (!cnt) return;
assert(size() < 10000000);
if (mStorage.size() < mWritePos + cnt)
{
mStorage.resize(mWritePos + cnt);
}
memcpy(&mStorage[mWritePos], src, cnt);
mWritePos += cnt;
}
void append(const ByteBuffer& buffer)
{
if (buffer.size()) append(buffer.contents(),buffer.size());
}
void put(size_t pos, const uint8 *src, size_t cnt)
{
assert(pos + cnt <= size() || PrintPosError(true,pos,cnt));
memcpy(&mStorage[pos], src, cnt);
}
//////////////////////////////////////////////////////////////////////////
public:
void print_storage()
{
}
void textlike()
{
}
void hexlike()
{
}
bool PrintPosError(bool add, size_t pos, size_t esize) const
{
printf("ERROR: Attempt %s in ByteBuffer (pos: %u size: %u) value with size: %u",(add ? "put" : "get"), pos, size(), esize);
return false;
}
protected:
size_t mReadPos;
size_t mWritePos;
std::vector<uint8> mStorage;
};
//////////////////////////////////////////////////////////////////////////
// std::vector
//////////////////////////////////////////////////////////////////////////
#ifdef _VECTOR_
template <typename T>
ByteBuffer& operator<<(ByteBuffer& b, const std::vector<T>& v)
{
b << (uint32)v.size();
typename std::vector<T>::const_iterator iter = v.begin();
typename std::vector<T>::const_iterator& iEnd = v.end();
for (; iter != iEnd; ++iter)
{
b << *iter;
}
return b;
}
template <typename T>
ByteBuffer& operator>>(ByteBuffer& b, std::vector<T>& v)
{
uint32 vsize;
b >> vsize;
v.clear();
while (vsize--)
{
T t;
b >> t;
v.push_back(t);
}
return b;
}
#endif
//////////////////////////////////////////////////////////////////////////
// std::list
//////////////////////////////////////////////////////////////////////////
#ifdef _LIST_
template <typename T>
ByteBuffer& operator<<(ByteBuffer& b, const std::list<T>& v)
{
b << (uint32)v.size();
typename std::list<T>::const_iterator iter = v.begin();
typename std::list<T>::const_iterator& iEnd = v.end();
for (; iter != iEnd; ++iter)
{
b << *iter;
}
return b;
}
template <typename T>
ByteBuffer& operator>>(ByteBuffer& b, std::list<T>& v)
{
uint32 vsize;
b >> vsize;
v.clear();
while (vsize--)
{
T t;
b >> t;
v.push_back(t);
}
return b;
}
#endif
//////////////////////////////////////////////////////////////////////////
// std::map
//////////////////////////////////////////////////////////////////////////
#ifdef _MAP_
template <typename K, typename V>
ByteBuffer& operator<<(ByteBuffer& b, const std::map<K, V>& m)
{
b << (uint32)m.size();
typename std::map<K, V>::const_iterator iter = m.begin();
typename std::map<K, V>::const_iterator iEnd = m.end();
for (; iter != iEnd; ++iter)
{
b << iter->first << iter->second;
}
return b;
}
template <typename K, typename V>
ByteBuffer &operator>>(ByteBuffer& b, std::map<K, V>& m)
{
uint32 msize;
b >> msize;
m.clear();
while (msize--)
{
K k;
V v;
b >> k >> v;
m.insert(std::make_pair(k, v));
}
return b;
}
#endif
如何利用ByteBuffer序列化和反序列化
假設我們要序列化std::string的數據,那麼我們這樣做:
ByteBuffer buf;
buf << str;
ByteBuffer buf;
buf >> str;
在實用下細節上的一些區別
通常情況下,一個協議的數據集會定義爲一個struct,然後重載其<<和>>算符用於序列化和反序列化。這個如果僅僅是在C++下倒還好,但如若放置在混合語言編程的情況下,這可能就不行了,如若純邏輯都在lua或者python神馬裏面做,那可就不行咯,只能爲每個基本類型寫一個read和write的方法:readInt8,readInt16,,readString,writeInt8,writeInt16,writeString等等。然後在每個協議處理方法裏面按照順序逐個的處理協議數據集的每一個數據了。
Google Protocol Buffer(ProtoBuf)
在開源工具裏面,不得不提到的就是它了,它很適合於混合語言的情況下。它自己有一套自己的數據描述語言,數據序列化的描述都寫在.proto。只需要寫一次.proto文件,便可以在多語言裏面使用了該協議了。比如,我曾經做過一個VC+Flash AS3的項目,就是用的它。如果沒有它,網絡協議我必須在c++裏面定義一次,flash裏面定義一次,麻煩死了,麻煩倒還是小事情,如果兩邊的定義不同步的話,那可就糟糕了。當然,如果沒有跨語言的需求,還是儘量簡單爲好,畢竟簡單的東西自己可以比較輕鬆的掌控。
主頁地址:http://code.google.com/p/protobuf/
代碼下載:testByteBuffer.rar
EDIT:
time_t解序列化寫錯了,參數應該是一個傳出值,爲一個引用,但是我把引用符給忘記了。特此訂正!
{
value = read<time_t>();
return *this;
}
posted on 2011-05-07 01:33 楊粼波 閱讀(3061) 評論(10) 編輯 收藏 引用 所屬分類: 原創文章 、網絡編程 、C++
評論
# re: 最簡單的TCP網絡封包解包(補充)-序列化[未登錄] 2011-05-07 09:56 true
mongos裏面的ByteBuffer確實好用,以前也做過把他單獨提取出來,再添加幾個方法使用 回覆 更多評論
# re: 最簡單的TCP網絡封包解包(補充)-序列化 2011-05-07 10:27 楊粼波
我是從arcemu裏面拿出來的,感覺好像和mongos裏面的是一樣的。
還有一種利用std::stringstream做序列化的,倒也還可以。
用std::vector可以利用STL的內存分配器,這個比較省心。 回覆 更多評論
# re: 最簡單的TCP網絡封包解包(補充)-序列化 2011-05-07 12:08 飯中淹
我使用的是類型加數值的序列化和反序列化,封包不僅用於網絡,還用於db,內部消息等地方。
回覆 更多評論
# re: 最簡單的TCP網絡封包解包(補充)-序列化 2011-05-07 12:10 飯中淹
這樣做有個好處是不需要定型的struct,處理過程只要傳入一個複合類型value的數組即可,對於統一整體架構,減少代碼工作量很有幫助。 回覆 更多評論
# re: 最簡單的TCP網絡封包解包(補充)-序列化[未登錄] 2011-05-07 12:12 楊粼波
@飯中淹
還有一個,就是數據持久化。
嗯。。。。反正是IO神馬的地方都可以用用。
我的服務器事件系統的一些數據有的也是用這個傳遞。 回覆 更多評論
# re: 最簡單的TCP網絡封包解包(補充)-序列化[未登錄] 2011-05-07 12:15 楊粼波
@飯中淹
我沒有嘗試過“類型加數值的序列化和反序列化”,這個的空間不太好吧?會相對多吃點字節數吧? 回覆 更多評論
# re: 最簡單的TCP網絡封包解包(補充)-序列化 2011-05-07 12:37 飯中淹
@楊粼波
目前的方式是用單字節來表示一個類型。
類型有 int, uint, float, string
int, uint, float 又有數組類型。
string和array是帶16位的Length字段。
array除了1字節的array類型指定,還帶一個1字節的元素數據類型。
對整體容量的增加,有限。
不過,現在這種方式並不是最好的方式。
我認爲的最好的方式,所有類型都提取成一個數據類型對象,也就是類似GOOGLE PROTOCOL BUFFER的用額外的描述生成的一個對結構體的描述。這個方式是跟我的數據對象和映射的整體邏輯架構相關的。目標是實現服務器端,在對所有數據和邏輯的描述上形成的整體架構的統一,同時將類型和表義信息從最終數據存儲中去掉。
不過這種情況下,可能會出現版本問題,由於雙方描述的版本差異,導致兼容性問題。我的解決方法,是把原有的類型信息,更換爲FIELDINDEX信息,也就是字段索引。在結構體描述的更改過程中,遵循字段出現增加,就增長其FIELDINDEX的原則。這樣,在有限的版本空間內,FIELDINDEX會精確對應到相應的字段上。如果FIELDINDEX超出值域限制,那只有放到新的結構描述中去了。
我在網絡封包這塊,有着很長的一個摸索過程,大概經歷過以下幾個階段:
1- 結構體直接作爲封包發送
2- 結構體序列化(封包內只有數據本身,手動編寫序列化和反序列化的方法)
3- 結構體序列化(封包內帶有類型信息,手動編寫序列化和反序列化的方法)
4- 數據對象和數據映射(封包內帶有字段信息,通過數據映射來序列化和反序列化)
回覆 更多評論
# re: 最簡單的TCP網絡封包解包(補充)-序列化[未登錄] 2011-05-07 13:04 楊粼波
@飯中淹
和我想的差不多。
比XML要緊湊一些。
以前看Jabber的時候,它是用XML序列化,不過,空間代價太大了。
版本兼容性,這是一個問題。我現在的做法是完全不兼容。只要是協議版本不一致,就強制性升級更新。我覺得這是一個簡單有效的方法。如果還要兼容協議版本,做的事情多得多,這樣就得耗費大量的時間在上面,而且對穩定性有一定的影響。 回覆 更多評論
# re: 最簡單的TCP網絡封包解包(補充)-序列化 2011-05-09 10:22 zuhd
template <typename T>
ByteBuffer& operator>>(ByteBuffer& b, std::vector<T>& v)
{
uint32 vsize;
b >> vsize;
v.clear();
while (vsize--)
{
T t;
b >> t;
v.push_back(t);
}
return b;
}
======================================
小夥子,怎麼支持vector<vector<int> >的數據類型啊? 回覆 更多評論
# re: 最簡單的TCP網絡封包解包(補充)-序列化 2011-05-14 11:21 楊粼波
@zuhd
C++的模板是支持遞歸的,是沒問題的。 回覆 更多評論