比特幣客戶端所有的序列化函數均在seriliaze.h中實現。其中,CDataStream類是數據序列化的核心結構。
CDataStream
CDataStream擁有一個字符類容器用來存放序列化之后的數據。它結合一個容器類型和一個流(stream)界面以處理數據。它使用6個成員函數實現這一功能:
[cpp]view plaincopy
classCDataStream
{
protected:
typedefvector
vector_typevch;
unsignedintnReadPos;
shortstate;
shortexceptmask;
public:
intnType;
intnVersion;
//......
}
vch存有序列化后的數據。它是一個擁有自定義內存分配器的字符容器類型。該內存分配器將由該容器的實現在需要分配/釋放內存時調用。該內存分配器會在向操作系統釋放內存前清空內存中的數據以防止本機的其他進程訪問此數據,從而保證數據存儲的安全性。該內存分配器的實現在此不進行討論,讀者可于serialize.h自行查找。
nReadPos是vch讀取數據的起始位置。
state是錯誤標識。該變量用于指示在序列化/反序列化當中可能出現的錯誤。
exceptmask是錯誤掩碼。它初始化為ios::badbit | ios::failbit。與state類似,它被用于指示錯誤種類。
nType的取值為SER_NETWORK,SER_DISK,SER_GETHASH,SER_SKIPSIG,SER_BLOCKHEADERONLY之一,其作用為通知CDataStream進行具體某種序列化操作。這5個符號被定義在一個枚舉類型enum里。每個符號均為一個int類型(4字節),并且其值為2的次方。
[cpp]view plaincopy
enum
{
SER_NETWORK=(1<0),??
SER_DISK=(1<1),??
SER_GETHASH=(1<2),??
//modifiers
SER_SKIPSIG=(1<16),??
SER_BLOCKHEADERONLY=(1<17),??
};
nVersion是版本號。
CDataStream::read()與CDataStream::write()
成員函數CDataStream::read()和CDataStream::write()是用于執行序列化/反序列化CDataStream對象的低級函數。
[cpp]view plaincopy
CDataStream&read(char*pch,intnSize)
{
//Readfromthebeginningofthebuffer
assert(nSize>=0);
unsignedintnReadPosNext=nReadPos+nSize;
if(nReadPosNext>=vch.size())
{
if(nReadPosNext>vch.size())
{
setstate(ios::failbit,"CDataStream::read():endofdata");
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(constchar*pch,intnSize)
{
//Writetotheendofthebuffer
assert(nSize>=0);
vch.insert(vch.end(),pch,pch+nSize);
return(*this);
}
CDataStream::read()從CDataStream復制nSize個字符到一個由char* pch所指向的內存空間。以下是它的實現過程:
計算將要從vch讀取的數據的結束位置,unsigned int nReadPosNext = nReadPos + nSize。
如果結束位置比vch的大小更大,則當前沒有足夠的數據供讀取。在這種情況下,通過調用函數setState()將state設為ios::failbit,并將所有的零復制到pch。
否則,調用memcpy(pch, &vch[nReadPos], nSize)復制nSize個字符,從vch的nReadPos位置開始,到由pch指向的一段預先分配的內存。接著從nReadPos向前移至下一個起始位置nReadPosNext(第22行)。
該實現表明1)當一段數據被從流中讀取之后,該段數據無法被再次讀取;2)nReadPos是第一個有效數據的讀取位置。
CDataStream::write()非常簡單。它將由pch指向的nSize個字符附加到vch的結尾。
宏READDATA()和WRITEDATA()
函數CDataStream::read()與CDataStream::write()的作用是序列化/反序列化原始類型(int,bool,unsigned long等)。為了序列化這些數據類型,這些類型的指針將被轉換為char*。由于這些類型的大小目前已知,它們可以從CDataStream中讀取或者寫入至字符緩沖。兩個用于引用這些函數的宏被定義為助手。
[cpp]view plaincopy
#defineWRITEDATA(s,obj)s.write((char*)&(obj),sizeof(obj))
#defineREADDATA(s,obj)s.read((char*)&(obj),sizeof(obj))
這里是如何使用這些宏的例子。下面的函數將序列化一個unsigned long類型。
[cpp]view plaincopy
[cpp]view plaincopy
template
把WRITEDATA(s, a)用自身的定義取代,以下是展開以后的函數:
[cpp]view plaincopy
template
該函數接受一個unsigned long參數a,獲取它的內存地址,轉換指針為char*并調用函數s.write()。
CDataStream中的操作符 << 和 >>
CDataStream重載了操作符<< 和 >>用于序列化和反序列化。
[cpp]view plaincopy
template
CDataStream&operator<<(const?T&?obj)??
{
//Serializetothisstream
::Serialize(*this,obj,nType,nVersion);
return(*this);
}
template
CDataStream&operator>>(T&obj)
{
//Unserializefromthisstream
::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)。因此,對于這些類型,你可以簡單地使用以下代碼來序列化/反序列化數據:
[cpp]view plaincopy
CDataStreamss(SER_GETHASH);
ss<
ss>>obj3>>obj4;//反序列化
如果沒有任何實現的類型符合第二個參數obj,則以下泛型T全局函數將會被調用。
[cpp]view plaincopy
template
inlinevoidSerialize(Stream&os,constT&a,longnType,intnVersion=VERSION)
{
a.Serialize(os,(int)nType,nVersion);
}
對于該泛型版本,類型T應該用于實現一個成員函數和簽名T::Serialize(Stream, int, int)。它將通過a.Serialize()被調用。
怎樣實現一個類型的序列化
在之前的介紹當中,泛型T需要實現以下三個成員函數進行序列化。
[cpp]view plaincopy
unsignedintGetSerializeSize(intnType=0,intnVersion=VERSION)const;
voidSerialize(Stream&s,intnType=0,intnVersion=VERSION)const;
voidUnserialize(Stream&s,intnType=0,intnVersion=VERSION);
這三個函數將由它們相對應的帶泛型T的全局函數調用。這些全局函數則由CDataStream中重載的操作符<<和>>調用。
一個宏IMPLEMENT_SERIALIZE(statements)用于定義任意類型的這三個函數的實現。
[cpp]view plaincopy
#defineIMPLEMENT_SERIALIZE(statements)\
unsignedintGetSerializeSize(intnType=0,intnVersion=VERSION)const\
{\
CSerActionGetSerializeSizeser_action;\
constboolfGetSize=true;\
constboolfWrite=false;\
constboolfRead=false;\
unsignedintnSerSize=0;\
ser_streamplaceholders;\
s.nType=nType;\
s.nVersion=nVersion;\
{statements}\
returnnSerSize;\
}\
template
voidSerialize(Stream&s,intnType=0,intnVersion=VERSION)const\
{\
CSerActionSerializeser_action;\
constboolfGetSize=false;\
constboolfWrite=true;\
constboolfRead=false;\
unsignedintnSerSize=0;\
{statements}\
}\
template
voidUnserialize(Stream&s,intnType=0,intnVersion=VERSION)\
{\
CSerActionUnserializeser_action;\
constboolfGetSize=false;\
constboolfWrite=false;\
constboolfRead=true;\
unsignedintnSerSize=0;\
{statements}\
}
以下例子示范怎樣使用該宏。
[cpp]view plaincopy
#include
#include"serialize.h"
usingnamespacestd;
classAClass{
public:
AClass(intxin):x(xin){};
intx;
IMPLEMENT_SERIALIZE(READWRITE(this->x);)
}
intmain(){
CDataStreamastream2;
AClassaObj(200);//一個x為200的AClass類型對象
cout<<"aObj="<
asream2<
AClassa2(1);//另一個x為1的對象
astream2>>a2
cout<<"a2="<
return0;
}
這段程序序列化/反序列化AClass對象。它將在屏幕上輸出下面的結果。
[cpp]view plaincopy
aObj=200
a2=200
AClass的這三個序列化/反序列化成員函數可以在一行代碼中實現:
IMPLEMENT_SERIALIZE(READWRITE(this->x);)
宏READWRITE()的定義如下
[cpp]view plaincopy
#defineREADWRITE(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)的三個函數主體部分:
nSerSize是一個unsigned int,在三個函數當中初始化為0;
ser_action是一個對象在三個函數當中均有聲明,但為三種不同類型。它在三個函數當中
分別為CSerActionGetSerializeSize、CSerActionSerialize和
CSerActionUnserialize;
s在第一個函數中定義為ser_streamplaceholder類型。它是第一個傳入至另外兩個函數
的參數,擁有參數類型Stream;
nType和nVersion在三個函數中均為傳入參數。
因此,一旦宏READWRITE()擴展至宏IMPLEMENT_SERIALIZE(),所有它的符號都將被計算,
因為它們已經存在于宏IMPLEMENT_SERIALIZE()的主體中。READWRITE(obj)的擴展調用
一個全局函數::SerReadWrite(s, (obj), nType, nVersion, ser_action)。
這里是這個函數的全部三種版本。
[cpp]view plaincopy
template
inlineunsignedintSerReadWrite(Stream&s,constT&obj,intnType,intnVersion,CSerActionGetSerializeSizeser_action)
{
return::GetSerializeSize(obj,nType,nVersion);
}
template
inlineunsignedintSerReadWrite(Stream&s,constT&obj,intnType,intnVersion,CSerActionSerializeser_action)
{
::Serialize(s,obj,nType,nVersion);
return0;
}
template
inlineunsignedintSerReadWrite(Stream&s,T&obj,intnType,intnVersion,CSerActionUnserializeser_action)
{
::Unserialize(s,obj,nType,nVersion);
return0;
}
如你所見,函數::SerReadWrite()被重載為三種版本。取決于最后一個參數,它將會調分別用全局函數::GetSerialize(),::Serialize()和::Unserialize();這三個函數在前面章節已經介紹。
如果你檢查三種不同版本的::SerReadWrite()的最后一個參數,你會發現它們全部為空類型。
這三種類型的唯一用途是區別::SerReadWrite()的三個版本,
繼而被宏IMPLEMENT_SERIALIZE()定義的所有函數使用。
-
比特幣
+關注
關注
57文章
7002瀏覽量
140330
原文標題:比特幣源碼技術分析-2
文章出處:【微信號:C_Expert,微信公眾號:C語言專家集中營】歡迎添加關注!文章轉載請注明出處。
發布評論請先 登錄
相關推薦
評論