在bitcoind.cpp文件中还剩下noui_connect()与AppInit(argc, argv)两个函数,其中noui_connect()用来链接bitcoind的信号处理,与区块链架构相关不大,所以接下来本篇主要分析AppInit(argc, argv)函数,由于该代码比较长,本文分以下几个部分来分析:
一、
ParseParameters(argc, argv); //将参数(argc, argv)传入解析,并作初步处理
// Process help and version before taking care about datadir 在处理文件目录之前先处理“help”与“version”请求
if (mapArgs.count("-?") || mapArgs.count("-h") || mapArgs.count("-help") || mapArgs.count("-version"))
{ //判断是否请求“help”文档或者“vesion”信息
//FormatFullVersion()返回格式化的全版本信息(版本信息与许可证信息请分开)
std::string strUsage = _("Bitcoin Core Daemon") + " " + _("version") + " " + FormatFullVersion() + "\n";
if (mapArgs.count("-version"))
{
strUsage += LicenseInfo(); //返回许可证信息
}
else
{
strUsage += "\n" + _("Usage:") + "\n" +
" bitcoind [options] " + _("Start Bitcoin Core Daemon") + "\n";
strUsage += "\n" + HelpMessage(HMM_BITCOIND); //返回帮助文档
}
fprintf(stdout, "%s", strUsage.c_str());
return false;
}
ParseParameters(argc, argv)函数是对(argc, argv)进行参数解析,稍后会对其详解,之后判断在命令行窗口接入的参数是否为"-?"、"-h"、"-help"、"-version",(mapArgs是map类型,由ParseParameters(argc, argv)函数返回),接下来就比较简单了,程序会直接判断用户请求的是“help”文档,还是“version”信息,然后进行相应的响应,返回对应的内容。下面请移步到ParseParameters(argc, argv)中,我将和大家细细品味该函数,该函数的容貌如下 (位置:src/util.cpp):
void ParseParameters(int argc, const char* const argv[])
{ // 清空映射
mapArgs.clear();//单键值映射
mapMultiArgs.clear(); //多键值映射
for (int i = 1; i < argc; i++)
{
std::string str(argv[i]); //将参数转为字符串
std::string strValue;
size_t is_index = str.find('='); //在参数字符串中寻找“=”
if (is_index != std::string::npos) //如果找到了“=”(npos是一个不存在的值,用于判断是否能够寻找到特征值)
{
strValue = str.substr(is_index+1); //参数中“=”右侧的值赋给strValue
str = str.substr(0, is_index); //参数左侧的值赋给str
}
#ifdef WIN32 //对于win32,如果str中以‘/’打头,将‘/’更换为‘-’
boost::to_lower(str);
if (boost::algorithm::starts_with(str, "/"))
str = "-" + str.substr(1);
#endif
if (str[0] != '-') //判断str(无参命令行)是否以‘-’打头
break;
// Interpret --foo as -foo.
// If both --foo and -foo are set, the last takes effect.
if (str.length() > 1 && str[1] == '-') //将‘--’打头的str换为‘-’打头
str = str.substr(1);
mapArgs[str] = strValue; //将str与对应的strValue加入单值映射mapArgs
mapMultiArgs[str].push_back(strValue);//将str与对应的strValue们加入多值映射mapMultiArgs
}
首先程序从bitcoind上接收的参数是字符数组const char* const argv[],传入的另一个参数是其长度,本函数是对bitcoind接收的命令进行解析,具体做法为: 1,将接受的字符数组转为字符串str 2,以‘=’为标志,将str截断,作为键值对存入map数据类型的 mapArgs中,之所以这么做是因为,命令行一般格式为“变量=值”,将变量与值隔开存入映射之中,便于后面的执行。
二、
try
{
if (!boost::filesystem::is_directory(GetDataDir(false)))//判断数据目录的正确性
{
fprintf(stderr, "Error: Specified data directory \"%s\" does not exist.\n", mapArgs["-datadir"].c_str());
return false;
}
try
{
ReadConfigFile(mapArgs, mapMultiArgs); //读取配置文件
} catch (const std::exception& e) {
fprintf(stderr,"Error reading configuration file: %s\n", e.what());
return false;
}
这部分代码的功能是读取配置文件,核心函数为GetDataDir(false)与ReadConfigFile(mapArgs, mapMultiArgs),稍后会对这两个函数进行详解。本段代码首先判断由GetDataDir(false)返回的数据目录是否正确,若正确调用ReadConfigFile(mapArgs, mapMultiArgs)读取配置文件。下面进行核心函数讲解:
1,GetDataDir(false)
const boost::filesystem::path &GetDataDir(bool fNetSpecific)
{
namespace fs = boost::filesystem;
LOCK(csPathCached);
fs::path &path = fNetSpecific ? pathCachedNetSpecific : pathCached; //判断是否指定网络路径目录,否则选择本地路径
// This can be called during exceptions by LogPrintf(), so we cache the
// value so we don't have to do memory allocations after that.
if (!path.empty()) //如果路径不空直接返回
return path;
if (mapArgs.count("-datadir")) { //判断是否从命令行中键入datair,如果有直接取其值作为path,否则path取默认值
path = fs::system_complete(mapArgs["-datadir"]);
if (!fs::is_directory(path)) {
path = "";
return path;
}
} else {
path = GetDefaultDataDir();
}
if (fNetSpecific) //判断是否指定了网络,根据指定网络路径或者本地路径构造完整路径
path /= BaseParams().DataDir();
fs::create_directories(path);
return path;}
输入的参数fNetSpecific为bool型,用于判断数据目录是指定的网络目录还是本地目录(这里设置为false),下面进入函数体,首先判断fNetSpecific是否为true,这里为false故将本地目录pathCached(pathCachedNetSpecific 与pathCached事先定义好了,后者定义一般为空)赋值给path,如果path不空,就直接返回,如果为空转为下一步,首先判断之前解析的参数中是否"-datadir",如果有将其对应的值赋给path,否则path取默认值,接下来还是判断fNetSpecific是否为true,如果为true需要根据指定的网络构造完整数据路径,最后构造数据路径path
2,ReadConfigFile(mapArgs, mapMultiArgs)
void ReadConfigFile(map<string, string>& mapSettingsRet,
map<string, vector<string> >& mapMultiSettingsRet)
{
boost::filesystem::ifstream streamConfig(GetConfigFile()); //定义流文件,将配置文件内容导入内存
if (!streamConfig.good())
return; // No bitcoin.conf file is OK
set<string> setOptions; //构造迭代器,消除文件中为“*”的行
setOptions.insert("*");
for (boost::program_options::detail::config_file_iterator it(streamConfig, setOptions), end; it != end; ++it) //将mapArgs中没有的值加入其中
{
// Don't overwrite existing settings so command line settings override bitcoin.conf
string strKey = string("-") + it->string_key; //取配置文件中的参数
if (mapSettingsRet.count(strKey) == 0) //查看配置文件的参数是否存在于mapArgs中,若不存在加入
{
mapSettingsRet[strKey] = it->value[0]; //取参数string_key对应的值
// interpret nofoo=1 as foo=0 (and nofoo=0 as foo=1) as long as foo not set)
InterpretNegativeSetting(strKey, mapSettingsRet); //向mapArgs中加入该键值对(参数与其对应的值)
}
mapMultiSettingsRet[strKey].push_back(it->value[0]);
}
// If datadir is changed in .conf file:
ClearDatadirCache();}
该函数传入的参数是由函数ParseParameters(argc, argv)返回的map数据(对用户键入命令行的参数解析),下面看函数体,第一行定义了流文件将配置文件内容导入内存(GetConfigFile()函数返回的是配置文件的路径),下面使用set<string>命令构造集合并插入"",用以跳过文件中只有""的行,之后请看for循环,这是本函数的核心,传入参数是迭代器类型it(内容为消除全是"*"的行的配置文件的每一行)与it的最后一个元素的索引end,下面看循环体,取出该行的参数(配置文件的中位于"="左侧的参数)赋值给strkey,下面判断mapSettingsRet(对命令行解析之后的参数对)中是否包含该命令,如果不包含,就将该参数与其对应的值加入到mapSettingsRet中。
AppInit(argc, argv)函数中剩余的命令执行与网络初始化等部分在后续章节中讲解,感谢阅读。