以太坊的网络通讯中封装了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网络中运行,并通过通道唤醒一个协程来处理相关的数据。
举一个简单例子,管道通信基本就是用在进程间通信,减少相关的编程复杂度,实现多个以太坊的进程间应用的数据交互。