Rust 中的设备 I/O 操作全解析
1. 缓冲读写操作
在处理文件和流等 I/O 类型时,读写操作是最基本的操作,对于使用多种系统资源至关重要。在 Rust 中,有不同的方式来进行 I/O 读写操作。
1.1 基本的 Read 和 Write 特性
Rust 标准库提供了
Read
和
Write
两个特性,为读写输入输出提供了通用接口。这些特性被不同类型的 I/O 实现,如文件、
TcpStream
、标准输入和进程的标准输出流。
以下是使用
Read
特性的示例代码:
use std::fs::File;
use std::io::Read;
fn main() {
// 打开一个文件
let mut f = File::open("records.txt").unwrap();
// 创建一个内存缓冲区用于从文件读取
let mut buffer = [0; 1024];
// 从文件读取到缓冲区
let _ = f.read(&mut buffer[..]).unwrap();
}
操作步骤:
1. 在项目根目录下创建一个名为
records.txt
的文件。
2. 使用
cargo run
运行程序。
3. 可选择打印缓冲区的值,将显示原始字节。
1.2 缓冲读写
Read
和
Write
是基于字节的接口,由于涉及对操作系统的连续系统调用,可能效率不高。为了克服这个问题,Rust 提供了
BufReader
和
BufWriter
两个结构体,它们有内置的缓冲区,可以减少对操作系统的调用次数。
使用
BufReader
的示例代码:
use std::fs::File;
use std::io::{BufRead, BufReader};
fn main() {
// 打开一个文件
let f = File::open("records.txt").unwrap();
// 创建一个 BufReader,传入文件句柄
let mut buf_reader = BufReader::new(f);
// 创建一个内存缓冲区用于从文件读取
let mut buffer = String::new();
// 读取一行到缓冲区
buf_reader.read_line(&mut buffer).unwrap();
println!("Read the following: {}", buffer);
}
操作步骤:
1. 在项目根目录下创建一个名为
records.txt
的文件。
2. 使用
cargo run
运行程序,验证文件中的值是否正确打印。
使用
BufWriter
的示例代码:
use std::fs::File;
use std::io::{BufWriter, Write};
fn main() {
// 创建一个文件
let f = File::create("file.txt").unwrap();
// 创建一个 BufWriter,传入文件句柄
let mut buf_writer = BufWriter::new(f);
// 创建一个内存缓冲区
let buffer = String::from("Hello, testing");
// 写入缓冲区
buf_writer.write(buffer.as_bytes()).unwrap();
println!("wrote the following: {}", buffer);
}
操作步骤:
1. 运行程序。
2. 验证指定的字符串值是否已写入项目根目录下名为
file.txt
的文件。
1.3 使用场景
| 场景 | 是否适合使用 | 原因 |
|---|---|---|
| 对磁盘进行小而频繁的读写操作 | 是 | 可以加快程序运行速度 |
| 偶尔涉及大尺寸数据的读写操作 | 否 | 不会带来明显好处 |
| 对内存数据结构进行读写操作 | 否 | 没有帮助 |
2. 标准输入输出操作
在 Linux/Unix 中,流是进程与其环境之间的通信通道。每个运行的进程默认会创建三个标准流:标准输入、标准输出和标准错误。
2.1 与标准输入输出流交互
以下是与进程的标准输入和标准输出流交互的代码示例:
use std::io::{self, Write};
fn main() {
// 创建一个内存缓冲区用于从文件读取
let mut buffer = String::new();
// 从标准输入读取一行到缓冲区
let _ = io::stdin().read_line(&mut buffer).unwrap();
// 将缓冲区写入标准输出
io::stdout().write(&mut buffer.as_bytes()).unwrap();
}
操作步骤:
1. 使用
cargo run
运行程序。
2. 输入一些文本并按回车键,将看到文本在终端回显。
2.2 锁定标准输入输出流
Stdin
是进程输入流的句柄,是对输入数据全局缓冲区的共享引用。
Stdout
是进程输出流,是对全局数据缓冲区的共享引用。为确保这些数据缓冲区的独占使用,可以锁定句柄。
以下是使用锁定引用的代码示例:
use std::io::{Read, Write};
fn main() {
// 创建一个内存缓冲区
let mut buffer = [8; 1024];
// 获取输入流句柄
let stdin_handle = std::io::stdin();
// 锁定输入流句柄
let mut locked_stdin_handle = stdin_handle.lock();
// 从输入流读取一行到缓冲区
locked_stdin_handle.read(&mut buffer).unwrap();
// 获取输出流句柄
let stdout_handle = std::io::stdout();
// 锁定输出流句柄
let mut locked_stdout_handle = stdout_handle.lock();
// 将缓冲区写入标准输出
locked_stdout_handle.write(&mut buffer).unwrap();
}
2.3 写入标准错误流
以下是写入标准错误流的代码示例:
use std::io::Write;
fn main() {
// 创建一个内存缓冲区
let buffer = b"Hello, this is error message from
standard
error stream\n";
// 获取输出错误流句柄
let stderr_handle = std::io::stderr();
// 锁定输出错误流句柄
let mut locked_stderr_handle = stderr_handle.lock();
// 从缓冲区写入错误流
locked_stderr_handle.write(buffer).unwrap();
}
3. I/O 迭代器和链式操作
3.1 使用迭代器
std::io
模块提供的许多数据结构都有内置的迭代器。迭代器可以让你处理一系列项目,如文件中的行或端口上的传入网络连接。
从标准输入逐行读取的示例代码:
use std::io::{BufRead, BufReader};
fn main() {
// 创建标准输入句柄
let s = std::io::stdin();
// 创建一个 BufReader 实例以优化系统调用
let file_reader = BufReader::new(s);
// 逐行从标准输入读取
for single_line in file_reader.lines() {
println!("You typed:{}", single_line.unwrap());
}
}
操作步骤:
1. 使用
cargo run
运行程序。
2. 输入一些文本并按回车键,重复此步骤,按
Ctrl + C
退出程序。
从文件逐行读取的示例代码:
use std::fs::File;
use std::io::{BufRead, BufReader};
fn main() {
// 打开一个文件用于读取
let f = File::open("file.txt").unwrap();
// 创建一个 BufReader 实例以优化系统调用
let file_reader = BufReader::new(f);
// 逐行从文件读取
for single_line in file_reader.lines() {
println!("Line read from file :{}", single_line.unwrap());
}
}
操作步骤:
1. 在项目根目录下创建一个名为
file.txt
的文件,并输入几行文本。
2. 使用
cargo run
运行程序,将看到文件内容打印到终端。
3.2 链式操作
std::io
模块中的
Read
特性有一个
chain()
方法,允许将多个
BufReader
链接成一个句柄。
链式操作的示例代码:
use std::fs::File;
use std::io::Read;
fn main() {
// 打开两个文件句柄用于读取
let f1 = File::open("file1.txt").unwrap();
let f2 = File::open("file2.txt").unwrap();
// 链接两个文件句柄
let mut chained_handle = f1.chain(f2);
// 创建一个缓冲区用于读取
let mut buffer = String::new();
// 从链式句柄读取到缓冲区
chained_handle.read_to_string(&mut buffer).unwrap();
// 打印读取到缓冲区的值
println!("Read from chained handle:\n{}", buffer);
}
操作步骤:
1. 在项目根目录下创建
file1.txt
和
file2.txt
两个文件,并在每个文件中输入几行文本。
2. 使用
cargo run
运行程序,将看到两个文件的数据逐行打印。
4. 错误处理和返回值
4.1 基本错误处理
在之前的代码示例中,使用
unwrap()
函数从
std::io
模块的方法和关联函数中提取返回值,这不是处理错误的正确方式。
std::io
模块有一个专门的
Result
类型,用于可能产生错误的函数或方法的返回值。
使用
io::Result
类型的示例代码:
use std::fs::File;
use std::io::Read;
fn main() -> std::io::Result<()> {
// 打开两个文件句柄用于读取
let f1 = File::open("file1.txt")?;
let f2 = File::open("file3.txt")?;
// 链接两个文件句柄
let mut chained_handle = f1.chain(f2);
// 创建一个缓冲区用于读取
let mut buffer = String::new();
// 从链式句柄读取到缓冲区
chained_handle.read_to_string(&mut buffer)?;
println!("Read from chained handle: {}", buffer);
Ok(())
}
操作步骤:
1. 确保项目根目录下既没有
file1.txt
也没有
file3.txt
。
2. 使用
cargo run
运行程序,将看到错误消息打印到终端。
4.2 自定义错误处理
use std::fs::File;
use std::io::Read;
fn read_files(handle: &mut impl Read) -> std::io::Result<String> {
// 创建一个缓冲区用于读取
let mut buffer = String::new();
// 从链式句柄读取到缓冲区
handle.read_to_string(&mut buffer)?;
Ok(buffer)
}
fn main() {
let mut chained_handle;
// 打开两个文件句柄用于读取
let file1 = "file1.txt";
let file2 = "file3.txt";
if let Ok(f1) = File::open(file1) {
if let Ok(f2) = File::open(file2) {
// 链接两个文件句柄
chained_handle = f1.chain(f2);
let content = read_files(&mut chained_handle);
match content {
Ok(text) => println!("Read from chained handle:\n{}", text),
Err(e) => println!("Error occurred in reading files: {}", e),
}
} else {
println!("Unable to read {}", file2);
}
} else {
println!("Unable to read {}", file1);
}
}
操作步骤:
1. 确保
file1.txt
和
file2.txt
都存在,使用
cargo run
运行程序,将看到两个文件的内容打印到终端。
2. 删除其中一个文件,重新运行程序,将看到自定义的错误消息。
5. 获取连接的 USB 设备详细信息(项目)
5.1 项目设计
当 USB 设备插入计算机时,计算机总线上的电信号触发计算机上的 USB 控制器(硬件设备)。USB 控制器在 CPU 上引发一个中断,CPU 然后执行内核中为该中断注册的中断处理程序。当 Rust 程序通过 Rust
libusb
包装器 crate 进行调用时,该调用被路由到
libusb
C 库,后者反过来在内核上进行系统调用以读取对应 USB 设备的设备文件。
graph TD;
A[USB 设备插入计算机] --> B[USB 控制器触发]
B --> C[CPU 执行中断处理程序]
C --> D[Rust 程序调用 libusb 包装器 crate]
D --> E[libusb C 库进行系统调用]
E --> F[内核返回系统调用值给 libusb 库]
F --> G[libusb 库返回值给 Rust 程序]
我们使用
libusb
库,因为从头编写 USB 设备驱动程序需要实现 USB 协议规范,而编写设备驱动程序本身是一个单独的主题。
5.2 项目实现思路
接下来我们会基于
libusb
库来实现获取连接的 USB 设备详细信息。以下是大致的实现步骤:
-
引入依赖
:需要在
Cargo.toml文件中添加libusb库的依赖。
toml [dependencies] libusb = "1.0" -
初始化
libusb上下文 :在使用libusb库之前,需要先初始化一个上下文。
```rust
use libusb::Context;fn main() {
let context = Context::new().unwrap();
// 后续操作
}
3. **获取设备列表**:通过初始化的上下文获取当前连接的 USB 设备列表。rust
use libusb::Context;fn main() {
let context = Context::new().unwrap();
let devices = context.devices().unwrap();
for device in devices.iter() {
// 处理每个设备
}
}
4. **获取设备详细信息**:对于每个设备,可以获取其详细信息,如厂商 ID、产品 ID 等。rust
use libusb::Context;fn main() {
let context = Context::new().unwrap();
let devices = context.devices().unwrap();
for device in devices.iter() {
let device_descriptor = device.device_descriptor().unwrap();
println!(“Vendor ID: 0x{:04x}”, device_descriptor.vendor_id());
println!(“Product ID: 0x{:04x}”, device_descriptor.product_id());
// 可以获取更多信息
}
}
```
5.3 完整代码示例
以下是一个完整的获取连接的 USB 设备详细信息的代码示例:
use libusb::Context;
fn main() {
let context = Context::new().unwrap();
let devices = context.devices().unwrap();
println!("Connected USB Devices:");
for device in devices.iter() {
let device_descriptor = device.device_descriptor().unwrap();
println!("----------------------");
println!("Vendor ID: 0x{:04x}", device_descriptor.vendor_id());
println!("Product ID: 0x{:04x}", device_descriptor.product_id());
println!("Device Class: 0x{:02x}", device_descriptor.device_class());
println!("Device Subclass: 0x{:02x}", device_descriptor.device_subclass());
println!("Device Protocol: 0x{:02x}", device_descriptor.device_protocol());
}
}
操作步骤:
1. 在
Cargo.toml
文件中添加
libusb
依赖。
2. 将上述代码保存为
main.rs
文件。
3. 使用
cargo run
运行程序,将看到连接的 USB 设备的详细信息。
5.4 注意事项
- 权限问题 :在某些系统中,可能需要以管理员权限运行程序才能访问 USB 设备。
- 错误处理 :在实际应用中,需要更完善的错误处理代码,以应对可能出现的各种错误情况。
总结
本文详细介绍了 Rust 中设备 I/O 操作的多个方面,包括缓冲读写、标准输入输出操作、I/O 迭代器和链式操作、错误处理以及获取连接的 USB 设备详细信息等内容。通过具体的代码示例和操作步骤,展示了如何在 Rust 中进行各种 I/O 操作,并处理可能出现的错误。同时,通过一个实际的项目示例,说明了如何使用
libusb
库来获取连接的 USB 设备详细信息。希望这些内容能帮助你更好地掌握 Rust 中的设备 I/O 操作。
| 操作类型 | 关键代码元素 | 示例代码 |
|---|---|---|
| 缓冲读写 |
BufReader
、
BufWriter
| 见前文相关代码 |
| 标准输入输出 |
Stdin
、
Stdout
、
Stderr
| 见前文相关代码 |
| I/O 迭代器和链式操作 |
lines()
、
chain()
| 见前文相关代码 |
| 错误处理 |
io::Result
| 见前文相关代码 |
| 获取 USB 设备信息 |
libusb
库
| 见前文完整代码示例 |
超级会员免费看
539

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



