您好,欢迎访问三七文档
当前位置:首页 > 金融/证券 > 投融资/租赁 > 兄弟连Go语言+区块链技术培训以太坊源码分析(50)p2p-udp.go源码分析
1/16兄弟连Go语言+区块链技术培训以太坊源码分析(50)p2p-udp.go源码分析p2p的网络发现协议使用了Kademliaprotocol来处理网络的节点发现。节点查找和节点更新。Kademliaprotocol使用了UDP协议来进行网络通信。阅读这部分的代码建议先看看references里面的Kademlia协议简介来看看什么是Kademlia协议。首先看看数据结构。网络传输了4种数据包(UDP协议是基于报文的协议。传输的是一个一个数据包),分别是ping,pong,findnode和neighbors。下面分别定义了4种报文的格式。//RPCpackettypesconst(pingPacket=iota+1//zerois'reserved'pongPacketfindnodePacketneighborsPacket)//RPCrequeststructurestype(pingstruct{Versionuint//协议版本From,TorpcEndpoint//源IP地址目的IP地址Expirationuint64//超时时间//Ignoreadditionalfields(forforwardcompatibility).//可以忽略的字段。为了向前兼容Rest[]rlp.RawValue`rlp:tail`}//pongisthereplytoping.//ping包的回应pongstruct{//ThisfieldshouldmirrortheUDPenvelopeaddress//ofthepingpacket,whichprovidesawaytodiscoverthe//theexternaladdress(afterNAT).//目的IP地址TorpcEndpoint2/16//说明这个pong包是回应那个ping包的。包含了ping包的hash值ReplyTok[]byte//Thiscontainsthehashofthepingpacket.//包超时的绝对时间。如果收到包的时候超过了这个时间,那么包被认为是超时的。Expirationuint64//Absolutetimestampatwhichthepacketbecomesinvalid.//Ignoreadditionalfields(forforwardcompatibility).Rest[]rlp.RawValue`rlp:tail`}//findnode是用来查询距离target比较近的节点//findnodeisaqueryfornodesclosetothegiventarget.findnodestruct{//目的节点TargetNodeID//doesn'tneedtobeanactualpublickeyExpirationuint64//Ignoreadditionalfields(forforwardcompatibility).Rest[]rlp.RawValue`rlp:tail`}//replytofindnode//findnode的回应neighborsstruct{//距离target比较近的节点值。Nodes[]rpcNodeExpirationuint64//Ignoreadditionalfields(forforwardcompatibility).Rest[]rlp.RawValue`rlp:tail`}rpcNodestruct{IPnet.IP//len4forIPv4or16forIPv6UDPuint16//fordiscoveryprotocolTCPuint16//forRLPxprotocolIDNodeID}rpcEndpointstruct{IPnet.IP//len4forIPv4or16forIPv6UDPuint16//fordiscoveryprotocolTCPuint16//forRLPxprotocol})3/16定义了两个接口类型,packet接口类型应该是给4种不同类型的包分派不同的handle方法。conn接口定义了一个udp的连接的功能。typepacketinterface{handle(t*udp,from*net.UDPAddr,fromIDNodeID,mac[]byte)errorname()string}typeconninterface{ReadFromUDP(b[]byte)(nint,addr*net.UDPAddr,errerror)WriteToUDP(b[]byte,addr*net.UDPAddr)(nint,errerror)Close()errorLocalAddr()net.Addr}udp的结构,需要注意的是最后一个字段*Table是go里面的匿名字段。也就是说udp可以直接调用匿名字段Table的方法。//udpimplementstheRPCprotocol.typeudpstruct{connconn//网络连接netrestrict*netutil.Netlistpriv*ecdsa.PrivateKey//私钥,自己的ID是通过这个来生成的。ourEndpointrpcEndpointaddpendingchan*pending//用来申请一个pendinggotreplychanreply//用来获取回应的队列closingchanstruct{}//用来关闭的队列natnat.Interface*Table}pending和reply结构。这两个结构用户内部的goroutine之间进行通信的结构体。4/16//pendingrepresentsapendingreply.//someimplementationsoftheprotocolwishtosendmorethanone//replypackettofindnode.ingeneral,anyneighborspacketcannot//bematchedupwithaspecificfindnodepacket.//ourimplementationhandlesthisbystoringacallbackfunctionfor//eachpendingreply.incomingpacketsfromanodearedispatched//toallthecallbackfunctionsforthatnode.//pending结构代表正在等待一个reply//我们通过为每一个pendingreply存储一个callback来实现这个功能。从一个节点来的所有数据包都会分配到这个节点对应的callback上面。typependingstruct{//thesefieldsmustmatchinthereply.fromNodeIDptypebyte//timewhentherequestmustcompletedeadlinetime.Time//callbackiscalledwhenamatchingreplyarrives.ifitreturns//true,thecallbackisremovedfromthependingreplyqueue.//ifitreturnsfalse,thereplyisconsideredincompleteand//thecallbackwillbeinvokedagainforthenextmatchingreply.//如果返回值是true。那么callback会从队列里面移除。如果返回false,那么认为reply还没有完成,会继续等待下一次reply.callbackfunc(respinterface{})(donebool)//errcreceivesnilwhenthecallbackindicatescompletionoran//errorifnofurtherreplyisreceivedwithinthetimeout.errcchan-error}typereplystruct{fromNodeIDptypebytedatainterface{}//loopindicateswhethertherewas//amatchingrequestbysendingonthischannel.//通过往这个channel上面发送消息来表示匹配到一个请求。5/16matchedchan-bool}UDP的创建//ListenUDPreturnsanewtablethatlistensforUDPpacketsonladdr.funcListenUDP(priv*ecdsa.PrivateKey,laddrstring,natmnat.Interface,nodeDBPathstring,netrestrict*netutil.Netlist)(*Table,error){addr,err:=net.ResolveUDPAddr(udp,laddr)iferr!=nil{returnnil,err}conn,err:=net.ListenUDP(udp,addr)iferr!=nil{returnnil,err}tab,_,err:=newUDP(priv,conn,natm,nodeDBPath,netrestrict)iferr!=nil{returnnil,err}log.Info(UDPlistenerup,self,tab.self)returntab,nil}funcnewUDP(priv*ecdsa.PrivateKey,cconn,natmnat.Interface,nodeDBPathstring,netrestrict*netutil.Netlist)(*Table,*udp,error){udp:=&udp{conn:c,priv:priv,netrestrict:netrestrict,closing:make(chanstruct{}),gotreply:make(chanreply),addpending:make(chan*pending),}realaddr:=c.LocalAddr().(*net.UDPAddr)ifnatm!=nil{//natmnatmapping用来获取外网地址if!realaddr.IP.IsLoopback(){//如果地址是本地环回地址gonat.Map(natm,udp.closing,udp,realaddr.Port,realaddr.Port,ethereumdiscovery)}6/16//TODO:reacttoexternalIPchangesovertime.ifext,err:=natm.ExternalIP();err==nil{realaddr=&net.UDPAddr{IP:ext,Port:realaddr.Port}}}//TODO:separateTCPportudp.ourEndpoint=makeEndpoint(realaddr,uint16(realaddr.Port))//创建一个table后续会介绍。Kademlia的主要逻辑在这个类里面实现。tab,err:=newTable(udp,PubkeyID(&priv.PublicKey),realaddr,nodeDBPath)iferr!=nil{returnnil,nil,err}udp.Table=tab//匿名字段的赋值goudp.loop()//goroutinegoudp.readLoop()//用来网络数据读取。returnudp.Table,udp,nil}ping方法与pending的处理,之前谈到了pending是等待一个reply。这里通过代码来分析是如何实现等待reply的。pending方法把pending结构体发送给addpending.然后等待消息的处理和接收。//pings
本文标题:兄弟连Go语言+区块链技术培训以太坊源码分析(50)p2p-udp.go源码分析
链接地址:https://www.777doc.com/doc-4107418 .html