25、使用异步 Rust 构建 P2P 节点

使用异步 Rust 构建 P2P 节点

在本文中,我们将深入探讨如何使用 Rust 语言和 libp2p 库构建 P2P 网络应用。我们将从理解 libp2p 网络的核心架构开始,逐步介绍如何生成节点的 Peer ID 和密钥对、使用多地址进行节点定位、管理网络连接的 Swarm 模块,以及实现节点间的 ping 消息交换和节点自动发现功能。

1. libp2p 网络核心架构理解

libp2p 网络的核心架构主要包含以下几个关键组件:
- 对等路由(Peer routing) :借助其他对等节点的信息,实现与目标对等节点的通信。
- 内容发现(Content discovery) :让对等节点在不清楚具体哪个节点拥有所需内容的情况下,仍能获取到该内容。
- 消息传递(Messaging) :实现向对特定主题感兴趣的一组对等节点发送消息。

下面是一个简单的 mermaid 流程图,展示了这些组件之间的关系:

graph LR
    A[对等路由] --> B[通信]
    C[内容发现] --> B
    D[消息传递] --> B

2. 生成 Peer ID 和密钥对

2.1 创建项目

首先,我们需要创建一个新的 Rust 项目:

cargo new p2p-learn

然后,在 Cargo.toml 文件中添加以下依赖:

libp2p = "0.42.2"
tokio = { version = "1.16.1", features = ["full"] }

2.2 编写代码

src 文件夹下创建 bin 文件夹,并在其中创建 iter1.rs 文件,添加以下代码:

use libp2p::{identity, PeerId};
#[tokio::main]
async fn main() {
    let new_key = identity::Keypair::generate_ed25519();
    let new_peer_id = PeerId::from(new_key.public());
    println!("New peer id: {:?}", new_peer_id);
}

2.3 代码解释

  • identity::Keypair::generate_ed25519() :生成一个 ED25519 类型的密钥对,包含私钥和公钥,私钥不会被共享。
  • PeerId::from(new_key.public()) :从密钥对的公钥生成一个 Peer ID,在 libp2p 中,公钥不会直接用于标识对等节点,而是使用其哈希版本作为 Peer ID。

2.4 运行程序

在终端中运行以下命令:

cargo run --bin iter1

你将看到类似以下的输出:

New peer id: PeerId("12D3KooWBu3fmjZgSMLkQ2p1DG35UmEayYBrhsk6WEe1xco1JFbV")

3. 多地址(Multiaddresses)

3.1 多地址的组成

在 P2P 网络中,节点通过多地址来共享其联系信息,多地址包含网络地址和 Peer ID。例如:
- Peer ID 部分 /p2p/12D3KooWBu3fmjZgSMLkQ2p1DG35UmEayYBrhsk6WEe1xco1JFbV
- 网络地址部分 /ip4/192.158.1.23/tcp/1234
- 完整多地址 /ip4/192.158.1.23/tcp/1234/p2p/12D3KooWBu3fmjZgSMLkQ2p1DG35UmEayYBrhsk6WEe1xco1JFbV

3.2 libp2p 的处理

libp2p 库会使用 DNS 协议将基于名称的地址(如 /ip4/192.158.1.23 )转换为常规的 IP 地址。

下面是一个表格,总结了多地址的相关信息:
| 部分 | 示例 | 说明 |
| ---- | ---- | ---- |
| Peer ID 部分 | /p2p/12D3KooWBu3fmjZgSMLkQ2p1DG35UmEayYBrhsk6WEe1xco1JFbV | 唯一标识节点 |
| 网络地址部分 | /ip4/192.158.1.23/tcp/1234 | 表示节点的网络位置和使用的协议 |
| 完整多地址 | /ip4/192.158.1.23/tcp/1234/p2p/12D3KooWBu3fmjZgSMLkQ2p1DG35UmEayYBrhsk6WEe1xco1JFbV | 用于节点间交换联系信息 |

4. Swarm 和网络行为

4.1 Swarm 简介

Swarm 是 libp2p 中 P2P 节点的网络管理模块,它维护着与远程节点的所有活动和待处理连接,并管理已打开的所有子流的状态。

4.2 编写代码

src/bin 文件夹下创建 iter2.rs 文件,添加以下代码:

use libp2p::swarm::{DummyBehaviour, Swarm, SwarmEvent};
use libp2p::futures::StreamExt;
use libp2p::{identity, PeerId};
use std::error::Error;
#[tokio::main]
async fn main() -> Result<(), Box<dyn Error>> {
    let new_key = identity::Keypair::generate_ed25519();
    let new_peer_id = PeerId::from(new_key.public());
    println!("local peer id is: {:?}", new_peer_id);
    let behaviour = DummyBehaviour::default();
    let transport = libp2p::development_transport(new_key).await?;
    let mut swarm = Swarm::new(transport, behaviour, new_peer_id);
    swarm.listen_on("/ip4/0.0.0.0/tcp/0".parse()?)?;
    loop {
        match swarm.select_next_some().await {
            SwarmEvent::NewListenAddr { address, .. } => {
                println!("Listening on local address {:?}", address)
            }
            _ => {}
        }
    }
}

4.3 代码解释

  • DummyBehaviour::default() :创建一个虚拟的网络行为,用于与 Swarm 关联。
  • libp2p::development_transport(new_key).await? :使用新的密钥对创建一个传输层。
  • Swarm::new(transport, behaviour, new_peer_id) :使用传输层、网络行为和 Peer ID 创建一个新的 Swarm。
  • swarm.listen_on("/ip4/0.0.0.0/tcp/0".parse()?)? :监听一个多地址,等待传入的连接。

4.4 运行程序

打开两个终端,分别运行以下命令:

# 终端 1
cargo run --bin iter2
# 终端 2
cargo run --bin iter2

你将看到类似以下的输出:

# 终端 1
local peer id is: PeerId("12D3KooWByvE1LD4W1oaD2AgeVWAEu9eK4RtD3GuKU1jVEZUvzNm")
Listening on local address "/ip4/127.0.0.1/tcp/55436"
Listening on local address "/ip4/192.168.1.74/tcp/55436"

# 终端 2
local peer id is: PeerId("12D3KooWQiQZA5zcLzhF86kuRoq9f6yAgiLtGqD5bDG516kVzW46")
Listening on local address "/ip4/127.0.0.1/tcp/55501"
Listening on local address "/ip4/192.168.1.74/tcp/55501"

5. 节点间交换 ping 命令

5.1 编写代码

src/bin 文件夹下创建 iter3.rs 文件,添加以下代码:

use libp2p::swarm::{Swarm, SwarmEvent};
use libp2p::futures::StreamExt;
use libp2p::ping::{Ping, PingConfig};
use libp2p::{identity, Multiaddr, PeerId};
use std::error::Error;
#[tokio::main]
async fn main() -> Result<(), Box<dyn Error>> {
    let new_key = identity::Keypair::generate_ed25519();
    let new_peer_id = PeerId::from(new_key.public());
    println!("local peer id is: {:?}", new_peer_id);
    let transport = libp2p::development_transport(new_key).await?;
    let behaviour = Ping::new(PingConfig::new().with_keep_alive(true));
    let mut swarm = Swarm::new(transport, behaviour, new_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!("Dialed remote peer: {:?}", remote_peer);
    }
    loop {
        match swarm.select_next_some().await {
            SwarmEvent::NewListenAddr { address, .. } => {
                println!("Listening on local address {:?}", address)
            }
            SwarmEvent::Behaviour(event) => println!(
                "Event received from peer is {:?}",
                event
            ),
            _ => {}
        }
    }
}

5.2 代码解释

  • Ping::new(PingConfig::new().with_keep_alive(true)) :创建一个新的网络行为,允许节点之间交换 ping 消息。
  • swarm.dial(remote_peer_multiaddr)? :如果指定了远程节点的多地址,则尝试连接到该远程节点。

5.3 运行程序

打开两个终端,分别运行以下命令:

# 终端 1
cargo run --bin iter3
# 终端 2
cargo run --bin iter3 /ip4/127.0.0.1/tcp/55872

节点 1 会打印出监听地址,节点 2 会连接到节点 1,并开始交换 ping 和 pong 消息。

6. 自动发现对等节点

6.1 编写代码

src/bin 文件夹下创建 iter4.rs 文件,添加以下代码:

use libp2p::{
    futures::StreamExt,
    identity,
    mdns::{Mdns, MdnsConfig, MdnsEvent},
    swarm::{Swarm, SwarmEvent},
    PeerId,
};
use std::error::Error;
#[tokio::main]
async fn main() -> Result<(), Box<dyn Error>> {
    let id_keys = identity::Keypair::generate_ed25519();
    let peer_id = PeerId::from(id_keys.public());
    println!("Local peer id: {:?}", peer_id);
    let transport = libp2p::development_transport(id_keys).await?;
    let behaviour = Mdns::new(MdnsConfig::default()).await?;
    let mut swarm = Swarm::new(transport, behaviour, peer_id);
    swarm.listen_on("/ip4/0.0.0.0/tcp/0".parse()?)?;
    loop {
        match swarm.select_next_some().await {
            SwarmEvent::NewListenAddr { address, .. } => {
                println!("Listening on local address {:?}", address)
            }
            SwarmEvent::Behaviour(MdnsEvent::Discovered(peers)) => {
                for (peer, addr) in peers {
                    println!("discovered {} {}", peer, addr);
                }
            }
            SwarmEvent::Behaviour(MdnsEvent::Expired(expired)) => {
                for (peer, addr) in expired {
                    println!("expired {} {}", peer, addr);
                }
            }
            _ => {}
        }
    }
}

6.2 代码解释

  • Mdns::new(MdnsConfig::default()).await? :创建一个 mDNS 网络行为,用于自动发现本地网络上的其他 libp2p 节点。
  • SwarmEvent::Behaviour(MdnsEvent::Discovered(peers)) :当发现新的节点时,打印出节点的信息。
  • SwarmEvent::Behaviour(MdnsEvent::Expired(expired)) :当某个节点的信息过期时,打印出过期信息。

6.3 运行程序

打开三个终端,分别运行以下命令:

# 终端 1
cargo run --bin iter4
# 终端 2
cargo run --bin iter4
# 终端 3
cargo run --bin iter4

每个节点会打印出自己的监听地址,并且会自动发现其他节点。例如,节点 2 会发现节点 1,节点 3 会发现节点 1 和节点 2。

通过以上步骤,我们成功地使用 Rust 和 libp2p 库构建了一个简单的 P2P 网络,实现了节点的身份标识、连接管理、消息交换和自动发现功能。

7. 总结与回顾

7.1 关键组件回顾

我们对之前介绍的关键组件进行一个总结,如下表所示:
| 组件 | 功能 | 代码实现示例 |
| ---- | ---- | ---- |
| 对等路由(Peer routing) | 借助其他对等节点信息与目标对等节点通信 | - |
| 内容发现(Content discovery) | 让对等节点在不知具体节点的情况下获取内容 | - |
| 消息传递(Messaging) | 向对特定主题感兴趣的节点组发送消息 | - |
| Peer ID 和密钥对 | 用于节点身份标识和认证 | ```rust
use libp2p::{identity, PeerId};

[tokio::main]

async fn main() {
let new_key = identity::Keypair::generate_ed25519();
let new_peer_id = PeerId::from(new_key.public());
println!(“New peer id: {:?}”, new_peer_id);
}
| | 多地址(Multiaddresses) | 节点共享联系信息,包含网络地址和 Peer ID | - | | Swarm | 网络管理模块,维护连接和子流状态 | rust
use libp2p::swarm::{DummyBehaviour, Swarm, SwarmEvent};
use libp2p::futures::StreamExt;
use libp2p::{identity, PeerId};
use std::error::Error;

[tokio::main]

async fn main() -> Result<(), Box > {
let new_key = identity::Keypair::generate_ed25519();
let new_peer_id = PeerId::from(new_key.public());
println!(“local peer id is: {:?}”, new_peer_id);
let behaviour = DummyBehaviour::default();
let transport = libp2p::development_transport(new_key).await?;
let mut swarm = Swarm::new(transport, behaviour, new_peer_id);
swarm.listen_on(“/ip4/0.0.0.0/tcp/0”.parse()?)?;
loop {
match swarm.select_next_some().await {
SwarmEvent::NewListenAddr { address, .. } => {
println!(“Listening on local address {:?}”, address)
}
_ => {}
}
}
}
| | ping 消息交换 | 节点间交换 ping 和 pong 消息 | rust
use libp2p::swarm::{Swarm, SwarmEvent};
use libp2p::futures::StreamExt;
use libp2p::ping::{Ping, PingConfig};
use libp2p::{identity, Multiaddr, PeerId};
use std::error::Error;

[tokio::main]

async fn main() -> Result<(), Box > {
let new_key = identity::Keypair::generate_ed25519();
let new_peer_id = PeerId::from(new_key.public());
println!(“local peer id is: {:?}”, new_peer_id);
let transport = libp2p::development_transport(new_key).await?;
let behaviour = Ping::new(PingConfig::new().with_keep_alive(true));
let mut swarm = Swarm::new(transport, behaviour, new_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!(“Dialed remote peer: {:?}”, remote_peer);
}
loop {
match swarm.select_next_some().await {
SwarmEvent::NewListenAddr { address, .. } => {
println!(“Listening on local address {:?}”, address)
}
SwarmEvent::Behaviour(event) => println!(
“Event received from peer is {:?}”,
event
),
_ => {}
}
}
}
| | 自动发现对等节点 | 节点自动发现本地网络上的其他节点 | rust
use libp2p::{
futures::StreamExt,
identity,
mdns::{Mdns, MdnsConfig, MdnsEvent},
swarm::{Swarm, SwarmEvent},
PeerId,
};
use std::error::Error;

[tokio::main]

async fn main() -> Result<(), Box > {
let id_keys = identity::Keypair::generate_ed25519();
let peer_id = PeerId::from(id_keys.public());
println!(“Local peer id: {:?}”, peer_id);
let transport = libp2p::development_transport(id_keys).await?;
let behaviour = Mdns::new(MdnsConfig::default()).await?;
let mut swarm = Swarm::new(transport, behaviour, peer_id);
swarm.listen_on(“/ip4/0.0.0.0/tcp/0”.parse()?)?;
loop {
match swarm.select_next_some().await {
SwarmEvent::NewListenAddr { address, .. } => {
println!(“Listening on local address {:?}”, address)
}
SwarmEvent::Behaviour(MdnsEvent::Discovered(peers)) => {
for (peer, addr) in peers {
println!(“discovered {} {}”, peer, addr);
}
}
SwarmEvent::Behaviour(MdnsEvent::Expired(expired)) => {
for (peer, addr) in expired {
println!(“expired {} {}”, peer, addr);
}
}
_ => {}
}
}
}
``` |

7.2 整体流程回顾

下面是一个 mermaid 流程图,展示了构建 P2P 节点的整体流程:

graph LR
    A[创建项目并添加依赖] --> B[生成 Peer ID 和密钥对]
    B --> C[使用多地址定位节点]
    C --> D[创建 Swarm 管理网络连接]
    D --> E[实现节点间 ping 消息交换]
    E --> F[实现节点自动发现]

8. 拓展与思考

8.1 更多网络行为

在前面的示例中,我们使用了 DummyBehaviour Ping 等网络行为。实际上,libp2p 还提供了许多其他的网络行为,如 mDNS 用于本地网络节点发现、Kademlia 用于对等路由和内容路由等。你可以根据自己的需求选择合适的网络行为进行组合,以实现更复杂的 P2P 网络功能。

8.2 安全性考虑

在 P2P 网络中,安全性是一个重要的问题。我们使用了 ED25519 密钥对来进行节点的身份认证,但在实际应用中,还需要考虑更多的安全措施,如数据加密、防止中间人攻击等。libp2p 提供了一些安全相关的功能,如 PKI、TLS、Noise 等,你可以根据需要进行配置。

8.3 性能优化

随着 P2P 网络中节点数量的增加,性能可能会成为一个瓶颈。你可以通过优化网络行为、合理配置传输层、使用缓存等方式来提高网络性能。同时,异步 Rust 提供了强大的并发处理能力,可以充分利用多核 CPU 的性能。

9. 总结

通过本文的学习,我们掌握了使用 Rust 和 libp2p 库构建 P2P 网络应用的基本方法。我们从理解 libp2p 网络的核心架构开始,逐步实现了节点的身份标识、连接管理、消息交换和自动发现等功能。同时,我们也对拓展功能和安全性、性能等方面进行了思考。希望这些知识能够帮助你在实际项目中构建更加健壮和高效的 P2P 网络。

总之,使用 Rust 和 libp2p 构建 P2P 网络是一个充满挑战和机遇的领域,随着技术的不断发展,我们可以期待更多创新的应用出现。

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符  | 博主筛选后可见
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值