比特币源码研读(6)-main函数(5)steemCreated with Sketch.

in #btc7 years ago

上一次,我们代码看到了

gArgs.ParseParameters(argc, argv);
分析输入的参数,今天我们继续看下面的代码。

// Process help and version before taking care about datadir

if (gArgs.IsArgSet("-?") || gArgs.IsArgSet("-h") || gArgs.IsArgSet("-help") || gArgs.IsArgSet("-version"))

{

std::string strUsage = strprintf(_("%s Daemon"), _(PACKAGE_NAME)) + " " + _("version") + " " + FormatFullVersion() + "\n";

if (gArgs.IsArgSet("-version"))

{

strUsage += FormatParagraph(LicenseInfo());

}
else

{

strUsage += "\n" + _("Usage:") + "\n" +

" bitcoind [options] " + strprintf(_("Start %s Daemon"), _(PACKAGE_NAME)) + "\n";

strUsage += "\n" + HelpMessage(HMM_BITCOIND);

}
fprintf(stdout, "%s", strUsage.c_str());

return true;

}
从注释,我们可以看出,这是在读取数据目录以前,先处理版本信息和帮助文档

如果if语句成立,则执行if中的语句,执行完成后,返回true,程序结束,如果不执行if语句,则执行if后面的语句。

其中if语句中的函数IsArgSet是昨天讲的类ArgsManager中的函数,定义在util.h,实现在util.cpp.我们看看他的实现函数

bool ArgsManager::IsArgSet(const std::string& strArg)

const

{

LOCK(cs_args);//所存这个变量,保证该线程调用时,其他线程不会调用

return mapArgs.count(strArg);

}
这个函数中我们先看看变量mapArgs,定义为

std::map mapArgs;
在标准库std中,map是一类关联式容器。map的key-value用于1对1的映射情况

count函数,查找map中,是否有这个value,如果有则返回true,如果没有则返回false.

因此

IsArgSet(const std::string& strArg)
就是检查mapArgs中是否有strArg,如果有,则返回true,如果没有,则返回false.

昨天看到的

mapArgs.clear();
clear是清空mapArgs中所有的元素

再返回去,看if语句

if (gArgs.IsArgSet("-?") ||gArgs.IsArgSet("-h") ||gArgs.IsArgSet("-help")|| gArgs.IsArgSet("-version"))
即查看gArgs中是否有“-?”,"-h","-help","-version",前3个是帮助标识,后一个是版本标识,如果检测到其中一个,则进入if语句,开始执行,执行完成后,返回true。否则不进入if'语句,执行下面的语句。

1) 版本信息

第一句是

std::string strUsage =strprintf(_("%s Daemon"), (PACKAGE_NAME)) + " " +("version") + " " + FormatFullVersion() + "\n";

读取版本信息,在菜菜子的文章中,对这句代码有详细说明。

/********************以下是菜菜子的分析****************************/

strprintf函数为字符串格式化命令,主要功能是把格式化的数据写入某个字符串中。此处是将PACKAGE_NAME写入_("%s Daemon")中,生成PACKAGE_NAME

Daemon形式的字符串内容。PACKAGE_NAME的定义位于src/config/bitcoin-config.h中,其定义为:

该文件在我们下载的源码中一开始是不存在的,需经过对源码进行./configure命令后才能生成。源码的./configure过程可参见我的《聊聊比特币(Bitcoin)客户端源码编译那些事》一文。

FormatFullVersion函数的功能是输出比特币核心的完整版本信息。该函数的实现位于src/clientversion.cpp中,其实现代码如下:

std::string FormatFullVersion()

{

return CLIENT_BUILD;

}
函数中直接调用了CLIENT_BUILD函数,该函数的定义也在当前文件

const std::stringCLIENT_BUILD(BUILD_DESC CLIENT_VERSION_SUFFIX);
再来看BUILD_DESC,其定义就在当前文件:

#ifndef BUILD_DESC

#ifdef BUILD_SUFFIX

#defineBUILD_DESCBUILD_DESC_WITH_SUFFIX(CLIENT_VERSION_MAJOR,CLIENT_VERSION_MINOR,CLIENT_VERSION_REVISION, CLIENT_VERSION_BUILD,BUILD_SUFFIX)

#elif defined(GIT_COMMIT_ID)

#defineBUILD_DESCBUILD_DESC_FROM_COMMIT(CLIENT_VERSION_MAJOR,CLIENT_VERSION_MINOR,CLIENT_VERSION_REVISION, CLIENT_VERSION_BUILD,GIT_COMMIT_ID)

#else

#define BUILD_DESCBUILD_DESC_FROM_UNKNOWN(CLIENT_VERSION_MAJOR,CLIENT_VERSION_MINOR,

CLIENT_VERSION_REVISION, CLIENT_VERSION_BUILD)

#endif
#endif
通过分析#ifndef BUILD_DESC,我们可以判断BUILD_DESC将在BUILD_DESC_FROM_UNKNOWN函数中执行,该函数采用的是预编译实现方式,这样的好处是对于小型、通用性函数采用预编译方式可以提高程序的执行效率。

#defineBUILD_DESC_FROM_UNKNOWN(maj, min,rev, build) \

"v" DO_STRINGIZE(maj)"." DO_STRINGIZE(min)"." DO_STRINGIZE(rev) "."DO_STRINGIZE(build)"-unk"
在函数实现中调用了DO_STRINGIZE函数,该函数的实现位于src/clientversion.h中

/**

  • Converts theparameter X to astring after macro replacement on X has been performed.

  • Don't mergethese into onemacro!

*/

#define STRINGIZE(X)DO_STRINGIZE(X)

#define DO_STRINGIZE(X) #X
通过其注释,我们可以知道该函数的作用是将宏定义的参数X转变为字符串。那问题来了,BUILD_DESC_FROM_UNKNOWN函数中调用的4次DO_STRINGIZE函数包含的变量是宏定义变量吗?答案肯定是的,不然程序就出错了。那包含的maj, min, rev, build在哪定义呢?那我们就需要看BUILD_DESC_FROM_UNKNOWN调用位置传入的四个变量:

CLIENT_VERSION_MAJOR

CLIENT_VERSION_MINOR

CLIENT_VERSION_REVISION

CLIENT_VERSION_BUILD
他们的定义位于src/clientversion.h中,通过定义我们可知其为宏定义,因此传入DO_STRINGIZE函数中是没问题的。

#define CLIENT_VERSION_MAJOR 0

#define CLIENT_VERSION_MINOR 14

#define CLIENT_VERSION_REVISION 2

#define CLIENT_VERSION_BUILD 0
再来看BUILD_DESC_FROM_UNKNOWN的实现,其功能是将版本信息的主要、次要、修正以及建立4个值进行拼接,从而输出完整的版本号信息。

/*****************************************以上为菜菜子分析*********************/

2)版本许可

下面开始判断,如果检测到的是版本信息

if (gArgs.IsArgSet("-version"))

{

strUsage +=FormatParagraph(LicenseInfo());

}
代码的意思是将LicenseInfo()返回的字符串拼接到strUsage上。

其中:FormatParagraph()函数

/**

  • Format aparagraph of text to a fixed width, adding spaces for

  • indentation toany added line.

*/

std::string FormatParagraph(const std::string& in,size_t width = 79, size_t indent = 0);
从注释可以看出,该函数主要是通过添加空格的方式把一段文本变成固定宽度。

函数处理的内容是LicenseInfo()而,这个函数包含着版权许可信息。

因此,if语句的功能就是输出版本许可信息,下面我们用ubuntu进行测试。在终端中输入命令"bitcoin -version",

1.png

3)帮助信息

else

{

strUsage += "\n" +_("Usage:") + "\n" +

"bitcoind [options]" +strprintf(_("Start %s Daemon"), _(PACKAGE_NAME)) + "\n";

strUsage += "\n" +HelpMessage(HMM_BITCOIND);

}
其中HelpMessage函数的注释如下

/**

  • Format a stringto be used as group of options in help messages

  • @param messageGroup name (e.g. "RPC server options:")

  • @return theformatted string

*/

std::string HelpMessageGroup(const std::string&message);
用于显示帮助信息。

HMM_BITCOIND定义如下

/** The help message mode determines what help message toshow */

enum HelpMessageMode {

HMM_BITCOIND,//比特币后台进程帮助信息

HMM_BITCOIN_QT//比特币前台进程帮助信息

};
此处我们研读的代码为后台进程bitcoind程序,所以参数为HMM_BITCOIND。在HelpMessage函数中,将会根据具体的类型输出相应的帮助信息内容,其帮助内容主要为后台进程涉及参数的使用方法说明。所以,大家后续在使用后台进程时,如果遇到不会的命令,可以通过“bitcoind -?”、"bitcoind -h"或" bitcoind -help"得到帮助信息。

下面在ubuntu中测试一下,输入命令“bitcoind -?”输出的信息,与HelpMessage函数中的一致

2.png
最后程序通过fprintf(stdout, "%s",strUsage.c_str());实现版本或帮助信息的输出

///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

区块链研习社比特币源码研读班 electroman

以下是广告:

我们区块链研习社已创建“区块链研习社币圈交流”小密圈”,在小密圈中,我们将带领大家一起学习区块链的原理与投资,还将提供区块链基本原理解答、交易所注册与交易操作、ICO交易与操作、投资分析、风险分析等内容。

区块链研习社币圈交流.png