在此文件夹中,您可以找到各种示例来帮助您开始使用go-libp2p。每个示例旨在帮助你延伸你对libp2p和p2p网络的了解,其中一些整合了您可以遵循的完整教程。
如果发现任何问题或者如果想贡献并添加新教程,请告诉我们,欢迎提交pr,谢谢!
- libp2p 'host'
- 使用libp2p构建http代理
- echo host
- Multicodecs with protobufs-多路复用器
- P2P聊天应用
- 整合节点发现的P2P聊天应用
- mdns实现节点发现的P2P聊天应用
构建示例时,确保您有一个干净的$GOPATH
。 如果您已经拉取并构建了其他libp2p
仓库,那么在构建示例时可能会出现类似于下面的错误。请注意,运行示例或使用libp2p
而不需要使用gx
包管理器。
$:~/go/src/github.com/libp2p/go-libp2p-examples/libp2p-host$ go build host.go
# command-line-arguments
./host.go:36:18: cannot use priv (type "github.com/libp2p/go-libp2p-crypto".PrivKey) as type "gx/ipfs/QmNiJiXwWE3kRhZrC5ej3kSjWHm337pYfhjLGSCDNKJP2s/go-libp2p-crypto".PrivKey in argument to libp2p.Identity:
"github.com/libp2p/go-libp2p-crypto".PrivKey does not implement "gx/ipfs/QmNiJiXwWE3kRhZrC5ej3kSjWHm337pYfhjLGSCDNKJP2s/go-libp2p-crypto".PrivKey (wrong type for Equals method)
have Equals("github.com/libp2p/go-libp2p-crypto".Key) bool
want Equals("gx/ipfs/QmNiJiXwWE3kRhZrC5ej3kSjWHm337pYfhjLGSCDNKJP2s/go-libp2p-crypto".Key) bool
要得到一个干净的$GOPATH
,可以执行如下操作:
> mkdir /tmp/libp2p-examples
> export GOPATH=/tmp/libp2p/examples
对于大多数应用来说,host是您需要开始使用的基本构建块。本指南将介绍如何搭建与使用一个简单的host。
host是一种抽象,可以在群集之上管理服务。它提供了一个干净的界面来连接指定的远程节点上的服务。
你若想以默认配置来创建一个host,可以执行以下操作:
import (
"context"
"crypto/rand"
"fmt"
libp2p "github.com/libp2p/go-libp2p"
crypto "github.com/libp2p/go-libp2p-crypto"
)
// The context governs the lifetime of the libp2p node
// context上下文控制libp2p节点的生存周期
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
// To construct a simple host with all the default settings, just use `New`
// 构造一个具有所有默认设置的简单host,只需使用“New”方法
h, err := libp2p.New(ctx)
if err != nil {
panic(err)
}
fmt.Printf("Hello World, my hosts ID is %s\n", h.ID())
如果想要更多地控制配置,可以为构造函数指定一些选项。有关构造函数支持的所有配置的完整列表,请参阅:options.go
在下面的代码片段中,我们生成自己的ID并指定我们想要监听的地址:
// Set your own keypair
// 配置自身的密钥对
priv, _, err := crypto.GenerateEd25519Key(rand.Reader)
if err != nil {
panic(err)
}
h2, err := libp2p.New(ctx,
// Use your own created keypair
// 使用自身创建的密钥对
libp2p.Identity(priv),
// Set your own listen address
// The config takes an array of addresses, specify as many as you want.
// 配置自身的监听地址
// 该配置采用地址数组的形式,想指定多少就可指定多少
libp2p.ListenAddrStrings("/ip4/0.0.0.0/tcp/9000"),
)
if err != nil {
panic(err)
}
fmt.Printf("Hello World, my second hosts ID is %s\n", h2.ID())
就这样,你有了一个libp2p host,已经准备好开始做一些很棒的p2p网络了!
在以后的指南中,我们将讨论host的使用方法,以不同的方式配置它们(提示:有很多方法可以设置它们),以及使用有趣的方式将这种技术应用于你要构建的各种应用程序上。
要查看这些包含各种配置的代码,请查看host.go。
该示例展示如何使用libp2p构建一个简单的HTTP代理服务:
XXX
XX XXXXXX
X XX
XXXXXXX XX XX XXXXXXXXXX
+----------------+ +-----------------+ XXX XXX XXX XXX
HTTP Request | | | | XX XX
+-----------------> | libp2p stream | | HTTP X X
| Local peer <----------------> Remote peer <-------------> HTTP SERVER - THE INTERNET XX
<-----------------+ | | | Req & Resp XX X
HTTP Response | libp2p host | | libp2p host | XXXX XXXX XXXXXXXXXXXXXXXXXXXX XXXX
+----------------+ +-----------------+ XXXXX
为代理一个HTTP请求,我们创建一个本地节点来监听localhost:9900
。对该地址执行的HTTP请求通过libp2p流经由隧道传输到远程节点,远程节点接着执行HTTP请求并将响应发送回本地节点,并由本地节点将其中继给用户。
注意,这是一种非常简单的代理方法,没有执行任何header管理,也不支持HTTPS。proxy.go
代码经过全面讨论,详细说明了每一步中发生的事情。
在go-libp2p-examples
目录运行以下命令:
> make deps
> cd http-proxy/
> go build
先运行“远程”节点,如下所示。它将打印本地节点地址。如果你想在单独的机器上运行,请相应地更换IP:
> ./http-proxy
Proxy server is ready
libp2p-peer addresses:
/ip4/127.0.0.1/tcp/12000/ipfs/QmddTrQXhA9AkCpXPTkcY7e22NK73TwkUms3a44DhTKJTD
然后运行本地节点,指定http请求要转发的远程节点,如下所示:
> ./http-proxy -d /ip4/127.0.0.1/tcp/12000/ipfs/QmddTrQXhA9AkCpXPTkcY7e22NK73TwkUms3a44DhTKJTD
Proxy server is ready
libp2p-peer addresses:
/ip4/127.0.0.1/tcp/12001/ipfs/Qmaa2AYTha1UqcFVX97p9R1UP7vbzDLY7bqWsZw1135QvN
proxy listening on 127.0.0.1:9900
正如所看到的,指定的代理服务打印出监听地址127.0.0.1:9900
。你现在可以用这个地址作为一个代理,用curl
进行测试:
> curl -x "127.0.0.1:9900" "http://ipfs.io/ipfs/QmfUX75pGRBRDnjeoMkQzuQczuCup2aYbeLxz5NzeSu9G6"
it works!
这是一个快速展示如何使用go-libp2p堆栈的示例,包括Host/Basichost,Network/Swarm,Streams,Peerstores和Multiaddresses。
此示例可以在侦听模式或拨号模式下启动。
在侦听模式下,它将等待/echo/1.0.0
协议上的传入连接。 每当它收到一个流时,它会在流上写下消息“Hello,world!”`并关闭它。
在拨号模式下,节点将启动,连接到给定地址,打开到目标节点的流,并读取基于协议/echo/1.0.0
的消息。
在go-libp2p-examples
目录运行以下命令:
> make deps
> cd echo/
> go build
> ./echo -l 10000
2017/03/15 14:11:32 I am /ip4/127.0.0.1/tcp/10000/ipfs/QmYo41GybvrXk8y8Xnm1P7pfA4YEXCpfnLyzgRPnNbG35e
2017/03/15 14:11:32 Now run "./echo -l 10001 -d /ip4/127.0.0.1/tcp/10000/ipfs/QmYo41GybvrXk8y8Xnm1P7pfA4YEXCpfnLyzgRPnNbG35e" on a different terminal
2017/03/15 14:11:32 listening for connections
作为监听方的libp2p主机将打印它的Multiaddress
,以明确如何被访问到(ip4 + tcp)及其随机生成的ID(QmYo41Gyb ...
)
现在运行另一个与监听方通信的节点:
> ./echo -l 10001 -d /ip4/127.0.0.1/tcp/10000/ipfs/QmYo41GybvrXk8y8Xnm1P7pfA4YEXCpfnLyzgRPnNbG35e
新节点向监听方发送消息“Hello,world!”`,然后在流上回显它并关闭它。监听方记录消息,发送方记录响应。
makeBasicHost()方法创建一个go-libp2p-basichost对象。basichost对象包装了go-libp2 swarm并且应该优先被使用。go-libp2p-swarm网络是一个符合go-libp2p-net网络接口的swarm,负责维护流,连接,在它们上复用不同的协议,处理传入的连接等。
为了创建swarm(和一个basichost
),这个例子需要:
ipfs协议ID
,如QmNtX1cvrm2K6mQmMEaMxAuB4rTexhd87vpYVot4sEZzxc。该示例在每次运行时自动生成密钥对,并使用从公钥中提取的ID(公钥的哈希值)。使用-insecure
时,它会使连接保持未加密状态(否则,它会使用密钥对来加密通信)。
Multiaddress
,以明确如何被访问到这个节点。可以有好几个(例如,使用不同的协议或位置)。示例:/ip4/127.0.0.1/tcp/1234。
go-libp2p-peerstore
,用作地址簿,在节点ID与multiaddresses之间进行匹配。当手动打开连接时(使用Connect()
),peertore会自动装载。或者,我们可以像示例一样手动添加Addddr()
。
basichost
,现在可用以使用NewStream
打开流(两个节点之间的双向通道),并使用它们发送和接收标记有Protocol.ID(字符串)的数据。主机还可以通过SetStreamHandle()
方法监听指定协议的传入连接。
该示例利用以上所有这些以保证监听方与发送方之间使用协议/echo/1.0.0
(也可以是其他协议)进行的通信。
该例展示如何通过protobufs使用libp2p Streams在libp2p主机之间编码和传输信息。进入该例前需要先熟悉echo示例。
在go-libp2p-examples
目录运行以下命令:运行以下命令:
> make deps
> cd multipro/
> go build
> ./multipro
该例创建了支持ping和echo两个协议的两个libp2p主机。
每个协议都包含RPC样式的请求和响应,每个请求和响应都是一个类型化的protobufs消息(和一个go数据对象)。
这与将整个p2p协议定义为具有许多可选字段的一个protobuf消息(在使用各种不同的基于protobuf的p2p-lib协议中可观察到-诸如dht)不同。
该例展示了如何将异步接收的响应与其请求进行匹配。当处理响应需要访问请求数据时很有用。
其思想是在每个消息的基础上使用libp2p协议多路复用。
1.两种基于类似RPC的请求-响应模式实现的协议-Ping和Echo
2.脚手架,用于快速实现新的应用级版本化RPC类协议
3.调用方可对传入消息数据进行完全认证(可能不是消息的发送方节点)
4.在protobufs中使用p2p格式,其中包含所有协议消息共享的字段
5.处理响应时对请求数据的完全访问权限
该程序演示了一个简单的p2p聊天应用程序。它可以在两个节点之间工作
1.两者都有一个私有IP地址(处于同一网络)
2.其中至少有一个拥有公共IP地址。
假设'A'和'B'在不同的网络上,主机'A'可能有也可能没有公共IP地址,但至少主机'B'有一个。
用法:在主机'B'上运行./chat-sp <SOURCE_PORT>
,其中<SOURCE_PORT>可以是任何端口号。然后在节点'A'上运行./chat -d <MULTIADDR_B>
[<MULTIADDR_B>
是主机'B'的multiaddress
,可以从主机'B'控制台获得]。
在go-libp2p-examples
目录运行以下命令:
> make deps
> cd chat
> go build
操作节点'B':
> ./chat -sp 3001
Run ./chat -d /ip4/127.0.0.1/tcp/3001/ipfs/QmdXGaeGiVA745XorV1jr11RHxB9z4fqykm6xCUPX1aTJo
2018/02/27 01:21:32 Got a new stream!
> hi (received messages in green colour)
> hello (sent messages in white colour)
> no
操作节点'A': 将127.0.0.1用节点'B'的公共IP<PUBLIC_IP>替代,如果节点'B'有的话.
> ./chat -d /ip4/127.0.0.1/tcp/3001/ipfs/QmdXGaeGiVA745XorV1jr11RHxB9z4fqykm6xCUPX1aTJo
Run ./chat -d /ip4/127.0.0.1/tcp/3001/ipfs/QmdXGaeGiVA745XorV1jr11RHxB9z4fqykm6xCUPX1aTJo
下面是节点的multiaddress:
/ip4/0.0.0.0/tcp/0/ipfs/QmWVx9NwsgaVWMRHNCpesq1WQAw2T3JurjGDNeVNWifPS7
> hi
> hello
注意: 默认情况下会启用调试模式,调试模式将始终在每次执行时生成相同的节点ID(在每个节点上)。运行可执行文件时,使用--debug false
标志禁用调试。
注意: 如果您正在寻找具有节点发现的实现,整合节点发现的P2P聊天应用支持通过rendezvous point
进行节点发现。
接下来演示一个简单的p2p聊天应用程序。您将学习如何在网络中发现节点(使用kad-dht),连接到它并打开聊天流。
在go-libp2p-examples
目录运行以下命令:
> make deps
> cd chat-with-rendezvous/
> go build -o chat
分别使用两个不同的终端窗口运行下面命令:
./chat -listen /ip4/127.0.0.1/tcp/6666
./chat -listen /ip4/127.0.0.1/tcp/6668
- 配置一个p2p host
ctx := context.Background()
// 使用libp2p.New方法构建一个新的libp2p Host.
// 其他选项参数也可在这里添加.
host, err := libp2p.New(ctx)
libp2p.New是一个节点的构造方法. 它基于指定配置创建一个host.但该例的所有配置选项均采用默认的, 参阅
- 为传入连接设置默认处理函数。
本函数当远程节点发起连接并向本地节点推送流时被调用。
// 配置流处理函数.
host.SetStreamHandler("/chat/1.1.0", handleStream)
handleStream
在每向本地节点新传入流时执行。stream
用于在本地和远程节点之间交换数据。本例使用非阻塞函数从此流中读取和写入。
func handleStream(stream net.Stream) {
// 为非阻塞式读写创建一个缓存流
rw := bufio.NewReadWriter(bufio.NewReader(stream), bufio.NewWriter(stream))
go readData(rw)
go writeData(rw)
// 'stream'将始终处于打开状态直至您将它关闭(或被其他节点关闭)
}
- 使用
host
作为本地节点启动新的DHT客户端。
dht, err := dht.New(ctx, host)
- 连接到IPFS引导节点。
这些节点用于查找附近使用DHT的节点。
for _, addr := range bootstrapPeers {
iaddr, _ := ipfsaddr.ParseString(addr)
peerinfo, _ := peerstore.InfoFromP2pAddr(iaddr.Multiaddr())
if err := host.Connect(ctx, *peerinfo); err != nil {
fmt.Println(err)
} else {
fmt.Println("Connection established with bootstrap node: ", *peerinfo)
}
}
- 使用
rendezvous point
告知您的存在。
routingDiscovery.Advertise使该节点宣布它可以为给定的密钥(称之为rendezvousString
)提供值。其他节点将使用相同的密钥来查找对应节点。
routingDiscovery := discovery.NewRoutingDiscovery(kademliaDHT)
discovery.Advertise(ctx, routingDiscovery, config.RendezvousString)
- 查找临近节点
routingDiscovery.FindPeers将返回已告知存在的节点通道。
peerChan, err := routingDiscovery.FindPeers(ctx, config.RendezvousString)
注意: 虽然routingDiscovery.Advertise和routingDiscovery.FindPeers适用于rendezvous
节点发现,但这不是正确的方法。Libp2p目前正在开发一个实际的rendezvous
协议(libp2p/specs#56),用以辅助实时节点发现并应用特定的路由。
- 向新发现的节点开启流
最终我们向新发现的节点开启流。
go func() {
for peer := range peerChan {
if peer.ID == host.ID() {
continue
}
fmt.Println("Found peer:", peer)
fmt.Println("Connecting to:", peer)
stream, err := host.NewStream(ctx, peer.ID, protocol.ID(config.ProtocolID))
if err != nil {
fmt.Println("Connection failed:", err)
continue
} else {
rw := bufio.NewReadWriter(bufio.NewReader(stream), bufio.NewWriter(stream))
go writeData(rw)
go readData(rw)
}
fmt.Println("Connected to:", peer)
}
}()
本例演示了一个简单的p2p聊天应用程序。 您将学习如何在网络中(使用mdns)发现节点,连接到它并打开聊天流。这个例子受到整合节点发现的P2P聊天应用例子的影响很大(并且无耻地复制)
go get -v -d ./...
go build
分别使用两个不同的终端窗口运行下面命令:
./chat-with-mdns -port 6666
./chat-with-mdns -port 6668
- 配置p2p host
ctx := context.Background()
host, err := libp2p.New(ctx)
- 为传入连接设置默认处理函数。
本函数当远程节点发起连接并向本地节点推送流时被调用。
// 配置流处理函数.
host.SetStreamHandler("/chat/1.1.0", handleStream)
handleStream
在每向本地节点新传入流时执行。stream
用于在本地和远程节点之间交换数据。本例使用非阻塞函数从此流中读取和写入。
func handleStream(stream net.Stream) {
// 为非阻塞式读写创建一个缓存流
rw := bufio.NewReadWriter(bufio.NewReader(stream), bufio.NewWriter(stream))
go readData(rw)
go writeData(rw)
// 'stream'将始终处于打开状态直至您将它关闭(或被其他节点关闭)
}
- 使用mdns查找附近节点
在host中开启mdns服务发现服务。
ser, err := discovery.NewMdnsService(ctx, peerhost, time.Hour, rendezvous)
注册Notifee接口与服务,以便我们收到有关节点发现的通知
n := &discoveryNotifee{}
ser.RegisterNotifee(n)
- 向新发现的节点开启流
最后,我们向找到的节点开启流
peer := <-peerChan // will block untill we discover a peer
fmt.Println("Found peer:", peer, ", connecting")
if err := host.Connect(ctx, peer); err != nil {
fmt.Println("Connection failed:", err)
}
// open a stream, this stream will be handled by handleStream other end
stream, err := host.NewStream(ctx, peer.ID, protocol.ID(cfg.ProtocolID))
if err != nil {
fmt.Println("Stream open failed", err)
} else {
rw := bufio.NewReadWriter(bufio.NewReader(stream), bufio.NewWriter(stream))
go writeData(rw)
go readData(rw)
fmt.Println("Connected to:", peer)
}