使用JS通过WebRTC创建libp2p网络并相互连接（理论）

最近发现一个好玩的数据库项目 OrbitDB，想做一个去中心化的公共动漫元数据库，但其去中心化的实现方式是通过 libp2p 的方式实现的，在开发过程中最难理解和设置的就是 libp2p 相关的内容了，好在这两周官方更新了一篇质量很高的入门教程，能学习到非常多的相关知识。值得我开一篇博客总结一下内容。
WebRTC with js-libp2p

为什么选择 WebRTC

什么是 WebRTC

WebRTC，名称源自网页即时通信（英语：Web Real-Time Communication）的缩写，是一个支持网页浏览器进行实时语音对话或视频对话的 API。通过 WebRTC 可以实现浏览器到浏览器直接的 P2P 连接，允许两个浏览器之间直接进行数据传输，并且在目前主流的浏览器中都已经支持 WebRTC 协议，配合 js-libp2p 可以直接让浏览器当作 libp2p 节点加入 libp2p 网络。

JS-libp2p 连通性和连通能力

libp2p 有很多语言实现方案，但是各个方案目前的实现程度和局限性各不相同，官网提供了目前各语言的实现进度和局限性。
libp2p 实现进度

由于浏览器的限制，浏览器无法直接通过 TCP 和 QUIC 协议进行传输，想要在浏览器中创建独立的 libp2p 节点，目前来看只有 3 个协议可以选择，分别是 WebSocket、WebTansport、WebRTC。然后官网还给出了 libp2p 各协议之间的连通性结果并解释了为什么浏览器不支持 TCP 和 QUIC 协议
libp2p 各协议间连通性

从官网给出的连通性结果，目前 WebSocket 和 WebTransport 只能支持连接到服务器，并不支持连接到浏览器，目前为止在所有实现方案中，只有 js-libp2p 配合 WebRTC 能够实现将浏览器视为独立的节点。

WebRTC 的局限性和解决方案

在进行 P2P 连接的时候，最重要的问题是如何让两个浏览器相互发现对方，使得节点能够进行后续的连接。为此提供了两个解决方案，分别是 STUN 服务器和 TURN 服务器。

STUN 服务器：由于网络中有很多的 NAT 结构，使得浏览器很难直接通过公网地址发现对方，STUN 服务器就是帮助浏览器相互发现各自节点，以便于建立相互的连接的服务器，其中有很多免费的公共 STUN 服务器可以选择。
免费的 STUN 公共服务器

TURN (Relay) 服务器：如果通过浏览器之间无法建立直接的 P2P 连接，即 STUN 服务使得节点相互发现了但仍然无法连接，则可以通过 TURN 服务器转发 P2P 连接的所有流量，也因此 TURN 的使用成本较高，不是很好的选择。

在浏览器进行 P2P 连接的时候，相互可能需要发送一些元数据信息或者对话描述信息，即信令（Signaling）交换。在 WebRTC 标准中没有强制规定这些内容。需要开发者自己去实现信令交换方案。
在 libp2p 中，设计了一个新的方案去解决以上两个问题。通过一个 libp2p relay 节点解决。libp2p relay 节点在网络中充当两个角色，分别为：

Circuit Relay V2：在浏览器节点无法直接建立 P2P 连接的时候，选择 TURN 服务的成本有太高，Circuit Relay V2 就能够很好的解决这个问题，因为他是去中心化的，让网络中活跃的节点帮助浏览器相互发现彼此，在无法进行 P2P 连接的时候转发所需的流量。

PubSub Peer Discovery：以上内容解决了节点之间能否连通的问题，而 PubSub Peer Discovery 就是解决了节点之间自动相互连接的问题。GossipSub 模式就是 PubSub Peer Discovery 的一个实现方案，节点通过订阅一个主题，每当有一个新的节点加入，并订阅了相同主题，那么 PubSub Peer Discovery 会向所有订阅该主题的节点广播新节点地址，各节点接受到地址后会尝试与其连接。但需要注意，在一个大型网络中，发送广播是一个资源消耗很大的行为，不合理使用很可能会导致堵塞进而失去可靠性，所以该方案可能不适合在生产环境中使用。

连接流程

关于两个节点如何创建 P2P 连接，以及 libp2p relay 节点在链接中起到什么作用，官方给出了一张流程图大致描述了工作流程。

关于该流程，大致思路为，两个节点在建立连接的时候，需要先知道 Relay 节点的地址，与 Relay 节点发起 WebSockets 连接，当两个节点成功与 Relay 节点建立稳定连接后，并且订阅了 GossipSub 相同的主题后，Relay 节点会通过 Circuit Relay V2 向网络中订阅了相同主题的节点告知对方的多地址（multiaddr）。多地址是形如以下的地址，

Copy
//一个节点的多地址（multiaddr）
//使用WebSocket 端口9001 支持WebRTC
/ip4/127.0.0.1/tcp/9001/ws/p2p/12D3KooWDpJEwVdPBrQf6ZcwRYPzynBU2PZNGTQs3X8uh6FpxRTZ/p2p-circuit/webrtc/p2p/12D3KooWR3oBwBzZxSTd5RTKGJC2WsCxqWumpBUP1WjsixjeMQ9s
//不使用WebSocket 端口9002 支持WebRTC
/ip4/127.0.0.1/tcp/9002/p2p/12D3KooWDpJEwVdPBrQf6ZcwRYPzynBU2PZNGTQs3X8uh6FpxRTZ/p2p-circuit/webrtc/p2p/12D3KooWR3oBwBzZxSTd5RTKGJC2WsCxqWumpBUP1WjsixjeMQ9s
//使用WebSocket 端口9001 不支持WebRTC
/ip4/127.0.0.1/tcp/9001/ws/p2p/12D3KooWDpJEwVdPBrQf6ZcwRYPzynBU2PZNGTQs3X8uh6FpxRTZ/p2p-circuit/p2p/12D3KooWR3oBwBzZxSTd5RTKGJC2WsCxqWumpBUP1WjsixjeMQ9s
//不使用WebSocket 端口9002 不支持WebRTC
/ip4/127.0.0.1/tcp/9002/p2p/12D3KooWDpJEwVdPBrQf6ZcwRYPzynBU2PZNGTQs3X8uh6FpxRTZ/p2p-circuit/p2p/12D3KooWR3oBwBzZxSTd5RTKGJC2WsCxqWumpBUP1WjsixjeMQ9s

最后两个节点会通过多地址尝试建立 WebRTC 连接，完成 P2P 连接。

官方还给出了一张更加详细的 WebRTC 连接流程。

在通过 libp2p relay 节点互相发现对方后，要开始建立 WebRTC 连接了。首先每个节点会从 STUN 服务器获取自身的公共 IP 和端口。然后连接发起节点会根据 STUN 返回的信息创建 RTCPeerConnection 对象（这是一个用于管理 WebRTC 连接状态的对象）并创建 DataChannel 和 SDP（Seesion Description Protoncol，用于描述对话信息）准备发起连接。连接发起者会创建一个 libp2p 中定义的信令（webrtc-singnaling）通过 libp2p Relay 节点向被连接方发送 SDP，当被连接放接受到 SDP 后，同样也会创建一个 RTCPeerConnection 和 SDP 回复给发送方，发送方接受到 SDP 后并记录，至此两个节点完成握手交换 SDP 信息。
然后通过 ICE（Interactive Connectivity Establishment，用于在 P2P 连接中找到最佳连接路径）将对方节点添加到连接管理对象 RTCPeerConnection 中。最后通过 Noise（一种加密通信协议）加密连接通道完成连接。

至此已经大致了解在 libp2p 中是如何通过 WebRTC 解决浏览器节点直接连接的问题，其中涉及到很多名词在以前并不是很了解，所以出现矛盾和差错以官方资料为准。

WebRTC with js-libp2p
libp2p Connectivity
Peers - libp2p
SDP - MDN Web
mutiaddr - libp2p
WebRTC - MDN Web
Circuit Relay - libp2p
lib2p2 signaling-protocol -Github
js-libp2p-pubsub-peer-discovery -Github

至此理论文章已经结束，为了更深入的了解其中的内容，可以继续阅读实践文章
使用 JS 通过 WebRTC 创建 libp2p 网络并相互连接（实践）