异步Rust编程与P2P网络开发
1. 理解异步Rust
在异步Rust编程中,程序不会出现挂起的情况,能够成功执行两个异步任务。为了进一步理解
futures
的工作原理,我们可以实现一个异步定时器。当时间到期时,
Waker
会通知
Tokio
运行时,与之关联的任务已准备好再次被轮询。当
Tokio
运行时第二次轮询该函数时,将从函数中获取一个值。
1.1 实现自定义
future
我们创建一个新的
future
来表示异步定时器,它需要完成以下操作:
1. 定时器接受一个过期时间。
2. 每当运行时执行器对其进行轮询时,会进行如下检查:
- 如果当前时间大于过期时间,将返回带有
String
值的
Poll::Ready
。
- 如果当前时间小于过期时间,它将进入休眠状态,直到过期时间,然后触发
Waker
上的
wake()
调用,这将通知异步运行时执行器再次调度和执行该任务。
以下是具体的代码实现:
use std::future::Future;
use std::pin::Pin;
use std::task::{Context, Poll};
use std::thread::sleep;
use std::time::{Duration, Instant};
struct AsyncTimer {
expiration_time: Instant,
}
impl Future for AsyncTimer {
type Output = String;
fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
if Instant::now() >= self.expiration_time {
println!("Hello, it's time for Future 1");
Poll::Ready(String::from("Future 1 has completed"))
} else {
println!("Hello, it's not yet time for Future 1. Going to sleep");
let waker = cx.waker().clone();
let expiration_time = self.expiration_time;
std::thread::spawn(move || {
let current_time = Instant::now();
if current_time < expiration_time {
std::thread::sleep(expiration_time - current_time);
}
waker.wake();
});
Poll::Pending
}
}
}
#[tokio::main]
async fn main() {
let h1 = tokio::spawn(async {
let future1 = AsyncTimer {
expiration_time: Instant::now() + Duration::from_millis(4000),
};
println!("{:?}", future1.await);
});
let h2 = tokio::spawn(async {
let file2_contents = read_from_file2().await;
println!("{:?}", file2_contents);
});
let _ = tokio::join!(h1, h2);
}
// function that simulates reading from a file
fn read_from_file2() -> impl Future<Output = String> {
async {
sleep(Duration::new(2, 0));
String::from("Future 2 has completed")
}
}
上述代码的具体步骤如下:
1. 定义
AsyncTimer
类型,用于存储过期时间。
2. 在
AsyncTimer
上实现
Future
特性。
3. 指定
future
的输出值为
String
类型。
4. 实现
poll()
函数。
- 在
poll()
函数中,首先检查
current_time >= expiration_time
。如果是,则从函数中返回带有
String
值的
Poll::Ready
。
- 如果
current_time < expiration_time
,则启动线程休眠所需的时间。
- 调用
wake()
函数,通知异步执行器再次调度该任务进行执行。
5. 在
main()
函数中,使用定时器的过期时间初始化
future
类型。
运行该程序,终端将输出以下内容:
Hello, it's not yet time for Future 1. Going to sleep
"Future 2 has completed"
Hello, it's time for Future 1
"Future 1 has completed"
下面是程序执行的流程图:
graph TD;
A[开始] --> B[创建异步任务1和2];
B --> C[执行任务1,调用AsyncTimer];
C --> D{过期时间是否到达};
D -- 否 --> E[输出未到时间,休眠并返回Poll::Pending];
E --> F[执行任务2,调用read_from_file2];
F --> G[任务2休眠2秒后完成];
D -- 是 --> H[输出时间已到,返回Poll::Ready];
G --> I[等待任务1完成];
H --> I;
I --> J[程序结束];
2. 引入对等网络
传统的分布式系统采用客户端/服务器模式,而对等(P2P)网络是另一种分布式系统。在P2P网络中,一组节点(或对等体)直接相互交互,共同提供一项公共服务,无需中央协调器或管理员。
2.1 P2P网络与客户端/服务器网络的比较
| 特性 | 客户端/服务器网络 | P2P网络 |
|---|---|---|
| 架构 | 有专门的服务器,客户端向服务器请求服务 | 节点既可以是客户端也可以是服务器,直接交互 |
| 权限 | 服务器可控制客户端访问 | 无服务器控制,数据和状态在多个节点复制 |
| 容错性 | 存在单点故障(服务器) | 无单点故障 |
| 抗审查性 | 数据集中存储,易被审查 | 数据在多个节点复制,难被审查 |
| 资源利用 | 边缘客户端资源未充分利用 | 更好地利用边缘客户端资源 |
2.2 构建P2P系统的技术要求
构建P2P系统比构建传统的客户端/服务器系统更为复杂,涉及以下几个技术要求:
1.
传输
:P2P网络中的每个对等体可以使用不同的协议,如HTTP(s)、TCP、UDP等。
2.
对等体身份
:每个对等体需要知道它想要连接并发送消息的对等体的身份。
3.
安全
:每个对等体应该能够以安全的方式与其他对等体进行通信,避免第三方拦截或修改消息。
4.
对等体路由
:每个对等体可以通过多种路由接收来自其他对等体的消息,这意味着每个对等体应该能够将消息路由到其他对等体(如果消息不是发给自己的)。
5.
消息传递
:P2P网络应该能够发送点对点消息或组消息(采用发布/订阅模式)。
6.
流复用
:P2P网络应该支持在公共通信链路上传输多个信息流,以实现与多个节点的并发通信。
下面我们将详细介绍这些技术要求:
-
传输
:TCP/IP和UDP协议非常普遍,常用于编写网络应用程序。此外,还有一些更高级的协议,如基于TCP的HTTP和基于UDP的QUIC。由于网络中对等体的多样性,P2P网络中的每个对等体应该能够发起与其他节点的连接,并能够监听多个协议的传入连接。
-
对等体身份
:与Web开发领域不同,在Web开发中,服务器通过唯一的域名(如
www.rust-lang.org
)来标识,然后通过域名服务解析为服务器的IP地址。而P2P网络中的节点需要一个唯一的身份,以便其他节点能够找到它们。P2P网络中的节点使用公钥和私钥对(非对称公钥加密)来与其他节点建立安全通信。节点在P2P网络中的身份称为
PeerId
,它是节点公钥的加密哈希值。
-
安全
:加密密钥对和
PeerId
使节点能够与其对等体建立安全、经过身份验证的通信通道。但这只是安全的一个方面。节点还需要实现授权框架,以建立哪些节点可以执行哪些操作的规则。此外,还需要解决网络级别的安全威胁,如Sybil攻击(其中一个节点操作员创建大量具有不同身份的节点,以在网络中获得有利地位)或日食攻击(一组恶意节点勾结以针对特定节点,使该节点无法访问任何合法节点)。
-
对等体路由
:P2P网络中的节点首先需要找到其他对等体才能进行通信。这通过维护一个对等体路由表来实现,该表包含网络中其他对等体的引用。但在一个拥有数千个节点且节点动态变化(即节点频繁加入和离开网络)的P2P网络中,任何单个节点都很难维护一个完整且准确的所有节点路由表。对等体路由使节点能够将不是发给自己的消息路由到目标节点。
-
消息传递
:P2P网络中的节点可以向特定节点发送消息,也可以参与广播消息协议。例如,发布/订阅模式中,节点注册对特定主题的兴趣(订阅),任何节点都可以在该主题上发送消息(发布),这些消息将被所有订阅该主题的节点接收。这种技术常用于将消息内容传输到整个网络。发布/订阅是分布式系统中消息传递的一种著名架构模式。
-
流复用
:前面提到P2P网络中的节点可以支持多种传输方式。流复用是一种在公共通信链路上发送多个信息流的方法。在P2P网络中,它允许多个独立的“逻辑”流共享一个公共的P2P传输层。当一个节点可能与不同的对等体有多个通信流,或者两个远程节点之间可能有许多并发连接时,这一点变得尤为重要。流复用有助于优化对等体之间建立连接的开销。在后端服务开发中,复用也很常见,客户端可以与服务器建立底层网络连接,然后在该底层网络连接上复用不同的流(每个流具有唯一的端口号)。
综上所述,我们对异步Rust编程中的自定义
future
和P2P网络的基础知识有了一定的了解。在后续内容中,我们将继续深入探讨如何使用
libp2p
库来构建P2P网络应用程序。
3. 理解libp2p网络的核心架构
编写自己的P2P应用程序网络层是一项艰巨的任务,因此我们可以使用一个名为
libp2p
的低级P2P网络库,它能让构建P2P应用程序变得更加容易。
libp2p
库是一个由协议、规范和库组成的模块化系统,可用于开发对等网络应用程序。截至目前,
libp2p
支持三种编程语言:Go、JavaScript和Rust。它被许多流行项目使用,如IPFS、Filecoin和Polkadot。
libp2p
构建强大对等网络的关键模块如下表所示:
| 模块 | 功能 |
| ---- | ---- |
| 传输 | 负责数据从一个对等节点到另一个对等节点的实际传输和接收 |
| 身份 | 使用公钥加密(PKI)作为对等节点身份的基础,为每个节点生成唯一的对等ID |
| 安全 | 节点使用私钥对消息进行签名,传输连接可升级为安全加密通道,确保远程对等体相互信任,防止第三方拦截通信 |
| 对等发现 | 使对等节点能够在
libp2p
网络中找到并相互通信 |
下面是这些模块协作的流程图:
graph LR;
A[传输] --> B[身份];
B --> C[安全];
C --> D[对等发现];
D --> A;
总结
-
异步Rust编程
:
-
并发和并行是不同的概念,多线程和异步是两种并发编程模型。多线程使用操作系统原生线程,由操作系统进行任务调度;而异步使用异步运行时(如
Tokio),通过轻量级的“绿色线程”在操作系统线程上调度多个任务。 -
未来(
Future)是一种异步计算,可在未来某个时间点返回值,它有Poll::Pending和Poll::Ready(future_value)两种状态。当任务未准备好返回值时,会与Waker注册,Waker的wake()方法可通知异步执行器再次轮询该任务。 -
通过实现自定义
Future(如AsyncTimer),我们可以更深入地理解异步编程的工作原理。
-
并发和并行是不同的概念,多线程和异步是两种并发编程模型。多线程使用操作系统原生线程,由操作系统进行任务调度;而异步使用异步运行时(如
-
P2P网络
:
- P2P网络与传统的客户端/服务器网络不同,它允许节点直接相互交互,具有无权限限制、容错和抗审查等优点。
- 构建P2P系统涉及传输、对等体身份、安全、对等体路由、消息传递和流复用等技术要求。
-
libp2p库为构建P2P应用程序提供了便利,其核心架构包含传输、身份、安全和对等发现等关键模块。
通过对异步Rust编程和P2P网络的学习,我们已经具备了构建各种异步应用程序或组件的基础。可以利用这些知识,以标准且易读(因此易于维护)的方式开发出高效、强大的分布式应用程序。
超级会员免费看
881

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



