標籤:dispatch contain app out cat private mat for repr
兄弟連區塊鏈入門教程eth源碼分析p2p-udp.go源碼分析(一)
p2p的網路發現協議使用了Kademlia protocol 來處理網路的節點發現。節點尋找和節點更新。Kademlia protocol使用了UDP協議來進行網路通訊。
閱讀這部分的代碼建議先看看references裡面的Kademlia協議簡介來看看什麼是Kademlia協議。
首先看看資料結構。 網路傳輸了4種資料包(UDP協議是基於報文的協議。傳輸的是一個一個資料包),分別是
ping,pong,findnode和neighbors。 下面分別定義了4種報文的格式。 // RPC packet types const ( pingPacket = iota + 1 // zero is ‘reserved‘ pongPacket findnodePacket neighborsPacket ) // RPC request structures type ( ping struct { Version uint //協議版本 From, To rpcEndpoint //源IP地址 目的IP地址 Expiration uint64 //逾時時間 // Ignore additional fields (for forward compatibility). //可以忽略的欄位。 為了向前相容 Rest []rlp.RawValue `rlp:"tail"` }`` // pong is the reply to ping. // ping包的回應 pong struct { // This field should mirror the UDP envelope address // of the ping packet, which provides a way to discover the // the external address (after NAT). // 目的IP地址 To rpcEndpoint // 說明這個pong包是回應那個ping包的。 包含了ping包的hash值 ReplyTok []byte // This contains the hash of the ping packet. //包逾時的絕對時間。 如果收到包的時候超過了這個時間,那麼包被認為是逾時的。 Expiration uint64 // Absolute timestamp at which the packet becomes invalid. // Ignore additional fields (for forward compatibility). Rest []rlp.RawValue `rlp:"tail"` } // findnode 是用來查詢距離target比較近的節點 // findnode is a query for nodes close to the given target. findnode struct { // 目的節點 Target NodeID // doesn‘t need to be an actual public key Expiration uint64 // Ignore additional fields (for forward compatibility). Rest []rlp.RawValue `rlp:"tail"` } // reply to findnode // findnode的回應 neighbors struct { //距離target比較近的節點值。 Nodes []rpcNode Expiration uint64 // Ignore additional fields (for forward compatibility). Rest []rlp.RawValue `rlp:"tail"` } rpcNode struct { IP net.IP // len 4 for IPv4 or 16 for IPv6 UDP uint16 // for discovery protocol TCP uint16 // for RLPx protocol ID NodeID } rpcEndpoint struct { IP net.IP // len 4 for IPv4 or 16 for IPv6 UDP uint16 // for discovery protocol TCP uint16 // for RLPx protocol } )定義了兩個介面類型,packet介面類型應該是給4種不同類型的包指派不同的handle方法。 conn介面定義了一個udp的串連的功能。 type packet interface { handle(t *udp, from *net.UDPAddr, fromID NodeID, mac []byte) error name() string } type conn interface { ReadFromUDP(b []byte) (n int, addr *net.UDPAddr, err error) WriteToUDP(b []byte, addr *net.UDPAddr) (n int, err error) Close() error LocalAddr() net.Addr }udp的結構, 需要注意的是最後一個欄位*Table是go裡面的匿名欄位。 也就是說udp可以直接調用匿名欄位Table的方法。 // udp implements the RPC protocol. type udp struct { conn conn //網路連接 netrestrict *netutil.Netlist priv *ecdsa.PrivateKey //私密金鑰,自己的ID是通過這個來產生的。 ourEndpoint rpcEndpoint addpending chan *pending //用來申請一個pending gotreply chan reply //用來擷取回應的隊列 closing chan struct{} //用來關閉的隊列 nat nat.Interface *Table }pending 和reply 結構。 這兩個結構使用者內部的go routine之間進行通訊的結構體。 // pending represents a pending reply. // some implementations of the protocol wish to send more than one // reply packet to findnode. in general, any neighbors packet cannot // be matched up with a specific findnode packet. // our implementation handles this by storing a callback function for // each pending reply. incoming packets from a node are dispatched // to all the callback functions for that node. // pending結構 代表正在等待一個reply // 我們通過為每一個pending reply 儲存一個callback來實現這個功能。從一個節點來的所有資料包都會分配到這個節點對應的callback上面。 type pending struct { // these fields must match in the reply. from NodeID ptype byte // time when the request must complete deadline time.Time // callback is called when a matching reply arrives. if it returns // true, the callback is removed from the pending reply queue. // if it returns false, the reply is considered incomplete and // the callback will be invoked again for the next matching reply. //如果傳回值是true。那麼callback會從隊列裡面移除。 如果返回false,那麼認為reply還沒有完成,會繼續等待下一次reply. callback func(resp interface{}) (done bool) // errc receives nil when the callback indicates completion or an // error if no further reply is received within the timeout. errc chan<- error } type reply struct { from NodeID ptype byte data interface{} // loop indicates whether there was // a matching request by sending on this channel. //通過往這個channel上面發送訊息來表示匹配到一個請求。 matched chan<- bool }UDP的建立 // ListenUDP returns a new table that listens for UDP packets on laddr. func ListenUDP(priv *ecdsa.PrivateKey, laddr string, natm nat.Interface, nodeDBPath string, netrestrict *netutil.Netlist) (*Table, error) { addr, err := net.ResolveUDPAddr("udp", laddr) if err != nil { return nil, err } conn, err := net.ListenUDP("udp", addr) if err != nil { return nil, err } tab, _, err := newUDP(priv, conn, natm, nodeDBPath, netrestrict) if err != nil { return nil, err } log.Info("UDP listener up", "self", tab.self) return tab, nil } func newUDP(priv *ecdsa.PrivateKey, c conn, natm nat.Interface, nodeDBPath string, netrestrict *netutil.Netlist) (*Table, *udp, error) { udp := &udp{ conn: c, priv: priv, netrestrict: netrestrict, closing: make(chan struct{}), gotreply: make(chan reply), addpending: make(chan *pending), } realaddr := c.LocalAddr().(*net.UDPAddr) if natm != nil { //natm nat mapping 用來擷取外網地址 if !realaddr.IP.IsLoopback() { //如果地址是本地迴路位址 go nat.Map(natm, udp.closing, "udp", realaddr.Port, realaddr.Port, "ethereum discovery") } // TODO: react to external IP changes over time. if ext, err := natm.ExternalIP(); err == nil { realaddr = &net.UDPAddr{IP: ext, Port: realaddr.Port} } } // TODO: separate TCP port udp.ourEndpoint = makeEndpoint(realaddr, uint16(realaddr.Port)) //建立一個table 後續會介紹。 Kademlia的主要邏輯在這個類裡面實現。 tab, err := newTable(udp, PubkeyID(&priv.PublicKey), realaddr, nodeDBPath) if err != nil { return nil, nil, err } udp.Table = tab //匿名欄位的賦值 go udp.loop() //go routine go udp.readLoop() //用來網路資料讀取。 return udp.Table, udp, nil }
兄弟連區塊鏈入門教程eth源碼分析p2p-udp.go源碼分析(一)