风险提示:理性看待区块链,提高风险意识!
比特币开发学习:P2P网络建立流程之生成地址对并连接到指定地址
首页 > 币界资讯 > 区块链知识 2019-01-12 07:45:01

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

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

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

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节点容器),进行下面处理。如果当前节点的地址是有效的,则加入mapConnectedmap 中,Key 为当前节点的地址,值标明当前节点是否为入站节点。获取当前节点的地址名称。如果名称不空,则放进mapConnectedByNamemap 中,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;
上一篇: 以太坊协议升级进入倒计时,敲定10月9号进行
下一篇: 继比特大陆IPO后发布两款终端AI芯片
推荐专栏
web3首席知识博主
一位相信价值投资的币圈KOL。稳定盈利的缠论野生交易员 #BTC行情分析师 #价值投资 #链上数据分析
爱Web 3,爱生活,爱科技,爱炒币的老韭菜
热门币种
更多
币种
价格
24H涨跌幅
BTC比特币
¥264,358.04
37,039.98 USDT
-0.08%
ETH以太坊
¥14,381.54
2,015.04 USDT
-0.33%
USDT泰达币
¥7.20
1.01 USDT
+0.03%
BNB币安币
¥1,622.26
227.30 USDT
-0.04%
XRP瑞波币
¥4.34
0.60860 USDT
+1.11%
USDC
¥7.14
1.00 USDT
+0.04%
SOLSolana
¥397.24
55.66 USDT
+0.89%
OKBOK币
¥398.18
55.79 USDT
-1.98%
ADA艾达币
¥2.68
0.37490 USDT
-1.52%
DOGE狗狗币
¥0.55410
0.07765 USDT
-1.04%
热搜币种
更多
币种
价格
24H涨跌幅
Terra Classic
¥0.00
9.433E-5 USDT
-17.92%
Gala
¥0.18
0.025395 USDT
-4.47%
dYdX
¥22.45
3.1738 USDT
-1.74%
比特股
¥0.05
0.006817 USDT
+3.93%
PancakeSwap
¥15.45
2.1845 USDT
-3.6%
Conflux
¥1.07
0.1514 USDT
-3.57%
Filecoin
¥31.38
4.4363 USDT
-1.25%
FTX Token
¥30.21
4.2702 USDT
+16.02%
Shiba Inu
¥0.00
8.12E-6 USDT
-2.52%
Yield Guild Games
¥2.54
0.3591 USDT
-0.91%
比特币
¥262,018.97
37039.98 USDT
-0.08%
比原链
¥0.07
0.010012 USDT
-5.35%
最新快讯
更多
Nansen2已公开测试
2023-11-28 18:53:38
dYdX基金会:主网启动以来超过1645万DYDX被质押
2023-11-28 18:52:07
西班牙公民须在2024年3月底前申报在外国加密交易所持有的资产
2023-11-28 18:44:58
ArkInvest上周至今购买约200万美元Robinhood股票
2023-11-28 18:38:25
FSB:可能需要加强跨境合作和信息共享以阻止加密货币在金融体系的风险
2023-11-28 18:31:36
Lambda推出的多链模块化永久存储服务LWS正式部署至Conflux网络
2023-11-28 18:23:06
Lambda推出的多链模块化永久存储服务LWS正式部署Conflux网络
2023-11-28 18:23:06
下载币界网APP