写点什么

Rust P2P 网络应用实战 -1 P2P 网络核心概念及 Ping 程序

作者:李明
  • 2022 年 7 月 29 日
  • 本文字数:2823 字

    阅读完需:约 9 分钟

Rust P2P网络应用实战-1 P2P网络核心概念及Ping程序

本系列文章首先研究 P2P 网络的核心概念,然后详细分析 libp2p-rust 库中的应用实例,为以后开发 P2P 网络应用程序打好基础。


P2P 网络

P2P(Peer-to-Peer)是一种网络技术,可以在网络中不同的计算机上共享各种计算资源,如 CPU、网络带宽和存储等。P2P 技术应用最广泛的是在网络中共享文件以及区块链网络,它们不依赖中央服务器或中介来连接多个客户端,用户的计算机即是客户端也是服务器。

由于 P2P 网络是一种分布式系统,不会像中央服务器一样存在单点故障,因此容错性极强。


下面让我们来看一看 P2P 网络的核心概念:

传输协议

在 P2P 网络底层一般使用 TCP/UDP 传输层协议。由于 P2P 节点应用的多样性,在 TCP/UDP 传输层协议之上,会使用多种应用层协议,如:HTTP,gRPC 及自定义协议等。为了有效利用资源,P2P 网络会在一个连接上监听、解析多种协议,即多路复用技术:多个逻辑子流可以在同一个底层(TCP)连接上共存。可以查看 yamux 库了解更多细节。

节点标识

P2P 网络中的节点需要一个唯一的标识,以便其他节点能够找到它们。P2P 网络中的节点使用公钥和私钥对(非对称公钥加密)与其他节点建立安全通信。在 P2P 网络中节点标识被称为 PeerId,它是通过对节点公钥进行加密哈希得到的。

安全规则

密钥对和节点身份标识使节点之间能够体建立安全的、经过身份验证的通信通道。但这只是安全的一个方面,节点还需要根据业务逻辑实现授权框架,该框架建立一些规则:哪些节点可以执行哪种类型的操作等。

节点路由

P2P 网络中的一个节点首先需要找到其他节点来进行通信。这是通过维护一个节点路由表来实现的。但是在 P2P 网络中,有成千上万个节点在动态变化(即节点的加入和离开),单个节点很难为网络中的所有节点维护一个完整、准确的路由表。所以节点路由表通常会由一系列路由节点维护。

消息

P2P 网络中的节点可以向特定节点发送消息,也可以广播消息。使用发布/订阅模式,节点订阅感兴趣 Topic,所有订阅该 Topic 的节点都能接收和发送消息。这种技术也通常用于将消息的内容传输到整个网络。

流多路复用

在 P2P 网络中,允许多个独立的“逻辑”流共享一个公共的 P2P 传输层。流多路复用有助于优化节点之间建立网络连接的开销。多路复用在后端服务开发中很常见,客户端可以与服务器建立底层网络连接,然后在底层网络连接上复用不同的流。


libp2p

自己编写 P2P 应用程序的网络层是一项庞大的工程,我们将使用底层 p2p 网络库—libp2p,在其上构建 p2p 应用程序会更加容易。libp2p 是一个模块化的系统,支持三种编程语言:Rust、Go、JS。许多流行的项目中都使用 libp2p 做为 P2P 网络底层,如 IPFS、 Filecoin、Polkadot 和 Substrate。

libp2p 将 P2P 网络基本概念分解成了不同的模块,可以在不同的应用场景中组合使用。

我们先通过 Ping 这个简单的程序来熟悉一下 libp2p 的组件及如何使用 libp2p 开发点对点网络。

PING

这个例子非常简单,主要就是一个节点向另一个节点发送 ping 消息,然后等待另一个节点返回 pong 消息。

新建一个项目名叫:libp2p-learn

master:p2p Justin$ cargo new libp2p-learn     Created binary (application) `libp2p-learn` packagemaster:p2p Justin$ cd libp2p-learn/master:libp2p-learn Justin$ code .
复制代码


在 Cargo.toml 文件中加入 libp2p 和 tokio 依赖:

[dependencies]libp2p = "0.46"tokio = { version = "1.19", features = ["full"] }
复制代码


然后在 src/bin/目录下创建 ping.rs 文件:

use std::error::Error;
use libp2p::{ futures::StreamExt, identity, ping::{Ping, PingConfig}, swarm::SwarmEvent, Multiaddr, PeerId, Swarm,};
#[tokio::main]async fn main() -> Result<(), Box<dyn Error>> { // 生成密钥对 let key_pair = identity::Keypair::generate_ed25519();
// 基于密钥对的公钥,生成节点唯一标识peerId let peer_id = PeerId::from(key_pair.public()); println!("节点ID: {peer_id}");
// 声明Ping网络行为 let behaviour = Ping::new(PingConfig::new().with_keep_alive(true));
// 传输 let transport = libp2p::development_transport(key_pair).await?;
// 网络管理模块 let mut swarm = Swarm::new(transport, behaviour, peer_id);
// 在节点随机开启一个端口监听 swarm.listen_on("/ip4/0.0.0.0/tcp/0".parse()?)?;
// 从命令行参数获取远程节点地址,进行链接。 if let Some(remote_peer) = std::env::args().nth(1) { let remote_peer_multiaddr: Multiaddr = remote_peer.parse()?; swarm.dial(remote_peer_multiaddr)?; println!("链接远程节点: {remote_peer}"); }
loop { // 匹配网络事件 match swarm.select_next_some().await { // 监听事件 SwarmEvent::NewListenAddr { address, .. } => { println!("本地监听地址: {address}"); } // 网络行为事件 SwarmEvent::Behaviour(event) => println!("{:?}", event), _ => {} } }}
复制代码
  • 网络行为 Behaviour:传输(transport)定义如何在网络中发送字节流,而网络行为定义发送什么样的字节流,在这里我们发送 ping/pong 消息。

  • 网络管理模块 Swarm:用于管理节点之间的所有活跃连接和挂起连接,并管理所有已打开的子流状态。Swarm 是通过传输、网络行为和节点 PeerId 来创建。

  • 节点地址:/ip4/0.0.0.0/tcp/0,表示在本机所有 ip 地址上,开一个随机的 Tcp 端口进行监听。


打开一个终端,运行:

cargo run --bin ping
复制代码


master:libp2p-learn Justin$ cargo run --bin ping   Compiling libp2p-learn v0.1.0 (/Users/Justin/workspace_rust_exercise/network-study/p2p/libp2p-learn)    Finished dev [unoptimized + debuginfo] target(s) in 8.65s     Running `target/debug/ping`节点ID: 12D3KooWR7H9SwB2yiFBKvzcVGFdpeKmuFG9qDTBTvuuuDarASST本地监听地址: /ip4/127.0.0.1/tcp/58645
复制代码


可以看到已经打印出 PeerId 和监听地址。


打开另一个终端,运行:

cargo run --bin ping /ip4/127.0.0.1/tcp/58645
复制代码


master:libp2p-learn Justin$ cargo run --bin ping /ip4/127.0.0.1/tcp/58645    Finished dev [unoptimized + debuginfo] target(s) in 0.36s     Running `target/debug/ping /ip4/127.0.0.1/tcp/58645`节点ID: 12D3KooWCUFTHNMJrR1p8vkFEFFYm4J8iPA1Wh6x2Dya5qmU1xdL链接远程节点: /ip4/127.0.0.1/tcp/58645本地监听地址: /ip4/127.0.0.1/tcp/58727Event { peer: PeerId("12D3KooWR7H9SwB2yiFBKvzcVGFdpeKmuFG9qDTBTvuuuDarASST"), result: Ok(Pong) }Event { peer: PeerId("12D3KooWR7H9SwB2yiFBKvzcVGFdpeKmuFG9qDTBTvuuuDarASST"), result: Ok(Ping { rtt: 1.234008ms }) }
复制代码


我们可以看到链接到刚刚的节点/ip4/127.0.0.1/tcp/58645 成功,也收到了发送过来的 ping/pong 的消息。


下一篇文章我们将详细解析 P2P 聊天程序。

发布于: 6 小时前阅读数: 16
用户头像

李明

关注

微信公众号:coding到灯火阑珊 2018.08.07 加入

专注于技术分享,包括Rust、Golang、分布式架构、云原生等。

评论

发布
暂无评论
Rust P2P网络应用实战-1 P2P网络核心概念及Ping程序_rust_李明_InfoQ写作社区