1. 程式人生 > >兄弟連區塊鏈入門教程eth源碼分析p2p-udp.go源碼分析(一)

兄弟連區塊鏈入門教程eth源碼分析p2p-udp.go源碼分析(一)

dsa 方法 nod nor 源碼 sid 通信 bili ip地址

兄弟連區塊鏈入門教程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源碼分析(一)