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
函数中进行分析。