likes
comments
collection
share

从浅到深-Tokio强大的Rust异步框架,强大的异步IO

作者站长头像
站长
· 阅读数 7

从浅到深-Tokio强大的Rust异步框架,强大的异步IO

大家好,我是梦兽编程。欢迎回来与梦兽编程一起刷Rust的系列。微信公众号【梦兽编程】即可加入梦兽编程微信交流群与我交流。

语言只是我们程序员实现途径中的工具,语言的生态丰富会让我们做起事来事半功倍。从今天开始我们开始进入Rust的生态学习。

在Rust中同样支持异步编程,只不过一般我们不会使用Rust官方,而是使用第三方依赖Tokio。今天就我们进入新的篇章《Tokio从浅到深之旅》。

IO

在计算机中IO是以个非常重要的指标,它决定你的处理速度。计算机无法就是数据的输入/输出。所谓的IO就是input/output的缩写。

同样Rust语言的标准库中也是提供了非常多的IO操作,例如TCP连接,UDP套接字,读取和写入文件等。这些操作都是同步或阻塞的,这意味着当它们被调用时,当前线程可能会停止执行并进入睡眠状态,直到它被解除阻塞。

如果有非常庞大的输入数据需要处理时,等待处理的时间将非常,上一次的分享中。使用Future的时候,这种行为会有问题,因为我们希望在等待I/O完成时继续执行我们可能拥有的其他Future。但是这些工作Tokio已经为我们实现了。今天就让我们一起学习Tokio中常用的异步IO操作。

AsyncRead和AsyncWrite

在Tokio中提供了两个traits分别AsyncRead和AsyncWrite。这两个 traits 分别继承自 std::io 中的 Read 和 Write,并添加了非阻塞的特性。在无可用数据时,所有非阻塞 I/O 对象都必须返回一个错误而不是阻塞当前线程。

use std::io::Read;
pub trait AsyncRead: Read {
    // ...
    // various provided methods
    // ...
}

// 核心的实现Future
fn poll_read(&mut self, buf: &mut [u8]) -> Poll<usize, std::io::Error> {
    match self.read(buf) {
        Ok(t) => Ok(Async::Ready(t)),
        Err(ref e) if e.kind() == std::io::ErrorKind::WouldBlock => {
            Ok(Async::NotReady)
        }
        Err(e) => Err(e),
    }
}

异步打开文件

如果你想使用异步读写文件,只需要把std::io 都替换成tokio::io 即可,比如我们需要打开一个文件。

use tokio::fs::File;
use tokio::io::{self, AsyncReadExt};
use tokio::runtime::Runtime;

#[tokio::main]
async fn main() -> io::Result<()> {
    let file_path = "path/to/your/file.txt";
    let mut file = File::open(file_path).await?;

    let mut contents = String::new();
    file.read_to_string(&mut contents).await?;

    println!("File contents:\n{}", contents);

    Ok(())
}

需要注意的是你未来的所有操作都需要添加await

拆分 I/O

通常指的是将一个单一的 I/O 资源(如一个 TCP 流)分成两个独立的半部分:一个用于读取(ReadHalf)和一个用于写入(WriteHalf)。这样做的目的是为了能够在不同的任务中独立地处理读和写操作,从而提高并发处理的效率。

例如,对于一个 TcpStream,你可以使用 split 方法将其拆分为两个部分:

use tokio::net::TcpStream;
use tokio::io::{self, AsyncReadExt, AsyncWriteExt};

#[tokio::main]
async fn main() -> io::Result<()> {
    let stream = TcpStream::connect("127.0.0.1:8080").await?;
    let (read_half, write_half) = stream.split();

    // 现在可以在不同的任务中独立地使用 read_half 和 write_half
    // 例如,一个任务可以读取数据,而另一个任务可以写入数据

    Ok(())
}

拆分后我们得到两个心跳,你可以分别把读和写分别放到两个异步管理器中单独运行。

		// 在一个任务中处理读取操作
    tokio::spawn(async move {
        let mut reader = read_half;
        let mut buffer = [0; 1024];

        loop {
            let n = reader.read(&mut buffer).await.unwrap();

            if n == 0 {
                break; // 连接已关闭
            }

            println!("Received: {}", String::from_utf8_lossy(&buffer[..n]));
        }
    });
    
    // 在另一个任务中处理写入操作
    tokio::spawn(async move {
        let mut writer = write_half;
        let message = "Hello, world!".as_bytes();

        writer.write_all(message).await.unwrap();
    });
转载自:https://juejin.cn/post/7353160406652862527
评论
请登录