Chat program based on go-ethereum/p2p module

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.

Guess you like

Origin http://43.154.161.224:23101/article/api/json?id=326001489&siteId=291194637