searchusermenu
  • 发布文章
  • 消息中心
点赞
收藏
评论
分享
原创

Tokio 缓存异步 IO 学习文档

2024-10-28 09:27:15
6
0

一、为什么使用带缓冲的读写?

在 Rust 的异步编程中,AsyncReadExtAsyncWriteExt 提供了方便的读写方法。然而,每次调用这些方法,都会向操作系统发起系统调用。如果处理大量数据时,每次只读或写少量字节,就会频繁地切换上下文,造成 ​CPU 时间浪费​,降低 IO 效率。

缓冲读写的好处

  1. 减少系统调用次数​:数据会先写入缓冲区,当缓冲满了或条件满足时才向操作系统发起系统调用。
  2. 按行读取​:缓冲读操作可以识别换行符并按行读取数据,这比按字节读取更方便。
  3. 高效的数据处理​:减少 CPU 资源浪费,提高 IO 性能。

二、使用 BufReaderBufWriter 实现缓冲读写

tokio::io 模块提供了 BufReader 和 ​BufWriter,用于为 Reader 和 Writer 添加缓冲功能。

BufReader 读取文件按行打印

use tokio::{fs::File, io::{AsyncBufReadExt, BufReader}, runtime};
​
fn main() {
    let rt = runtime::Runtime::new().unwrap();
    rt.block_on(async {
        let f = File::open("a.log").await.unwrap();
        let mut lines = BufReader::new(f).lines();
​
        while let Some(line) = lines.next_line().await.unwrap() {
            println!("read line: {}", line);
        }
    });
}

解释​:

  • BufReader 读取文件时,会将内容缓存在内部缓冲区中。
  • 按行读取​:lines() 方法将内容按行分割,next_line() 异步获取下一行。

BufWriter 写文件

use tokio::{fs::File, io::{AsyncWriteExt, BufWriter}, runtime};
​
fn main() {
    let rt = runtime::Runtime::new().unwrap();
    rt.block_on(async {
        let f = File::create("output.txt").await.unwrap();
        let mut writer = BufWriter::new(f);
​
        writer.write_all(b"Hello, world!\n").await.unwrap();
        writer.flush().await.unwrap();  // 确保缓冲区数据写入文件
    });
}

解释​:

  • BufWriter 将数据写入缓冲区,当缓冲区满或调用 flush() 时,才会将数据写入文件。
  • ​**write_all()**​:写入字节数据。

三、按自定义分隔符读取数据:split()

split() 可以将读取的内容按指定的字节分隔符分割成多个片段。

示例:按换行符分割文件

use tokio::{fs::File, io::{AsyncBufReadExt, BufReader}, runtime};
​
fn main() {
    let rt = runtime::Runtime::new().unwrap();
    rt.block_on(async {
        let f = File::open("a.log").await.unwrap();
        let mut segments = BufReader::new(f).split(b'\n');
​
        while let Some(segment) = segments.next_segment().await.unwrap() {
            println!("segment: {}", String::from_utf8(segment).unwrap());
        }
    });
}

解释​:

  • ​**split(b'\n')**​:按换行符分割文件内容。
  • **每次调用 **next_segment() 获取一个片段,返回 Vec<u8>

四、随机读写文件:Seek

tokio::io::AsyncSeekExt 提供了 随机读写 功能,可以设置文件的偏移位置,实现非顺序的读写。

示例:设置偏移位置读取文件

use std::io::SeekFrom;
use tokio::{fs::File, io::{AsyncReadExt, AsyncSeekExt}, runtime};
​
fn main() {
    let rt = runtime::Runtime::new().unwrap();
    rt.block_on(async {
        let mut f = File::open("a.log").await.unwrap();
​
        f.seek(SeekFrom::Start(5)).await.unwrap();  // 从第5个字节开始读取
        let mut content = String::new();
        f.read_to_string(&mut content).await.unwrap();
        println!("Data: {}", content);
​
        f.rewind().await.unwrap();  // 将偏移指针重置到文件开头
    });
}

解释​:

  • ​**seek()**​:将偏移指针移动到指定位置。
  • ​**rewind()**​:重置偏移指针到文件开头。

五、标准输入输出

tokio::io 提供了 stdin() 和 ​**stdout()**​,用于异步读取标准输入和输出。

示例:读取用户输入并回显

use tokio::{io::{AsyncReadExt, AsyncWriteExt}, runtime};
​
fn main() {
    let rt = runtime::Runtime::new().unwrap();
    rt.block_on(async {
        let mut stdin = tokio::io::stdin();
        let mut stdout = tokio::io::stdout();
​
        let mut buffer = vec![0; 1024];
        loop {
            stdout.write(b"Enter something: ").await.unwrap();
            stdout.flush().await.unwrap();
​
            let n = stdin.read(&mut buffer).await.unwrap();
            if n == 0 {
                break;  // 用户输入结束
            }
​
            stdout.write(&buffer[..n]).await.unwrap();
            stdout.flush().await.unwrap();
        }
    });
}

解释​:

  • 读取用户输入​:stdin.read() 从标准输入读取数据。
  • 输出到终端​:stdout.write() 将数据回显。

六、全双工通信:DuplexStream

tokio::io::duplex() 提供了类似套接字的全双工管道,支持读写同时进行。

示例:模拟客户端和服务端的通信

use tokio::{io::{self, AsyncReadExt, AsyncWriteExt}, runtime, time};
​
#[tokio::main]
async fn main() {
    let (mut client, mut server) = io::duplex(64);  // 创建双向管道
​
    // 启动一个任务:服务端发送消息
    tokio::spawn(async move {
        server.write_all(b"Hello from server").await.unwrap();
    });
​
    // 客户端读取来自服务端的消息
    let mut buf = vec![0; 64];
    let n = client.read(&mut buf).await.unwrap();
    println!("Client received: {}", String::from_utf8_lossy(&buf[..n]));
}

解释​:

  • ​**duplex()**​:创建全双工读写管道。
  • 客户端和服务端通信​:服务端写入数据,客户端读取数据。

七、拆分 Reader 和 Writer:split()

split() 方法可以将一个可读写的目标(如 TcpStream)拆分为 读半部分 和 ​写半部分​。

示例:拆分 DuplexStream

use tokio::{io::{self, AsyncReadExt, AsyncWriteExt}, runtime};
​
#[tokio::main]
async fn main() {
    let (client, server) = io::duplex(64);
​
    let (mut reader, writer) = io::split(client);  // 拆分为读和写部分
​
    // 关闭不使用的写部分
    drop(writer);
​
    let mut buf = vec![0; 64];
    let n = reader.read(&mut buf).await.unwrap();
    println!("Read from server: {}", String::from_utf8_lossy(&buf[..n]));
}

八、总结

**在这一部分,教程介绍了如何使用 **Tokio 实现异步 IO,包括:

  1. ​**BufReaderBufWriter**​:用于高效的缓冲读写。
  2. w按行读取和自定义分隔符​:方便处理文本数据。
  3. 随机读写文件​:通过设置偏移指针实现非顺序读写。
  4. 标准输入输出​:异步处理用户输入和终端输出。
  5. 全双工通信​:通过 DuplexStream 实现双向数据传输。
  6. 拆分 Reader 和 Writer​:提高代码的灵活性。w
0条评论
0 / 1000
l****n
17文章数
0粉丝数
l****n
17 文章 | 0 粉丝
原创

Tokio 缓存异步 IO 学习文档

2024-10-28 09:27:15
6
0

一、为什么使用带缓冲的读写?

在 Rust 的异步编程中,AsyncReadExtAsyncWriteExt 提供了方便的读写方法。然而,每次调用这些方法,都会向操作系统发起系统调用。如果处理大量数据时,每次只读或写少量字节,就会频繁地切换上下文,造成 ​CPU 时间浪费​,降低 IO 效率。

缓冲读写的好处

  1. 减少系统调用次数​:数据会先写入缓冲区,当缓冲满了或条件满足时才向操作系统发起系统调用。
  2. 按行读取​:缓冲读操作可以识别换行符并按行读取数据,这比按字节读取更方便。
  3. 高效的数据处理​:减少 CPU 资源浪费,提高 IO 性能。

二、使用 BufReaderBufWriter 实现缓冲读写

tokio::io 模块提供了 BufReader 和 ​BufWriter,用于为 Reader 和 Writer 添加缓冲功能。

BufReader 读取文件按行打印

use tokio::{fs::File, io::{AsyncBufReadExt, BufReader}, runtime};
​
fn main() {
    let rt = runtime::Runtime::new().unwrap();
    rt.block_on(async {
        let f = File::open("a.log").await.unwrap();
        let mut lines = BufReader::new(f).lines();
​
        while let Some(line) = lines.next_line().await.unwrap() {
            println!("read line: {}", line);
        }
    });
}

解释​:

  • BufReader 读取文件时,会将内容缓存在内部缓冲区中。
  • 按行读取​:lines() 方法将内容按行分割,next_line() 异步获取下一行。

BufWriter 写文件

use tokio::{fs::File, io::{AsyncWriteExt, BufWriter}, runtime};
​
fn main() {
    let rt = runtime::Runtime::new().unwrap();
    rt.block_on(async {
        let f = File::create("output.txt").await.unwrap();
        let mut writer = BufWriter::new(f);
​
        writer.write_all(b"Hello, world!\n").await.unwrap();
        writer.flush().await.unwrap();  // 确保缓冲区数据写入文件
    });
}

解释​:

  • BufWriter 将数据写入缓冲区,当缓冲区满或调用 flush() 时,才会将数据写入文件。
  • ​**write_all()**​:写入字节数据。

三、按自定义分隔符读取数据:split()

split() 可以将读取的内容按指定的字节分隔符分割成多个片段。

示例:按换行符分割文件

use tokio::{fs::File, io::{AsyncBufReadExt, BufReader}, runtime};
​
fn main() {
    let rt = runtime::Runtime::new().unwrap();
    rt.block_on(async {
        let f = File::open("a.log").await.unwrap();
        let mut segments = BufReader::new(f).split(b'\n');
​
        while let Some(segment) = segments.next_segment().await.unwrap() {
            println!("segment: {}", String::from_utf8(segment).unwrap());
        }
    });
}

解释​:

  • ​**split(b'\n')**​:按换行符分割文件内容。
  • **每次调用 **next_segment() 获取一个片段,返回 Vec<u8>

四、随机读写文件:Seek

tokio::io::AsyncSeekExt 提供了 随机读写 功能,可以设置文件的偏移位置,实现非顺序的读写。

示例:设置偏移位置读取文件

use std::io::SeekFrom;
use tokio::{fs::File, io::{AsyncReadExt, AsyncSeekExt}, runtime};
​
fn main() {
    let rt = runtime::Runtime::new().unwrap();
    rt.block_on(async {
        let mut f = File::open("a.log").await.unwrap();
​
        f.seek(SeekFrom::Start(5)).await.unwrap();  // 从第5个字节开始读取
        let mut content = String::new();
        f.read_to_string(&mut content).await.unwrap();
        println!("Data: {}", content);
​
        f.rewind().await.unwrap();  // 将偏移指针重置到文件开头
    });
}

解释​:

  • ​**seek()**​:将偏移指针移动到指定位置。
  • ​**rewind()**​:重置偏移指针到文件开头。

五、标准输入输出

tokio::io 提供了 stdin() 和 ​**stdout()**​,用于异步读取标准输入和输出。

示例:读取用户输入并回显

use tokio::{io::{AsyncReadExt, AsyncWriteExt}, runtime};
​
fn main() {
    let rt = runtime::Runtime::new().unwrap();
    rt.block_on(async {
        let mut stdin = tokio::io::stdin();
        let mut stdout = tokio::io::stdout();
​
        let mut buffer = vec![0; 1024];
        loop {
            stdout.write(b"Enter something: ").await.unwrap();
            stdout.flush().await.unwrap();
​
            let n = stdin.read(&mut buffer).await.unwrap();
            if n == 0 {
                break;  // 用户输入结束
            }
​
            stdout.write(&buffer[..n]).await.unwrap();
            stdout.flush().await.unwrap();
        }
    });
}

解释​:

  • 读取用户输入​:stdin.read() 从标准输入读取数据。
  • 输出到终端​:stdout.write() 将数据回显。

六、全双工通信:DuplexStream

tokio::io::duplex() 提供了类似套接字的全双工管道,支持读写同时进行。

示例:模拟客户端和服务端的通信

use tokio::{io::{self, AsyncReadExt, AsyncWriteExt}, runtime, time};
​
#[tokio::main]
async fn main() {
    let (mut client, mut server) = io::duplex(64);  // 创建双向管道
​
    // 启动一个任务:服务端发送消息
    tokio::spawn(async move {
        server.write_all(b"Hello from server").await.unwrap();
    });
​
    // 客户端读取来自服务端的消息
    let mut buf = vec![0; 64];
    let n = client.read(&mut buf).await.unwrap();
    println!("Client received: {}", String::from_utf8_lossy(&buf[..n]));
}

解释​:

  • ​**duplex()**​:创建全双工读写管道。
  • 客户端和服务端通信​:服务端写入数据,客户端读取数据。

七、拆分 Reader 和 Writer:split()

split() 方法可以将一个可读写的目标(如 TcpStream)拆分为 读半部分 和 ​写半部分​。

示例:拆分 DuplexStream

use tokio::{io::{self, AsyncReadExt, AsyncWriteExt}, runtime};
​
#[tokio::main]
async fn main() {
    let (client, server) = io::duplex(64);
​
    let (mut reader, writer) = io::split(client);  // 拆分为读和写部分
​
    // 关闭不使用的写部分
    drop(writer);
​
    let mut buf = vec![0; 64];
    let n = reader.read(&mut buf).await.unwrap();
    println!("Read from server: {}", String::from_utf8_lossy(&buf[..n]));
}

八、总结

**在这一部分,教程介绍了如何使用 **Tokio 实现异步 IO,包括:

  1. ​**BufReaderBufWriter**​:用于高效的缓冲读写。
  2. w按行读取和自定义分隔符​:方便处理文本数据。
  3. 随机读写文件​:通过设置偏移指针实现非顺序读写。
  4. 标准输入输出​:异步处理用户输入和终端输出。
  5. 全双工通信​:通过 DuplexStream 实现双向数据传输。
  6. 拆分 Reader 和 Writer​:提高代码的灵活性。w
文章来自个人专栏
rust与golang等并发编程
10 文章 | 1 订阅
0条评论
0 / 1000
请输入你的评论
0
0