使用异步 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 网络是一个充满挑战和机遇的领域,随着技术的不断发展,我们可以期待更多创新的应用出现。
超级会员免费看
669

被折叠的 条评论
为什么被折叠?



