以太坊的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.
}
}
- 一个子协议对象被称为
Protocol{}
每次一个能够处理该协议的Peer 发起连接时会用到该对象; - 这个服务在网络上发布的名称;
- 这个协议的版本;
- 这个协议需要依赖的信息数目,因为p2p网络是可扩展的,因此其需要具有能够发送随意个数的信息的能力(需要携带type,在下文中我们能够看到说明),p2p的handler需要知道应该预留多少空间以用来服务你的协议。这是也是共识信息能够通过message ID到达各个peer并实现协商的保障。我们的协议仅仅支持一个
message
(详见下文); - 在你的协议主要的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
}
- 其中有且唯一的已知信息ID;
- 将Messages alias 为string类型;
-
ReadMsg
将一直阻塞等待,直到其收到了一条新的信息,一个错误或者EOF
; - 如果在读取流信息的过程当中收到了一个错误,最好的解决实践是将其返回给p2p server进行处理。这种错误通常是对端节点已经断开连接;
-
msg
包括两个属性和一个decode方法-
Code
包括了信息ID,Code == messageId
(i.e.0) -
Payload
是信息的内容 -
Decode(<ptr>)
是一个工具方法:取得msg.Payload
并将其解码,并将其内容设置到传入的message指针中,如果失败了则返回一个error
-
- 如果解码出来的信息是
foo
将发回一个NewMessage
并用messageId
标记信息类型,信息内容是bar
;而bar
信息在被对端收到之后将被default
case捕获。
现在,我们将上述的所有部分整合起来,得到下面的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,
PrivateKey: nodekey,
Name: "my node name",
ListenAddr: ":30300",
Protocols: []p2p.Protocol{MyProtocol()},
}
if err := srv.Start(); err != nil {
fmt.Println(err)
os.Exit(1)
}
select {}
}
func msgHandler(peer *p2p.Peer, ws p2p.MsgReadWriter) error {
for {
msg, 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, messageId, "bar"))
if err != nil {
return err
}
default:
fmt.Println("recv:", myMessage)
}
}
return nil
}
原文: Peer to Peer
未经允许不得转载。
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。