您的位置:首页 > 编程语言 > Go语言

基于go-ethereum/p2p模块的聊天程序

2018-03-29 17:18 274 查看
以太坊的p2p模块实现了一个p2p分布式网络,是实现以太坊分布式钱包的关键技术。p2p模块的说明见官方github的wiki。本文要实现的是使用以太坊的p2p模块来实现一个简单的聊天程序。
1 P2P基本原理
p2p的基本原理有一篇博客写的很清楚,详见《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{emit
995b
ter.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:



刚开始连接过程有点慢,等了一小会俩个客户端聊天互相才有反应。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: