以太坊源码分析之三网络分析之三逻辑层消息机制


以太坊的网络通讯中封装了peer这个逻辑的概念,这样就有利于方便的进行数据的通信,
先看一个最简单的golang的网络通信:
服务端:
func main() {
    service := ":3456"
    tcpAddr, err := net.ResolveTCPAddr("ip4", service)
    checkError(err)
    listener, err := net.ListenTCP("tcp", tcpAddr)
    checkError(err)
    for {
        conn, err := listener.Accept()
        if err != nil {
            continue
        }
        daytime := time.LocalTime().String()
        conn.Write([]byte(daytime)) // don't care about return value
        conn.Close()                // we're finished with this client
    }
}
客户端:
func main()  {
    //net.dial 拨号 获取tcp连接
    conn, err := net.Dial("tcp", ":3456")
    if err != nil {
        fmt.Println(err)
        return
    }
    defer conn.Close()


    conn.Write([]byte("echo data to server ,then to client!!!"))
    //建立缓冲区
    buffer := make([]byte, 1024)
    //阻塞式读取数据。
    conn.Read(buffer)
    fmt.Println(string(buffer))
}
其实一个Accept返回的conn,就可以直接操作数据了,这里需要说明一下的是C++/c程序员可能对这个比较别扭,明明是一个SOCKET,为什么返回是一个Conn呢?不过还是那句话,人家这么定义是 有人家的目的的,不必特别强求。不过一开始确实还是挺误导c++程序员的,还真以为是从Conn直接操作数据呢。
以太坊中的通信(p2p目录下)有服务端Server.go,doal.go和rlpx.go三个文件来完成。相应的对应着服务端,客户端和加密链路,其实最终的通信仍然是落在peer.go这块代码上。
在某些IP地址不丰富的地方,大多数还是需要NAT打洞的技术的,当然uPnp也是一个重要的方式,这里不介绍这些具体的技术,如果感兴趣可以在网上搜索相关技术资料,很多。
以太坊的通信机制是隔离开真正的P2P通信,利用消息、通道和回调handle实现了一层逻辑意义上的通信。最终演化到peer这个逻辑概念上。
这样和一开始看到的网络通信机制,就有很大的区别了。逻辑上隔离的好处不用多说了,编程稍微多一些的都知道。这里就重点分析一下这部分。
Event.go已经被标记为Deprecated(不推荐),它实现了同一个进程内部的事件发布和订阅模式。推荐使用Event目录下的feed.go。后者的优势在于实现了1:N的多订阅模式,直接投递通道channel 来传递事件,在Send函数中,并不是直接阻塞发送而调用TrySend函数尝试异步发送,发送未成功者则在下面的Select中继续发送。它会映射到下面的三个消息结构当中的msgEventer结构中。
所以分析一下p2p/message.go
type Msg struct {
Code       uint64
Size       uint32 // size of the paylod
Payload    io.Reader
ReceivedAt time.Time
}




type MsgReader interface {
ReadMsg() (Msg, error)
}


type MsgWriter interface {
// WriteMsg sends a message. It will block until the message's
// Payload has been consumed by the other end.
//
// Note that messages can be sent only once because their
// payload reader is drained.
WriteMsg(Msg) error
}


// MsgReadWriter provides reading and writing of encoded messages.
// Implementations should ensure that ReadMsg and WriteMsg can be
// called simultaneously from multiple goroutines.
type MsgReadWriter interface {
MsgReader
MsgWriter
}


// Send writes an RLP-encoded message with the given code.
// data should encode as an RLP list.
func Send(w MsgWriter, msgcode uint64, data interface{}) error {
size, r, err := rlp.EncodeToReader(data)
if err != nil {
return err
}
return w.WriteMsg(Msg{Code: msgcode, Size: uint32(size), Payload: r})
}


// SendItems writes an RLP with the given code and data elements.
// For a call such as:
//
//    SendItems(w, code, e1, e2, e3)
//
// the message payload will be an RLP list containing the items:
//
//    [e1, e2, e3]
//
func SendItems(w MsgWriter, msgcode uint64, elems ...interface{}) error {
return Send(w, msgcode, elems)
}


// netWrapper wraps a MsgReadWriter with locks around
// ReadMsg/WriteMsg and applies read/write deadlines.
type netWrapper struct {
rmu, wmu sync.Mutex


rtimeout, wtimeout time.Duration
conn               net.Conn
wrapped            MsgReadWriter
}


func (rw *netWrapper) ReadMsg() (Msg, error) {
rw.rmu.Lock()
defer rw.rmu.Unlock()
rw.conn.SetReadDeadline(time.Now().Add(rw.rtimeout))
return rw.wrapped.ReadMsg()
}


func (rw *netWrapper) WriteMsg(msg Msg) error {
rw.wmu.Lock()
defer rw.wmu.Unlock()
rw.conn.SetWriteDeadline(time.Now().Add(rw.wtimeout))
return rw.wrapped.WriteMsg(msg)
}


// eofSignal wraps a reader with eof signaling. the eof channel is
// closed when the wrapped reader returns an error or when count bytes
// have been read.
type eofSignal struct {
wrapped io.Reader
count   uint32 // number of bytes left
eof     chan<- struct{}
}


// note: when using eofSignal to detect whether a message payload
// has been read, Read might not be called for zero sized messages.
func (r *eofSignal) Read(buf []byte) (int, error) {
if r.count == 0 {
if r.eof != nil {
r.eof <- struct{}{}
r.eof = nil
}
return 0, io.EOF
}


max := len(buf)
if int(r.count) < len(buf) {
max = int(r.count)
}
n, err := r.wrapped.Read(buf[:max])
r.count -= uint32(n)
if (err != nil || r.count == 0) && r.eof != nil {
r.eof <- struct{}{} // tell Peer that msg has been consumed
r.eof = nil
}
return n, err
}


// MsgPipe creates a message pipe. Reads on one end are matched
// with writes on the other. The pipe is full-duplex, both ends
// implement MsgReadWriter.
func MsgPipe() (*MsgPipeRW, *MsgPipeRW) {
var (
c1, c2  = make(chan Msg), make(chan Msg)
closing = make(chan struct{})
closed  = new(int32)
rw1     = &MsgPipeRW{c1, c2, closing, closed}
rw2     = &MsgPipeRW{c2, c1, closing, closed}
)
return rw1, rw2
}




// MsgPipeRW is an endpoint of a MsgReadWriter pipe.
type MsgPipeRW struct {
w       chan<- Msg
r       <-chan Msg
closing chan struct{}
closed  *int32
}


// WriteMsg sends a messsage on the pipe.
// It blocks until the receiver has consumed the message payload.
func (p *MsgPipeRW) WriteMsg(msg Msg) error {
if atomic.LoadInt32(p.closed) == 0 {
consumed := make(chan struct{}, 1)
msg.Payload = &eofSignal{msg.Payload, msg.Size, consumed}
select {
case p.w <- msg:
if msg.Size > 0 {
// wait for payload read or discard
select {
case <-consumed:
case <-p.closing:
}
}
return nil
case <-p.closing:
}
}
return ErrPipeClosed
}


// ReadMsg returns a message sent on the other end of the pipe.
func (p *MsgPipeRW) ReadMsg() (Msg, error) {
if atomic.LoadInt32(p.closed) == 0 {
select {
case msg := <-p.r:
return msg, nil
case <-p.closing:
}
}
return Msg{}, ErrPipeClosed
}




// ExpectMsg reads a message from r and verifies that its
// code and encoded RLP content match the provided values.
// If content is nil, the payload is discarded and not verified.
func ExpectMsg(r MsgReader, code uint64, content interface{}) error {
msg, err := r.ReadMsg()
if err != nil {
return err
}
if msg.Code != code {
return fmt.Errorf("message code mismatch: got %d, expected %d", msg.Code, code)
}
if content == nil {
return msg.Discard()
} else {
contentEnc, err := rlp.EncodeToBytes(content)
if err != nil {
panic("content encode error: " + err.Error())
}
if int(msg.Size) != len(contentEnc) {
return fmt.Errorf("message size mismatch: got %d, want %d", msg.Size, len(contentEnc))
}
actualContent, err := ioutil.ReadAll(msg.Payload)
if err != nil {
return err
}
if !bytes.Equal(actualContent, contentEnc) {
return fmt.Errorf("message payload mismatch:\ngot:  %x\nwant: %x", actualContent, contentEnc)
}
}
return nil
}


// msgEventer wraps a MsgReadWriter and sends events whenever a message is sent
// or received
type msgEventer struct {
MsgReadWriter


feed     *event.Feed
peerID   discover.NodeID
Protocol string
}


// newMsgEventer returns a msgEventer which sends message events to the given
// feed
func newMsgEventer(rw MsgReadWriter, feed *event.Feed, peerID discover.NodeID, proto string) *msgEventer {
return &msgEventer{
MsgReadWriter: rw,
feed:          feed,
peerID:        peerID,
Protocol:      proto,
}
}


// ReadMsg reads a message from the underlying MsgReadWriter and emits a
// "message received" event
func (self *msgEventer) ReadMsg() (Msg, error) {
msg, err := self.MsgReadWriter.ReadMsg()
if err != nil {
return msg, err
}
self.feed.Send(&PeerEvent{
Type:     PeerEventTypeMsgRecv,
Peer:     self.peerID,
Protocol: self.Protocol,
MsgCode:  &msg.Code,
MsgSize:  &msg.Size,
})
return msg, nil
}


// WriteMsg writes a message to the underlying MsgReadWriter and emits a
// "message sent" event
func (self *msgEventer) WriteMsg(msg Msg) error {
err := self.MsgReadWriter.WriteMsg(msg)
if err != nil {
return err
}
self.feed.Send(&PeerEvent{
Type:     PeerEventTypeMsgSend,
Peer:     self.peerID,
Protocol: self.Protocol,
MsgCode:  &msg.Code,
MsgSize:  &msg.Size,
})
return nil
}
从上面的代码来看,消息主要分为几类,主要包括管道MsgPipeRW、网络netWrapper以及事件msgEventer,当然消息首先要从Msg中编码或者解码而来数据。
这三种消息主要用来处理不同的内容,他们可能同时在P2P网络中运行,并通过通道唤醒一个协程来处理相关的数据。
举一个简单例子,管道通信基本就是用在进程间通信,减少相关的编程复杂度,实现多个以太坊的进程间应用的数据交互。

猜你喜欢

转载自blog.csdn.net/fpcc/article/details/80722185