libp2p 连接详解

整体结构

image-20241121162549943

libp2p 模块的整体结构如上图所示:

  • **一个 libp2p 节点可以同时与多个节点建立连接:**如 peer A、peer B等。
  • **每个节点可以同时存在多个连接:**如与 peer A 同时建立了 QUIC 连接 A、TCP 连接 B、Relay 连接C,与 peer B 同时建立了 TCP 连接 A、TCP 连接 B、Relay 连接C。
  • **每个连接下可以存在多个 Stream:**如 peer A 的 QUIC 连接 A 下面有 stream1、stream2、stream3 等。

Stream 创建过程

**Stream 是 libp2p 传输数据的通道。**想要通过使用 libp2p 与对端传输数据,则必须要创建一个到对端的流,通过流进行数据传输。

下图展示了 libp2p 中创建 stream 的主要逻辑:

image-20241121162615106

下面对流创建过程中的重要步骤进行详细讲解。

选择连接

当需要创建流时,本地 libp2p 节点到 peer A 可能有多个可用连接,libp2p 需要从众多连接中挑选出一个连接。

libp2p 默认的连接排序逻辑为:

  • 先建立的连接 > 后建立的连接;
  • 直连连接 > 中继连接;
  • 当前打开 Stream 数量较多的连接 > 当前打开 Stream 数量较少的连接。

由于 libp2p 的连接选择逻辑较为简单,因此我们对连接排序逻辑进行了部分优化:

  • 先建立的连接 > 后建立的连接;
  • Private IP 连接 > Public IP 连接 > 中继连接;
  • QUIC > WebTransport > WebRTC > TCP > WebSocket;
  • 当前打开 Stream 数量较多的连接 > 当前打开 Stream 数量较少的连接。

由于固定逻辑的排序选择无法适应复杂的网络环境,因此将连接排序函数设置为可自定义,后续可由外层做基于更多指标的连接排序,如连接带宽、丢包率等。

建立连接

当本地到对端节点没有可用连接时,libp2p 需要根据地址列表尝试建立连接。

在建立连接时,由于到 peer A 有多个可能地址,libp2p 需要对这些地址都进行尝试,直到建立一个可用连接或者全部失败。

在尝试 peer A 的地址列表时,libp2p 首先会对地址列表进行排序.

libp2p 默认的地址排序逻辑为:

  • Private IP 地址 > Public IP 地址 > 中继连接地址;
  • WebRTC > QUIC IPv6 > QUIC IPv4 > WebTransport IPv6 > WebTransport IPv4 > TCP IPv6 > TCP IPv4。

对连接地址进行排序后,libp2p 会对所有的地址依次进行连接。但是 libp2p 不会等到连接结果返回再连接下一个。在尝试一个连接后,如果一定延时内(毫秒级别)没有结果返回的话,libp2p 会立即尝试下一个地址。

当有任意一个地址连接成功后,libp2p 会立即返回当前连接,但是不会终止整个连接过程。直到整个地址列表尝试完成才会终止连接过程。

触发打洞

libp2p 的打洞是由连接事件驱动。

当成功建立一个连接时,libp2p 会在整个节点内部广播这个事件。libp2p 的打洞模块便会监听该事件。

打洞模块在收到一个连接建立事件后,会对该事件进行判断:

  • 自身是连接的服务端?
  • 该连接是 Relay 连接?

如果满足以上条件,libp2p 会启动打洞逻辑,尝试与该连接所属节点尝试进行打洞。

libp2p 的默认打洞逻辑仅会在连接建立成功那一刻触发,触发之后如果打洞失败,后续不会再进行尝试。对于运行在 PC 端的 libp2p 来说该逻辑是足够的,因为大部分 PC 端的网络环境不会轻易变化。

但是对于移动端场景来说是不够的,因为移动端网络变化较为频繁。因此对于打洞模块的触发逻辑需要进行优化:

  • 新增打洞重试事件,当打洞模块监听到该事件后,需要重新对指定节点尝试重新打洞;
  • 对本机网络进行监听,当本机网络发生改变时触发打洞重试事件;
  • 启用定时器,定期对打洞失败的节点尝试重新打洞。
Licensed under CC BY-NC-SA 4.0
最后更新于 Nov 21, 2024 16:29 +0800
使用 Hugo 构建
主题 StackJimmy 设计