Linux IO 高级函数:readv() 和 writev()
在Linux系统中,处理文件读写时,我们通常会用到 read()
和 write()
函数。但是,当我们需要处理的内存分散在多个不同的缓冲区中时,传统的读写函数就显得有些力不从心。这时,我们可以使用 readv()
和 write()
函数,这两个函数提供了更加高效的读写方式,它们可以一次性处理多个分散的内存区域。
readv() 和 writev() 函数详解
原型
#include <sys/uio.h>ssize_t readv(int fd, const struct iovec *iov, int iovcnt);
ssize_t writev(int fd, const struct iovec *iov, int iovcnt);
作用
这两个函数允许我们一次性读取或写入多个分散的内存缓冲区,而不需要像传统方法那样多次调用 read()
或 write()
函数。
参数
fd
: 文件描述符,指定要读取或写入的文件。iov
: 指向iovec
结构体的指针数组,每个iovec
结构体描述了一个缓冲区。iovcnt
:iov
数组中的元素数量,即缓冲区的数量。
结构体 iovec
struct iovec {void *iov_base; /* 指向缓冲区的指针 */size_t iov_len; /* 缓冲区的大小 */
};
示例
想象一下,你有一个非常大的拼图,这个拼图由很多小块组成,每块都分布在不同的地方。传统的 read()
和 write()
函数就像是一块块地搬运这些拼图块,而 readv()
和 writev()
函数则像是用一个篮子一次性把所有拼图块都搬走。
例子
假设我们有一个文本文件input.txt,内容如下:
Hello, World!
This is a test file.
我们要读取这个文件的内容,并将其写入另一个文件中,但是文件内容要分两次读取,分别存放在两个不同的缓冲区中。
#include <stdio.h>
#include <stdlib.h>
#include <sys/uio.h>
#include <fcntl.h>
#include <unistd.h>int main() {// 打开源文件和目标文件int fd_in = open("input.txt", O_RDONLY);if (fd_in == -1) {perror("Error opening input file");return 1;}int fd_out = open("output.txt", O_WRONLY | O_CREAT | O_TRUNC, 0666);if (fd_out == -1) {perror("Error opening output file");close(fd_in);return 1;}// 设置缓冲区char buffer1[10];char buffer2[25];// 创建iovec结构体数组struct iovec iov[2];iov[0].iov_base = buffer1;iov[0].iov_len = sizeof(buffer1);iov[1].iov_base = buffer2;iov[1].iov_len = sizeof(buffer2);// 循环读取并写入直到文件结束while (1) {// 读取文件内容到缓冲区ssize_t bytes_read = readv(fd_in, iov, 2);if (bytes_read == -1) {perror("readv failed");break;}if (bytes_read == 0) { // 文件结束break;}// 将缓冲区内容写入目标文件ssize_t total_written = 0;while (total_written < bytes_read) {ssize_t bytes_written = writev(fd_out, iov, 2);if (bytes_written == -1) {perror("writev failed");break;}total_written += bytes_written;// 更新iovec数组中的缓冲区位置和长度if (iov[0].iov_len <= bytes_written) {// 如果第一个缓冲区的内容已全部写入,调整iovecbytes_written -= iov[0].iov_len; // 已写入的字节数减去第一个缓冲区的大小iov[0].iov_base = NULL; // 将第一个缓冲区设为NULL,表示不再使用iov[0].iov_len = 0;iov[1].iov_base = (char *)iov[1].iov_base + bytes_written; // 移动第二个缓冲区的指针iov[1].iov_len -= bytes_written; // 减少第二个缓冲区的长度} else {// 如果第一个缓冲区还有剩余,调整第一个缓冲区的指针和长度iov[0].iov_base = (char *)iov[0].iov_base + bytes_written;iov[0].iov_len -= bytes_written;}}}// 关闭文件描述符close(fd_in);close(fd_out);return 0;
}
使用了一个循环来读取文件直到到达文件末尾(readv()
返回0)。在每次循环中,我们尽可能多地读取数据,并将这些数据写入到输出文件中。如果 writev()
写入的字节数少于 readv()
读取的字节数,说明数据没有完全写入,我们需要调整 iovec
数组,将未写入的数据再次传递给 writev()
函数,直到所有数据都被写入。
这种方法能够确保我们正确地处理分散在多个缓冲区中的数据
在这个例子中,我们首先定义了两个缓冲区 buffer1
和 buffer2
,然后创建了一个 iovec
数组,其中包含了这两个缓冲区的信息。我们使用 readv()
函数一次性从源文件读取内容到这两个缓冲区中。然后,我们使用 writev()
函数将这两个缓冲区的内容一次性写入目标文件。
这种方式不仅代码简洁,而且效率高,因为它减少了系统调用的次数,避免了多次在用户空间和内核空间之间切换上下文的开销。
通过使用 readv()
和 writev()
函数,我们可以更加灵活和高效地处理分散在多个缓冲区中的数据,这在网络编程和多线程编程中尤其有用。