1. 首页
  2. 资讯
  3. 技术指南

从零开始学习比特币开发(七):P2P网络建立流程之生成地址对并连接到指定地址

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

本节继续讲解比特币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 connectedn");
    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 %sn", 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 connectedn");
    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++ 我希望能聚集更多区块链开发者,一起学习共同进步。
为了更高效的交流探讨区块链开发过程中遇到的问题,
欢迎将以上问题的答案在帖子下面留言。

声明:登载此文出于传递更多信息之目的,观点仅代表作者本人,绝不代表Hi区块链赞同其观点或证实其描述。
提示:投资有风险,入市须谨慎。本资讯不作为投资理财建议。

特此通告:由于运营管理等问题,本站已转让出售。