微信公众号搜"智元新知"关注
微信扫一扫可直接关注哦!

你不知道的Ethereum1Peer to Peer

以太坊的peer to peer (go-ethereum/p2p)模块能够让你便捷地在p2p网络上开发任何应用。这个p2p 包采用现代化的模块设计,能够很容易地在其之上扩展自己的额外通信协议。

开始一个p2p服务需要你先从构造一个p2p.Server{}实例开始:

import "github.com/ethereum/go-ethereum/crypto"
import "github.com/ethereum/go-ethereum/p2p"

nodekey,_ := crypto.GenerateKey()
srv := p2p.Server{
    MaxPeers:   10,PrivateKey: nodekey,Name:       "my node name",ListenAddr: ":30300",Protocols:  []p2p.Protocol{},}
srv.Start()

如果你想要在该网络上扩展自己的协议,你需要在p2p.Protocol{}中传入一个子协议:

func MyProtocol() p2p.Protocol {
    return p2p.Protocol{ // 1.
        Name:    "MyProtocol",// 2.
        Version: 1,// 3.
        Length:  1,// 4.
        Run:     func(peer *p2p.Peer,ws p2p.MsgReadWriter) error { return nil },// 5.
    }
}
  1. 一个子协议对象被称为 Protocol{} 每次一个能够处理该协议的Peer 发起连接时会用到该对象;
  2. 这个服务在网络上发布的名称
  3. 这个协议的版本;
  4. 这个协议需要依赖的信息数目,因为p2p网络是可扩展的,因此其需要具有能够发送随意个数的信息的能力(需要携带type,在下文中我们能够看到说明),p2p的handler需要知道应该预留多少空间以用来服务你的协议。这是也是共识信息能够通过message ID到达各个peer并实现协商的保障。我们的协议仅仅支持一个message(详见下文);
  5. 在你的协议主要的handler中,我们现在故意将其留空。这个peer变量是指代连接到当前节点,其携带了一些peer本身的信息。其ws变量是reader和writer允许你同该peer进行通信,如果信息能够发送到当前节点,则反之也能够从本节点发送到对端peer节点。

现在让我们将前面留空的handler代码实现,以让它能够同别的peer通信:

const messageId = 0   // 1.
type Message string   // 2.

func msgHandler(peer *p2p.Peer,ws p2p.MsgReadWriter) error {
    for {
        msg,err := ws.ReadMsg()   // 3.
        if err != nil {            // 4.
            return err // if reading fails return err which will disconnect the peer.
        }

        var myMessage [1]Message
        err = msg.Decode(&myMessage) // 5.
        if err != nil {
            // handle decode error
            continue
        }
        
        switch myMessage[0] {
        case "foo":
            err := p2p.SendItems(ws,messageId,"bar")  // 6.
            if err != nil {
                return err // return (and disconnect) error if writing fails.
            }
         default:
             fmt.Println("recv:",myMessage)
         }
    }

    return nil
}
  1. 其中有且唯一的已知信息ID;
  2. 将Messages alias 为string类型;
  3. ReadMsg将一直阻塞等待,直到其收到了一条新的信息,一个错误或者EOF
  4. 如果在读取流信息的过程当中收到了一个错误,最好的解决实践是将其返回给p2p server进行处理。这种错误通常是对端节点已经断开连接;
  5. msg包括两个属性一个decode方法

    1. Code 包括了信息ID,Code == messageId (i.e.0)
    2. Payload 是信息的内容
    3. Decode(<ptr>)一个工具方法:取得 msg.Payload并将其解码,并将其内容设置到传入的message指针中,如果失败了则返回一个error
  6. 如果解码出来的信息是foo将发回一个NewMessage并用messageId标记信息类型,信息内容bar;而bar信息在被对端收到之后将被defaultcase捕获。

现在,我们将上述的所有部分整合起来,得到下面的p2p样例代码

package main

import (
    "fmt"
    "os"

    "github.com/ethereum/go-ethereum/crypto"
    "github.com/ethereum/go-ethereum/p2p"
)

const messageId = 0

type Message string

func MyProtocol() p2p.Protocol {
    return p2p.Protocol{
        Name:    "MyProtocol",Version: 1,Length:  1,Run:     msgHandler,}
}

func main() {
    nodekey,_ := crypto.GenerateKey()
    srv := p2p.Server{
        MaxPeers:   10,Protocols:  []p2p.Protocol{MyProtocol()},}

    if err := srv.Start(); err != nil {
        fmt.Println(err)
        os.Exit(1)
    }

    select {}
}

func msgHandler(peer *p2p.Peer,err := ws.ReadMsg()
        if err != nil {
            return err
        }

        var myMessage Message
        err = msg.Decode(&myMessage)
        if err != nil {
            // handle decode error
            continue
        }

        switch myMessage {
        case "foo":
            err := p2p.SendItems(ws,"bar"))
            if err != nil {
                return err
            }
        default:
            fmt.Println("recv:",myMessage)
        }
    }

    return nil
}

原文: Peer to Peer

未经允许不得转载。

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。

相关推荐