1.例子简介
这个项目演示了使用Go中的libp2p库建立一个基于中继的点对点通信。它的特点是创建不可访问的libp2p主机,并通过中继节点促进它们的通信。
- 创建两个“不可达”的libp2p主机。
- 建立中继节点,使这些主机之间能够通信。
2. 步骤
- 存在两个不能直接存在的节点Node1,Node2(并且这两个节点接受来自中继节点的入站连接)
- 存在一个中继节点Relay
- Node1和Node2应该都可以连接到中继节点Relay,并将其加入到自己的地址簿
- Node2需要向Relay预定一个端口用来转发自己的消息
- Node1通过realy新建一个连接地址
/p2p/relayAddress/p2p-circuit/p2p/targetAddress
- Node1尝试Connect Node2,成功后会将其加入到targetAddress加入到自己的地址簿
- 可以直接用Node2的ID和协议进行通讯
3. 代码分析
删除了一部分错误处理,使代码看起来更加简洁
func run(){
// 创建一个在NAT后的节点1
unreachable1, err := libp2p.New(
libp2p.NoListenAddrs,
// 启动中继功能是一个默认选项,但是我们在之前的NoListenAddrs覆盖了它,所以需要再次显式调用
// 该选项仅将libp2p配置为接受来自中继的入站连接,并在远程对等体请求时进行出站连接。
libp2p.EnableRelay(),
)
// 创建一个在NAT后的节点2
unreachable2, err := libp2p.New(
libp2p.NoListenAddrs,
libp2p.EnableRelay(),
)
// 构建一个连接节点2的地址
unreachable2info := peer.AddrInfo{
ID: unreachable2.ID(),
Addrs: unreachable2.Addrs(),
}
err = unreachable1.Connect(context.Background(), unreachable2info)
if err != nil {
log.Printf("This is normal because NAT2 does not listen to addresses")
return
}
// 正如我们所怀疑的那样,我们无法在无法到达的主机之间直接拨号
// 创建一个主机作为中间人,代表我们传递消息
relay1, err := libp2p.New()
// 配置主机以提供电路中继服务。任何可以在网络(或互联网)中直接拨号的主机都可以提供电路中继服务,这不仅仅是“专用”中继服务的工作。在电路继电器v2(我们在这里使用!)中,它的速率是有限的,因此任何节点都可以安全地提供此服务
_, err = relay.New(relay1)
// 获得中继节点的连接地址
relay1info := peer.AddrInfo{
ID: relay1.ID(),
Addrs: relay1.Addrs(),
}
// 将unreachable1和unreachable2都连接到relay1
if err := unreachable1.Connect(context.Background(), relay1info); err != nil {
log.Printf("Failed to connect unreachable1 and relay1: %v", err)
return
}
if err := unreachable2.Connect(context.Background(), relay1info); err != nil {
log.Printf("Failed to connect unreachable2 and relay1: %v", err)
return
}
// 现在为节点2设置流处理器
unreachable2.SetStreamHandler("/customprotocol", func(s network.Stream) {
log.Println("Awesome! We're now communicating via the relay!")
// End the example
s.Close()
})
// 节点2向中继节点relay1预留一个槽位,以便后续中继节点转发需要发送到node2的请求
// 但我们还没告诉relay1如何识别node2的请求
_, err = client.Reserve(context.Background(), unreachable2, relay1info)
// 为node2创建一个新的地址
relayaddr, err := ma.NewMultiaddr("/p2p/" + relay1info.ID.String() + "/p2p-circuit/p2p/" + unreachable2.ID().String())
//由于我们刚刚尝试拨号失败,拨号系统将默认阻止我们如此迅速地重新拨号。既然我们知道我们在做什么,我们可以使用这个丑陋的hack(它在我们的TODO列表中,使它更简洁)来告诉拨号器“不,没关系,让我们再试一次”。
// 清理上一次的拨号失败记录,因为拨号系统会
unreachable1.Network().(*swarm.Swarm).Backoff().Clear(unreachable2.ID())
// 通过中继地址打开到先前不可达主机的连接(relayaddr这是节点2的新地址,这是经过中继relay1的地址)
unreachable2relayinfo := peer.AddrInfo{
ID: unreachable2.ID(),
Addrs: []ma.Multiaddr{relayaddr},
}
// 拨号成功后会将其加入到自己的地址库
if err := unreachable1.Connect(context.Background(), unreachable2relayinfo); err != nil {
log.Printf("Unexpected error here. Failed to connect unreachable1 and unreachable2: %v", err)
return
}
log.Println("Yep, that worked!")
//因为我们没有到目标节点的直接连接——我们有一个中继连接——连接被标记为瞬态。由于中继限制了可以通过中继连接交换的数据量,因此应用程序需要显式地选择使用中继连接。一般来说,只有在带宽要求较低的情况下,我们才应该这样做,并且当中继连接被直接(holepched)连接取代时,我们很高兴连接被终止。
// 节点1尝试连接到节点2的customprotocol的协议上
s, err := unreachable1.NewStream(network.WithUseTransient(context.Background(), "customprotocol"), unreachable2.ID(), "/customprotocol")
s.Read(make([]byte, 1))
}