网易首页 > 网易号 > 正文 申请入驻

从零开始学习比特币开发(四):网络初始化,加载区块链和钱包,导入区块启动节点

0
分享至

来源:巴比特 作者:区小白

第6步,网络初始化( src/init.cpp::AppInitMain()

  1. 生成智能指针对象 g_connman,类型为 CConnman

    g_connman = std::unique_ptr (new CConnman(GetRand(std::numeric_limits ::max()), GetRand(std::numeric_limits ::max()))); CConnman& connman = *g_connman;

  2. 生成智能指针对象 peerLogic,类型为 PeerLogicValidation

    peerLogic.reset(new PeerLogicValidation(&connman, scheduler, gArgs.GetBoolArg("-enablebip61", DEFAULT_ENABLE_BIP61)));

    PeerLogicValidation 继承了 CValidationInterface、NetEventsInterface 两个类。实现 CValidationInterface 这个类可以订阅验证过程中产生的事件。实现 NetEventsInterface 这个类可以处理消息网络消息。

  3. 注册各种验证处理器,即信号处理器,在发送信号时会调用这些处理器。

    RegisterValidationInterface(peerLogic.get());

    方法具体实现如下:

    void RegisterValidationInterface(CValidationInterface* pwalletIn) { g_signals.m_internals->UpdatedBlockTip.connect(boost::bind(&CValidationInterface::UpdatedBlockTip, pwalletIn, _1, _2, _3)); g_signals.m_internals->TransactionAddedToMempool.connect(boost::bind(&CValidationInterface::TransactionAddedToMempool, pwalletIn, _1)); g_signals.m_internals->BlockConnected.connect(boost::bind(&CValidationInterface::BlockConnected, pwalletIn, _1, _2, _3)); g_signals.m_internals->BlockDisconnected.connect(boost::bind(&CValidationInterface::BlockDisconnected, pwalletIn, _1)); g_signals.m_internals->TransactionRemovedFromMempool.connect(boost::bind(&CValidationInterface::TransactionRemovedFromMempool, pwalletIn, _1)); g_signals.m_internals->ChainStateFlushed.connect(boost::bind(&CValidationInterface::ChainStateFlushed, pwalletIn, _1)); g_signals.m_internals->Broadcast.connect(boost::bind(&CValidationInterface::ResendWalletTransactions, pwalletIn, _1, _2)); g_signals.m_internals->BlockChecked.connect(boost::bind(&CValidationInterface::BlockChecked, pwalletIn, _1, _2)); g_signals.m_internals->NewPoWValidBlock.connect(boost::bind(&CValidationInterface::NewPoWValidBlock, pwalletIn, _1, _2)); }

    静态变量 g_signals 在程序启动前生成,m_internals 在第4a 步应用程序初始化过程中生成。

  4. 根据命令行参数 -uacomment,处理追加到用户代理的字符串。

    std::vector uacomments; for (const std::string& cmt : gArgs.GetArgs("-uacomment")) { if (cmt != SanitizeString(cmt, SAFE_CHARS_UA_COMMENT)) return InitError(strprintf(_("User Agent comment (%s) contains unsafe characters."), cmt)); uacomments.push_back(cmt); }

  5. 构造并检查版本字符串长度是否大于 version 消息中版本的最大长度。

    strSubVersion = FormatSubVersion(CLIENT_NAME, CLIENT_VERSION, uacomments); if (strSubVersion.size() > MAX_SUBVERSION_LENGTH) { return InitError(strprintf(_("Total length of network version string (%i) exceeds maximum length (%i). Reduce the number or size of uacomments."), strSubVersion.size(), MAX_SUBVERSION_LENGTH)); }

  6. 如果指定了 onlynet 参数,则设置可以对接进行连接的类型,比如:ipv4、ipv6、onion。

    if (gArgs.IsArgSet("-onlynet")) { std::set nets; for (const std::string& snet : gArgs.GetArgs("-onlynet")) { enum Network net = ParseNetwork(snet); if (net == NET_UNROUTABLE) return InitError(strprintf(_("Unknown network specified in -onlynet: '%s'"), snet)); nets.insert(net); } for (int n = 0; n < NET_MAX; n++) { enum Network net = (enum Network)n; if (!nets.count(net)) SetLimited(net); } }

    上面的代码首先把 -onlynet 参数指定的只允许对外连接的网络类型加入集合中,然后进行 for 遍历,如果当前的类型不在允许的集合中,则调用 SetLimited 方法,设置这些类型为受限的。

  7. 获取是否允许进行 DNS 查找,是否进行代理随机

    fNameLookup = gArgs.GetBoolArg("-dns", DEFAULT_NAME_LOOKUP); bool proxyRandomize = gArgs.GetBoolArg("-proxyrandomize", DEFAULT_PROXYRANDOMIZE);

    两者默认都为真。

  8. 处理网络代理。如果指定了 -proxy,且不等于 0,即指定了代理地址,进行下面的处理:具体代码如下:

    std::string proxyArg = gArgs.GetArg("-proxy", ""); SetLimited(NET_ONION); if (proxyArg != "" && proxyArg != "0") { CService proxyAddr; if (!Lookup(proxyArg.c_str(), proxyAddr, 9050, fNameLookup)) { return InitError(strprintf(_("Invalid -proxy address or hostname: '%s'"), proxyArg)); }


    proxyType addrProxy = proxyType(proxyAddr, proxyRandomize); if (!addrProxy.IsValid()) return InitError(strprintf(_("Invalid -proxy address or hostname: '%s'"), proxyArg));

    SetProxy(NET_IPV4, addrProxy); SetProxy(NET_IPV6, addrProxy); SetProxy(NET_ONION, addrProxy); SetNameProxy(addrProxy); SetLimited(NET_ONION, false); // by default, -proxy sets onion as reachable, unless -noonion later }

    • 调用 Lookup 方法,根据指定的代理,通过 DNS查找,发现代理服务器的地址。

    • 生成 proxyType 对象。

    • 设置 IPv4、IPv6、Tor 网络的代理。

    • 设置命名(域名)代理。

    • 设置不限制连接到 Tor 网络。

  9. 处理洋葱网络。 如果指定了 onion 参数,则处理洋葱网络的相关设置。如果指定了 -onion,且不等于空字符串,即指定了洋葱代理地址,进行下面的处理:具体代码如下:

    std::string onionArg = gArgs.GetArg("-onion", ""); if (onionArg != "") { if (onionArg == "0") { // Handle -noonion/-onion=0 SetLimited(NET_ONION); // set onions as unreachable } else { CService onionProxy; if (!Lookup(onionArg.c_str(), onionProxy, 9050, fNameLookup)) { return InitError(strprintf(_("Invalid -onion address or hostname: '%s'"), onionArg)); } proxyType addrOnion = proxyType(onionProxy, proxyRandomize); if (!addrOnion.IsValid()) return InitError(strprintf(_("Invalid -onion address or hostname: '%s'"), onionArg)); SetProxy(NET_ONION, addrOnion); SetLimited(NET_ONION, false); } }

    • 如果参数等于 0,设置洋葱网络受限,即不可达。否则,进行下面的处理。

    • 调用 Lookup 方法,根据指定的代理,通过 DNS查找,发现代理服务器的地址。

    • 生成 proxyType 对象。

    • 设置 Tor 网络的代理。

    • 设置不限制连接到 Tor 网络。

  10. 处理通过 -externalip 参数设置的外部 IP地址。获取并遍历所有指定的外部地址,进行如下处理:调用 Lookup 方法进行DNS 查找。如果成功则调用 AddLocal 方法,保存新的地址。否则,抛出初始化错误。

    for (const std::string& strAddr : gArgs.GetArgs("-externalip")) { CService addrLocal; if (Lookup(strAddr.c_str(), addrLocal, GetListenPort(), fNameLookup) && addrLocal.IsValid()) AddLocal(addrLocal, LOCAL_MANUAL); else return InitError(ResolveErrMsg("externalip", strAddr)); }

  11. 如果设置了 maxuploadtarget 参数,则设置最大出站限制。

    if (gArgs.IsArgSet("-maxuploadtarget")) { nMaxOutboundLimit = gArgs.GetArg("-maxuploadtarget", DEFAULT_MAX_UPLOAD_TARGET)*1024*1024; }

第7步,加载区块链( src/init.cpp::AppInitMain()

首先,计算缓存的大小。包括:区块索引数据库、区块状态数据库、内存中 UTXO 集。代码如下:

fReindex = gArgs.GetBoolArg("-reindex", false); bool fReindexChainState = gArgs.GetBoolArg("-reindex-chainstate", false);


// cache size calculations int64_t nTotalCache = (gArgs.GetArg("-dbcache", nDefaultDbCache) << 20); nTotalCache = std::max(nTotalCache, nMinDbCache << 20); // total cache cannot be less than nMinDbCache nTotalCache = std::min(nTotalCache, nMaxDbCache << 20); // total cache cannot be greater than nMaxDbcache int64_t nBlockTreeDBCache = std::min(nTotalCache / 8, nMaxBlockDBCache << 20); nTotalCache -= nBlockTreeDBCache; int64_t nTxIndexCache = std::min(nTotalCache / 8, gArgs.GetBoolArg("-txindex", DEFAULT_TXINDEX) ? nMaxTxIndexCache << 20 : 0); nTotalCache -= nTxIndexCache; int64_t nCoinDBCache = std::min(nTotalCache / 2, (nTotalCache / 4) + (1 << 23)); // use 25%-50% of the remainder for disk cache nCoinDBCache = std::min(nCoinDBCache, nMaxCoinsDBCache << 20); // cap total coins db cache nTotalCache -= nCoinDBCache; nCoinCacheUsage = nTotalCache; // the rest goes to in-memory cache int64_t nMempoolSizeMax = gArgs.GetArg("-maxmempool", DEFAULT_MAX_MEMPOOL_SIZE) * 1000000; LogPrintf("Cache configuration:\n"); LogPrintf("* Using %.1fMiB for block index database\n", nBlockTreeDBCache * (1.0 / 1024 / 1024)); if (gArgs.GetBoolArg("-txindex", DEFAULT_TXINDEX)) { LogPrintf("* Using %.1fMiB for transaction index database\n", nTxIndexCache * (1.0 / 1024 / 1024)); }

然后,只要加载标志为真且没有收到关闭系统的请求,即进行以下 while 循环。

  1. 进行 do … while 循环处理:

    • 调用 UnloadBlockIndex 方法,卸载区块相关的索引。

    • 重置指向活跃 CCoinsView 的全局智能指针变量 pcoinsTip。

    • 重置指向 coins 数据库的全局智能指针变量 pcoinsdbview。

    • 重置 CCoinsViewErrorCatcher 的智能指针静态变量 pcoinscatcher。

    • 重置指向活动区块树的全局智能指针变量 pblocktree。

    • 如果 -reset 参数为真,那么:调用活跃区块树 pblocktree 的 WriteReindexing 方法,保存重写索引标志。如果当前处于修剪模式,调用 CleanupBlockRevFiles 方法,清除特定区块的数据文件。

      if (fReset) { pblocktree->WriteReindexing(true); //If we're reindexing in prune mode, wipe away unusable block files and all undo data files if (fPruneMode) CleanupBlockRevFiles(); }

    • 如果收到结束请求,则退出循环。

      if (ShutdownRequested()) break;

    • 调用 LoadBlockIndex 方法,加载区块索引。

      if (!LoadBlockIndex(chainparams)) { strLoadError = _("Error loading block database"); break; }

    • 如果区块索引成功加载,则检查是否包含区块。

      if (!mapBlockIndex.empty() && !LookupBlockIndex(chainparams.GetConsensus().hashGenesisBlock)) { return InitError(_("Incorrect or no genesis block found. Wrong datadir for network?")); }

    • 如果指定有修剪,但又没有处于修剪模式,则退出循环。

      if (fHavePruned && !fPruneMode) { strLoadError = _("You need to rebuild the database using -reindex to go back to unpruned mode. This will redownload the entire blockchain"); break; }

    • 如果不重建索引,调用 LoadGenesisBlock 加载创世区块。如果失败,则退出循环。

      if (!fReindex && !LoadGenesisBlock(chainparams)) { strLoadError = _("Error initializing block database"); break; }

    • 生成两个智能指针对象。

      pcoinsdbview.reset(new CCoinsViewDB(nCoinDBCache, false, fReset || fReindexChainState)); pcoinscatcher.reset(new CCoinsViewErrorCatcher(pcoinsdbview.get()));

      两个变量的含义见前面说明。

    • 升级数据库格式。

      if (!pcoinsdbview->Upgrade()) { strLoadError = _("Error upgrading chainstate database"); break; }

    • 重放区块,用来处理数据库不一致。

      if (!ReplayBlocks(chainparams, pcoinsdbview.get())) { strLoadError = _("Unable to replay blocks. You will need to rebuild the database using -reindex-chainstate."); break; }

    • 当系统走到这一步时,硬盘上的 coinsdb 数据库已经片于一致状态了。现在创建指向活跃 CCoinsView 的全局智能指针变量 pcoinsTip。

      pcoinsTip.reset(new CCoinsViewCache(pcoinscatcher.get()));

第8步,开始索引( src/init.cpp::AppInitMain()

如果指定了 -txindex 参数,则生成交易索引对象 g_txindex,类型为 TxIndex;然后调用其 Start 方法,开始建立索引。

if (gArgs.GetBoolArg("-txindex", DEFAULT_TXINDEX)) { g_txindex = MakeUnique (nTxIndexCache, false, fReindex); g_txindex->Start(); }

start 方法处理如下:

  1. 首先,调用 RegisterValidationInterface 方法注册 TxIndexMainSignalsInstance 上各种事件的信号处理器,在发送信号时会调用这些处理器。

    RegisterValidationInterface(this);

  2. 然后,调用 Init 方法升级交易索引从老的数据库到新的数据库。TxIndex 子类重载了这个方法,会调用 m_db->MigrateData(*pblocktree, chainActive.GetLocator()) 方法来升级数据库。然后,调用父类 BaseIndex 的同名方法进行处理。在父类的 Init 方法中,首先会调用 ReadBestBlock 方法从数据库中读取 Key 为 B 的区块做为定位器(可能是所有没有分叉的区块)。然后,调用 FindForkInGlobalIndex 方法,找到活跃区块链上的分叉前的最后一区块索引(从这个区块产生了分叉)。如果这个索引对应的区块和活跃区块链的顶端区块是相同的,设置同步完成标志为真。

  3. 启动一个线程,线程执行的真正方法为 BaseIndex::ThreadSync。线程的主要作用在于当没有同步完成时,通过读取活跃区块链的下一个区块来进行同步,并把没有分叉的区块以 Key 为 B 写入数据库中。

第9步,加载钱包( src/init.cpp::AppInitMain()

调用钱包接口对象的 Open 方法,开始加载钱包。具体方法在 wallet/init.cpp 文件中。内容如下:

bool WalletInit::Open() const { if (gArgs.GetBoolArg("-disablewallet", DEFAULT_DISABLE_WALLET)) { LogPrintf("Wallet disabled!\n"); return true; }


for (const std::string& walletFile : gArgs.GetArgs("-wallet")) { std::shared_ptr pwallet = CWallet::CreateWalletFromFile(walletFile, fs::absolute(walletFile, GetWalletDir())); if (!pwallet) { return false; } AddWallet(pwallet); }

return true; }

首先检查是否禁止钱包,如果禁止直接返回。否则遍历所有钱包,调用 CWallet::CreateWalletFromFile 方法,要根据钱包文件生成钱包对象,如果成功生成钱包,则调用 AddWallet 方法把钱包加入 vpwallets 集合中。

第10步,数据目录维护( src/init.cpp::AppInitMain()

如果当前为修剪模式,本地服务去掉 NODE_NETWORK 标志,然后如果不需要索引则调用 PruneAndFlush 函数,修剪并刷新到硬盘中。

if (fPruneMode) { LogPrintf("Unsetting NODE_NETWORK on prune mode\n"); nLocalServices = ServiceFlags(nLocalServices & ~NODE_NETWORK); if (!fReindex) { uiInterface.InitMessage(_("Pruning blockstore...")); PruneAndFlush(); } } 第11步,导入区块( src/init.cpp::AppInitMain()

  1. 调用 CheckDiskSpace 函数,检查硬盘空间是否足够。如果没有足够的硬盘空间,则退出。

  2. 检查最佳区块链顶端指示指针是否为空。如果顶端打针为空,UI界面进行通知。如果不空,则设置有创世区块,即 fHaveGenesis 设为真。

    if (chainActive.Tip() == nullptr) { uiInterface.NotifyBlockTip_connect(BlockNotifyGenesisWait); } else { fHaveGenesis = true; }

  3. 如果指定了 blocknotify 参数,设置界面通知为 BlockNotifyCallback

  4. 遍历参数 loadblock 指定要加载的区块文件,放进向量变量 vImportFiles 集合中。然后调用 threadGroup.create_thread 方法,创建一个线程。线程执行的函数为 ThreadImport,参数为要加载的区块文件。

    std::vector vImportFiles; for (const std::string& strFile : gArgs.GetArgs("-loadblock")) { vImportFiles.push_back(strFile); }


    threadGroup.create_thread(boost::bind(&ThreadImport, vImportFiles));

  5. 获取 cs_GenesisWait 锁,等待创世区块被处理完成。

    { WaitableLock lock(cs_GenesisWait); // We previously could hang here if StartShutdown() is called prior to // ThreadImport getting started, so instead we just wait on a timer to // check ShutdownRequested() regularly. while (!fHaveGenesis && !ShutdownRequested()) { condvar_GenesisWait.wait_for(lock, std::chrono::milliseconds(500)); } uiInterface.NotifyBlockTip_disconnect(BlockNotifyGenesisWait); }

第12步,启动节点( src/init.cpp::AppInitMain()
  1. 获取活跃区块链的当前调度。

    chain_active_height = chainActive.Height();


  2. 如果指定了监听洋葱网络 -listenonion,调用 StartTorControl 函数,开始 Tor 控制。代码如下所示:

    void StartTorControl() { assert(!gBase); #ifdef WIN32 evthread_use_windows_threads(); #else evthread_use_pthreads(); #endif gBase = event_base_new(); if (!gBase) { LogPrintf("tor: Unable to create event_base\n"); return; }


    torControlThread = std::thread(std::bind(&TraceThread , "torcontrol", &TorControlThread)); }

    libevent默认情况下是单线程,每个线程有且仅有一个event_base。为了保存多线程下是安全的,首先需要调用 evthread_use_pthreadsevthread_use_windows_threads 等两个方法,前面是 linux 下的,后面是 windows 下的。

    在处理完多线程设置后,调用 event_base_new 方法,创建一个默认的 event_base。

    最后,启动一个 Tor 控制线程。具体调用 std::thread 方法,创建一个线程,线程的具体执行方法为 std::bind 返回的绑定函数。标准绑定函数的第一个参数为要执行的函数,此处为 TraceThread,第二个参数为线程的名字 torcontrol,第三个参数为线程要执行的真正方法,此处为 TorControlThread 函数,后面两个参数都会做为参数,传递到第一个函数。

    TraceThread 函数,调用 RenameThread 方法,把线程名字设置为 bitcoin-torcontrol,然后执行传递进来的 TorControlThread 函数。后者会生成一个 Tor 控制器,然后调用 event_base_dispatch 方法,分发事件。代码如下:

    static void TorControlThread() { TorController ctrl(gBase, gArgs.GetArg("-torcontrol", DEFAULT_TOR_CONTROL));


    event_base_dispatch(gBase); }

    TorController 构造函数中会做几件重要的事情:

    struct ifaddrs { struct ifaddrs *ifa_next; /* 列表中的下一个条目 */ char *ifa_name; /* 接口的名称 */ unsigned int ifa_flags; /* 来自 SIOCGIFFLAGS 的标志 */ struct sockaddr *ifa_addr; /* 接口的地址 */ struct sockaddr *ifa_netmask; /* 接口的网络掩码 */ union { struct sockaddr *ifu_broadaddr; /* 接口的广播地址 */ struct sockaddr *ifu_dstaddr; /* 点对点的目标地址 */ } ifa_ifu; #define ifa_broadaddr ifa_ifu.ifu_broadaddr #define ifa_dstaddr ifa_ifu.ifu_dstaddr void *ifa_data; /* Address-specific data */ };


    如果可以获取接口信息,则遍历每一个接口,进行如下处理:代码如下所示:

    if (getifaddrs(&myaddrs) == 0) { for (struct ifaddrs* ifa = myaddrs; ifa != nullptr; ifa = ifa->ifa_next) { if (ifa->ifa_addr == nullptr) continue; if ((ifa->ifa_flags & IFF_UP) == 0) continue; if (strcmp(ifa->ifa_name, "lo") == 0) continue; if (strcmp(ifa->ifa_name, "lo0") == 0) continue; if (ifa->ifa_addr->sa_family == AF_INET) { struct sockaddr_in* s4 = (struct sockaddr_in*)(ifa->ifa_addr); CNetAddr addr(s4->sin_addr); if (AddLocal(addr, LOCAL_IF)) LogPrintf("%s: IPv4 %s: %s\n", __func__, ifa->ifa_name, addr.ToString()); } else if (ifa->ifa_addr->sa_family == AF_INET6) { struct sockaddr_in6* s6 = (struct sockaddr_in6*)(ifa->ifa_addr); CNetAddr addr(s6->sin6_addr); if (AddLocal(addr, LOCAL_IF)) LogPrintf("%s: IPv6 %s: %s\n", __func__, ifa->ifa_name, addr.ToString()); } } freeifaddrs(myaddrs); }


    • 如果接口地址为空,则处理下一个。

    • 如果不是接口标志不是 IFF_UP ,则处理下一个。

    • 如果接口名称是 lo 或 lo0,则处理下一个。

    • 如果接口是 tcp,TCP 等,则生成 IP 地址对象,然后调用 AddLocal 方法,保存本地地址。

    • 如果接口是 IPV6,则则生成 IP 地址对象,然后调用 AddLocal 方法,保存本地地址。

    • 解析 Tor 控制器的地址。

    • 调用 bufferevent_socket_new 方法,基于套接字生成一个 bufferevent。

    • 设置 bufferevent 的回调方法,包括:读取回调函数为 TorControlConnection::readcb,写入回调函数为空,事件回调函数为 TorControlConnection::eventcb,同时指定 bufferevent 启用读写标志。

    • 设置 TorControlConnection 连接、断开连接的两个指针函数分别为:TorController::connected_cbTorController::disconnected_cb

    • 调用 bufferevent_socket_connect 方法,连接到前面生成的 bufferevent。方法在连接成功后,会立即调用事件回调函数 TorControlConnection::eventcb

    • 首先,调用 event_new 方法生成一个 event 对象,event 对象的回调函数为 reconnect_cb

    • 然后,调用 TorControlConnection::Connect 方法连接到 Tor 控制器。这个方法又会做几件事情:

    1. 调用 Discover 函数,开始发现本节点的地址。3. 调用 Discover 函数,开始发现本节点的地址。3. 调用 Discover 函数,开始发现本节点的地址。方法内首先判断是否已经处理过。如果没有,那么开始发现本节点的地址。具体处理分为 windows 和 linux,下面主要讲述 linux 下的处理。方法内首先判断是否已经处理过。如果没有,那么开始发现本节点的地址。具体处理分为 windows 和 linux,下面主要讲述 linux 下的处理。调用 getifaddrs 方法,查找系统所有的网络接口的信息,包括以太网卡接口和回环接口等。本方法返回一个如下的结构体:调用 getifaddrs 方法,查找系统所有的网络接口的信息,包括以太网卡接口和回环接口等。本方法返回一个如下的结构体:

  3. 如果指定了 upnp 参数,则调用 StartMapPort 函数,开始进行端口映射。

    if (gArgs.GetBoolArg("-upnp", DEFAULT_UPNP)) { StartMapPort(); }


  4. 生成选项对象,并进行初始化。

    CConnman::Options connOptions; connOptions.nLocalServices = nLocalServices; connOptions.nMaxConnections = nMaxConnections; connOptions.nMaxOutbound = std::min(MAX_OUTBOUND_CONNECTIONS, connOptions.nMaxConnections); connOptions.nMaxAddnode = MAX_ADDNODE_CONNECTIONS; connOptions.nMaxFeeler = 1; connOptions.nBestHeight = chain_active_height; connOptions.uiInterface = &uiInterface; connOptions.m_msgproc = peerLogic.get(); connOptions.nSendBufferMaxSize = 1000*gArgs.GetArg("-maxsendbuffer", DEFAULT_MAXSENDBUFFER); connOptions.nReceiveFloodSize = 1000*gArgs.GetArg("-maxreceivebuffer", DEFAULT_MAXRECEIVEBUFFER); connOptions.m_added_nodes = gArgs.GetArgs("-addnode");


    connOptions.nMaxOutboundTimeframe = nMaxOutboundTimeframe; connOptions.nMaxOutboundLimit = nMaxOutboundLimit;

    上面的代码基本就是设置本地支持的服务、最大连接数、最大出站数、最大节点数、最大费率、活跃区块链的高度、节点逻辑验证器、发送的最大缓冲值、接收的最大缓冲值、连接的节点数等。

  5. 如果指定了 -bind 参数,则处理绑定参数。

    for (const std::string& strBind : gArgs.GetArgs("-bind")) { CService addrBind; if (!Lookup(strBind.c_str(), addrBind, GetListenPort(), false)) { return InitError(ResolveErrMsg("bind", strBind)); } connOptions.vBinds.push_back(addrBind); }


    遍历所有的绑定地址,调用 Lookup 方法,进行 DNS查找。如果可以找到对应 IP地址,把生成的 CService 对象放入选项对象的 vBinds 属性中。

  6. 如果指定了 -whitebind 参数,则处理绑定参数。

    for (const std::string& strBind : gArgs.GetArgs("-whitebind")) { CService addrBind; if (!Lookup(strBind.c_str(), addrBind, 0, false)) { return InitError(ResolveErrMsg("whitebind", strBind)); } if (addrBind.GetPort() == 0) { return InitError(strprintf(_("Need to specify a port with -whitebind: '%s'"), strBind)); } connOptions.vWhiteBinds.push_back(addrBind); }


    遍历所有的绑定地址,调用 Lookup 方法,进行 DNS查找。如果可以找到对应 IP地址,且对应的端口号不等于0,把生成的 CService 对象放入选项对象的 vWhiteBinds 属性中。

  7. 如果指定了 -whitelist 参数,则处理白名单列表。

    for (const auto& net : gArgs.GetArgs("-whitelist")) { CSubNet subnet; LookupSubNet(net.c_str(), subnet); if (!subnet.IsValid()) return InitError(strprintf(_("Invalid netmask specified in -whitelist: '%s'"), net)); connOptions.vWhitelistedRange.push_back(subnet); }


    遍历白名单列表,调用 LookupSubNet 方法,查找对应的子网掩码,如果对应的子网掩码是有效的,那么放入选项对象的 vWhitelistedRange 属性中。

  8. 取得参数 seednode 指定的值,放入选项对象的 vSeedNodes 属性中。

    connOptions.vSeedNodes = gArgs.GetArgs("-seednode");


  9. 调用 CConnman 对象的 Start 方法,初始所有的出站连接。本方法非常非常重要,因为它启动了一个重要的流程,即底层的 P2P 网络建立和消息处理流程。具体分析如下:

    `InitBinds` 方法,接收 `-bind` 和 `-whitebind` 参数生成的集合,并解析各个地址,生成套接字,并进行监听。具体分析如下:


    - 首先,处理`-bind` 地址集合。

    for (const auto& addrBind : binds) { fBound |= Bind(addrBind, (BF_EXPLICIT | BF_REPORT_ERROR)); }

    - 然后,处理 `-whitebind` 地址集合。

    for (const auto& addrBind : whiteBinds) { fBound |= Bind(addrBind, (BF_EXPLICIT | BF_REPORT_ERROR | BF_WHITELIST)); }

    - 如果,两个参数都没有指定,则使用下面代码进行处理。

    if (binds.empty() && whiteBinds.empty()) { struct in_addr inaddr_any; inaddr_any.s_addr = INADDR_ANY; struct in6_addr inaddr6_any = IN6ADDR_ANY_INIT; fBound |= Bind(CService(inaddr6_any, GetListenPort()), BF_NONE); fBound |= Bind(CService(inaddr_any, GetListenPort()), !fBound ? BF_REPORT_ERROR : BF_NONE); }

    从以上代码可以看出来,三种情况下,处理基本相同,都是调用 `Bind` 方法来处理。下面,我们进进入这个方法一控究竟。这个方法的主体是调用 `BindListenPort` 方法进行处理。下面我们开始讲解这个方法。

    - 首先,生成一个通用的网络地址 sockaddr 对象,类型为 sockaddr_storage,它的长度是 128个字节。

    - 然后,调用 `addrBind.GetSockAddr((struct sockaddr*)&sockaddr, &len)` 方法来设置网络地址 sockaddr。

    `GetSockAddr` 方法内部根据地址是 IPV4 或 IPV6,分别进行处理。

    如果是 IPV4,则生成 sockaddr_in 地址对象,然后调用 `memset` 把结构体所占内存用0填充,然后调用 `GetInAddr` 方法来设置地址对象的地址字段,最后设置地址类型为 AF_INET 和端口号。

    如果是 IPV6,则生成 sockaddr_in6 地址对象,然后调用 `memset` 把结构体所占内存用0填充,然后调用 `GetIn6Addr` 方法来设置地址对象的地址字段,最后设置地址类型为 AF_INET6 和端口号。

    - 再然后,调用 `CreateSocket(addrBind)` 方法生成套接字对象。

    方法处理如下:

    - 首先,生成一个通用的网络地址 sockaddr 对象,类型为 sockaddr_storage,然后,调用 `addrBind.GetSockAddr((struct sockaddr*)&sockaddr, &len)` 方法来设置网络地址 sockaddr。具体分析详见上面。

    - 然后,生成套接字。

    socket(((struct sockaddr*)&sockaddr)->sa_family, SOCK_STREAM, IPPROTO_TCP)

    - 再然后,对套接字进行一些检查和处理,不详述。

    - 套接字生成之后,接下来把套接字绑定到指定的地址上,并监听入站请求。

    if (::bind(hListenSocket, (struct sockaddr*)&sockaddr, len) == SOCKET_ERROR) { int nErr = WSAGetLastError(); if (nErr == WSAEADDRINUSE) strError = strprintf(_("Unable to bind to %s on this computer. %s is probably already running."), addrBind.ToString(), _(PACKAGE_NAME)); else strError = strprintf(_("Unable to bind to %s on this computer (bind returned error %s)"), addrBind.ToString(), NetworkErrorString(nErr)); LogPrintf("%s\n", strError); CloseSocket(hListenSocket); return false; } LogPrintf("Bound to %s\n", addrBind.ToString());

    // Listen for incoming connections if (listen(hListenSocket, SOMAXCONN) == SOCKET_ERROR) { strError = strprintf(_("Error: Listening for incoming connections failed (listen returned error %s)"), NetworkErrorString(WSAGetLastError())); LogPrintf("%s\n", strError); CloseSocket(hListenSocket); return false; }

    - 最后,进行一些收尾工作。

    把套接字放入 `vhListenSocket` 集合中。如果地址是可达的,并且不是白名单中的地址,则调用 `AddLocal` 方法,加入本地地址集合中。

    这个方法非常简单,把每个种子节点加入 `vOneShots` 集合。


    代码比较简单,一看便知,不作具体展开。


    • 首先,生成套接字相关的线程,以便进行网络的接收和发送。处理方法和前面线程的类似,代码如下:

      threadSocketHandler = std::thread(&TraceThread >, "net", std::function (std::bind(&CConnman::ThreadSocketHandler, this)));


    • 接下来,处理 DNS 种子节点线程,处理 DNS 种子相关的逻辑。代码如下:

      if (!gArgs.GetBoolArg("-dnsseed", true)) LogPrintf("DNS seeding disabled\n"); else threadDNSAddressSeed = std::thread(&TraceThread >, "dnsseed", std::function (std::bind(&CConnman::ThreadDNSAddressSeed, this)));


    • 接下来,处理出站连接。代码如下:

      threadOpenAddedConnections = std::thread(&TraceThread >, "addcon", std::function (std::bind(&CConnman::ThreadOpenAddedConnections, this)));


    • 接下来,处理打开连接的线程。代码如下:

      if (connOptions.m_use_addrman_outgoing || !connOptions.m_specified_outgoing.empty()) threadOpenConnections = std::thread(&TraceThread >, "opencon", std::function (std::bind(&CConnman::ThreadOpenConnections, this, connOptions.m_specified_outgoing)));


    • 最最重要的线程–处理消息的线程,隆重登场。

    • 最后,重中之重的线程相关处理终于要到来了。

      真正执行的方法是 `ThreadSocketHandler`,这个方法太重要了,我们留在下一课网络处理中细讲。


      真正执行的方法是 `ThreadDNSAddressSeed`,这个方法太重要了,我们留在下一课网络处理中细讲。


      真正执行的方法是 `ThreadOpenAddedConnections`,这个方法太重要了,我们留在下一课网络处理中细讲。


      真正执行的方法是 `ThreadOpenConnections`,这个方法太重要了,我们留在下一课网络处理中细讲。


    • 接下来,从文件数据库中加载地址列表和禁止地址列表。

      { CAddrDB adb; if (adb.Read(addrman)) LogPrintf("Loaded %i addresses from peers.dat %dms\n", addrman.size(), GetTimeMillis() - nStart); else { addrman.Clear(); // Addrman can be in an inconsistent state after failure, reset it LogPrintf("Invalid or missing peers.dat; recreating\n"); DumpAddresses(); } }


      CBanDB bandb; banmap_t banmap; if (bandb.Read(banmap)) { SetBanned(banmap); // thread save setter SetBannedSetDirty(false); // no need to write down, just read data SweepBanned(); // sweep out unused entries

      LogPrint(BCLog::NET, "Loaded %d banned node ips/subnets from banlist.dat %dms\n", banmap.size(), GetTimeMillis() - nStart); } else { LogPrintf("Invalid or missing banlist.dat; recreating\n"); SetBannedSetDirty(true); // force write DumpBanlist(); }

    • 处理完地址绑定之后,接下来处理种子节点参数指定集合。

      void CConnman::AddOneShot(const std::string& strDest) { LOCK(cs_vOneShots); vOneShots.push_back(strDest); }


    • 调用 Init 方法,根据选项对象设置对象的属性,不细说,代码如下:

      void Init(const Options& connOptions) { nLocalServices = connOptions.nLocalServices; nMaxConnections = connOptions.nMaxConnections; nMaxOutbound = std::min(connOptions.nMaxOutbound, connOptions.nMaxConnections); nMaxAddnode = connOptions.nMaxAddnode; nMaxFeeler = connOptions.nMaxFeeler; nBestHeight = connOptions.nBestHeight; clientInterface = connOptions.uiInterface; m_msgproc = connOptions.m_msgproc; nSendBufferMaxSize = connOptions.nSendBufferMaxSize; nReceiveFloodSize = connOptions.nReceiveFloodSize; { LOCK(cs_totalBytesSent); nMaxOutboundTimeframe = connOptions.nMaxOutboundTimeframe; nMaxOutboundLimit = connOptions.nMaxOutboundLimit; } vWhitelistedRange = connOptions.vWhitelistedRange; { LOCK(cs_vAddedNodes); vAddedNodes = connOptions.m_added_nodes; } }


    • 接下来,使用锁初始一些比较重要的属性。

      { LOCK(cs_totalBytesRecv); nTotalBytesRecv = 0; } { LOCK(cs_totalBytesSent); nTotalBytesSent = 0; nMaxOutboundTotalBytesSentInCycle = 0; nMaxOutboundCycleStartTime = 0; }


    • 再接下来,获取节点绑定的本地地址和端口,并生成对应的套接字,接受别的节点的请求。

      if (fListen && !InitBinds(connOptions.vBinds, connOptions.vWhiteBinds)) { if (clientInterface) { clientInterface->ThreadSafeMessageBox( _("Failed to listen on any port. Use -listen=0 if you want this."), "", CClientUIInterface::MSG_ERROR); } return false; }


threadMessageHandler = std::thread(&TraceThread >, "msghand", std::function (std::bind(&CConnman::ThreadMessageHandler, this)));

真正执行的方法是 ThreadMessageHandler,这个方法太重要了,我们留在下一课网络处理中细讲。

- 最后,定时把节点地址和禁止列表刷新到数据库文件中。 第13步,结束启动( src/init.cpp::AppInitMain()

  1. 调用 SetRPCWarmupFinished() 方法,设置热身结束。方法内部主要设置 fRPCInWarmup 变量为假,表示热身结束。

  2. 调用钱包接口对象的 Start 方法,开始进行钱包相关的处理,并定时刷新钱包数据到数据库中。代码如下:

    for (const std::shared_ptr & pwallet : GetWallets()) { pwallet->postInitProcess(); }


    // Run a thread to flush wallet periodically scheduler.scheduleEvery(MaybeCompactWalletDB, 500);

    方法中,首先便利所有钱包对象,调用其 postInitProcess 方法,进行后初始化设置。主要是把钱包中存在,但是交易池中不存在的交易添加到交易池中。

    然后,设置调度器定时调用 MaybeCompactWalletDB 方法,刷新钱包数据到数据库中。

特别声明:以上内容(如有图片或视频亦包括在内)为自媒体平台“网易号”用户上传并发布,本平台仅提供信息存储服务。

Notice: The content above (including the pictures and videos if any) is uploaded and posted by a user of NetEase Hao, which is a social media platform and only provides information storage services.

相关推荐
热点推荐
上海人民广场下面的店全关完了?有人直言“太惨淡”?巧了,我们刚去过→

上海人民广场下面的店全关完了?有人直言“太惨淡”?巧了,我们刚去过→

新民晚报
2024-04-24 13:34:39
日本的恐惧:中国已悄然改变了对日本的策略!

日本的恐惧:中国已悄然改变了对日本的策略!

星辰故事屋
2024-04-24 13:45:41
抢先看 支付宝大楼换上新Logo:无比简洁

抢先看 支付宝大楼换上新Logo:无比简洁

搞笑的阿万
2024-04-24 08:05:00
“没把持住就犯法了!”江苏男子称被女技师色诱“加钟”,东郊到家回应

“没把持住就犯法了!”江苏男子称被女技师色诱“加钟”,东郊到家回应

潇湘晨报
2024-04-24 21:15:08
美国计划将中国踢出国际贸易交易体系,一旦成功对中国影响有多大

美国计划将中国踢出国际贸易交易体系,一旦成功对中国影响有多大

叮当当科技
2024-04-24 21:57:01
方媛带两个女儿参加婚礼,姐姐郭咏希像爸爸,姐妹当花童超可爱

方媛带两个女儿参加婚礼,姐姐郭咏希像爸爸,姐妹当花童超可爱

素素娱乐
2024-04-24 08:59:49
苏纳克宣布“有史以来最大规模军援”!英国军工业转入战时状态

苏纳克宣布“有史以来最大规模军援”!英国军工业转入战时状态

鹰眼Defence
2024-04-24 16:47:10
特斯拉在国内取消所有应届毕业生offer

特斯拉在国内取消所有应届毕业生offer

南方都市报
2024-04-24 17:32:11
无冠魔咒延续!丁俊晖9-10遭绝杀世锦赛一轮游 第18次冲冠失败

无冠魔咒延续!丁俊晖9-10遭绝杀世锦赛一轮游 第18次冲冠失败

厝边人侃体育
2024-04-24 20:31:27
金价继续大跌!还能买吗?业内人士建议这么做

金价继续大跌!还能买吗?业内人士建议这么做

21世纪经济报道
2024-04-24 20:09:58
加拿大华人女律师白天上法庭,晚上去卖淫:律协回应……

加拿大华人女律师白天上法庭,晚上去卖淫:律协回应……

巴蜀法眼
2024-04-23 20:19:51
美参议院通过涉TikTok法案,法案等待拜登签署,外交部回应

美参议院通过涉TikTok法案,法案等待拜登签署,外交部回应

环球网资讯
2024-04-24 15:44:22
周鸿祎和贾跃亭掐起来了:别光讲ppt,送辆车到360大厦

周鸿祎和贾跃亭掐起来了:别光讲ppt,送辆车到360大厦

三言科技
2024-04-24 18:17:01
4月24日俄乌最新:第115旅不战而退,乌军王牌血战Ocheretyne

4月24日俄乌最新:第115旅不战而退,乌军王牌血战Ocheretyne

西楼饮月
2024-04-24 09:18:02
中国被扣上“全球唯一搞医疗商业化”的帽子

中国被扣上“全球唯一搞医疗商业化”的帽子

今日养生之道
2024-04-24 13:45:53
梅艳芳母亲提前庆祝100岁,戴佛珠吃寿桃,希望还能再活20年

梅艳芳母亲提前庆祝100岁,戴佛珠吃寿桃,希望还能再活20年

素素娱乐
2024-04-24 14:37:00
中央决定免去职务后落马的“老虎”,受贿超1.2亿,违法放贷33.2亿余元,当庭认罪!

中央决定免去职务后落马的“老虎”,受贿超1.2亿,违法放贷33.2亿余元,当庭认罪!

鲁中晨报
2024-04-24 22:41:09
被指香港金融史重大时刻 华夏博时嘉实三家现货比特币、以太币ETF获批

被指香港金融史重大时刻 华夏博时嘉实三家现货比特币、以太币ETF获批

财联社
2024-04-24 19:20:13
时隔8年,叶诗文重回奥运会!夺冠直通巴黎,中国游泳首个金满贯

时隔8年,叶诗文重回奥运会!夺冠直通巴黎,中国游泳首个金满贯

黑色柳丁
2024-04-24 21:36:01
作秀?纪云浩副书记在认真工作的照片,连电脑都没开双手却在打字

作秀?纪云浩副书记在认真工作的照片,连电脑都没开双手却在打字

飞哥AI矩阵
2024-04-24 13:50:14
2024-04-25 01:46:44
三言科技
三言科技
聚焦新未来新科技,严肃又活泼。
75366文章数 66178关注度
往期回顾 全部

头条要闻

男童被武术俱乐部教练轮番殴打去世 家属:希望判死刑

头条要闻

男童被武术俱乐部教练轮番殴打去世 家属:希望判死刑

体育要闻

足智多谋的哈姆,温水里的青蛙

娱乐要闻

方媛带两女儿参加婚礼,当花童超可爱

财经要闻

居民气价确实在涨,多地正普遍发生

科技要闻

特斯拉被爆大量毁约应届生 友商"在线抢人"

汽车要闻

这灯效我能看半小时 奥迪Q6L e-tron有备而来

态度原创

手机
数码
时尚
艺术
本地

手机要闻

iQOO Z9 Turbo对比红米Turbo 3,参数之间,有多大区别?

数码要闻

苹果5月7日发布会邀请函: 全新iPad Pro 2024将亮相

六年后全民倒戈,支持魏嬿婉扶正!

艺术要闻

艺术名画︱爱尔兰画家大卫·科因的刀画作品

本地新闻

荒野求生贝爷都得靠边站,真求生还得看留子

无障碍浏览 进入关怀版