以太坊原始碼分析(46)p2p-peer.go原始碼分析

尹成發表於2018-05-14

nat是網路地址轉換的意思。 這部分的原始碼比較獨立而且單一,這裡就暫時不分析了。 大家瞭解基本的功能就行了。

nat下面有upnp和pmp兩種網路協議。

### upnp的應用場景(pmp是和upnp類似的協議)

如果使用者是通過NAT接入Internet的,同時需要使用BC、電騾eMule等P2P這樣的軟體,這時UPnP功能就會帶來很大的便利。利用UPnP能自動的把BC、電騾eMule等偵聽的埠號對映到公網上,以便公網上的使用者也能對NAT私網側發起連線。


主要功能就是提供介面可以把內網的IP+埠 對映為 路由器的IP+埠。 這樣就等於內網的程式有了外網的IP地址, 這樣公網的使用者就可以直接對你進行訪問了。 不然就需要通過UDP打洞這種方式來進行訪問。



### p2p中的UDP協議

現在大部分使用者執行的環境都是內網環境。 內網環境下監聽的埠,其他公網的程式是無法直接訪問的。需要經過一個打洞的過程。 雙方才能聯通。這就是所謂的UDP打洞。

在p2p程式碼裡面。 peer代表了一條建立好的網路鏈路。在一條鏈路上可能執行著多個協議。比如以太坊的協議(eth)。 Swarm的協議。 或者是Whisper的協議。

peer的結構

    type protoRW struct {
        Protocol
        in chan Msg // receices read messages
        closed <-chan struct{} // receives when peer is shutting down
        wstart <-chan struct{} // receives when write may start
        werr chan<- error // for write results
        offset uint64
        w MsgWriter
    }

    // Protocol represents a P2P subprotocol implementation.
    type Protocol struct {
        // Name should contain the official protocol name,
        // often a three-letter word.
        Name string
    
        // Version should contain the version number of the protocol.
        Version uint
    
        // Length should contain the number of message codes used
        // by the protocol.
        Length uint64
    
        // Run is called in a new groutine when the protocol has been
        // negotiated with a peer. It should read and write messages from
        // rw. The Payload for each message must be fully consumed.
        //
        // The peer connection is closed when Start returns. It should return
        // any protocol-level error (such as an I/O error) that is
        // encountered.
        Run func(peer *Peer, rw MsgReadWriter) error
    
        // NodeInfo is an optional helper method to retrieve protocol specific metadata
        // about the host node.
        NodeInfo func() interface{}
    
        // PeerInfo is an optional helper method to retrieve protocol specific metadata
        // about a certain peer in the network. If an info retrieval function is set,
        // but returns nil, it is assumed that the protocol handshake is still running.
        PeerInfo func(id discover.NodeID) interface{}
    }

    // Peer represents a connected remote node.
    type Peer struct {
        rw *conn
        running map[string]*protoRW //執行的協議
        log log.Logger
        created mclock.AbsTime
    
        wg sync.WaitGroup
        protoErr chan error
        closed chan struct{}
        disc chan DiscReason
    
        // events receives message send / receive events if set
        events *event.Feed
    }

peer的建立,根據匹配找到當前Peer支援的protomap

    func newPeer(conn *conn, protocols []Protocol) *Peer {
        protomap := matchProtocols(protocols, conn.caps, conn)
        p := &Peer{
            rw: conn,
            running: protomap,
            created: mclock.Now(),
            disc: make(chan DiscReason),
            protoErr: make(chan error, len(protomap)+1), // protocols + pingLoop
            closed: make(chan struct{}),
            log: log.New("id", conn.id, "conn", conn.flags),
        }
        return p
    }

peer的啟動, 啟動了兩個goroutine執行緒。 一個是讀取。一個是執行ping操作。

    func (p *Peer) run() (remoteRequested bool, err error) {
        var (
            writeStart = make(chan struct{}, 1) //用來控制什麼時候可以寫入的管道。
            writeErr = make(chan error, 1)
            readErr = make(chan error, 1)
            reason DiscReason // sent to the peer
        )
        p.wg.Add(2)
        go p.readLoop(readErr)
        go p.pingLoop()
    
        // Start all protocol handlers.
        writeStart <- struct{}{}
        //啟動所有的協議。
        p.startProtocols(writeStart, writeErr)
    
        // Wait for an error or disconnect.
    loop:
        for {
            select {
            case err = <-writeErr:
                // A write finished. Allow the next write to start if
                // there was no error.
                if err != nil {
                    reason = DiscNetworkError
                    break loop
                }
                writeStart <- struct{}{}
            case err = <-readErr:
                if r, ok := err.(DiscReason); ok {
                    remoteRequested = true
                    reason = r
                } else {
                    reason = DiscNetworkError
                }
                break loop
            case err = <-p.protoErr:
                reason = discReasonForError(err)
                break loop
            case err = <-p.disc:
                break loop
            }
        }
    
        close(p.closed)
        p.rw.close(reason)
        p.wg.Wait()
        return remoteRequested, err
    }

startProtocols方法,這個方法遍歷所有的協議。

    func (p *Peer) startProtocols(writeStart <-chan struct{}, writeErr chan<- error) {
        p.wg.Add(len(p.running))
        for _, proto := range p.running {
            proto := proto
            proto.closed = p.closed
            proto.wstart = writeStart
            proto.werr = writeErr
            var rw MsgReadWriter = proto
            if p.events != nil {
                rw = newMsgEventer(rw, p.events, p.ID(), proto.Name)
            }
            p.log.Trace(fmt.Sprintf("Starting protocol %s/%d", proto.Name, proto.Version))
            // 等於這裡為每一個協議都開啟了一個goroutine。 呼叫其Run方法。
            go func() {
                // proto.Run(p, rw)這個方法應該是一個死迴圈。 如果返回就說明遇到了錯誤。
                err := proto.Run(p, rw)
                if err == nil {
                    p.log.Trace(fmt.Sprintf("Protocol %s/%d returned", proto.Name, proto.Version))
                    err = errProtocolReturned
                } else if err != io.EOF {
                    p.log.Trace(fmt.Sprintf("Protocol %s/%d failed", proto.Name, proto.Version), "err", err)
                }
                p.protoErr <- err
                p.wg.Done()
            }()
        }
    }


回過頭來再看看readLoop方法。 這個方法也是一個死迴圈。 呼叫p.rw來讀取一個Msg(這個rw實際是之前提到的frameRLPx的物件,也就是分幀之後的物件。然後根據Msg的型別進行對應的處理,如果Msg的型別是內部執行的協議的型別。那麼傳送到對應協議的proto.in佇列上面。

    
    func (p *Peer) readLoop(errc chan<- error) {
        defer p.wg.Done()
        for {
            msg, err := p.rw.ReadMsg()
            if err != nil {
                errc <- err
                return
            }
            msg.ReceivedAt = time.Now()
            if err = p.handle(msg); err != nil {
                errc <- err
                return
            }
        }
    }
    func (p *Peer) handle(msg Msg) error {
        switch {
        case msg.Code == pingMsg:
            msg.Discard()
            go SendItems(p.rw, pongMsg)
        case msg.Code == discMsg:
            var reason [1]DiscReason
            // This is the last message. We don't need to discard or
            // check errors because, the connection will be closed after it.
            rlp.Decode(msg.Payload, &reason)
            return reason[0]
        case msg.Code < baseProtocolLength:
            // ignore other base protocol messages
            return msg.Discard()
        default:
            // it's a subprotocol message
            proto, err := p.getProto(msg.Code)
            if err != nil {
                return fmt.Errorf("msg code out of range: %v", msg.Code)
            }
            select {
            case proto.in <- msg:
                return nil
            case <-p.closed:
                return io.EOF
            }
        }
        return nil
    }

在看看pingLoop。這個方法很簡單。就是定時的傳送pingMsg訊息到對端。
    
    func (p *Peer) pingLoop() {
        ping := time.NewTimer(pingInterval)
        defer p.wg.Done()
        defer ping.Stop()
        for {
            select {
            case <-ping.C:
                if err := SendItems(p.rw, pingMsg); err != nil {
                    p.protoErr <- err
                    return
                }
                ping.Reset(pingInterval)
            case <-p.closed:
                return
            }
        }
    }

最後再看看protoRW的read和write方法。 可以看到讀取和寫入都是阻塞式的。
    
    func (rw *protoRW) WriteMsg(msg Msg) (err error) {
        if msg.Code >= rw.Length {
            return newPeerError(errInvalidMsgCode, "not handled")
        }
        msg.Code += rw.offset
        select {
        case <-rw.wstart: //等到可以寫入的受在執行寫入。 這難道是為了多執行緒控制麼。
            err = rw.w.WriteMsg(msg)
            // Report write status back to Peer.run. It will initiate
            // shutdown if the error is non-nil and unblock the next write
            // otherwise. The calling protocol code should exit for errors
            // as well but we don't want to rely on that.
            rw.werr <- err
        case <-rw.closed:
            err = fmt.Errorf("shutting down")
        }
        return err
    }
    
    func (rw *protoRW) ReadMsg() (Msg, error) {
        select {
        case msg := <-rw.in:
            msg.Code -= rw.offset
            return msg, nil
        case <-rw.closed:
            return Msg{}, io.EOF
        }
    }

相關文章