Linux的高性能服务器编程 高级I/O函数总结

dup函数和dup2函数

dupdup2是Linux操作系统中用于复制文件描述符的函数。文件描述符是指向打开文件的引用,每个打开的文件都有一个唯一的文件描述符。通过复制文件描述符,可以在不同的文件描述符之间共享打开的文件。

dup函数:

        用于复制一个现有的文件描述符,并返回一个新的文件描述符,该文件描述符指向相同的文件。

#include <unistd.h>

int dup(int oldfd);

        oldfd:要复制的现有文件描述符。

dup2函数:

        用于复制一个现有的文件描述符,并将其赋值给指定的文件描述符。如果指定的文件描述符已经打开,则会被关闭并替换。

#include <unistd.h>

int dup2(int oldfd, int newfd);

        oldfd:要复制的现有文件描述符。

        newfd:指定的新文件描述符

代码演示

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>

int main() {
    int fd1, fd2, fd3;

    //打开文件
    fd1 = open("example.txt", O_RDONLY);
    if (fd1 == -1) {
        perror("open");
        return 1;
    }

    //使用dup复制文件描述符
    fd2 = dup(fd1);
    if (fd2 == -1) {
        perror("dup");
        close(fd1);
        return 1;
    }

    //使用dup2复制文件描述符,并指定新文件描述符
    fd3 = dup2(fd1, 10);
    if (fd3 == -1) {
        perror("dup2");
        close(fd1);
        close(fd2);
        return 1;
    }

    //关闭原始文件描述符
    close(fd1);

    //使用复制的文件描述符fd2和fd3访问文件
    //...

    //关闭复制的文件描述符
    close(fd2);
    close(fd3);

    return 0;
}

区别和用途

dup函数是复制一个现有的文件描述符,并返回一个新的文件描述符。新的文件描述符与源石文件描述符指向相同的文件,但它们是独立的。dup2函数相比而言是复制一个现有的文件描述符,并将其赋值给指定的文件描述符。如果指定的文件描述符已经打开,则会被关闭并替换。一般来说用于重定向标准输入、输出。

readv函数和writev函数

readvwritev函数是POSIX标准中定义的,用于高效地从文件描述符读取数据到多个缓冲区(readv),或者将多个缓冲区的数据写入文件描述符(writev)的函数。

这两个函数通过减少系统调用的次数和内存拷贝操作,可以显著提高数据传输的效率,尤其是在处理多个小数据块时。

readv函数

        readv函数用于从文件描述符fd读取数据到多个缓冲区中。

#include <sys/uio.h>

ssize_t readv(int fd, const struct iovec *iov, int iovcnt);

writev函数

        writev函数用于将多个缓冲区中的数据写入文件描述符fd

#include <sys/uio.h>

ssize_t writev(int fd, const struct iovec *iov, int iovcnt);

 其中:

         fd:文件描述符。

        iov:指向iovec结构数组的指针,每个iovec结构包含缓冲区的指针和长度。

        iovcntiovec数组中的元素数量。

iovec结构用于指定缓冲区的指针和长度

struct iovec {
    void *iov_base;  // 缓冲区的起始地址
    size_t iov_len;  // 缓冲区的长度
};

代码演示:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/uio.h>
#include <unistd.h>

int main() {
    int fd;
    struct iovec iov[2];
    char buffer1[] = "Hello, ";
    char buffer2[] = "world!";
    char read_buffer[20];
    ssize_t bytes;

    //打开文件
    fd = open("example.txt", O_WRONLY | O_CREAT | O_TRUNC, 0666);
    if (fd == -1) {
        perror("open");
        return 1;
    }

    //设置writev的缓冲区
    iov[0].iov_base = buffer1;
    iov[0].iov_len = strlen(buffer1);
    iov[1].iov_base = buffer2;
    iov[1].iov_len = strlen(buffer2);

    //使用writev写入数据
    bytes = writev(fd, iov, 2);
    if (bytes == -1) {
        perror("writev");
        close(fd);
        return 1;
    }

    //关闭文件
    close(fd);

    //重新打开文件进行读取
    fd = open("example.txt", O_RDONLY);
    if (fd == -1) {
        perror("open");
        return 1;
    }

    //设置readv的缓冲区
    iov[0].iov_base = read_buffer;
    iov[0].iov_len = sizeof(read_buffer) / 2;
    iov[1].iov_base = read_buffer + sizeof(read_buffer) / 2;
    iov[1].iov_len = sizeof(read_buffer) / 2;

    //使用readv读取数据
    bytes = readv(fd, iov, 2);
    if (bytes == -1) {
        perror("readv");
        close(fd);
        return 1;
    }

    //输出读取的数据
    printf("Read %zd bytes: %.*s%.*s\n", bytes, (int)iov[0].iov_len, (char *)iov[0].iov_base, (int)iov[1].iov_len, (char *)iov[1].iov_base);

    //关闭文件
    close(fd);

    return 0;
}

主要作用:

        减少系统调用,可以通过一次性读取或者写入多个缓冲区,减少了系统调用的次数。减少内存拷贝,数据可以直接在内核空间和用户空间之间传输,减少了内存拷贝操作。从而提高效率

sendfile函数

sendfile函数是一种在Linux系统中用于高效传输文件内容的系统调用。它能够将一个文件的内容直接发送到一个套接字,而无需在用户空间和内核空间之间进行数据拷贝。这种直接在内核空间进行数据传输的方式可以显著提高文件传输的效率,因为它减少了数据复制的次数和上下文切换的开销。

函数原型如下:

#include <sys/sendfile.h>

ssize_t sendfile(int out_fd, int in_fd, off_t *offset, size_t count);

    out_fd:输出文件描述符,通常是套接字的文件描述符。

    in_fd:输入文件描述符,即要发送的文件的文件描述符。

    offset:文件中开始发送的位置。如果此参数为 NULL,则从文件的当前位置开始发送。

    count:要发送的最大字节数。

成功时,sendfile返回发送的字节数。

代码演示:

#include <sys/socket.h>
#include <sys/types.h>
#include <sys/sendfile.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdio.h>

int main() {
    int sockfd, filefd;
    ssize_t sent;
    off_t offset = 0;
    size_t count = 4096;  //发送的字节数

    //创建套接字
    sockfd = socket(AF_INET, SOCK_STREAM, 0);
    if (sockfd == -1) {
        perror("socket");
        return 1;
    }

    //打开文件
    filefd = open("example.txt", O_RDONLY);
    if (filefd == -1) {
        perror("open");
        close(sockfd);
        return 1;
    }

    //发送文件
    sent = sendfile(sockfd, filefd, &offset, count);
    if (sent == -1) {
        perror("sendfile");
        close(sockfd);
        close(filefd);
        return 1;
    }

    printf("Sent %zd bytes\n", sent);

    //关闭文件描述符
    close(sockfd);
    close(filefd);

    return 0;
}

减少数据复制:sendfile直接在内核空间中将数据从一个文件描述符传输到另一个文件描述符,避免了数据在内核空间和用户空间之间的复制。

提高传输效率:由于减少了数据复制,sendfile 通常比 readwrite 的组合更高效,特别是在处理大文件或大量数据时。

简化编程模型:使用 sendfile 可以简化代码,因为它只需要一个系统调用就可以完成数据的传输,而使用 readwrite 需要至少两个系统调用(一个用于读取,一个用于写入)。

零拷贝sendfile 支持零拷贝操作,即数据在传输过程中不需要实际复制,只是更改了数据在内存中的引用。

减少内存使用:由于减少了数据复制,sendfile 可以减少对用户空间内存的需求,这对于内存受限的环境特别有益。

splice函数

splice函数是Linux系统中的一个系统调用,它用于在两个文件描述符之间直接传输数据,而不需要将数据复制到用户空间。这种也是直接在内核空间进行数据传输的方式,提高了数据传输的效率,因为它减少了数据复制的次数和上下文切换的开销。

splice函数特别适用于在管道、文件、网络套接字等之间的数据传输,尤其是在需要处理大量数据时。

函数原型如下:

#include <linux/fs.h>

ssize_t splice(int fd_in, loff_t *off_in, int fd_out, loff_t *off_out, size_t len, unsigned int flags);

 fd_in输入文件描述符。

off_in输入文件描述符的偏移量指针,可以是 NULL,表示内核自动管理偏移量。

fd_out输出文件描述符。

off_out输出文件描述符的偏移量指针,可以是 NULL,表示内核自动管理偏移量。

len要传输的最大数据量。

flags控制操作的标志,可以是以下值的组合:

  SPLICE_F_MOVE尝试将数据从输入移动到输出,而不是复制。

  SPLICE_F_NONBLOCK非阻塞操作。

  SPLICE_F_MORE还有更多的数据要传输。

成功时,返回传输的字节数。

#include <fcntl.h>
#include <stdio.h>
#include <unistd.h>
#include <linux/fs.h>

int main() {
    int fd_in, fd_out;
    ssize_t bytes;

    //打开输入文件
    fd_in = open("input.txt", O_RDONLY);
    if (fd_in == -1) {
        perror("open input.txt");
        return 1;
    }

    //打开输出文件
    fd_out = open("output.txt", O_WRONLY | O_CREAT | O_TRUNC, 0666);
    if (fd_out == -1) {
        perror("open output.txt");
        close(fd_in);
        return 1;
    }

    //使用splice传输数据
    bytes = splice(fd_in, NULL, fd_out, NULL, 4096, 0);
    if (bytes == -1) {
        perror("splice");
        close(fd_in);
        close(fd_out);
        return 1;
    }

    printf("Transferred %zd bytes\n", bytes);

    //关闭文件描述符
    close(fd_in);
    close(fd_out);

    return 0;
}

        splice函数通过直接在内核空间内进行数据传输,避免了数据在内核与用户空间之间的不必要复制,从而减少了内存拷贝操作和上下文切换的开销。

        利用这种零拷贝机制不仅提高了大文件或大量数据传输的效率,还简化了编程模型,因为只需一个系统调用就能完成数据传输

        相较于传统的readwrite组合方法,splice提供了一种更为高效和简洁的数据传输方式。

tee

tee是一个命令行工具。tee命令用于读取标准输入的数据,然后同时将数据写入到标准输出和文件中。

了解即可...

fcntl函数

fcntl函数是Linux操作系统中用于对文件描述符执行各种控制操作的系统调用。

它提供了一种机制来获取或修改文件描述符的各种属性,如文件锁定、文件状态标志、文件访问模式等。

#include <fcntl.h>

int fcntl(int fd, int cmd, ... /* arg */ );

//fd:文件描述符。

//cmd:指定要执行的操作。

//...:依赖于 cmd 参数的附加参数。

譬如:

#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>

int main() {
    int fd;
    struct flock lock;

    //打开文件
    fd = open("example.txt", O_RDWR);
    if (fd == -1) {
        perror("open");
        return 1;
    }

    //设置文件锁定
    lock.l_type = F_WRLCK;  //写锁
    lock.l_whence = SEEK_SET;
    lock.l_start = 0;
    lock.l_len = 0;  //锁定整个文件

    if (fcntl(fd, F_SETLK, &lock) == -1) {
        perror("fcntl");
        close(fd);
        return 1;
    }

    //执行文件操作...

    //释放文件锁定
    lock.l_type = F_UNLCK;
    if (fcntl(fd, F_SETLK, &lock) == -1) {
        perror("fcntl");
    }

    //关闭文件描述符
    close(fd);

    return 0;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值