上一节讲了ParseParameters函数,接下来就是进行操作数据目录之前判断参数中是否包含帮助或版本信息,如果是的话,就返回相应的提示信息给用户,并退出程序。
代码截图如下:
IsArgSet函数,在文件中src/util.cpp中做了定义,主要是判断是否存在关键字.由于map的特性——一对一的映射关系,决定了返回值只有两种情况:0或1。存在就返回1.
bool ArgsManager::IsArgSet(const std::string& strArg)
{
LOCK(cs_args);
return mapArgs.count(strArg);
}
所以,这里是判断参数中如果存在“-?”或者“-h”或者“-help”或者“-version”,就将包名,版本,许可证等信息按给定格式打印出来。 strprintf函数是将参数格式化并返回字符串。
Tips:PACKAGE_NAME 这个常量,当你一开始下载源码时去src/config/这个目录下去找,是找不到bitcoin-config.h这个文件,只有当你进行./configure编译后才会生成。特别是在window环境下看源码,一开始找不着北,怀疑下的是假的源码,本人亲身经历(掩脸)。
FormatFullVersion函数,定义于 src/clientversion.cpp 文件中:
std::string FormatFullVersion()
{
return CLIENT_BUILD;
}
CLIENT_BUILD的声明(同个文件)如下:
const std::string CLIENT_BUILD(BUILD_DESC CLIENT_VERSION_SUFFIX);
按照逻辑分析到最后,会发现进入BUILD_DESC_FROM_UNKNOWN这个分支,
定义代码还是在该文件里面:
#define BUILD_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 文件中, 作用是将宏定义的参数X转化为字符串。
/**
* Converts the parameter X to a string after macro replacement on X has been performed.
* Don't merge these into one macro!
*/
#define STRINGIZE(X) DO_STRINGIZE(X)
#define DO_STRINGIZE(X) #X
至于 maj,min,rev,build这四个参数,由调用处可知,来自config/bitcoin-config.h
#define CLIENT_VERSION_MAJOR 0
#define CLIENT_VERSION_MINOR 14
#define CLIENT_VERSION_REVISION 2
#define CLIENT_VERSION_BUILD 0
由此可知,FormatFullVersion函数的作用就是拼接客户端的主要版本、次要版本、修正版本以及构建版本,打印出完整版本信息。
同理,下面是判断参数是否有-version,有的话就格式化许可证信息;没有的话就直接拼接字符串,最后打印出来,并退出程序。
if (IsArgSet("-version"))
{
strUsage += FormatParagraph(LicenseInfo());
}
else
{
strUsage += "\n" + _("Usage:") + "\n" +
" bitcoind [options] " + strprintf(_("Start %s Daemon"), _(PACKAGE_NAME)) + "\n";
strUsage += "\n" + HelpMessage(HMM_BITCOIND);
}
其中, HMM_BITCOIND是定义在src/init.h中的枚举常量。
/** The help message mode determines what help message to show */
enum HelpMessageMode {
HMM_BITCOIND,
HMM_BITCOIN_QT
};
接下来看看try里面的代码:
(1)数据目录
if (!fs::is_directory(GetDataDir(false)))
{
fprintf(stderr, "Error: Specified data directory \"%s\" does not exist.\n", GetArg("-datadir", "").c_str());
return false;
}
首先判断GetDataDir(false)返回的是否为目录,如果不是,则打印错误信息,并结束程序。
函数GetDataDir 定义于src/util.cpp.
函数详解如下:
1. 根据fNetSpecific确定路径类型,网络路径或本地路径,由false可知为本地路径。
2. 如果该路径不为空,则直接返回。
3. 如果参数中包含“-datadir”,则获取参数路径信息:GetArg("-datadir", ""),否则,获取默认的数据路径。
4. 判断是否为网络路径,是则获取Path中的BaseParams.DataDir()目录,该目录定义在该目录的定义在 src/chainparamsbase.h。由false可知这一段代码不会执行。
5. 最后创建该数据目录并返回。
(2)配置文件
看第二个try里面的代码:
try
{
ReadConfigFile(GetArg("-conf", BITCOIN_CONF_FILENAME));
} catch (const std::exception& e) {
fprintf(stderr,"Error reading configuration file: %s\n", e.what());
return false;
}
其中,BITCOIN_CONF_FILENAME定义在src/util.cpp,其值为“bitcoin.conf”,表示配置文件。
const char * const BITCOIN_CONF_FILENAME = "bitcoin.conf";
ReadConfigFile函数,顾名思义,读取配置文件。定义于util.h文件,具体实现于util.cpp.
static inline void ReadConfigFile(const std::string& confPath)
{
gArgs.ReadConfigFile(confPath);
}
其目的是将配置文件中的参数与参数值存储于之前说到的 mapArgs 与 mapMultiArgs Map变量映射中。最后 ClearDatadirCache 函数的作用在于将数据目录还原为空目录,防止由于在配置文件中设置了数据目录而导致下次读取的是缓存的旧目录,不会创建新的数据目录。
(3)选择网络
检查“-testnet” 或者“-regtest” 参数,这里得说下,比特币网络有三种类型:主网(Main network)、测试网(testnet)、私有网(regtest)。主网可以理解为生产环境,测试网即可供测试的公共网络,私有网是供开发者进行开发和测试的网络,因为难度比较低,区块很容易产生。
// Check for -testnet or -regtest parameter (Params() calls are only valid after this clause)
try {
SelectParams(ChainNameFromCommandLine());
} catch (const std::exception& e) {
fprintf(stderr, "Error: %s\n", e.what());
return false;
}
首先看下函数SelectParams的参数ChainNameFromCommandLine(),其定义于 src/chainparamsbase.cpp 文件中。
两个变量 fRegTest 和 fTestNet, 分别表示参数是否为回归测试网络和是否为测试网络。
假如同时为是的话,即抛出异常,因为只能选择一种网络类型。
假如fRegTest的值是true,则返回 CBaseChainParams::REGTEST;反之,是测试网络 fTestNet为true, 返回 CBaseChainParams::TESTNET。
假如以上均为false,则返回 CBaseChainParams::MAIN,表示主网络。
以上的常量均在src/chainparamsbase.h中声明。
然后回到函数 SelectParams。
void SelectParams(const std::string& network)
{
SelectBaseParams(network);
globalChainParams = CreateChainParams(network);
}
第一个函数为选择网络函数 SelectBaseParams(network),将前面确定下来的网络作为参数
void SelectBaseParams(const std::string& chain)
{
globalChainBaseParams = CreateBaseChainParams(chain);
}
继续深入后方,CreateBaseChainParams 函数定义于src/chainparamsbase.h,逻辑大概如下:判断如果是主网,则 new 一个 CBaseMainParams对象,其端口为8332
三个类都定义于 src/chainparamsbase.h,其构造函数包含了端口号和数据目录,其中,主网的数据目录为默认。
然后将对象赋给全局变量的智能指针 globalChainParams, 此时该指针将指向新生成的对象,也就包含了端口号和数据目录。
std::unique_ptr
globalChainParams
tips: 智能指针持有对对象的独有权,两个unique_ptr不能指向一个对象,不能进行复制操作只能进行移动操作
那么我们怎么指定网络类型呢?
答案:可以在启动bitcoind或bitcoin-qt时,指定参数-testnet或-regtest,不指定的话为主网。
好了,今天内容有点多,下一节将讲述AppInit() 函数的参数处理和日志初始化操作。
参考:
http://www.jianshu.com/p/bd1903a31b4b
http://www.jianshu.com/p/dd36d13ba781
作者:区块链研习社比特币源码研读班 Jacky