0
  • 聊天消息
  • 系统消息
  • 评论与回复
登录后你可以
  • 下载海量资料
  • 学习在线课程
  • 观看技术视频
  • 写文章/发帖/加入社区
会员中心
创作中心

完善资料让更多小伙伴认识你,还能领取20积分哦,立即完善>

3天内不再提示

比特币源码技术分析

C语言专家集中营 来源:未知 作者:李建兵 2018-03-16 17:19 次阅读

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

CDataStream

CDataStream拥有一个字符类容器用来存放序列化之后的数据。它结合一个容器类型和一个流(stream)界面以处理数据。它使用6个成员函数实现这一功能:

[cpp]view plaincopy

classCDataStream

{

protected:

typedefvector>vector_type;

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

{

//primaryactions

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

templateinlinevoidSerialize(Stream&s,unsignedlonga,int,int=0){WRITEDATA(s,a);}

把WRITEDATA(s, a)用自身的定义取代,以下是展开以后的函数:

[cpp]view plaincopy

templateinlinevoidSerialize(Stream&s,unsignedlonga,int,int=0){s.write((char*)&(a),sizeof(a));}

该函数接受一个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="<>endl;

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

    文章

    7005

    浏览量

    140506

原文标题:比特币源码技术分析-2

文章出处:【微信号:C_Expert,微信公众号:C语言专家集中营】欢迎添加关注!文章转载请注明出处。

收藏 人收藏

    评论

    相关推荐

    #硬声创作季 区块链:1.1认识比特

    区块链比特
    Mr_haohao
    发布于 :2022年10月16日 23:44:12

    #硬声创作季 区块链:1.6比特的分叉

    区块链比特
    Mr_haohao
    发布于 :2022年10月16日 23:48:32

    #硬声创作季 区块链与加:1.2比特的来源

    区块链比特
    Mr_haohao
    发布于 :2022年10月17日 09:42:07

    #硬声创作季 区块链与加:1.3比特是什么

    区块链比特
    Mr_haohao
    发布于 :2022年10月17日 09:42:44

    #硬声创作季 区块链与加:1.6比特分叉

    区块链比特
    Mr_haohao
    发布于 :2022年10月17日 09:44:29

    #硬声创作季 区块链与加:2.4比特回顾

    区块链比特
    Mr_haohao
    发布于 :2022年10月17日 09:47:11

    究竟比特是什么

    。  然而,比特却在大洋彼岸赢得了肯定,华尔街最大的机构之一——美银美林宣布其研究范围正式覆盖比特,并对比特
    发表于 12-15 11:17

    时代周刊:为什么比特是自由的源泉?

    银行账户,但他无法冻结比特钱包;在难民营,你可能无法找到一家银行,但只要有网络,你就可以收到比特,你不需要获得任何人的批准,也不用证明自己的身份。
    发表于 01-01 23:23

    莱特比特的区别

    本文详细的介绍了莱特比特的相关概念,其中包括了莱特矿池介绍、比特特征和
    发表于 01-09 11:21 6546次阅读

    比特是不是电子货币_比特怎么交易

    本文开始详细的介绍了比特的特征,其次介绍了比特的几个易平台和分析比特
    发表于 01-30 16:25 1w次阅读

    关于比特源码技术分析

    为了存储、搜索、读取在内存与磁盘中的区块和交易信息,比特引入了一些访问类。它们包括:CBlockIndex和CDiskBlockIndex用于索引区块,CDiskTxPos和CTxIndex用于索引交易。
    的头像 发表于 04-02 16:55 5266次阅读

    比特现金BCH才是原始的比特区块链

    美国国家标准和技术研究所(NIST ):比特是一个分叉,BCH才是最初的区块链。NIST 于2018年1月29日发布了一份详细的比特和区
    发表于 11-08 14:19 1531次阅读

    比特价格的上涨推动了比特矿业的利润

    加密货币分析师亚历克斯克鲁格(Alex Kruger)表示,有效的比特开采业务目前的盈亏平衡点在3550美元至4350美元之间,而在撰写本文时比特
    发表于 06-18 10:59 677次阅读
    <b class='flag-5'>比特</b><b class='flag-5'>币</b>价格的上涨推动了<b class='flag-5'>比特</b><b class='flag-5'>币</b>矿业的利润

    比特比特现金的区别是什么

    比特核心(BTC)的支持者说,比特不是用来买咖啡的。他们认为比特的目的是作为一种价值储备,
    发表于 12-27 09:27 2674次阅读

    Twitter的比特情感分析器怎样来构建

    我们的比特情绪分析仪功能完善,为进一步分析比特情绪提供了良好的基础。
    发表于 01-12 10:49 1179次阅读