22、Rust 中的设备 I/O 操作全解析

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 设备详细信息。以下是大致的实现步骤:

  1. 引入依赖 :需要在 Cargo.toml 文件中添加 libusb 库的依赖。
    toml [dependencies] libusb = "1.0"
  2. 初始化 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 见前文完整代码示例
提供了一个基于51单片机的RFID门禁系统的完整资源文件,包括PCB图、原理图、论文以及源程序。该系统设计由单片机、RFID-RC522频射卡模块、LCD显示、灯控电路、蜂鸣器报警电路、存储模块和按键组成。系统支持通过密码和刷卡两种方式进行门禁控制,灯亮表示开门成功,蜂鸣器响表示开门失败。 资源内容 PCB图:包含系统的PCB设计图,方便用户进行硬件电路的制作和调试。 原理图:详细展示了系统的电路连接和模块布局,帮助用户理解系统的工作原理。 论文:提供了系统的详细设计思路、实现方法以及测试结果,适合学习和研究使用。 源程序:包含系统的部源代码,用户可以根据需要进行修改和优化。 系统功能 刷卡开门:用户可以通过刷RFID卡进行门禁控制,系统会自动识别卡片并判断是否允许开门。 密码开门:用户可以通过输入预设密码进行门禁控制,系统会验证密码的正确性。 状态显示:系统通过LCD显示屏显示当前状态,如刷卡成功、密码错误等。 灯光提示:灯亮表示开门成功,灯灭表示开门失败或未操作。 蜂鸣器报警:当刷卡或密码输入错误时,蜂鸣器会发出报警声,提示用户操作失败。 适用人群 电子工程、自动化等相关专业的学生和研究人员。 对单片机和RFID技术感兴趣的爱好者。 需要开发类似门禁系统的工程师和开发者。
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符  | 博主筛选后可见
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值