本章继上一章交易创建之后介绍比特币客户端序列化数据的过程。
比特币客户端所有的序列化函数均在seriliaze.h中实现。其中,CDataStream类是数据序列化的核心结构。
CDataStream拥有一个字符类容器用来存放序列化之后的数据。它结合一个容器类型和一个流(stream)界面以处理数据。它使用6个成员函数实现这一功能:
class CDataStream { protected: typedef vector<char, secure_allocator<char> > vector_type; vector_type vch; unsigned int nReadPos; short state; short exceptmask; public: int nType; int nVersion; //...... }
enum { // primary actions SER_NETWORK = (1 << 0), SER_DISK = (1 << 1), SER_GETHASH = (1 << 2), // modifiers SER_SKIPSIG = (1 << 16), SER_BLOCKHEADERONLY = (1 << 17), };
CDataStream& read(char* pch, int nSize) { // Read from the beginning of the buffer assert(nSize >= 0); unsigned int nReadPosNext = nReadPos + nSize; if (nReadPosNext >= vch.size()) { if (nReadPosNext > vch.size()) { setstate(ios::failbit, "CDataStream::read() : end of data"); memset(pch, 0, nSize); nSize = vch.size() - nReadPos; } memcpy(pch, &vch[nReadPos], nSize); nReadPos = 0; vch.clear(); return (*this); } memcpy(pch, &vch[nReadPos], nSize); nReadPos = nReadPosNext; return (*this); } CDataStream& write(const char* pch, int nSize) { // Write to the end of the buffer assert(nSize >= 0); vch.insert(vch.end(), pch, pch + nSize); return (*this); }
CDataStream::read()从CDataStream复制nSize个字符到一个由char* pch所指向的内存空间。以下是它的实现过程:
#define WRITEDATA(s, obj) s.write((char*)&(obj), sizeof(obj)) #define READDATA(s, obj) s.read((char*)&(obj), sizeof(obj))
这里是如何使用这些宏的例子。下面的函数将序列化一个unsigned long类型。
template<typename Stream> inline void Serialize(Stream& s, unsigned long a, int, int=0) { WRITEDATA(s, a); }
把WRITEDATA(s, a)用自身的定义取代,以下是展开以后的函数:
template<typename Stream> inline void Serialize(Stream& s, unsigned long a, int, int=0) { s.write((char*)&(a), sizeof(a)); }
该函数接受一个unsigned long参数a,获取它的内存地址,转换指针为char*并调用函数s.write()。
template<typename T> CDataStream& operator<<(const T& obj) { // Serialize to this stream ::Serialize(*this, obj, nType, nVersion); return (*this); } template<typename T> CDataStream& operator>>(T& obj) { // Unserialize from this stream ::Unserialize(*this, obj, nType, nVersion); return (*this); }
头文件serialize.h包含了14个重载后的这两个全局函数给14个原始类型(signed和unsigned版本char,short,int,long和long long,以及char,float,double和bool)以及6个重载版本的6个复合类型(string,vector,pair,map,set和CScript)。因此,对于这些类型,你可以简单地使用以下代码来序列化/反序列化数据:
CDataStream ss(SER_GETHASH); ss<<obj1<<obj2; //序列化 ss>>obj3>>obj4; //反序列化
如果没有任何实现的类型符合第二个参数obj,则以下泛型T全局函数将会被调用。
template<typename Stream, typename T> inline void Serialize(Stream& os, const T& a, long nType, int nVersion=VERSION) { a.Serialize(os, (int)nType, nVersion); }
对于该泛型版本,类型T应该用于实现一个成员函数和签名T::Serialize(Stream, int, int)。它将通过a.Serialize()被调用。
unsigned int GetSerializeSize(int nType=0, int nVersion=VERSION) const; void Serialize(Stream& s, int nType=0, int nVersion=VERSION) const; void Unserialize(Stream& s, int nType=0, int nVersion=VERSION);
这三个函数将由它们相对应的带泛型T的全局函数调用。这些全局函数则由CDataStream中重载的操作符<<和>>调用。
一个宏IMPLEMENT_SERIALIZE(statements)用于定义任意类型的这三个函数的实现。#define IMPLEMENT_SERIALIZE(statements) \ unsigned int GetSerializeSize(int nType=0, int nVersion=VERSION) const \ { \ CSerActionGetSerializeSize ser_action; \ const bool fGetSize = true; \ const bool fWrite = false; \ const bool fRead = false; \ unsigned int nSerSize = 0; \ ser_streamplaceholder s; \ s.nType = nType; \ s.nVersion = nVersion; \ {statements} \ return nSerSize; \ } \ template<typename Stream> \ void Serialize(Stream& s, int nType=0, int nVersion=VERSION) const \ { \ CSerActionSerialize ser_action; \ const bool fGetSize = false; \ const bool fWrite = true; \ const bool fRead = false; \ unsigned int nSerSize = 0; \ {statements} \ } \ template<typename Stream> \ void Unserialize(Stream& s, int nType=0, int nVersion=VERSION) \ { \ CSerActionUnserialize ser_action; \ const bool fGetSize = false; \ const bool fWrite = false; \ const bool fRead = true; \ unsigned int nSerSize = 0; \ {statements} \ }
以下例子示范怎样使用该宏。
#include <iostream> #include "serialize.h" using namespace std; class AClass { public: AClass(int xin) : x(xin){}; int x; IMPLEMENT_SERIALIZE(READWRITE(this->x);) } int main() { CDataStream astream2; AClass aObj(200); //一个x为200的AClass类型对象 cout<<"aObj="<<aObj.x>>endl; asream2<<aObj; AClass a2(1); //另一个x为1的对象 astream2>>a2 cout<<"a2="<<a2.x<<endl; return 0; }
这段程序序列化/反序列化AClass对象。它将在屏幕上输出下面的结果。
aObj=200 a2=200
AClass的这三个序列化/反序列化成员函数可以在一行代码中实现:
IMPLEMENT_SERIALIZE(READWRITE(this->x);) 宏READWRITE()的定义如下#define READWRITE(obj) (nSerSize += ::SerReadWrite(s, (obj), nType, nVersion, ser_action))
该宏的展开被放在宏IMPLEMENT_SERIALIZE(statements)的全部三个函数里。因此,它一次需要完成三件事情:1)返回序列化后数据的大小,2)序列化(写入)数据至流;3)从流中反序列化(读取)数据。参考宏IMPLEMENT_SERIALIZE(statements)中对这三个函数的定义。
想要了解宏READWRITE(obj)怎样工作,你首先需要明白它的完整形式当中的nSerSize,s,nType,nVersion和ser_action是怎么来的。它们全部来自宏IMPLEMENT_SERIALIZE(statements)的三个函数主体部分:template<typename Stream, typename T> inline unsigned int SerReadWrite(Stream& s, const T& obj, int nType, int nVersion, CSerActionGetSerializeSize ser_action) { return ::GetSerializeSize(obj, nType, nVersion); } template<typename Stream, typename T> inline unsigned int SerReadWrite(Stream& s, const T& obj, int nType, int nVersion, CSerActionSerialize ser_action) { ::Serialize(s, obj, nType, nVersion); return 0; } template<typename Stream, typename T> inline unsigned int SerReadWrite(Stream& s, T& obj, int nType, int nVersion, CSerActionUnserialize ser_action) { ::Unserialize(s, obj, nType, nVersion); return 0; }
如你所见,函数::SerReadWrite()被重载为三种版本。取决于最后一个参数,它将会调分别用全局函数::GetSerialize(),::Serialize()和::Unserialize();这三个函数在前面章节已经介绍。
如果你检查三种不同版本的::SerReadWrite()的最后一个参数,你会发现它们全部为空类型。这三种类型的唯一用途是区别::SerReadWrite()的三个版本,继而被宏IMPLEMENT_SERIALIZE()定义的所有函数使用。