上一篇文章我们大致分析了一下比特币源码src文件夹的目录结构以及数据目录结构,接下来我们将进入源码的分析。本篇涉及的文件包括:
bitcoind.cpp、noui.cpp、ui_interface.hui_interface.h、util.cpp、init.cpp、chainparamsbase.cpp、chainparams.cpp
下图整理了本篇文章中涉及的一些主要方法调用,以及相互的调用关系。
在编译完源码后,我们可以通过src/bitcoind命令启动比特币守护进程。根据该名称,我们很容易可以在src目录下找到一个名为bitcoind.cpp的文件。打开该文件,其main函数的内容如下:
int main(int argc, char* argv[])
{
SetupEnvironment();
// Connect bitcoind signal handlers
noui_connect();
return (AppInit(argc, argv) ? EXIT_SUCCESS : EXIT_FAILURE);
}
SetupEnvironment()
该方法中处理了32位虚拟地址分配以及locale相关的问题,具体可参考http://www.jianshu.com/p/4fc762796f83 。
noui_connect()
方法定义如下:
void noui_connect()
{
// Connect bitcoind signal handlers
uiInterface.ThreadSafeMessageBox.connect(noui_ThreadSafeMessageBox);
uiInterface.ThreadSafeQuestion.connect(noui_ThreadSafeQuestion);
uiInterface.InitMessage.connect(noui_InitMessage);
}
ThreadSafeMessageBox、ThreadSafeQuestion、InitMessage是在uiInterface定义的三个信号量boost::signal:signal2,其定义如下:
/** Show message box. */
boost::signals2::signal <bool (const std::string& message, const std::string& caption, unsigned int style), boost::signals2::last_value<bool>>ThreadSafeMessageBox;
/** If possible, ask the user a question. If not, falls back to ThreadSafeMessageBox(noninteractive_message, caption, style) and returns false. */
boost::signals2::signal<bool (const std::string& message, const std::string& noninteractive_message, const std::string& caption, unsigned int style), boost::signals2::last_value<bool>> ThreadSafeQuestion;
/** Progress message during initialization. */
boost::signals2::signal<void (const std::string &message)> InitMessage;
<>中定义了其插槽需要满足的接口以及返回值需求,以ThreadSafeMessageBox为例,其对插槽的要求是函数返回值为bool且形参分别是std::string&、std::string&、std::string&和unsigned int.据上分析,我们看下为ThreadSafeMessageBox绑定的插槽noui_ThreadSafeMessageBox的定义(noui.cpp中), 其符合信号ThreadSafeMessageBox的定义。
static bool noui_ThreadSafeMessageBox(const std::string& message, const std::string& caption, unsigned int style);
AppInit
首先方法处理了用户提供的运行参数gArgs.ParseParameters(argc, argv)。gArgs为定义在util.cpp中的一个全局ArgsManager变量,我们来看下其提供的ParseParameters方法,相关代码解释如下。
void ArgsManager::ParseParameters(int argc, const char* const argv[])
{
LOCK(cs_args);//cs_args是一个boost::recursive_mutex变量,即只有获取了该锁,才能往下操作
mapArgs.clear();//类型为std::map <std::string, std::string>,存储键值一一对应的参数键值对
mapMultiArgs.clear();//类型为std::map<std::string, std::vector<std::string>>,可存储多值
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)
{
strValue = str.substr(is_index+1);//“=”号前面的为value
str = str.substr(0, is_index); //“=”号后面的为key
}
#ifdef WIN32//如果是WIN32系统且参数以“/”开头,则将“/”替换为“-”
boost::to_lower(str);
if (boost::algorithm::starts_with(str, "/"))
str = "-" + str.substr(1);
#endif
if (str[0] != '-')//如果不是以“-”开头,则退出循环
break;
// Interpret --foo as -foo.--foo当作-foo处理
// If both --foo and -foo are set, the last takes effect.
if (str.length() > 1 && str[1] == '-')
str = str.substr(1);
InterpretNegativeSetting(str, strValue);//将-noX替换为-X=0
mapArgs[str] = strValue;
mapMultiArgs[str].push_back(strValue);
}
}
我们回到AppInit方法中,在解析完用户提供的运行参数后,如果参数中包含了以下几项,将向用户提供相应的信息提示,程序终止。其中HelpMessage定义在init.cpp中,提供了各种参数的解释帮助信息。
if (gArgs.IsArgSet("-?") || gArgs.IsArgSet("-h") || gArgs.IsArgSet("-help") || gArgs.IsArgSet("-version")){
…………
if (gArgs.IsArgSet("-version")){
strUsage += FormatParagraph(LicenseInfo());
}else{
strUsage += "\n" + HelpMessage(HMM_BITCOIND);
}
fprintf(stdout, "%s", strUsage.c_str());
return true;
}
接下来,程序将检查用户是否提供了datadir参数,如果没有,将根据不同的系统为其设定默认值。
if (!fs::is_directory(GetDataDir(false))){
fprintf(stderr, "Error: Specified data directory \"%s\" does not exist.\n", gArgs.GetArg("-datadir", "").c_str());
return false;
}
GetDataDir()方法位于util.cpp中,方法将通过gArgs.IsArgSet("-datadir")检查用户是否提供datadir参数,若没有则将调用util.cpp中的GetDefaultDataDir()获取默认值,根据不同的操作系统数据目录的具体默认位置如下:
// Windows < Vista: C:\Documents and Settings\Username\Application Data\Bitcoin
// Windows >= Vista: C:\Users\Username\AppData\Roaming\Bitcoin
// Mac: ~/Library/Application Support/Bitcoin
// Unix: ~/.bitcoin
紧接着程序将读取相关的参数配置文件,BITCOIN_CONF_FILENAME为定义在util.cpp中的一个常量,其值为bitcoin.conf,表示默认的配置文件名。ReadConfigFile方法将会读取用户提供的配置文件路径或默认读取$datadir/bitcoin.conf文件,并将配置存储在mapArgs和mapMultiArgs中。代码如下:
gArgs.ReadConfigFile(gArgs.GetArg("-conf", BITCOIN_CONF_FILENAME));
比特币源码中设置了三个不同的网络,分别是main、testnet和regtest。可以在启动bitcoind或bitcoin-qt时,加入参数-testnet或-regtest来选择不同的网络,默认为main。函数ChainNameFromCommandLine将返回一个CBaseChainParams类型,表示选择的网络类型。SelectParams函数将根据网络类型创建两个全局变量globalChainBaseParams和globalChainParams.
SelectParams(ChainNameFromCommandLine());
void SelectParams(const std::string& network){
// globalChainBaseParams = CreateBaseChainParams(chain);
SelectBaseParams(network);
globalChainParams = CreateChainParams(network);
}
globalChainBaseParams中存储了nRPCPort端口号以及数据存储目录strDataDir。如主网络的该配置中,nRPCPort的默认值为8333,testnet的strDataDir为“testnet3”。方法CreateChainParams将根据网络类型创建不同的网络参数存储对象。这些对象分别存储了每个网络各自拥有的特定参数,可以说,整个比特币网络就是通过这些参数来确定两个节点是否位于同一网络的依据。后续我们也将分析位于chainparams.cpp中的类CMainParams、CTestNetParams以及CRegTestParams,分析各个网络参数的含义。(顺便提一下,基于比特币的山寨币制作,主要就是修改这些网络参数)
std::unique_ptr<CChainParams> CreateChainParams(const std::string& chain){
if (chain == CBaseChainParams::MAIN)
return std::unique_ptr(new CMainParams());
else if (chain == CBaseChainParams::TESTNET)
return std::unique_ptr(new CTestNetParams());
else if (chain == CBaseChainParams::REGTEST)
return std::unique_ptr(new CRegTestParams());
throw std::runtime_error(strprintf("%s: Unknown chain %s.", __func__, chain));
}
本篇主要分析了bitcoind守护进程启动时,程序如何对用户提供的参数进行解析,程序将根据不同的参数配置,作出与之对应的操作选择。
因本人水平有限,如有问题,欢迎大家批评指出,非常感谢。