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` package
master: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/58727
Event { 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聊天程序。


更多文章,请关注公众号:coding到灯火阑珊

猜你喜欢

转载自blog.csdn.net/lonewolf79218/article/details/125959241
P2P