比特币源码解析(19) - 可执行程序 - Bitcoind
2017-11-09 15:06
435 查看
0x01 StartRPC
bool StartRPC() { LogPrint(BCLog::RPC, "Starting RPC\n"); fRPCRunning = true; g_rpcSignals.Started(); return true; }
启动RPC就是将之前的连接到
Started的信号全部触发运行,并修改变量
fRPCRunning为
true,而
Started信号连接的函数就是通过
RPCServer::OnStarted()函数,
void RPCServer::OnStarted(std::function<void ()> slot) { g_rpcSignals.Started.connect(slot); } void OnRPCStarted() { uiInterface.NotifyBlockTip.connect(&RPCNotifyBlockChange); }
在
AppInitServers中通过
RPCServer::OnStarted(&OnRPCStarted);连接了
OnRPCStarted函数。从上面这些函数可以看出这里面涉及了信号的传递,
g_rpcSignals.Started信号触发的时候执行
OnRPCStarted函数,这个函数又将
RPCNotifyBlockChange函数连接到新的信号槽。
0x02 StartHTTPRPC
bool StartHTTPRPC() { LogPrint(BCLog::RPC, "Starting HTTP RPC server\n"); if (!InitRPCAuthentication()) return false; RegisterHTTPHandler("/", true, HTTPReq_JSONRPC); #ifdef ENABLE_WALLET // ifdef can be removed once we switch to better endpoint support and API versioning RegisterHTTPHandler("/wallet/", false, HTTPReq_JSONRPC); #endif assert(EventBase()); httpRPCTimerInterface = new HTTPRPCTimerInterface(EventBase()); RPCSetTimerInterface(httpRPCTimerInterface); return true; }
RPC身份验证环境初始化
启动RPC Server首先要验证用户的身份,是通过InitRPCAuthentication来实现的,来看看这个函数的实现,
static bool InitRPCAuthentication() { if (gArgs.GetArg("-rpcpassword", "") == "") { LogPrintf("No rpcpassword set - using random cookie authentication\n"); if (!GenerateAuthCookie(&strRPCUserColonPass)) { uiInterface.ThreadSafeMessageBox( _("Error: A fatal internal error occurred, see debug.log for details"), // Same message as AbortNode "", CClientUIInterface::MSG_ERROR); return false; } } else { LogPrintf("Config options rpcuser and rpcpassword will soon be deprecated. Locally-run instances may remove rpcuser to use cookie-based auth, or may be replaced with rpcauth. Please see share/rpcuser for rpcauth auth generation.\n"); strRPCUserColonPass = gArgs.GetArg("-rpcuser", "") + ":" + gArgs.GetArg("-rpcpassword", ""); } return true; }
这个函数首先获取命令行中的
-rpcpassword参数,看是否为空,如果是说明没有使用密码,这时就从本地文件中生成的一个Cookie字符串,然后存到
strRPCUserColonPass中;如果密码不为空,就读取命令行中的
-rpcuser和
rpcpassword并用
:连接,存到
strRPCUserColonPass中。所以这整个函数就只进行了验证环境的初始化,还没有进行真正的验证过程。
注册URL处理函数
接下来的两行代码都是通过RegisterHTTPHandler来注册url处理函数,这个函数的第一个参数是请求的路径,第二个是精确匹配还是前缀匹配,最后一个参数是处理的函数。在上一篇文章http://blog.csdn.net/pure_lady/article/details/78465561#t5中我们提到了一个
pathHandlers变量,而
RegisterHTTPHandler就是将参数存储到这个变量当中。
设置http timer interface
EventBase()返回一个
event_base对象,这个对象名称为
eventBase,它的值是在http://blog.csdn.net/pure_lady/article/details/78465561#t3的最后设置的,后续所有的event的创建都需要这个对象作为其中的一个参数。所以接下来的代码通过一个
assert判断
eventBase是否为空,从而为以后的调试更好的找出原因。在接下来一句代码创建了一个
HTTPRPCTimerInterface对象,这个对象的实现如下,
class HTTPRPCTimerInterface : public RPCTimerInterface { public: explicit HTTPRPCTimerInterface(struct event_base* _base) : base(_base) { } const char* Name() override { return "HTTP"; } RPCTimerBase* NewTimer(std::function<void(void)>& func, int64_t millis) override { return new HTTPRPCTimer(base, func, millis); } private: struct event_base* base; };
传入一个
event_base*对象,类中的主要的函数就是
NewTimer函数,功能也非常简单,就是在指定时间间隔后执行某个函数一次。最后通过
RPCSetTimerInterface函数将新生成的
httpRPCTimerInterface变量传到了
httpserver中的
RPCTimerInterface类型的
timerInterface变量中去,之后主要有
timerInterface变量来形式功能。
0x03 StartREST
再接下来就是启动REST服务,这个服务通过命令行中的-rest进行启动,函数的实现如下,
static const struct { const char* prefix; bool (*handler)(HTTPRequest* req, const std::string& strReq); } uri_prefixes[] = { {"/rest/tx/", rest_tx}, {"/rest/block/notxdetails/", rest_block_notxdetails}, {"/rest/block/", rest_block_extended}, {"/rest/chaininfo", rest_chaininfo}, {"/rest/mempool/info", rest_mempool_info}, {"/rest/mempool/contents", rest_mempool_contents}, {"/rest/headers/", rest_headers}, {"/rest/getutxos", rest_getutxos}, }; bool StartREST() { for (unsigned int i = 0; i < ARRAYLEN(uri_prefixes); i++) RegisterHTTPHandler(uri_prefixes[i].prefix, false, uri_prefixes[i].handler); return true; }
可以看出实现还是比较简单,具体就是将一堆URL路径和对应的处理函数通过
RegisterHTTPHandler函数存储到
pathHandlers中,以便在对应的请求到达时能调用对应的函数进行处理,这里面使用的都是前缀匹配。
0x04 StartHTTPServer
AppInitServers中的最后一个函数
StartHTTPServer,先来看看它的实现,
bool StartHTTPServer() { LogPrint(BCLog::HTTP, "Starting HTTP server\n"); int rpcThreads = std::max((long)gArgs.GetArg("-rpcthreads", DEFAULT_HTTP_THREADS), 1L); LogPrintf("HTTP: starting %d worker threads\n", rpcThreads); std::packaged_task<bool(event_base*, evhttp*)> task(ThreadHTTP); threadResult = task.get_future(); threadHTTP = std::thread(std::move(task), eventBase, eventHTTP); for (int i = 0; i < rpcThreads; i++) { std::thread rpc_worker(HTTPWorkQueueRun, workQueue); rpc_worker.detach(); } return true; }
程序首先从命令行中通过
-rpcthreads获取rpc执行的最大线程数,接下来使用了
<future>库中的
packaged_task类创建了一个task对象,这个类的基本用法如下:
// 转自https://www.cnblogs.com/haippy/p/3279565.html #include <iostream> // std::cout #include <utility> // std::move #include <future> // std::packaged_task, std::future #include <thread> // std::thread int main () { std::packaged_task<int(int)> foo; // 默认构造函数. // 使用 lambda 表达式初始化一个 packaged_task 对象. std::packaged_task<int(int)> bar([](int x){return x*2;}); foo = std::move(bar); // move-赋值操作,也是 C++11 中的新特性. // 获取与 packaged_task 共享状态相关联的 future 对象. std::future<int> ret = foo.get_future(); std::thread(std::move(foo), 10).detach(); // 产生线程,调用被包装的任务. int value = ret.get(); // 等待任务完成并获取结果. std::cout << "The double of 10 is " << value << ".\n"; return 0; }
基本过程就是:
新建一个
packaged_task对象,同时绑定一个函数;
新建一个
future对象,通过
packaged_task中的
get_future函数进行赋值,用于获取函数的返回值;
新建线程,调用包装的任务;
通过
future对象中的
get函数获取线程的返回值。
回到源码首先创建了一个
task,绑定了函数
ThreadHTTP,并将返回最终的结果保存在
threadResult中,然后创建了线程
threadHTTP来执行任务。
thread中第一个参数使用了
std::move()函数,这个函数作用是返回输出参数的右值类型,与右值相对应的有左值类型,这两者的区别是:右值类型只能出现在赋值语句的右边,一般的情况有常数、临时变量(函数返回值)等;左值则可以出现在等号的两边,同时需要进行初始化,普通的变量都是左值类型。不过一般从程序的执行结果上来看,使用move和不使用没有什么区别,但是在某些变量的赋值和拷贝情形下是使用move能更好的提升程序执行效率(参见https://www.cnblogs.com/catch/p/3507883.html)。而
packaged_task禁用了普通的赋值操作,只允许使用
move进行赋值。
/** Simple wrapper to set thread name and run work queue */ static void HTTPWorkQueueRun(WorkQueue<HTTPClosure>* queue) { RenameThread("bitcoin-httpworker"); queue->Run(); }
接下来的程序根据命令行设置的rpc线程数创建对应的
rpc_worker来执行
workQueue,创建完线程之后便让线程从当前线程脱离出去,通过
detach()操作,交给了系统去管理。再来看看
workQueue的实现,
/** Thread function */ void Run() { ThreadCounter count(*this); while (true) { std::unique_ptr<WorkItem> i; { std::unique_lock<std::mutex> lock(cs); while (running && queue.empty()) cond.wait(lock); if (!running) break; i = std::move(queue.front()); queue.pop_front(); } (*i)(); } } /** Interrupt and exit loops */ void Interrupt() { std::unique_lock<std::mutex> lock(cs); running = false; cond.notify_all(); }
函数通过全局变量
running来控制程序的退出,该变量在
Intertupt()中进行修改。实现的功能就是不断的从队列中读取任务,每一个任务是一个
WorkItem类型,而这个
WorkItem是一个模板类型,实际传入的存放的内容是函数地址,所以从队列中取出后就可以直接当成函数运行。
到此整个
AppInitServers就结束了,主要内容就是
HTTP Server的初始化,将外部的请求和内部相应的处理函数对应起来,并做好相应的任务分配。接下来我们继续回到我们的
AppInitMain函数中进行分析。
相关文章推荐
- 比特币源码解析(11) - 可执行程序 - Bitcoind
- 比特币源码解析(12) - 可执行程序 - Bitcoind
- 比特币源码解析(12) - 可执行程序 - Bitcoind
- 比特币源码解析(15) - 可执行程序 - Bitcoind
- 比特币源码解析(14) - 可执行程序 - Bitcoind
- 比特币源码解析(20) - 可执行程序 - Bitcoind
- 比特币源码解析(9) - 可执行程序 - Bitcoind
- 比特币源码解析(14) - 可执行程序 - Bitcoind
- 比特币源码解析(15) - 可执行程序 - Bitcoind
- 比特币源码解析(9) - 可执行程序 - Bitcoind
- 比特币源码解析(18) - 可执行程序 - Bitcoind
- 比特币源码解析(21) - 可执行程序 - Bitcoind
- 比特币源码解析(16) - 可执行程序 - Bitcoind
- 比特币源码解析(22) - 可执行程序 - Bitcoind
- 比特币源码解析(17) - 可执行程序 - Bitcoind
- 比特币源码解析(11) - 可执行程序 - Bitcoind
- 比特币源码解析(16) - 可执行程序 - Bitcoind
- 比特币源码解析(20) - 可执行程序 - Bitcoind
- 比特币源码解析(21) - 可执行程序 - Bitcoind
- 比特币源码解析(13) - 可执行程序 - Bitcoind