风险提示:理性看待区块链,提高风险意识!
从零开始学习比特币开发(七):P2P网络建立流程之生成地址对并连接到指定地址
首页 > 币界资讯 > 区块链知识 2018-10-18 10:56:00

本节继续讲解比特币P2P网络建立流程,这节讲解的线程为’ThreadOpenAddedConnections’,它的作用是生成地址对并连接到指定地址。
本文可以结合比特币系统启动的的第12步的讲解来看,可以更加系统的了解比特币系统启动的过程。

P2P 网络的建立是在比特币系统启动的第 12 步,最后时刻调用 CConnman::Start 方法开始的。

本部分内容在 net.cppnet_processing.cpp 等文件中。

下面开始讲解各个线程的具体处理。

1、ThreadSocketHandler

见文章从零开始学习比特币(五)–P2P网络建立的流程之套接字的读取和发送

2、ThreadDNSAddressSeed

见文章从零开始学习比特币(六)–P2P网络建立的流程之查询DNS节点

3、ThreadOpenAddedConnections

这个线程的主要作用是生成地址对象,并且调用 OpenNetworkConnection 方法,连接到指定地址。

线程定义在 net.cpp 文件的 1959 行。下面我们开始进行具体的解读。

线程的主体是一个 while 循环。在循环中进行下面的处理。

  1. 调用 GetAddedNodeInfo 方法,获取所有的节点信息。本方法返回所有的节点信息,其中即有已连接的,也有未连接的地址。
    • 首先,生成保存节点信息的容器变量 ret 和保存地址字符串的列表对象 lAddresses。然后把 vAddedNodes 集合中的所有地址拷贝到 lAddresses中。
      std::vector<AddedNodeInfo> ret;
      
      std::list<std::string> lAddresses(0);
      {
          LOCK(cs_vAddedNodes);
          ret.reserve(vAddedNodes.size());
          std::copy(vAddedNodes.cbegin(), vAddedNodes.cend(), std::back_inserter(lAddresses));
      }
      
    • 遍历所有的节点(vNodes 节点容器),进行下面处理。如果当前节点的地址是有效的,则加入 mapConnected map 中,Key 为当前节点的地址,值标明当前节点是否为入站节点。获取当前节点的地址名称。如果名称不空,则放进 mapConnectedByName map 中,Key 为当前节点的地址名称,值为一个 std::pair 对象,其中第一个值表明当前节点是否为入站节点,第二个值为节点的地址。
      std::map<CService, bool> mapConnected;
      std::map<std::string, std::pair<bool, CService>> mapConnectedByName;
      {
          LOCK(cs_vNodes);
          for (const CNode* pnode : vNodes) {
              if (pnode->addr.IsValid()) {
                  mapConnected[pnode->addr] = pnode->fInbound;
              }
              std::string addrName = pnode->GetAddrName();
              if (!addrName.empty()) {
                  mapConnectedByName[std::move(addrName)] = std::make_pair(pnode->fInbound, static_cast<const CService&>(pnode->addr));
              }
          }
      }
      
    • 遍历 lAddresses 变量,进行下面处理。根据当前地址和当前网络类型,生成一个 service 对象,类型为 CService,和一个节点信息对象。如果当前地址是 IP:Port 形式,那么查找 mapConnected 集合对应的地址。如果可以找到,则设置节点信息对象的相关属性。

      如果当前地址是名称的形式,那么查找 mapConnectedByName 集合对应的地址。如果可以找到,则设置节点信息对象的相关属性。

      把当前地址信息对象加入 ret 集合中。

      for (const std::string& strAddNode : lAddresses) {
          CService service(LookupNumeric(strAddNode.c_str(), Params().GetDefaultPort()));
          AddedNodeInfo addedNode{strAddNode, CService(), false, false};
          if (service.IsValid()) {
              // strAddNode is an IP:port
              auto it = mapConnected.find(service);
              if (it != mapConnected.end()) {
                  addedNode.resolvedAddress = service;
                  addedNode.fConnected = true;
                  addedNode.fInbound = it->second;
              }
          } else {
              // strAddNode is a name
              auto it = mapConnectedByName.find(strAddNode);
              if (it != mapConnectedByName.end()) {
                  addedNode.resolvedAddress = it->second.second;
                  addedNode.fConnected = true;
                  addedNode.fInbound = it->second.first;
              }
          }
          ret.emplace_back(std::move(addedNode));
      }
      
    • 返回ret 集合。
  2. 遍历所有的节点信息,如果当前节点还没有连接,进行下面的处理:生成地址对象 addr,类型为 CAddress。调用 OpenNetworkConnection 方法,连接到当前的节点。
    for (const AddedNodeInfo& info : vInfo) {
        if (!info.fConnected) {
            if (!grant.TryAcquire()) {
                // If we've used up our semaphore and need a new one, let's not wait here since while we are waiting
                // the addednodeinfo state might change.
                break;
            }
            tried = true;
            CAddress addr(CService(), NODE_NONE);
            OpenNetworkConnection(addr, false, &grant, info.strAddedNode.c_str(), false, false, true);
            if (!interruptNet.sleep_for(std::chrono::milliseconds(500)))
                return;
        }
    }
    

下面我们具体看下 OpenNetworkConnection 函数的处理。

  1. 如果 interruptNet 为真,则返回。如果网络没有激活(fNetworkActive 为假),则返回。
     if (interruptNet) {
         return;
     }
     if (!fNetworkActive) {
         return;
     }
    
  2. 如果参数 pszDest 为空(当前节点信息的地址),进一步,如果要连接的节点是本地的,或是已连接的,或是禁止的,则返回。如果参数 pszDest 不为空,进一步,如果节点是已连接的,则返回。
     if (!pszDest) {
         if (IsLocal(addrConnect) ||
             FindNode(static_cast<CNetAddr>(addrConnect)) || IsBanned(addrConnect) ||
             FindNode(addrConnect.ToStringIPPort()))
             return;
     } else if (FindNode(std::string(pszDest)))
         return;
    
  3. 调用 ConnectNode 方法,连接到指定地址,并返回对等节点 CNode 对象。如果连接失败,则返回。
  4. 如果参数 grantOutbound 对象存在,则调用其 MoveTo 方法,进行处理。
  5. 如果参数 fOneShot 为真,则设置对等节点的 fOneShot 属性为真。
  6. 如果是临时探测节点(参数fFeeler 为真),则设置对等节点的 fFeeler 属性为真。
  7. 如果是手动连接的,则设置对等节点的 m_manual_connection 属性为真。
  8. 调用网络事件处理器的 InitializeNode 方法,进行对等节点初始化。具体代码在 net_processing.cpp 文件的第 611 行,如下所示:
    void PeerLogicValidation::InitializeNode(CNode *pnode) {
        CAddress addr = pnode->addr;
        std::string addrName = pnode->GetAddrName();
        NodeId nodeid = pnode->GetId();
        {
            LOCK(cs_main);
            mapNodeState.emplace_hint(mapNodeState.end(), std::piecewise_construct, std::forward_as_tuple(nodeid), std::forward_as_tuple(addr, std::move(addrName)));
        }
        if(!pnode->fInbound)
            PushNodeVersion(pnode, connman, GetTime());
    }
    

    代码最主要的动作是,检查节点是否为出站节点,即连接到别的对等节点,如果是则调用 PushNodeVersion 方法,发送版本信息。具体消息消息处理部分。

  9. 把生成的对等节点保存到 vNodes 向量中。

3.1、ConnectNode

在上文讲解 OpenNetworkConnection 函数“3.调用 ConnectNode 方法,连接到指定地址,并返回对等节点 CNode 对象” 中我提到了‘ConnectNode’方法,这个方法负责连接到具体的对等节点。我们来看下具体的处理。

  1. 如果参数 pszDest 为空指针,则处理如下:如果要连接的地址是本地地址,则直接返回空指针。调用 FindNode 方法,查看指定的节点是否存在。如果存在,即已经连接,则返回空指针。
    if (pszDest == nullptr) {
        if (IsLocal(addrConnect))
            return nullptr;
        // Look for an existing connection
        CNode* pnode = FindNode(static_cast<CService>(addrConnect));
        if (pnode)
        {
            LogPrintf("Failed to open new connection, already connected\n");
            return nullptr;
        }
    }
    
  2. 如果参数 pszDest 不是空指针,那么调用 Lookup 方法,查找/生成地址字符串对应的地址对象。如果找到,则进行下面的处理:生成要连接的地址对象。如果地址地址对象是无效的,则返回空指针。调用 FindNode 方法,查找对应的地址对象。如果存在,即已经连接,则返回空指针。这个地方解析要连接的地址字符串生成要连接的地址对象。
    const int default_port = Params().GetDefaultPort();
    if (pszDest) {
        std::vector<CService> resolved;
        if (Lookup(pszDest, resolved,  default_port, fNameLookup && !HaveNameProxy(), 256) && !resolved.empty()) {
            addrConnect = CAddress(resolved[GetRand(resolved.size())], NODE_NONE);
            if (!addrConnect.IsValid()) {
                LogPrint(BCLog::NET, "Resolver returned invalid address %s for %s\n", addrConnect.ToString(), pszDest);
                return nullptr;
            }
            LOCK(cs_vNodes);
            CNode* pnode = FindNode(static_cast<CService>(addrConnect));
            if (pnode)
            {
                pnode->MaybeSetAddrName(std::string(pszDest));
                LogPrintf("Failed to open new connection, already connected\n");
                return nullptr;
            }
        }
    }
    
  3. 如果要连接的地址对象是有效的,进行下面的处理。调用 GetProxy 方法,返回代理类型。如果方法返回为真,即存在代理,那么调用 CreateSocket 方法,创建代理套接字。如果成功创建,调用 ConnectThroughProxy 方法,通过代理连接到对等节点。如果不存在代理,那么调用 CreateSocket 方法,创建对等节点的套接字。如果成功创建,调用 ConnectSocketDirectly 方法,直接连接到对等节点。
    bool proxyConnectionFailed = false;
    
    if (GetProxy(addrConnect.GetNetwork(), proxy)) {
        hSocket = CreateSocket(proxy.proxy);
        if (hSocket == INVALID_SOCKET) {
            return nullptr;
        }
        connected = ConnectThroughProxy(proxy, addrConnect.ToStringIP(), addrConnect.GetPort(), hSocket, nConnectTimeout, &proxyConnectionFailed);
    } else {
        // no proxy needed (none set for target network)
        hSocket = CreateSocket(addrConnect);
        if (hSocket == INVALID_SOCKET) {
            return nullptr;
        }
        connected = ConnectSocketDirectly(addrConnect, hSocket, nConnectTimeout, manual_connection);
    }
    if (!proxyConnectionFailed) {
        // If a connection to the node was attempted, and failure (if any) is not caused by a problem connecting to
        // the proxy, mark this as an attempt.
        addrman.Attempt(addrConnect, fCountFailure);
    }
    
  4. 如果要连接的字符串不空,且存在代理,那么:调用 CreateSocket 方法,生成代理的套接字。然后,调用 ConnectThroughProxy方法,通过代理连接到指定的对等节点。
    hSocket = CreateSocket(proxy.proxy);
    if (hSocket == INVALID_SOCKET) {
        return nullptr;
    }
    std::string host;
    int port = default_port;
    SplitHostPort(std::string(pszDest), port, host);
    connected = ConnectThroughProxy(proxy, host, port, hSocket, nConnectTimeout, nullptr);
    
  5. 如果以上都没有连接到主节点,则关闭套接字并返回空指针。
     if (!connected) {
         CloseSocket(hSocket);
         return nullptr;
     }
    
  6. 最后,生成并返回主节点对象。
     NodeId id = GetNewNodeId();
     uint64_t nonce = GetDeterministicRandomizer(RANDOMIZER_ID_LOCALHOSTNONCE).Write(id).Finalize();
     CAddress addr_bind = GetBindAddress(hSocket);
     CNode* pnode = new CNode(id, nLocalServices, GetBestHeight(), hSocket, addrConnect, CalculateKeyedNetGroup(addrConnect), nonce, addr_bind, pszDest ? pszDest : "", false);
     pnode->AddRef();
     return pnode;
    

我是区小白,Ulord全球社区联盟(优得社区)核心区块链技术开发者,深入研究比特币,以太坊,EOS Dash,Rsk,Java, Nodejs,PHP,Python,C++ 我希望能聚集更多区块链开发者,一起学习共同进步。
为了更高效的交流探讨区块链开发过程中遇到的问题,
欢迎将以上问题的答案在帖子下面留言。

上一篇: Fabric基础架构原理(4):链码
下一篇: 从零开始学比特币开发:P2P 网络建立之消息处理
推荐专栏
web3首席知识博主
一位相信价值投资的币圈KOL。稳定盈利的缠论野生交易员 #BTC行情分析师 #价值投资 #链上数据分析
爱Web 3,爱生活,爱科技,爱炒币的老韭菜
热门币种
更多
币种
价格
24H涨跌幅
BTC比特币
¥264,723.74
37,091.22 USDT
+0.1%
ETH以太坊
¥14,416.22
2,019.90 USDT
-0.12%
USDT泰达币
¥7.20
1.01 USDT
0%
BNB币安币
¥1,625.40
227.74 USDT
+0.36%
XRP瑞波币
¥4.32
0.60460 USDT
+0.37%
USDC
¥7.14
1.00 USDT
+0.03%
SOLSolana
¥398.85
55.89 USDT
+1.54%
OKBOK币
¥398.61
55.85 USDT
-1.64%
ADA艾达币
¥2.68
0.37580 USDT
-1.16%
DOGE狗狗币
¥0.55160
0.07730 USDT
-1.52%
热搜币种
更多
币种
价格
24H涨跌幅
Terra Classic
¥0.00
9.402E-5 USDT
-18.95%
Gala
¥0.18
0.025374 USDT
-4.66%
dYdX
¥22.58
3.1918 USDT
-0.91%
比特股
¥0.05
0.006964 USDT
+4.28%
PancakeSwap
¥15.52
2.1936 USDT
-2.74%
Conflux
¥1.08
0.1524 USDT
-2.87%
Filecoin
¥31.45
4.4454 USDT
-0.69%
FTX Token
¥29.82
4.2155 USDT
+16.96%
Yield Guild Games
¥2.55
0.3608 USDT
-0.52%
Shiba Inu
¥0.00
8.14E-6 USDT
-2.51%
比特币
¥262,381.44
37091.22 USDT
+0.1%
比原链
¥0.07
0.010011 USDT
-4.38%
最新快讯
更多
汇丰、恒生、渣打、富邦华一四家外资银行入围首批“数字人民币”业务试点名单
2023-11-28 19:06:57
摩根大通和Apollo计划建立代币化“企业主网”
2023-11-28 19:03:57
Nansen2公测版本上线,新增链上数据异动、智能搜索等功能
2023-11-28 18:59:52
西班牙公民需在明年3月底前申报其海外平台上加密货币持仓
2023-11-28 18:53:43
Nansen2已公开测试
2023-11-28 18:53:38
dYdX基金会:主网启动以来超过1645万DYDX被质押
2023-11-28 18:52:07
NicCarter等比特币倡导者发文:比特币挖矿是清洁能源和平衡电网的关键工具
2023-11-28 18:47:58
下载币界网APP