readv() 和 writev()

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) {
                // 如果第一个缓冲区的内容已全部写入,调整iovec
                bytes_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() 函数,直到所有数据都被写入。

这种方法能够确保我们正确地处理分散在多个缓冲区中的数据
在这个例子中,我们首先定义了两个缓冲区 buffer1buffer2,然后创建了一个 iovec 数组,其中包含了这两个缓冲区的信息。我们使用 readv() 函数一次性从源文件读取内容到这两个缓冲区中。然后,我们使用 writev() 函数将这两个缓冲区的内容一次性写入目标文件。

这种方式不仅代码简洁,而且效率高,因为它减少了系统调用的次数,避免了多次在用户空间和内核空间之间切换上下文的开销。

通过使用 readv()writev() 函数,我们可以更加灵活和高效地处理分散在多个缓冲区中的数据,这在网络编程和多线程编程中尤其有用。

  • 4
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

成了大锦鲤

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值