第二章
本章继上一章交易创建之后介绍比特币客户端序列化数据的过程。
比特币客户端所有的序列化函数均在seriliaze.h中实现。其中,CDataStream类是数据序列化的核心结构。
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;
//......
}
- 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的次方。
enum
{
// primary actions
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(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所指向的内存空间。以下是它的实现过程:
- 计算将要从vch读取的数据的结束位置,unsigned int nReadPosNext = nReadPos + nSize。
- 如果结束位置比vch的大小更大,则当前没有足够的数据供读取。在这种情况下,通过调用函数setState()将state设为ios::failbit,并将所有的零复制到pch。
- 否则,调用memcpy(pch, &vch[nReadPos], nSize)复制nSize个字符,从vch的nReadPos位置开始,到由pch指向的一段预先分配的内存。接着从nReadPos向前移至下一个起始位置nReadPosNext(第22行)。
宏READDATA()和WRITEDATA()
#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()。
CDataStream中的操作符 << 和 >>
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中重载的操作符<<和>>调用。
#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的这三个序列化/反序列化成员函数可以在一行代码中实现:
#define READWRITE(obj) (nSerSize += ::SerReadWrite(s, (obj), nType, nVersion, ser_action))
该宏的展开被放在宏IMPLEMENT_SERIALIZE(statements)的全部三个函数里。因此,它一次需要完成三件事情:1)返回序列化后数据的大小,2)序列化(写入)数据至流;3)从流中反序列化(读取)数据。参考宏IMPLEMENT_SERIALIZE(statements)中对这三个函数的定义。
- nSerSize是一个unsigned int,在三个函数当中初始化为0;
- ser_action是一个对象在三个函数当中均有声明,但为三种不同类型。它在三个函数当中分别为CSerActionGetSerializeSize、CSerActionSerialize和CSerActionUnserialize;
- s在第一个函数中定义为ser_streamplaceholder类型。它是第一个传入至另外两个函数的参数,拥有参数类型Stream;
- nType和nVersion在三个函数中均为传入参数。
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();这三个函数在前面章节已经介绍。