您的位置:首页 > 其它

比特币源码解析(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
函数中进行分析。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: