比特币源码解析(19) - 可执行程序 - Bitcoind

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/u012183589/article/details/78489880

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

猜你喜欢

转载自blog.csdn.net/u012183589/article/details/78489880
今日推荐