兄弟連區塊鏈入門教程eth源碼分析p2p-udp.go源碼分析(一)
阿新 • • 發佈:2018-10-17
dsa 方法 nod nor 源碼 sid 通信 bili ip地址 兄弟連區塊鏈入門教程eth源碼分析p2p-udp.go源碼分析(一)
p2p的網絡發現協議使用了Kademlia protocol 來處理網絡的節點發現。節點查找和節點更新。Kademlia protocol使用了UDP協議來進行網絡通信。
閱讀這部分的代碼建議先看看references裏面的Kademlia協議簡介來看看什麽是Kademlia協議。
首先看看數據結構。 網絡傳輸了4種數據包(UDP協議是基於報文的協議。傳輸的是一個一個數據包),分別是
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源碼分析(一)