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

当前位置:首页 > 币圈百科 > 从零开始学习比特币(六):P2P网络建立的流程之查询DNS节点

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

2022-12-08币圈百科292

从上一节开始,我们开始解释比特币系统中P2P网络是如何建立的。还记得我们在比特币系统启动第12步的讲解中提到,几个线程相关的进程很重要吗?以下是基于此的详细解释。因为篇幅太长,我们分几篇文章依次讲。

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

这一部分的内容在net.cpp、net_processing.cpp等文件中

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

1、ThreadSocketHandler

详见上一篇文章:

从零开始学习比特币(五)——读取和发送-P2P网络建立的进程的套接字

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

2、ThreadDNSAddressSeed

这个线程的目标是查询,找到后可以连接到比特币网络进行同步。

仅在需要地址时查询DNS种子。当我们不需要DNS种子时,我们将避免DNS种子查询。这可以通过创建更少的用于识别的DNS请求来提高用户隐私。net.cpp文件的第1603行定义了

Thread。先来具体解读一下。

如果对等节点数大于0,且未指定-forcednseed,或者指定了,但值为false,则执行以下处理:遍历所有节点,如果该节点已成功连接且不是引导节点,且fOneShot属性为false,且不是未手动连接且不是入站节点,则变量nRelevant加1。如果变量nRelevant大于2,即P2P网络已经可用,则函数退出。if ((addrman.size() 0)(!葛格斯。GetBoolArg('-forcednsseed ',DEFAULT _ FORCEDNSSEED)){ if(!interrupt net . sleep _ for(STD:chrono:seconds(11)))返回;LOCK(cs _ vNodes);int nre Levant=0;for(auto pnode:vNodes){ nRelevant=pnode-fSuccessfullyConnected!pnode-fFeeler!pnode-fOneShot!pnode-m_manual_connection!pnode-fin bound;} if(nRelevant=2){ LogPrintf(' P2P对等可用。跳过了DNS播种。\ n’);返回;}}获取并遍历所有DNS种子节点。for(const STD:string seed:vSeeds){ if(interrupt net){ return;} if(HaveNameProxy()){ AddOneShot(seed);} else { STD:vector vIPs;STD:vector vAdd;service flags requiredServiceBits=GetDesirableServiceFlags(NODE _ NONE);STD:string host=str printf(' x % x . % s ',requiredServiceBits,seed);CNetAddr resolveSource如果(!resolveSource。SetInternal(host)){ continue;} unsigned int nMaxIPs=256//如果(LookupHost(host.c_str(),vIPs,nMaxIPs,true)){ for(const CNetAddr IP:vIPs){ int none day=24 * 3600;CAD address addr=CAD address(c service(IP,Params()。GetDefaultPort())、requiredServiceBits);addr . ntime=GetTime()-3 * none day-GetRand(4 * none day);//使用3到7天之间的随机年龄vadd . push _ back(addr);找到了;} addrman。Add(vAdd,resolve source);} else { //我们现在避免直接使用来自不支持服务位过滤的DNS种子的结果,//而是一次性使用它们来获得具有我们想要的服务位的节点。AddOneShot(种子);}} }

接下来解释一下上面的代码。

如果指定了代理,则调用AddOneShot方法将当前DNS种子节点保存到vOneShots集合中。否则,执行以下处理:

生成两个集合vIPs和vAdd。vIPs集合保存CNetAddr对象,它表示一个IP地址。vAdd集保存ciaddress对象,该对象继承自CService,而CService又继承自ciaddress并包含一些关于对等节点的信息。调用GetDesirableServiceFlags方法获取服务标志位。 调用strprintf函数来格式化DNS种子节点的地址。Strprintf是一个宏定义,实际上调用的是Boost库的tfm:格式。生成CNetAddr类型的地址对象resolveSource,并调用其SetInternal方法来设置resolveSource的IP。如果有错误,回到下一个。根据DNS种子节点调用LookupHost方法获取保存的对等节点列表。并保存在VIP收藏中。LookupHost方法主要调用LookupIntern方法进行处理。我们来看看后者的具体处理方式。生成一个地址对象addr。然后调用其SetSpecial方法进行处理。在此方法中,如果DNS种子节点不以结尾。洋葱,也就是不是暗网地址,会直接返回false。否则,执行以下处理。调用DecodeBase32方法来解析不包括暗网后缀的特定地址。接下来,检查地址的长度是否不等于指定的长度,如果是,则返回false。否则,该地址将被处理并转换为IP地址,然后返回true。bool CNetAddr:set special(const STD:string strName){ if(strName . size()6 strName . substr(strName . size()-6,6)=='。onion '){ STD:vector vchAddr=decode base 32(strname . substr(0,strName.size() - 6)。c _ str());if (vchAddr.size()!=16-sizeof(pchOnionCat))返回falsememcpy(ip,pchOnionCat,sizeof(pchOnionCat));for(无符号int I=0;i16-sizeof(pchOnionCat);I)IP[I sizeof(pchOnionCat)]=vchAddr[I];返回true}返回false}

如果上一个方法返回的结果为真,即DNS种子为暗网地址,则将当前地址加入vIP集合并返回。

CNetAddr addr;if (addr。set special(STD:string(pszName)){ VIP . push _ back(addr);返回true}生成addrinfo类型的结构对象aiHint,并设置其各种属性值。生成addrinfo类型的结构对象aiRes,然后调用getaddrinfo方法,根据DNS种子节点获取地址链表。该方法由系统提供,返回一个sockaddr结构的链表,而不是地址列表。第一个参数是主机名或地址字符串,第二个参数是服务名或十进制端口号字符串,第三个参数可以是空指针或指向addrinfo结构的指针。调用者用关于预期信息类型的提示填充结构,最后一个参数是返回的结果。int nErr=getaddrinfo(pszName,nullptr,aiHint,aiRes);if (nErr)返回false接下来,只要地址信息链表不为空,且当前获取的对等节点IP数小于指定数或指定数为0(即对等节点数不限),则循环该链表进行后续处理。根据返回的地址信息对象是IPV4还是IPV6,生成不同的CNetAddr对象。如果此地址对象不是内部IP,它将被保存在vIP收藏中。从地址信息链表中获取下一个地址信息对象。struct addrinfo * aiTrav=aiRes而(艾特拉夫!=nullptr(nmax solutions==0 | | VIP . size()ai _ family==AF _ INET){ assert(aiTrav-ai _ addrlen=sizeof(sockaddr _ in));resolved=CNetAddr((struct sockaddr _ in *)(aiTrav-ai _ addr))-sin _ addr);} if(aiTrav-ai _ family==AF _ inet 6){ assert(aiTrav-ai _ addrlen=sizeof(sockaddr _ in6));struct sockaddr _ in6 * S6=(struct sockaddr _ in6 *)aiTrav-ai _ addr;resolved=CNetAddr(s6-sin6_addr,S6-sin 6 _ scope _ id);} /*绝不允许解析到内部地址。认为任何这样的结果都是无效的*/if(!已解决。IsInternal()) { vIP.push_back(已解析);} ait rav=ait rav-ai _ next;}调用freeaddrinfo方法释放getaddrinfo方法请求的内存空间。根据vIP设置的大小,返回真假。如果LookupHost方法返回的结果为真,即根据当前DNS种子节点找到至少一个对等节点,则执行以下处理。 遍历vIPs集合,根据当前IP地址生成一个CAD address对象,并保存在vAdd集合中。同时,将代表找到的节点的变量found加1。调用地址管理器的Add方法保存多个地址。

具体代码如下:

for(const CNET addr IP:VIPs){ int none day=24 * 3600;CAD address addr=CAD address(c service(IP,Params()。GetDefaultPort())、requiredServiceBits);addr . ntime=GetTime()-3 * none day-GetRand(4 * none day);//使用3到7天之间的随机年龄vadd . push _ back(addr);找到了;}addrman。Add(vAdd,resolve source);如果LookupHost方法返回的结果为false,即根据当前DNS种子节点,没有找到对等节点,则调用AddOneShot方法进行处理。AddOneShot方法只是将当前DNS种子添加到vOneShots集合中。

2.1,CAddrMan:Add方法

下面介绍一下地址管理器的Add方法。这个方法位于addrman.h文件的第540行。

此方法的主体是一个for循环,它遍历CAD address集合并为每个CAD address对象调用Add_ method。并返回添加是否成功。比如代码下:

bool Add(const std::vector &vAddr, const CNetAddr& source, int64_t nTimePenalty = 0){ LOCK(cs); int nAdd = 0; Check(); for (std::vector::const_iterator it = vAddr.begin(); it != vAddr.end(); it++) nAdd += Add_(*it, source, nTimePenalty) ? 1 : 0; Check(); if (nAdd) { LogPrint(BCLog::ADDRMAN, "Added %i addresses from %s: %i tried, %i new\n", nAdd, source.ToString(), nTried, nNew); } return nAdd > 0;}

接下来,我们来看一下 Add_ 方法。这个方法在 addrman.cpp 文件的第254行。

如果当前地址是不可路由的,则直接返回假。 if (!addr.IsRoutable()) return false;调用 Find 方法,根据地址对象找到其对应的地址信息。 std::map::iterator it = mapAddr.find(addr); if (it == mapAddr.end()) return nullptr; if (pnId) *pnId = (*it).second; std::map::iterator it2 = mapInfo.find((*it).second); if (it2 != mapInfo.end()) return &(*it2).second; return nullptr;如果地址对象来源对象,设置变量 nTimePenalty 等于0。如果找到对应的地址信息,则设置地址信息的相关属性 bool fCurrentlyOnline = (GetAdjustedTime() - addr.nTime nTime || pinfo->nTime nTime = std::max((int64_t)0, addr.nTime - nTimePenalty); // add services pinfo->nServices = ServiceFlags(pinfo->nServices | addr.nServices); // do not update if no new information is present if (蓑衣网小编2022!addr.nTime || (pinfo->nTime && addr.nTime nTime)) return false; // do not update if the entry was already in the "tried" table if (pinfo->fInTried) return false; // do not update if the max reference count is reached if (pinfo->nRefCount == ADDRMAN_NEW_BUCKETS_PER_ADDRESS) return false; // stochastic test: previous nRefCount == N: 2^N times harder to increase it int nFactor = 1; for (int n = 0; n nRefCount; n++) nFactor *= 2; if (nFactor > 1 && (RandomInt(nFactor) != 0)) return false;如果没有找到对应的地址信息,则生成新的地址信息。 pinfo = Create(addr, source, &nId); pinfo->nTime = std::max((int64_t)0, (int64_t)pinfo->nTime - nTimePenalty); nNew++; fNew = true;

在 Create 方法中,生成一个新的 CAddrInfo 对象,并放到 mapInfo 集合中,同时在在 mapAddr 集合中增加对应的条目。具体代码如下:

int nId = nIdCount++; mapInfo[nId] = CAddrInfo(addr, addrSource); mapAddr[addr] = nId; 蓑衣网小编2022 mapInfo[nId].nRandomPos = vRandom.size(); vRandom.push_back(nId); if (pnId) *pnId = nId; return &mapInfo[nId];接下来处理其他一些信息,代码比较简单不详述。 int nUBucket = pinfo->GetNewBucket(nKey, source); int nUBucketPos = pinfo->GetBucketPosition(nKey, true, nUBucket); if (vvNew[nUBucket][nUBucketPos] != nId) { bool fInsert = vvNew[nUBucket][nUBucketPos] == -1; if (!fInsert) { CAddrInfo& infoExisting = mapInfo[vvNew[nUBucket][nUBucketPos]]; if (infoExisting.IsTerrible() || (infoExisting.nRefCount > 1 && pinfo->nRefCount == 0)) { // Overwrite the existing new table entry. fInsert = true; } } if (fInsert) { ClearNew(nUBucket, nUBucketPos); pinfo->nRefCount++; vvNew[nUBucket][nUBucketPos] = nId; } else { if (pinfo->nRefCount == 0) { 蓑衣网小编2022 Delete(nId); } } }返回真。

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

从零开始学习比特币(六):P2P网络建立的流程之查询DNS节点 | 分享给朋友: