The p2p module of Ethereum implements a p2p distributed network, which is the key technology to realize the distributed wallet of Ethereum. For the description of the p2p module, see the official github wiki . What this article wants to achieve is to use the p2p module of Ethereum to implement a simple chat program.
1 Fundamentals of P2P
The basic principle of p2p is very clearly written in a blog. For details, please refer to "The principle and common implementation of p2p" .
2 Compile and start the bootnode of Ethereum
The bootnode node can act as the routing node of the p2p network. The two p2p nodes in the chat program will use this bootnode as a route.
Build the go language compilation environment in the ubuntu environment, go to https://github.com/ethereum/go-ethereum to download the Ethereum source code,
sudo git https://github.com/ethereum/go-ethereum
Download the Ethereum source code to the current directory. After the download is complete, the go-ethereum directory will appear in the current directory. Enter the directory, use sudo make all to generate the bootnode executable file in the go-ethereum/build/bin directory, and copy the file to a folder:
sudo cp bootnode ~/p2ptest/
Enter the p2ptest directory:
Generate key:
Start bootnode:
Note that you need to change the [::] after @ in the enode string to the IP address of ubuntu.
3 p2p chat programs
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" ) where ( 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 from command line parsing cli.IntFlag{Name: "port", Value: 11200, Usage: "listen port", Destination: &port}, //Command line parsing to get 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()}, }, } //Parse the bootNode node from the bootnode string bootNode, err := discover.ParseNode(bootnode) if err != nil { return err } //p2p server gets neighboring nodes from BootstrapNodes node.Config.BootstrapNodes = []*discover.Node{bootNode} //node.Start() starts the p2p service 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: was 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 run
Compile the above program in the Windows environment to generate the exe executable file p2pTest.exe, open a cmd client, and execute:
p2pText.exe --port 3401 --bootnode "The enode of the bootnode node in step 2"
Open the second cmd client and execute:
p2pText.exe --port 3402 --bootnode "The enode of the bootnode node in step 2"
Ready to chat:
cmd1:
cmd2:
The connection process was a bit slow at the beginning, and it took a while for the two clients to chat with each other before they could respond.