Linux下C语言文件描述符操作(dup / dup2 / sendfile / splice / tee)

news2024/11/26 16:51:36

Linux的哲学是一切皆文件,而操作文件是通过文件描述符来进行。本文梳理一下dup / dup2 / sendfile / splice/ tee函数对文件描述符的操作。

目录

1.dup

2.dup2

3.sendfile

4.splice

5.tee


1.dup

#include <unistd.h>
int  dup(int fd);

复制一个现有的文件描述符,dup会返回一个新的描述符,这个描述一定是当前可用文件描述符中的最小值。我们知道,一般的0,1,2描述符分别被标准输入、输出、错误占用,所以在程序中如果close掉标准输出1后,调用dup函数,此时返回的描述符就是1。函数返回后fd和dup返回值指向同一个文件,只是文件描述符不同。

以下下代码演示将标准输出复制成普通文件:

#include <assert.h>
#include <stdio.h>
#include <unistd.h>
#include <errno.h>
#include <string.h>
#include <fcntl.h>

int main(int argc, char* argv[]) {
    if (argc != 2) {
        printf("usage: %s <file>\n", argv[0]);
        return 1;
    }
    int filefd = open(argv[1], O_CREAT | O_WRONLY | O_TRUNC, 0666);
    assert(filefd > 0);
	close(STDOUT_FILENO);
    printf("=== %d\n", dup(filefd));
    write(filefd, "hello world\n", 12);
    close(filefd);
	printf("dup函数,你好啊");
   
    return 0;
}

运行结果:

2.dup2

#include <unistd.h>
int dup2(int fd, int fd2);

复制一个现有的文件描述符,用fd2指定新描述符的值,如果fd2本身已经打开了,则会先将其关闭。如果fd等于fd2,则返回fd2,并不关闭它。函数返回后fd和fd2指向同一个文件,只是文件描述符不同。

一下代码演示将标准输出复制成普通文件:

#include <assert.h>
#include <stdio.h>
#include <unistd.h>
#include <errno.h>
#include <string.h>
#include <fcntl.h>

int main(int argc, char* argv[]) {
    if (argc != 2) {
        printf("usage: %s <file>\n", argv[0]);
        return 1;
    }
    int filefd = open(argv[1], O_CREAT | O_WRONLY | O_TRUNC, 0666);
    assert(filefd > 0);
	write(filefd, "hello liudehua\n", 15);
    printf("=== %d\n", dup2(filefd, STDOUT_FILENO));
	write(filefd, "hello world\n", 12);
    close(filefd);
   
    return 0;
}

文件内容如下:

 使用strace跟踪:

3.sendfile

#include<sys/sendfile.h>
ssize_t sendfile(int out_fd,int in_fd , off_t* offset ,size_t count);
功能:在两个文件描述符之间传递数据(完全在内核中操作),从而避免内核缓冲区和用户缓冲区之间的数据拷贝,效率很高,这就是传说中的零拷贝。

参数:
in_fd:参数是待读出内容的文件描述符
out_fd:参数是待写入内容的文件描述符
offset:参数执行从读入文件流的哪个位置开始读,如果为空,则使用读入文件流的默认起始位置
count:参数指定在文件描述符in_fd和out_fd之间传输的字节数
返回值:成功时返回传输的字节数,失败则返回-1并设置errno

重点说明:该函数的man手册明确指出,in_fd必须是一个支持mmap函数的文件描述符,即它必须指向真实的文件,而不能是socket和管道,
而out_fd则必须是一个socket。所以sendfile几乎是专门为在网络上传输文件而设计的

以下测试代码编写一个简单的tcp服务器。通过sendfile向客户端发送文件的例子。

#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <assert.h>
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <errno.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <sys/sendfile.h>

int main( int argc, char* argv[] ) {
    if( argc <= 3 ) {
        printf("usage: %s ip port filename\n", basename(argv[0]));
        return 1;
    }
    const char* ip = argv[1];
    int port = atoi(argv[2]);
    const char* file_name = argv[3];
    int filefd = open(file_name, O_RDONLY);
    assert(filefd > 0);
    struct stat stat_buf;
    fstat(filefd, &stat_buf);
    struct sockaddr_in address;
    bzero(&address, sizeof(address));
    address.sin_family = AF_INET;
    inet_pton(AF_INET, ip, &address.sin_addr);
    address.sin_port = htons(port);
    int sock = socket(PF_INET, SOCK_STREAM, 0);
    assert(sock >= 0);
    int ret = bind(sock, (struct sockaddr*)&address, sizeof(address));
    assert(ret != -1);
    ret = listen(sock, 5);
    assert(ret != -1);
    struct sockaddr_in client;
    socklen_t client_addrlength = sizeof(client);
    int connfd = accept(sock, (struct sockaddr*)&client, &client_addrlength);
    if (connfd < 0) {
        printf( "errno is: %d\n", errno);
    }
    else {
        off_t len = 0;
        while (len < stat_buf.st_size) {
            int ret = sendfile(connfd, filefd, &len, stat_buf.st_size - len);
        //printf("ret value %d \n", ret);
            if (-1 == ret) {
                if (EAGAIN == errno) {
                    //printf("no data\n");
                    perror("sendfile");
                }
                else {
                    printf("client quit \n");
                    break;
                }
            }
        
        }
        close(connfd);
    }
    close(sock);

    return 0;

}

使用nc测试,结果如下:

 可以看到文件内容被nc程序正确接收。

4.splice

#include<fcntl.h>
ssize_t splice(int fd_in,loff_t* off_in,int fd_out,loff_t* off_out,size_t len ,unsigned int flags);
功能:splice函数用于在两个文件描述符之间移动数据,也是零拷贝

参数:
fd_in/off_in:fd_in为待输入数据的文件描述符, 如果fd_in是一个管道文件描述符,那么off_in参数必须设置为NULL。
如果fd_in不是一个管道文件(比如是一个socket),那么off_in表示从输入数据流的何处开始读取数据。此时,如果off_in被设置为NULL,则表示从输入数据流的当前偏移位置读入;
若off_in不为NULL,则它将指出具体的偏移位置。
fd_out/off_out参数的含义与fd_in/off_in相同,不过用于输出数据流。
len:移动数据的长度
flag:控制数据如何移动,它可以设置为下表中的某些值的按位或。

返回值:

     使用splice函数时,fd_in 和fd_out必须至少有一个是管道文件描述符。splice函数调用成功时返回移动字节的数量,它可能返回0,表示没有数据需要移动,这发生在从管道中读取数据,而该管道没有被写入任何数据时。spice函数失败时返回-1并设置errno.常见的errno如下图   

 以下测试代码编写一个简单的tcp服务器,使用匿名管道再结合splice向客户端回显信息。

#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <assert.h>
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <errno.h>
#include <string.h>
#include <fcntl.h>

int main(int argc, char* argv[]) {

    if (argc <= 2) {
        printf("usage: %s ip port\n", basename(argv[0]));
        return 1;
    }
    const char* ip = argv[1];
    int port = atoi(argv[2]);
    struct sockaddr_in address;
    bzero(&address, sizeof(address));
    address.sin_family = AF_INET;
    inet_pton(AF_INET, ip, &address.sin_addr);
    address.sin_port = htons(port);
    int sock = socket(PF_INET, SOCK_STREAM, 0);
    assert(sock >= 0);
    int ret = bind(sock, (struct sockaddr*)&address, sizeof(address));
    assert(ret != -1);
    ret = listen(sock, 5);
    assert(ret != -1);
    struct sockaddr_in client;
    socklen_t client_addrlength = sizeof(client);
    int connfd = accept(sock, (struct sockaddr*)&client, &client_addrlength);
    if (connfd < 0) {
        printf("errno is: %d\n", errno);
    }
    else {
        //创建匿名队列,pipefd[0]读,pipefd[1]写
        int pipefd[2];
        assert(ret != -1);
        ret = pipe(pipefd);
        // connfd -> pipefd[1]
        ret = splice(connfd, NULL, pipefd[1], NULL, 32768, SPLICE_F_MORE | SPLICE_F_MOVE); 
        assert(ret != -1);
        // pipefd[0] -> connfd
        ret = splice(pipefd[0], NULL, connfd, NULL, 32768, SPLICE_F_MORE | SPLICE_F_MOVE);
        assert(ret != -1);
        close(pipefd[0]);
        close(pipefd[1]);
        close(connfd);
    }
    close(sock);
    
    return 0;
}

运行结果如下:

可以看到客户端输入的内容被正确回显。 

5.tee

#include <fcntl.h>
ssize_t tee(int fd_in, int fd_out, size_t len, unsigned int flags);
功能:在两个管道文件描述符之间复制数据,也是零拷贝操作。它不消耗数据,因此源文件描述符上的数据仍然可以用于后续的读操作。
参数:
fd_in:待输入数据的文件描述符,必须是管道文件。
fd_out:待输出数据的文件描述符,必须是管道文件。
len:赋值的数据长度(字节数)
flags 修饰标志,跟splice(2)/vmsplice(2) 共享命名空间:
1)SPLICE_F_MOVE 当前对tee没有效果。
2)SPLICE_F_NONBLOCK 非阻塞的I/O操作,实际效果还会受文件描述符本身的阻塞状态的影响。
3)SPLICE_F_MORE当前对tee没有效果。
4)SPLICE_F_GIFT 对tee没有效果。
返回值:
成功时,返回两个文件描述符之间复制的数据量(字节数)。返回0表示没有复制任何数据,可能碰到EOF。失败时,返回-1,并设置errno。

如下代码显示从标准输入接收数据,通过管道将数据写入文件,可以看到tee(pipefd_stdout[0]调用两次,pipefd_stdout[0]中数据一直存在。

#include <assert.h>
#include <stdio.h>
#include <unistd.h>
#include <errno.h>
#include <string.h>
#include <fcntl.h>

int main(int argc, char* argv[]) {
    if (argc != 2) {
        printf("usage: %s <file>\n", argv[0]);
        return 1;
    }
    int filefd = open(argv[1], O_CREAT | O_WRONLY | O_TRUNC, 0666);
    assert(filefd > 0);
    int pipefd_stdout[2];
    int ret = pipe(pipefd_stdout);
    assert(ret != -1);
    int pipefd_file[2];
    ret = pipe(pipefd_file);
    assert(ret != -1);

    // 标准输入 STDIN_FILENO -> pipefd_stdout[1]
    ret = splice(STDIN_FILENO, NULL, pipefd_stdout[1], NULL, 32768, SPLICE_F_MORE | SPLICE_F_MOVE);
    assert(ret != -1);

    // pipefd_stdout[0] -> pipefd_file[1]
    ret = tee(pipefd_stdout[0], pipefd_file[1], 32768, SPLICE_F_NONBLOCK); 
    assert(ret != -1);
    ret = tee(pipefd_stdout[0], pipefd_file[1], 32768, SPLICE_F_NONBLOCK); 
    assert(ret != -1);

    // pipefd_file[0] -> filefd
    ret = splice(pipefd_file[0], NULL, filefd, NULL, 32768, SPLICE_F_MORE | SPLICE_F_MOVE);
    assert(ret != -1);
    
    close(filefd);
    close(pipefd_stdout[0]);
    close(pipefd_stdout[1]);
    close(pipefd_file[0]);
    close(pipefd_file[1]);

    return 0;
}

运行结果如下:

tee和splice的区别
tee类似于splice,都用于两个fd之间数据拷贝。区别在于:
1)对参数fd的要求
splice要求2个fd中至少必须有一个fd是管道文件;
tee要求两个fd都是管道文件。

2)对fd数据的消耗
splice是两个fd之间数据移动,splice会消耗fd数据;
tee是两个fd之间数据复制,tee不会消耗fd数据。

3)flags参数
Linux2.6.21以前,SPLICE_F_MOVE 对splice有效果,之后没效果。SPLICE_F_NONBLOCK 和SPLICE_F_MORE都对splice有效果;
只有SPLICE_F_NONBLOCK 才对tee有效果;

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/598543.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Java基础(maven)——maven新建项目 常用IO工具 Durid数据库工具 案例

目录 引出用Maven建项目0.Maven配置方式1.io流的工具IOUtils/FileUtils1&#xff09;可以读文件、按照行读、读网页等&#xff1b;2&#xff09;配合hasmap进行简体繁体转换 2.durid数据库连接工具1&#xff09;创建连接&#xff0c;durid进行连接管理2&#xff09;查询的方式q…

chatgpt赋能python:Python中的转置函数-一种简单而高效的矩阵操作

Python中的转置函数 - 一种简单而高效的矩阵操作 作为一名10年的Python编程经验工程师&#xff0c;掌握利用Python进行矩阵操作是必不可少的。Python中提供了各种高效的矩阵操作功能&#xff0c;其中之一就是转置函数。 什么是转置&#xff1f; 在数学中&#xff0c;矩阵转置…

追寻幸福:探索幸福的关键特征和行为

目录 1. 积极的心态 2. 良好的人际关系 3. 自我接纳和自尊 4. 追求意义和目标 5. 健康的身心状态 6. 感知和实现个人价值 幸福是一个主观的感受&#xff0c;因此不同的人对于幸福的定义和追求方式可能会有所不同。然而&#xff0c;有一些共同的特点和行为模式&#xff0c…

【数据结构】难度上一个台阶的二叉树实现

【数据结构】难度上一个台阶的二叉树实现 一、什么是树和二叉树&#xff1f;二、目标三、实现3.1、初始化工作3.2、二叉树的前序遍历3.2.1、原理图解3.2.2、代码实现 3.3、二叉树的创建3.3.1、原理解析3.3.2、代码实现 3.4、二叉树的中序遍历3.5、二叉树的后序遍历3.6、二叉树的…

K8S系列文章之快速入门K8S

搭建K8S环境 前置 目前生产部署Kubernetes 集群主要有两种方式&#xff1a; kubeadm Kubeadm 是一个K8s 部署工具&#xff0c;提供kubeadm init 和kubeadm join&#xff0c;用于快速部署Kubernetes 集群。 官方地址&#xff1a;https://kubernetes.io/docs/reference/setu…

ChatGPT出来后,为什么老有人想让我们程序员失业?

&#x1f431; 个人主页&#xff1a;不叫猫先生&#xff0c;公众号&#xff1a;前端舵手 &#x1f64b;‍♂️ 作者简介&#xff1a;2022年度博客之星前端领域TOP 2&#xff0c;前端领域优质作者、阿里云专家博主&#xff0c;专注于前端各领域技术&#xff0c;共同学习共同进步…

【LeetCode每日一题】——1248.统计「优美子数组」

文章目录 一【题目类别】二【题目难度】三【题目编号】四【题目描述】五【题目示例】六【题目提示】七【解题思路】八【时间频度】九【代码实现】十【提交结果】 一【题目类别】 滑动窗口 二【题目难度】 中等 三【题目编号】 1248.统计「优美子数组」 四【题目描述】 给…

NVIDIA NCCL 源码学习(十)- 多机间ncclSend和ncclRecv的过程

先回忆一下单机的执行流程&#xff0c;用户执行ncclSend之后通过ncclEnqueueCheck将sendbuff&#xff0c;sendbytes&#xff0c;peer等信息保存到了comm->p2plist中&#xff1b;然后执行ncclGroupEnd&#xff0c;如果发现channel没有建立到peer的链接则先建链&#xff0c;然…

深入理解设计原则之接口隔离原则(ISP)

系列文章目录 C高性能优化编程系列 深入理解设计原则系列 深入理解设计模式系列 高级C并发线程编程 LSP&#xff1a;接口隔离原则 系列文章目录1、接口隔离原则的定义和解读2、案例解读3、如何判断一个接口是否符合接口隔离原则&#xff1f;小结 1、接口隔离原则的定义和解读…

网络工程师一定要会关键技能:如何进行IP子网划分?

对于所有从事IP网络方面工作的工程师来说&#xff0c;进行IP子网划分操作属于一个必备的关键技能&#xff0c;也属于必须掌握的专业内容&#xff1b;但对于初学者来说&#xff0c;真正理解IP子网划分的概念也是一件相当困难的事情。 怎样才能正确地进行子网划分操作 IP子网划…

影响现代办公室隔断设计的因素有哪些,办公室隔断设计方案要求

影响现代办公室隔断设计的因素有哪些 1. 办公空间的用途和功能要求 2. 员工数量及工作场所的布局 3. 设计风格和企业文化要求 4. 预算和材料选择 5. 环保节能和安全性要求 办公室隔断设计方案要求&#xff1a; 1. 合理利用空间&#xff0c;满足办公室的功能需求 2. 设计…

Creepypastsa VoxEdit 竞赛

召唤所有恐怖都市传闻爱好者。 通过 Creepypasta VoxEdit 竞赛&#xff0c;潜入黑暗领域&#xff0c;并释放你们的创造力&#xff01;踏入阴森恐怖的神秘世界&#xff0c;把你最可怕的噩梦变成现实&#xff01; 设计终极的 Creepypasta 体素资产 你是恐怖大师吗&#xff1f;是一…

RHCE 作业三

1.基于域名访问网站 [rootserver ~]# setenforce 0 [rootserver ~]# systemctl stop firewalld [rootserver ~]# systemctl disable firewalld [rootserver ~]# yum install httpd -y [rootserver ~]# systemctl start httpd [rootserver ~]# syst…

npm发包/发布群组过程记录

目录 创建群组 本地发包 1、初始化本地的包到群组 2、 登录npm账号 3、设置公开库 4、发布到npm上 5、升级更新发布 创建群组 打开npm的网站&#xff0c;登录个人账户密码--->点击右侧上方个人的头像--->在下拉框点击【add Organization】 接下来会出现一个添加群…

C语言 打印杨辉三角

杨辉三角的前五行如下&#xff1a; 1 1 1 1 2 1 1 3 3 1 1 4 6 4 1 1、首先我们可以定义定义一个5行5列的二维数组 代码&#xff1a; int main() {int arr[5][5] { 0 };初始化int i 0;int j 0;//循环输出for (i 0; i < 5; i) {for (j 0; j <5; j) {printf("…

华为OD机试之寻找相同子串(Java源码)

寻找相同子串 题目描述 给定一个字符串s&#xff0c;最多只能进行一次变换&#xff0c;返回变换后能得到的最小字符串&#xff08;按照字典序进行比较&#xff09;。 变换规则&#xff1a;交换字符串中任意两个不同位置的字符。 输入描述 一串小写字母组成的字符串s 输出描述 …

华硕天选FA506IU(R7 4600h)电脑 Hackintosh 黑苹果efi引导文件

原文来源于黑果魏叔官网&#xff0c;转载需注明出处。&#xff08;下载请直接百度黑果魏叔&#xff09; 硬件配置 硬件型号驱动情况 主板华硕天选FA506IU 处理器AMD Ryzen 7 4600h已驱动 内存8GB DDR4 3200MHz已驱动 硬盘东芝 KXG5AZNV256G Windows 10已驱动 显卡集成显卡…

基于MapReduce的京东20年口红数据的分析与实现

基于MapReduce的京东20年口红数据的分析与实现 文章目录 基于MapReduce的京东20年口红数据的分析与实现一、前言二、数据可视化1、安装Python和Flask框架2、创建Flask应用程序3、创建基于Echarts的网页展示代码4、最终结果 三、最后我想说 一、前言 这是一个利用HadoopMapRedu…

Java设计模式(二)

系列文章目录 里氏替换原则 开闭原则 文章目录 系列文章目录前言一、里氏替换原则1.OO中的继承性的思考和说明2.里氏替换原则基本介绍 二、开闭原则1.开闭原则基本介绍 总结 前言 大家好呀&#xff0c;欢迎来到柚子的博客~让我们一起成长吧o(&#xffe3;▽&#xffe3;)ブ 提…

二、浏览器广告屏蔽插件_Adblock Plus

1、浏览器广告 在浏览器浏览内容时&#xff0c;有广告无可厚非&#xff0c;但有些网页做的太过分了。如广告影响了正常网页的浏览&#xff1b;广告内容低俗恶心。特推荐以下浏览器广告屏蔽插件。 2、浏览器广告屏蔽插件 2.1、Adblock Plus 简介 (1)这里推荐Adblock Plus插件…