比特币源码学习笔记(二)

当前位置:首页 > 币圈百科 > 比特币源码学习笔记(二)

比特币源码学习笔记(二)

2023-01-01币圈百科206

第二章

本章介绍上一章交易创建后,比特币客户端进行数据序列化的过程。

比特币客户端的所有序列化功能都在seriliaze.h中实现,其中,CDataStream类是数据序列化的核心结构。

蓑衣网小编2022bitcoin-code

CDATA流有一个字符类容器来存储序列化数据。它结合了容器类型和流接口来处理数据。它使用六个成员函数来实现这个功能:

class CDATA stream { protected:typedef vector

vector _ type;vector _ type vch无符号int nReadPos短状态;短例外掩码;public:int nType;转换;//.}vch存储序列化数据。它是一个带有自定义内存分配器的字符容器类型。当需要分配/释放内存时,容器的实现将调用内存分配器。内存分配器会在将内存释放给操作系统之前清除内存中的数据,防止机器的其他进程访问这些数据,从而保证数据存储的安全性。这里不讨论这个内存分配器的实现,但是读者可以在serialize上找到,h NReadPos是vch读取数据的起始位置。是状态错误标识符。此变量用于指示序列化/反序列化中可能出现的错误。Exceptmask是一个错误掩码。它被初始化为ios:badbit | ios:failbit。类似于state,它用于指示错误的种类。nType的值为SER_NETWORK、SER_DISK、SER_GETHASH、SER_SKIPSIG、SER_BLOCKHEADERONLY中的一个,其作用是通知CDataStream执行特定的序列化操作。这五个符号在枚举类型中定义。每个符号都是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),};n是转换数。

CDataStream:read()和CDataStream:write()成员函数CDataStream:read()和CDataStream:write()是用于序列化/反序列化CDataStream对象的低级函数。CDataStream read(char* pch,int nSize) { //从缓冲区断言开始读取(nSize=0);unsigned int nReadPos next=nReadPos nSize;if(nReadPosNext=vch . size()){ if(nReadPosNext vch . size()){ setstate(IOs:fail bit,' CDATA stream:read():end of data ');memset(pch,0,nSize);nSize=vch . size()-nReadPos;} memcpy(pch,vch[nReadPos],nSize);nReadPos=0;vch . clear();返回(* this);} memcpy(pch,vch[nReadPos],nSize);nReadPos=nReadPosNext返回(* this);} CDATA stream Write(const char * PCH,int nSize) { //写到缓冲区断言的末尾(nSize=0);vch.insert(vch.end()、pch、PCH nSize);返回(* this);}

CDataStream:read()将nSize字符从CDataStream复制到char* pch指向的内存空间。下面是它的实现过程:

计算要从vch中读取的数据的结束位置,unsigned int nReadPosNext?=nReadPos nSize .如果结束位置大于vch的大小,则当前没有足够的数据来读取。在这种情况下,通过调用函数setState()将状态设置为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中读取或写入字符缓冲区。用于引用这些函数的两个宏被定义为帮助器。# DEFINE WRITE DATA (s,obj) S. WRITE ((char *) (obj),sizeof (obj)) # DEFINE READ DATA (s,obj) S. READ ((char *) (obj),sizeof (obj))

以下是如何使用这些宏的示例:下面的函数序列化一个无符号长整型。

template inline void Serialize(Stream s,unsigned long a,int,int=0) { WRITEDATA(s,a);}

用自己的定义替换WRITEDATA(s,a)。下面是扩展的函数:

Template inline void serialize(streams s,unsigned long a,int,int=0) {s.write ((char *) (a),sizeof(a));}

这个函数接受一个无符号长整型参数a,获取它的内存地址,将指针转换为char*并调用函数s.write()。

CDataStream中的运算符

CDataStream重载该运算符以进行序列化和反序列化。模板CDataStream运算符

(T obj) { //从此流中取消序列化:取消序列化(*this,obj,nType,n version);返回(* this);}

头文件serialize.h包含14个重载的全局函数,用于14种原始类型(char、short、int、long和long long以及char、float、double和bool的有符号和无符号版本)和6种复合类型(string、vector、pair、map、set和CScript)的6个重载版本。因此,对于这些类型,可以简单地使用下面的代码来序列化/反序列化数据:<(const T& obj) { // Serialize to this stream ::Serialize(*this, obj, nType, nVersion); return (*this); } template CDataStream& operator>CDATA stream ss(SER _ get hash);ss

obj 4;//deserialize

如果没有实现的类型匹配第二个参数obj,将调用下面的泛型T全局函数。template inline void Serialize(Stream OS,const T a,long nType,int n VERSION=VERSION){ a . Serialize(OS,(int)nType,n VERSION);}

对于这个泛型版本,应该使用类型T来实现成员函数和签名T:Serialize(Stream,int,int)。它将通过. Serialize()调用。

如何实现一个类型的序列化

在前面的介绍中,泛型T需要实现以下三个成员函数进行序列化。unsigned int GetSerializeSize(int nType=0,int n VERSION=VERSION)const;void Serialize(Stream s,int nType=0,int n VERSION=VERSION)const;void Unserialize(Stream s,int nType=0,int n VERSION=VERSION);

这三个函数将由它们对应的带有泛型t的全局函数调用,这些全局函数由CDataStream中的重载运算符调用。

宏IMPLEMENT_SERIALIZE(statements)用于定义这三个函数的任何类型的实现。 # define implements _ serialization(statements)\ signed int get serialize(int type=0,int n version=version)const \ { cs generationetserialize ser _ action;\ const bool fgetsize=true \ const bool fwrite=false \ const bool fread=false \ signed int insertsize=0;\ s。类型=类型:\ s。版本=版本:\ {语句} \ return nser size \ } \ template \ void serialization(stream s,int ntype=0,int n version=version)const \ { cseractivationsser _ action;\ const bool fgetsize=false \ const bool fwrite=true \ const bool fread=false \ signed int insertsize=0;\ {语句} \ } \模板\无效重新初始化(Stream s,int ntype=0,int nversion=version)\ n重新初始化ser _ action \ const bool fgetsize=false \ const bool fwrite=false \ const bool fread=true \ signed int insertsize=0;\ {语句}}

以下例子示范怎样使用该宏。

#include #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="endl; asream2a2 cout<<"a2="<

这段程序序列化/反序列化AClass对象。它将在屏幕上输出下面的结果。

aObj=200a2=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)的三个函数主体部分: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)。这里是这个函数的全部三种版本。templateinline unsigned int SerReadWrite(Stream& s, const T& obj, int nType, int nVersion, CSerActionGetSerializeSize ser_action){ return ::GetSerializeSize(obj, nType, nVersion);}templateinline unsigned int SerReadWrite(Stream& s, const T& obj, int nType, int nVersion, CSerActionSerialize ser_action){ ::Serialize(s, obj, nType, nVersion); return 0;}templateinline unsigned int SerReadWrite(Stream& s, T& obj, int nType, int nVersion, CSerActionUnserialize ser_action){ ::Unserialize(s, obj,蓑衣网小编2022 nType, nVersion); return 0;}

如你所见,函数::SerReadWrite()被重载为三种版本。取决于最后一个参数,它将会调分别用全局函数::GetSerialize(),::Serialize()和::Unserialize();这三个函数在前面章节已经介绍。

如果你检查三种不同版本的::SerReadWrite()的最后一个参数,你会发现它们全部为空类型。这三种类型的唯一用途是区别::SerReadWrite()的三个版本,继而被宏IMPLEMENT_SERIALIZE()定义的所有函数使用。
比特币源码学习笔记(二) | 分享给朋友: