基於go-ethereum/p2p模組的聊天程式
阿新 • • 發佈:2019-02-06
以太坊的p2p模組實現了一個p2p分散式網路,是實現以太坊分散式錢包的關鍵技術。p2p模組的說明見官方github的wiki。本文要實現的是使用以太坊的p2p模組來實現一個簡單的聊天程式。
1 P2P基本原理
2 編譯並啟動以太坊的bootnode
bootnode節點可以作為p2p網路的路由節點。聊天程式中的倆個p2p節點將以該bootnode作為路由。
在ubuntu環境下搭建go語言編譯環境,去https://github.com/ethereum/go-ethereum下載以太坊原始碼,
sudo git https://github.com/ethereum/go-ethereum
將下載以太坊原始碼到當前目錄下,下載完成後,當前目錄將出現go-ethereum文錄。進入該目錄,使用sudo make all將在go-ethereum/build/bin目錄下生成bootnode可執行檔案,將該檔案拷貝到一個資料夾下:
sudo cp bootnode ~/p2ptest/
進入p2ptest目錄:
生成key:
啟動bootnode:
注意,需要將enode字串中將@後面的[::]改成ubuntu的IP地址。
3 p2p聊天程式
package main import ( "bufio" "fmt" "log" "os" "sync" "github.com/ethereum/go-ethereum/crypto" "github.com/ethereum/go-ethereum/p2p" "github.com/ethereum/go-ethereum/p2p/discover" "gopkg.in/urfave/cli.v1" ) var ( port int bootnode string ) const ( msgTalk = 0 msgLength = iota ) func main() { app := cli.NewApp() app.Usage = "p2p package demo" app.Action = startP2pNode app.Flags = []cli.Flag{ //命令列解析得到的port cli.IntFlag{Name: "port", Value: 11200, Usage: "listen port", Destination: &port}, //命令列解析得到bootnode cli.StringFlag{Name: "bootnode", Value: "", Usage: "boot node", Destination: &bootnode}, } if err := app.Run(os.Args); err != nil { log.Fatal(err) } } func startP2pNode(c *cli.Context) error { emitter := NewEmitter() nodeKey, _ := crypto.GenerateKey() node := p2p.Server{ Config: p2p.Config{ MaxPeers: 100, PrivateKey: nodeKey, Name: "p2pDemo", ListenAddr: fmt.Sprintf(":%d", port), Protocols: []p2p.Protocol{emitter.MyProtocol()}, }, } //從bootnode字串中解析得到bootNode節點 bootNode, err := discover.ParseNode(bootnode) if err != nil { return err } //p2p伺服器從BootstrapNodes中得到相鄰節點 node.Config.BootstrapNodes = []*discover.Node{bootNode} //node.Start()開啟p2p服務 if err := node.Start(); err != nil { return err } emitter.self = node.NodeInfo().ID[:8] go emitter.talk() select {} return nil } func (e *Emitter) MyProtocol() p2p.Protocol { return p2p.Protocol{ Name: "rad", Version: 1, Length: msgLength, Run: e.msgHandler, } } type peer struct { peer *p2p.Peer ws p2p.MsgReadWriter } type Emitter struct { self string peers map[string]*peer sync.Mutex } func NewEmitter() *Emitter { return &Emitter{peers: make(map[string]*peer)} } func (e *Emitter) addPeer(p *p2p.Peer, ws p2p.MsgReadWriter) { e.Lock() defer e.Unlock() id := fmt.Sprintf("%x", p.ID().String()[:8]) e.peers[id] = &peer{ws: ws, peer: p} } func (e *Emitter) talk() { for { func() { e.Lock() defer e.Unlock() inputReader := bufio.NewReader(os.Stdin) fmt.Println("Please enter some input: ") input, err := inputReader.ReadString('\n') if err == nil { fmt.Printf("The input was: %s\n", input) for _, p := range e.peers { if err := p2p.SendItems(p.ws, msgTalk, input); err != nil { log.Println("Emitter.loopSendMsg p2p.SendItems err", err, "peer id", p.peer.ID()) continue } } } }() } } func (e *Emitter) msgHandler(peer *p2p.Peer, ws p2p.MsgReadWriter) error { e.addPeer(peer, ws) for { msg, err := ws.ReadMsg() if err != nil { return err } switch msg.Code { case msgTalk: var myMessage []string if err := msg.Decode(&myMessage); err != nil { log.Println("decode msg err", err) } else { log.Println("read msg:", myMessage[0]) } default: log.Println("unkown msg code") } } return nil }
4 執行
在windows環境下編譯以上程式生成exe可執行檔案p2pTest.exe,開啟一個cmd客戶端,執行:
p2pText.exe --port 3401 --bootnode "第2步中bootnode節點的enode"
開啟第二個cmd客戶端,執行:
p2pText.exe --port 3402 --bootnode "第2步中bootnode節點的enode"
可以聊天了:
cmd1:
cmd2:
剛開始連線過程有點慢,等了一小會倆個客戶端聊天互相才有反應。