比特币源码剖析(一)
该篇主要分析 SetupEnvironment() 和 noui_connect() 函数。
源码剖析
1.调用 SetupEnvironment() 函数设置程序运行环境。该函数声明在“util.h”文件中。
void SetupEnvironment(); // 设置运行环境
实现在“util.cpp”文件中,没有入参。
void SetupEnvironment()
{
// On most POSIX systems (e.g. Linux, but not BSD) the environment's locale // 在多数系统(例如:Linux,而非 BSD)上,环境的区域设置(场所或地点)可能无效,
// may be invalid, in which case the "C" locale is used as fallback. // “C” 区域设置用于后备。
#if !defined(WIN32) && !defined(MAC_OSX) && !defined(__FreeBSD__) && !defined(__OpenBSD__) // 若非(为定义) WIN32、MAC_OSX、__FreeBSD__、__OpenBSD__
try { // 1.尝试进行本地区域设置
std::locale(""); // Raises a runtime error if current locale is invalid // 若当前区域设置无效,则导致运行时错误
} catch (const std::runtime_error&) {
setenv("LC_ALL", "C", 1); // POSIX 接口,回退到 “C” 环境变量
}
#endif
// The path locale is lazy initialized and to avoid deinitialization errors // 路径区域设置是懒加载的,且为了避免在多线程环境中的反初始化错误,
// in multithreading environments, it is set explicitly by the main thread. // 它通过主线程显示设置。
// A dummy locale is used to extract the internal default locale, used by // 虚拟区域设置通过使用 boost::filesystem::path 用于提取内部默认的区域设置,
// boost::filesystem::path, which is then used to explicitly imbue the path. // 然后用于显示填充路径。
std::locale loc = boost::filesystem::path::imbue(std::locale::classic()); // 2.先设置一个虚假的用于提取出原有设置
boost::filesystem::path::imbue(loc); // 2.再填充
}
1.1.先尝试调用 std::locale(“”) 进行本地的区域设置,若因系统使该接口无效,则调用 setenv 改变区域设置为 “C” 环境变量。
1.2.先用一个虚假的区域设置获取原来内部的区域设置,然后再显示的填充路径区域设置。
区域设置 locale 是一组特定文化的功能,程序可以使用这些功能在国际上更佳的方便被使用。
简单来说就是根据系统进行该程序的区域设置,用于日期、时间、语言的显示风格提供支持。
详见 locale - C++ Reference。
POSIX 函数 setenv 用于添加或改变环境变量,详见 setenv。
Boost 库中的 imbue 用于嵌入路径区域设置,详见 Filesystem Reference
2.调用 noui_connect() 函数连接比特币核心服务的信号处理函数。该函数在“noui.h”文件中被引用。
extern void noui_connect(); // 连接信号处理函数
实现在“noui.cpp”文件中,没有入参。
static bool noui_ThreadSafeMessageBox(const std::string& message, const std::string& caption, unsigned int style)
{
bool fSecure = style & CClientUIInterface::SECURE; // 通过消息类型获取安全标志
style &= ~CClientUIInterface::SECURE;
std::string strCaption; // 字符串类型标题
// Check for usage of predefined caption // 检查预定义标题的用法
switch (style) { // 根据类型选择消息标题
case CClientUIInterface::MSG_ERROR:
strCaption += _("Error"); // 错误标题
break;
case CClientUIInterface::MSG_WARNING:
strCaption += _("Warning"); // 警告标题
break;
case CClientUIInterface::MSG_INFORMATION:
strCaption += _("Information"); // 信息标题
break;
default:
strCaption += caption; // Use supplied caption (can be empty) // 使用提供的标题(可能为空)
}
if (!fSecure) // 若不安全
LogPrintf("%s: %s\n", strCaption, message);
fprintf(stderr, "%s: %s\n", strCaption.c_str(), message.c_str()); // 字符串拼接重定向到标准错误
return false; // 成功返回 false
}
static void noui_InitMessage(const std::string& message)
{
LogPrintf("init message: %s\n", message); // 记录日志
}
void noui_connect()
{
// Connect bitcoind signal handlers // 连接比特币核心服务信号处理函数
uiInterface.ThreadSafeMessageBox.connect(noui_ThreadSafeMessageBox); // 1.连接无 UI 线程安全消息框(类型+消息)
uiInterface.InitMessage.connect(noui_InitMessage); // 2.连接无 UI 初始化消息
}
uiInterface 是一个客户端 UI 通讯信号接口类对象,定义在“init.cpp”文件中。
CClientUIInterface uiInterface; // Declared but not defined in ui_interface.h
uiInterface.ThreadSafeMessageBox 和 uiInterface.InitMessage 均为 boost::signals2::signal 信号,类似于 function/bind,它们定义在“ui_interface.h”文件的 CClientUIInterface 类中。
详见 Chapter 67. Boost.Signals2 - Signals。
关于 function/bind 可以参考孟岩的 function/bind的救赎(上) - CSDN博客。
/** Signals for UI communication. */ // UI 通讯信号
class CClientUIInterface // 客户端 UI 接口类
{
...
/** Show message box. */ // 显示消息框
boost::signals2::signal<bool (const std::string& message, const std::string& caption, unsigned int style), boost::signals2::last_value<bool> > ThreadSafeMessageBox;
/** Progress message during initialization. */ // 初始化期间的进度消息
boost::signals2::signal<void (const std::string &message)> InitMessage;
...
};
2.1.连接无 UI 线程安全消息框(消息类型+内容)函数 noui_ThreadSafeMessageBox。
2.2.连接无 UI 初始化消息 noui_InitMessage。
其中均调用 LogPrintf 函数进行记录打印,该函数定义在“util.h”文件中,是宏定义函数。
/** Return true if log accepts specified category */ // 如果日志接受特殊的类别返回 true
bool LogAcceptCategory(const char* category); // category 类似于 printf 中的格式控制
/** Send a string to the log output */ // 发送一个字符串到日志输出
int LogPrintStr(const std::string &str);
#define LogPrintf(...) LogPrint(NULL, __VA_ARGS__) // 日志输出(标准输出或调试日志)
...
/**
* Zero-arg versions of logging and error, these are not covered by
* TINYFORMAT_FOREACH_ARGNUM
*/ // TINYFORMAT_FOREACH_ARGNUM 不涵盖零参数版本的日志记录和错误
static inline int LogPrint(const char* category, const char* format)
{
if(!LogAcceptCategory(category)) return 0; // 检验类别,这里类型为空直接返回 true
return LogPrintStr(format); // 日志输出字符串
}
函数 LogPrint 类似于 C 语言的标准库函数 printf。 其中第一个参数为类别(用于调试,这里为 NULL),第二个为可变参数的宏 VA_ARGS(包含格式控制字符串)。
首先调用 LogAcceptCategory(category) 函数进行类型检查(与调试有关), 然后调用 LogPrintStr(format) 进行日志输出,即把指定字符串以指定格式进行输出到控制台或日志文件。 该函数定义在“util.cpp”文件中。
/**
* LogPrintf() has been broken a couple of times now
* by well-meaning people adding mutexes in the most straightforward way.
* It breaks because it may be called by global destructors during shutdown.
* Since the order of destruction of static/global objects is undefined,
* defining a mutex as a global object doesn't work (the mutex gets
* destroyed, and then some later destructor calls OutputDebugStringF,
* maybe indirectly, and you get a core dump at shutdown trying to lock
* the mutex).
*/
static boost::once_flag debugPrintInitFlag = BOOST_ONCE_INIT;
/**
* We use boost::call_once() to make sure mutexDebugLog and
* vMsgsBeforeOpenLog are initialized in a thread-safe manner.
*
* NOTE: fileout, mutexDebugLog and sometimes vMsgsBeforeOpenLog
* are leaked on exit. This is ugly, but will be cleaned up by
* the OS/libc. When the shutdown sequence is fully audited and
* tested, explicit destruction of these objects can be implemented.
*/
static FILE* fileout = NULL; // 日志文件指针
static boost::mutex* mutexDebugLog = NULL; // 日志文件锁
static list<string> *vMsgsBeforeOpenLog; // 打开日志文件前的消息链表
static int FileWriteStr(const std::string &str, FILE *fp)
{
return fwrite(str.data(), 1, str.size(), fp); // 写入字符串到文件指针关联的文件
}
static void DebugPrintInit()
{
assert(mutexDebugLog == NULL); // 若调试日志锁为空
mutexDebugLog = new boost::mutex(); // 新建一个互斥锁
vMsgsBeforeOpenLog = new list<string>; // 新建一个字符串类型的链表
}
...
bool LogAcceptCategory(const char* category)
{
if (category != NULL) // 若类型非空
{
if (!fDebug) // 若调试选项未开启
return false; // 直接返回 false
// Give each thread quick access to -debug settings. // 让每个线程快速访问 -debug 选项设置。
// This helps prevent issues debugging global destructors, // 这有助于防止调试全局析构函数的问题,
// where mapMultiArgs might be deleted before another // mapMultiArgs 可能在另一个全局析构函数
// global destructor calls LogPrint() // 调用 LogPrint() 之前被删除
static boost::thread_specific_ptr<set<string> > ptrCategory; // 线程局部存储(TLS)为每个线程独有
if (ptrCategory.get() == NULL) // 初始为空
{
const vector<string>& categories = mapMultiArgs["-debug"]; // 获取调试选项指定的值(调试内容)存入类型列表
ptrCategory.reset(new set<string>(categories.begin(), categories.end())); // 获取类型列表每个元素的地址存入 TLS 中
// thread_specific_ptr automatically deletes the set when the thread ends.
} // thread_specific_ptr 在线程结束时自动删除该集合。RAII 技术。
const set<string>& setCategories = *ptrCategory.get(); // 获取类别字符串集合的引用
// if not debugging everything and not debugging specific category, LogPrint does nothing. // 如果不调试全部内容而调试特定类别,LogPrint 什么也不做。
if (setCategories.count(string("")) == 0 && // 若类别集中含有空串
setCategories.count(string("1")) == 0 && // 且含有字符串 “1”
setCategories.count(string(category)) == 0) // 且含有指定类别
return false; // 直接返回 false
}
return true; // 返回 true
}
/**
* fStartedNewLine is a state variable held by the calling context that will
* suppress printing of the timestamp when multiple calls are made that don't
* end in a newline. Initialize it to true, and hold it, in the calling context.
*/ // fStartedNewLine 是一个调用上下文保存的状态变量,它将在多次调用不以换行符结束时禁止打印时间戳。初始化为 true,并在调用上下文中保存该值。
static std::string LogTimestampStr(const std::string &str, bool *fStartedNewLine)
{
string strStamped; // 保存打上时间戳的字符串
if (!fLogTimestamps) // 记录时间戳标志若为 false
return str; // 直接返回该字符串
if (*fStartedNewLine) { // 换行标志,默认为 true
int64_t nTimeMicros = GetLogTimeMicros(); // 获取当前时间,微秒
strStamped = DateTimeStrFormat("%Y-%m-%d %H:%M:%S", nTimeMicros/1000000); // 转换为秒,并格式化日期时间字符串
if (fLogTimeMicros) // 若记录微秒时间
strStamped += strprintf(".%06d", nTimeMicros%1000000); // 追加微秒到时间戳
strStamped += ' ' + str; // 空格隔开拼接字符串
} else // 否则
strStamped = str; // 不打时间戳
if (!str.empty() && str[str.size()-1] == '\n') // 若字符串非空 且 最后一个字符为换行符
*fStartedNewLine = true; // 换行标志置为 true
else // 若字符串为空
*fStartedNewLine = false; // 换行标志置为 false
return strStamped; // 返回打上时间戳的字符串
}
int LogPrintStr(const std::string &str)
{
int ret = 0; // Returns total number of characters written // 返回写入字符的总数
static bool fStartedNewLine = true; // 开始新的一行标志,初始化为 true
string strTimestamped = LogTimestampStr(str, &fStartedNewLine); // 把字符串加上时间戳
if (fPrintToConsole) // 若输出到控制台选项开启
{
// print to console // 输出到控制台
ret = fwrite(strTimestamped.data(), 1, strTimestamped.size(), stdout); // 把数据写入标准输出
fflush(stdout); // 刷新标准输出
}
else if (fPrintToDebugLog) // 若输出到调试日志选项开启
{
boost::call_once(&DebugPrintInit, debugPrintInitFlag); // 注册只调用一次调试打印初始化
boost::mutex::scoped_lock scoped_lock(*mutexDebugLog); // 区域锁
// buffer if we haven't opened the log yet // 如果我们还未打开日志,进行缓冲
if (fileout == NULL) { // 若文件指针为空
assert(vMsgsBeforeOpenLog); // 检查消息链表已创建完毕
ret = strTimestamped.length(); // 获取打上时间戳的字符串长度
vMsgsBeforeOpenLog->push_back(strTimestamped); // 加入该消息链表
}
else // 若已经打开
{
// reopen the log file, if requested // 若有需求,再次打开日志文件
if (fReopenDebugLog) { // 若指定在再次打开日志文件
fReopenDebugLog = false; // 该标志先置为 false
boost::filesystem::path pathDebug = GetDataDir() / "debug.log"; // 获取日志文件的路径
if (freopen(pathDebug.string().c_str(),"a",fileout) != NULL) // 再次打开日志文件,以追加的方式打开
setbuf(fileout, NULL); // unbuffered // 关闭该文件指针的缓冲机制
}
ret = FileWriteStr(strTimestamped, fileout); // 把打上时间戳的字符串写入日志文件
}
}
return ret; // 返回写入调试日志文件的字符总数
}