以太坊p2p网络(六):以太坊peer的连接和消息处理

当TCP连接池跟一个节点进行rlpx握手,建立加密连接后,接下来会触发server.run 函数的addpeer管道传递消息,在server.run()函数里面捕获这个消息,进而创建一个peer实例,创建一个协程单独处理这个节点,进行业务层面的后续处理。

func (srv *Server) run(dialstate dialer) {
//连接池管理协程,负责维护TCP连接列表
    //在协程里面运行, 开始监听各个信号量,处理peer的增删改

	...
case c := <-srv.addpeer:
		err := srv.protoHandshakeChecks(peers, inboundCount, c)
		if err == nil {
			p := newPeer(c, srv.Protocols)
			if srv.EnableMsgEvents {
				p.events = &srv.peerFeed
			}
			name := truncateName(c.name)
			srv.log.Debug("Adding p2p peer", "name", name, "addr", c.fd.RemoteAddr(), "peers", len(peers)+1)
			//创建协程 去处理,增加这个peer
			go srv.runPeer(p)
			peers[c.id] = p
			if p.Inbound() {
				inboundCount++
			}
		}

建立连接握手完成以后,调用newPeer()函数新建一个peer,新建peer的时候,通过matchProtocols函数找出匹配的子协议。

连接建立以后,通过 srv.runPeer来处理连接消息。

func (srv *Server) runPeer(p *Peer) {
	//个peer连接建立完成后,调用这里来启动一个peer的维护,监听工作

	if srv.newPeerHook != nil {
		srv.newPeerHook(p)
	}

	// broadcast peer add
	srv.peerFeed.Send(&PeerEvent{
		Type: PeerEventTypeAdd,
		Peer: p.ID(),
	})

	// peer处理函数,启动,开始进行消息读取,以及协议方面的处理,通知对应的协议层,增加了一个连接了
	remoteRequested, err := p.run()

	// broadcast peer drop
	srv.peerFeed.Send(&PeerEvent{
		Type:  PeerEventTypeDrop,
		Peer:  p.ID(),
		Error: err.Error(),
	})

	srv.delpeer <- peerDrop{p, err, remoteRequested}
}

peer处理函数,启动,开始进行消息读取,以及协议方面的处理,通知对应的协议层,增加了一个连接了。Peer.run函数承担了启动对一个节点的读写循环。
当P2P模块对一个节点握手协议处理完后,addpeer队列接收到,然后调用go srv.runPeer§ 创建协程,最后调用这里, 在协里面维护本节点对于其他节点的链接。怎么维护呢:建立一个事件读写协程,以及管理协程。

func (p *Peer) run() (remoteRequested bool, err error) {
	//当P2P模块对一个节点握手协议处理完后,addpeer队列接收到,然后调用srv.runPeer(p) 创建协程,最后调用这里。                  
    //在协里面维护本节点对于其他节点的链接。怎么维护呢:建立一个事件读写协程,以及管理协程。
	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)

p.readLoop()协程主要不断的进行消息的读取:

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
		}
	}
}

p.rw.ReadMsg()是调用rlpx.go里面的ReadMsg()来进行消息的读取。
读取消息之后,通过handle()函数来进行消息的不同类型的相应的处理。

func (p *Peer) handle(msg Msg) error {
//根据消息Code处理,简单消息直接处理,应用层消息放入对应的协议的in管道里面
	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:
		//通过偏移量offset和长度Length取出对应协议对应msg.Code
		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
}

P2P.Peer模块完成了对一个刚刚建立rlpx加密协议的链接的数据读取逻辑,最后将读取到的消息通过proto.in <- msg; 放入管道里,交给对应的协议的代码逻辑进行处理。

每个子协议启动之后都会调用p.rw.ReadMsg()阻塞等待消息,而ReadMsg()实际读取为:

p2p/peer.go中的ReadMsg()函数

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
	}
}

那子协议是如果启动的呢?上面在Peer.run()函数中,除了需要通过协程readLoop()接受消息,还建立了协程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))
		go func() {
		//调用协议对应的Run()函数
			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()
		}()
	}
}

每个子协议都有Run()函数,通过Run()函数在阻塞的读取数据。

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值