前言
本文介绍如何使用libp2p开发一个简单的点对点聊天应用,读者若想按步骤实现以下介绍的例子请按安装 golang(go version>=1.11)。
创建go module
接下来我们将以命令行的方式创建go module。
首先需要创建一个新的目录并使用go mod 对其初始化。请不要在 GOPATH 路径下创建该测试目录。 创建目录完成,初始化目录时我们把模块名初始化为 github.com/demo/go-libp2p-chat (模块名可以为任意名称)。
$ mkdir -p ~/tmp/go-libp2p-chat
$ cd ~/tmp/go-libp2p-chat
$ go mod init github.com/demo/go-libp2p-chat
创建完毕后在go-libp2p-chat目录将会产生一个go.mod文件,该文件包含了模块的名字和go 的版本。如下:
$ cat go.mod
module github.com/demo/go-libp2p-chat
go 1.14
聊天程序代码
接下来在 go-libp2p-chat 目录下创建 chat.go 文件。
文件内容如下:
package main
import (
"bufio"
"context"
"crypto/rand"
"flag"
"fmt"
"io"
"log"
mrand "math/rand"
"os"
"github.com/libp2p/go-libp2p"
"github.com/libp2p/go-libp2p-core/crypto"
"github.com/libp2p/go-libp2p-core/network"
"github.com/libp2p/go-libp2p-core/peer"
"github.com/libp2p/go-libp2p-core/peerstore"
"github.com/multiformats/go-multiaddr"
)
func handleStream(s network.Stream) {
log.Println("Got a new stream!")
// Create a buffer stream for non blocking read and write.
rw := bufio.NewReadWriter(bufio.NewReader(s), bufio.NewWriter(s))
go readData(rw)
go writeData(rw)
// stream 's' will stay open until you close it (or the other side closes it).
}
func readData(rw *bufio.ReadWriter) {
for {
str, _ := rw.ReadString('\n')
if str == "" {
return
}
if str != "\n" {
// Green console colour: \x1b[32m
// Reset console colour: \x1b[0m
fmt.Printf("\x1b[32m%s\x1b[0m> ", str)
}
}
}
func writeData(rw *bufio.ReadWriter) {
stdReader := bufio.NewReader(os.Stdin)
for {
fmt.Print("> ")
sendData, err := stdReader.ReadString('\n')
if err != nil {
panic(err)
}
rw.WriteString(fmt.Sprintf("%s\n", sendData))
rw.Flush()
}
}
func main() {
sourcePort := flag.Int("sp", 0, "Source port number")
dest := flag.String("d", "", "Destination multiaddr string")
help := flag.Bool("help", false, "Display help")
debug := flag.Bool("debug", false, "Debug generates the same node ID on every execution")
flag.Parse()
if *help {
fmt.Printf("This program demonstrates a simple p2p chat application using libp2p\n\n")
fmt.Println("Usage: Run './chat -sp <SOURCE_PORT>' where <SOURCE_PORT> can be any port number.")
fmt.Println("Now run './chat -d <MULTIADDR>' where <MULTIADDR> is multiaddress of previous listener host.")
os.Exit(0)
}
// If debug is enabled, use a constant random source to generate the peer ID. Only useful for debugging,
// off by default. Otherwise, it uses rand.Reader.
var r io.Reader
if *debug {
// Use the port number as the randomness source.
// This will always generate the same host ID on multiple executions, if the same port number is used.
// Never do this in production code.
r = mrand.New(mrand.NewSource(int64(*sourcePort)))
} else {
r = rand.Reader
}
// Creates a new RSA key pair for this host.
prvKey, _, err := crypto.GenerateKeyPairWithReader(crypto.RSA, 2048, r)
if err != nil {
panic(err)
}
// 0.0.0.0 will listen on any interface device.
sourceMultiAddr, _ := multiaddr.NewMultiaddr(fmt.Sprintf("/ip4/0.0.0.0/tcp/%d", *sourcePort))
// libp2p.New constructs a new libp2p Host.
// Other options can be added here.
host, err := libp2p.New(
context.Background(),
libp2p.ListenAddrs(sourceMultiAddr),
libp2p.Identity(prvKey),
)
if err != nil {
panic(err)
}
if *dest == "" {
// Set a function as stream handler.
// This function is called when a peer connects, and starts a stream with this protocol.
// Only applies on the receiving side.
host.SetStreamHandler("/chat/1.0.0", handleStream)
// Let's get the actual TCP port from our listen multiaddr, in case we're using 0 (default; random available port).
var port string
for _, la := range host.Network().ListenAddresses() {
if p, err := la.ValueForProtocol(multiaddr.P_TCP); err == nil {
port = p
break
}
}
if port == "" {
panic("was not able to find actual local port")
}
fmt.Printf("Run './chat -d /ip4/127.0.0.1/tcp/%v/p2p/%s' on another console.\n", port, host.ID().Pretty())
fmt.Println("You can replace 127.0.0.1 with public IP as well.")
fmt.Printf("\nWaiting for incoming connection\n\n")
// Hang forever
<-make(chan struct{})
} else {
fmt.Println("This node's multiaddresses:")
for _, la := range host.Addrs() {
fmt.Printf(" - %v\n", la)
}
fmt.Println()
// Turn the destination into a multiaddr.
maddr, err := multiaddr.NewMultiaddr(*dest)
if err != nil {
log.Fatalln(err)
}
// Extract the peer ID from the multiaddr.
info, err := peer.AddrInfoFromP2pAddr(maddr)
if err != nil {
log.Fatalln(err)
}
// Add the destination's peer multiaddress in the peerstore.
// This will be used during connection and stream creation by libp2p.
host.Peerstore().AddAddrs(info.ID, info.Addrs, peerstore.PermanentAddrTTL)
// Start a stream with the destination.
// Multiaddress of the destination peer is fetched from the peerstore using 'peerId'.
s, err := host.NewStream(context.Background(), info.ID, "/chat/1.0.0")
if err != nil {
panic(err)
}
// Create a buffered stream so that read and writes are non blocking.
rw := bufio.NewReadWriter(bufio.NewReader(s), bufio.NewWriter(s))
// Create a thread to read and write data.
go writeData(rw)
go readData(rw)
// Hang forever.
select {}
}
}
编译并启动
go build chat.go
模拟启动节点1:
$ ./chat
Run './chat -d /ip4/127.0.0.1/tcp/62217/p2p/QmPTvaZ7x9YbENjam7XZ3GmcPrwTvfwi4gPATKQ4QjX7rV' on another console.
You can replace 127.0.0.1 with public IP as well.
Waiting for incoming connection
模拟启动节点2:
$ ./chat -d /ip4/127.0.0.1/tcp/62217/p2p/QmPTvaZ7x9YbENjam7XZ3GmcPrwTvfwi4gPATKQ4QjX7rV