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

区块链技术巴比特2017-11-25 06:57:40  阅读 -评论 0

第二章

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

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

CDataStream

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

class CDataStream { protected: typedef vector > 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()和CDataStream::write()是用于执行序列化/反序列化CDataStream对象的低级函数。

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行)。

该实现表明1)当一段数据被从流中读取之后,该段数据无法被再次读取;2)nReadPos是第一个有效数据的读取位置。

CDataStream::write()非常简单。它将由pch指向的nSize个字符附加到vch的结尾。

宏READDATA()和WRITEDATA()

函数CDataStream::read()与CDataStream::write()的作用是序列化/反序列化原始类型(int,bool,unsigned long等)。为了序列化这些数据类型,这些类型的指针将被转换为char*。由于这些类型的大小目前已知,它们可以从CDataStream中读取或者写入至字符缓冲。两个用于引用这些函数的宏被定义为助手。

#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中的操作符 << 和 >>

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 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()被调用。

怎样实现一个类型的序列化

在之前的介绍当中,泛型T需要实现以下三个成员函数进行序列化。

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)的三个函数主体部分:

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)。这里是这个函数的全部三种版本。

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()定义的所有函数使用。

声明:链世界登载此文仅出于分享区块链知识,并不意味着赞同其观点或证实其描述。文章内容仅供参考,不构成投资建议。投资者据此操作,风险自担。此文如侵犯到您的合法权益,请联系我们100@7234.cn

    参与讨论 (0 人参与讨论)

    相关推荐

    区块链投资趋势报告:巨头入场布局行业趋于成熟

    区块链投资趋势报告:巨头入场布局行业趋于成熟

    来自:https://mp.weixin.qq.com/s?__biz=MzI4NzIxOTY1NA==&amp;mid=2650632639&amp;idx=1&amp;sn=e6d1c29731d992a80410aaee82ec3ea6&amp;chksm=f3d8db16c4af520097e4a64a71b1d4743ac326b9f027

    重新发明货币

    重新发明货币

    一、货币的演化过程 先简单回顾一下人类货币的演化过程,大概有以下阶段: a. 1.0版本:自然货币(贝壳、牲口、金银……) 这个阶段,货币基于一般等价物的稀有性或者实用性,货币不可能出现人为操纵的超发。 b. 2.0版本:早期纸币、银票到本位纸币 当贸易量越来越大,实物货币太不方便了,而且大家发现其实并不在意货币本身有什么价值,在意的只是这么多的货币能不能交换到足够的物品,于是纸币这种信用货

    从比特币交易看欧洲央行虚拟货币分类

    从比特币交易看欧洲央行虚拟货币分类

      互联网对传统社会的颠覆从未停止,在其完成对信息流、商流、物流、资金流的初步改造之后,或将以虚拟货币的形式打破现有货币体系   4月18日,在中国极客张沈鹏创办的比特币交易平台(42BTC.com)上,比特币对人民币的平均交易价为576元。当天,该平台完成了100个比特币的交易量。仅仅过去一周,4月25日上午,比特币对人民币的平均交易价已达到906元。据42BTC网站统计:在过去的32个月

    欧洲央行-比特币报告

    3.1 比特币 3.1.1 基本特征          比特币可能是最成功的,也可能是最有争议的虚拟货币方案,由日本程序员中本聪(译者注:事实上,中本聪是不是日本人,甚至是不是单个人无从考证)在2009年设计并实现。该计划基于一个类似于BitTorrent的P2P网络。BitTorrent是互联网上著名的共享文件协议,应用在电影,游戏和音乐领域。比特币在全球层面上运作,可用于各类货币交易(虚

    彻底玩转比特币地址和私匙

    彻底玩转比特币地址和私匙

    比特币地址和私匙是所有比特币初学者面对的一大难题,再加上那一串超长的字符串,让人更是摸不到头脑。 现在编者以问答的形式,带你一步步的揭开比特币地址和私匙的面纱。 还不知道什么是比特币地址和私匙的同学请点这里 问题一、比特币钱包由什么组成? 答 我们知道,比特币地址和私匙组成了比特币钱包,而私匙则决定了比特币地址上比特币的归属。 地址和私匙 问题二、如果只记得私匙我们还能还原比特币地址么? 答

    用GO语言实现比特币算法

    用GO语言实现比特币算法

    本节的这个例子展示一点点高精度数学包math/big、一点点散列包hash、一点点加密包crypto,还有一点点测试包testing的知识。这里不介绍bitcoin协议和算法——尽管它们很有趣,而是试图指出,Go对多种操作系统的支持,是实现这种跨平台应用的理想语言。 位钱(bitcoin)是一种使用加密手段制作的分布式电子货币。它最初于1998年由Wei Dai提出,并由中本聪(Satoshi

    详解比特币的找零机制

    详解比特币的找零机制

    比特币的找零机制一直让人有些迷惑,明明只向一个地址发送了比特币为什么 blockchain 上面的显示的有时是1个地址对多个地址,有时是多个地址对1个地址,有时又显示多个地址对多个地址? 为什么比特币资深用户要提醒大家当比特币钱包交易100次以上时再次交易后要重新备份钱包,恢复以前的钱包备份有可能会遭遇损失? 是的,这一切都是因为比特币的找零(Change)机制。本文参考 Bitcoin的维

    玩转比特币客户端之一:C盘转移和加速下载

    玩转比特币客户端之一:C盘转移和加速下载

    C盘空间不足?交易数据下载速度太慢?别着急,乐享比特币教你轻松玩转比特币官方客户端。 所有新人开始接触比特币时做的第一件事情大多数是安装比特币的官方客户端。 安全起见大家最好直接访问官方发布渠道sourceforge的地址进行下载:http://sourceforge.net/projects/bitcoin/files/Bitcoin/ 该网页列出了各版本的官方比特币客户端,目前

    麦妖榜
    更新日期 2019-01-16
    排名用户贡献值
    1等待的宿命23695
    2BitettFan23632
    3六叶树20309
    4天下无双16192
    5lizhen00214782
    6区块大康14461
    7让时间淡忘14188
    8冷风大q11188
    9momo11174
    10linjm122710562
    返回顶部 ↑