你不知道的Ethereum(1)Peer to Peer

以太坊的peer to peer (go-ethereum/p2p)模塊可以讓你便捷地在p2p網絡上開發任何應用。這個p2p 包採用現代化的模塊設計,可以很容易地在其之上擴展本身的額外通訊協議。node

開始一個p2p服務須要你先從構造一個p2p.Server{}實例開始:git

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{}中傳入一個子協議:github

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,
        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指針

未經容許不得轉載。code

相關文章
相關標籤/搜索