比特币源码解析(19)

来源:互联网 发布:手机淘宝怎么指纹支付 编辑:程序博客网 时间:2024/05/22 16:53

0x01 StartRPC

bool StartRPC(){    LogPrint(BCLog::RPC, "Starting RPC\n");    fRPCRunning = true;    g_rpcSignals.Started();    return true;}

启动RPC就是将之前的连接到Started的信号全部触发运行,并修改变量fRPCRunningtrue,而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中;如果密码不为空,就读取命令行中的-rpcuserrpcpassword并用:连接,存到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::threadint 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函数中进行分析。

原创粉丝点击