以太坊p2p网络子协议开发框架运用

以太坊的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: []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() 是一个工具方法:取得 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,
		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
}

阅读原文

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值