高级I/O函数pipe,dup,readv,splice,sendfile,tee,fcntl

本文介绍了Unix/Linux系统中用于进程间通信的pipe函数,以及dup和dup2函数如何实现标准输入/输出的重定向。示例代码展示了如何在服务器端通过dup将标准输出重定向到网络连接。此外,还详细讲解了sendfile函数在两个文件描述符间高效传输数据的零拷贝机制,以及splice函数在同一系统内两个文件描述符间移动数据的零拷贝方法。这些技术在提高程序性能和资源利用效率方面具有重要意义。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

pipe函数

用于创建一个管道,实现进程间通信。

#include<unistd.h>

int pipe(int fd[2]);

 dup函数和dup2函数

有时我们希望把标准输入重定向到一个文件,或者把标准输出重定向到一个网络连接(比如CGI编程),可以通过dup和dup2实现

 看个例子:

server.c

#include<sys/socket.h>
#include<netinet/in.h>
#include<arpa/inet.h>
#include<signal.h>
#include<unistd.h>
#include<stdlib.h>
#include<assert.h>
#include<stdio.h>
#include<errno.h>
#include<string.h>
#include<stdbool.h>
#include<libgen.h>
const int BUFFER_SIZE=1024;
int main(int argc,char* argv[])
{
    if(argc<=2)
    {
        printf("usage: %s ip_address port_number\n",basename(argv[0]));
        return 1;
    }
    const char* ip=argv[1];
    int port=atoi(argv[2]);

    int sock=socket(PF_INET,SOCK_STREAM,0);
    assert(sock>=0);

    // 创建一个ipv4地址
    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  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
    {
        close(STDOUT_FILENO);
        dup(connfd);
        printf("abcd\n");
        close(connfd);
    }

    close(sock);
    return 0;
}

client.c

#include<sys/socket.h>
#include<netinet/in.h>
#include<arpa/inet.h>
#include<signal.h>
#include<unistd.h>
#include<stdlib.h>
#include<assert.h>
#include<stdio.h>
#include<errno.h>
#include<string.h>
#include<stdbool.h>
#include<libgen.h>
const int BUFFER_SIZE=512;
int main(int argc,char* argv[])
{
    if(argc<=2)
    {
        printf("usage: %s ip_address port_number send_buffer_size\n",basename(argv[0]));
        return 1;
    }
    const char* ip=argv[1];
    int port=atoi(argv[2]);

    int sockfd=socket(PF_INET,SOCK_STREAM,0);
    assert(sockfd>=0);

    // 创建一个ipv4地址
    struct sockaddr_in server_address;
    bzero(&server_address,sizeof(server_address));
    server_address.sin_family=AF_INET;
    inet_pton(AF_INET,ip,&server_address.sin_addr);
    server_address.sin_port=htons(port);

    if(connect(sockfd,(struct sockaddr*)&server_address,sizeof(server_address))<0)
    {
        printf("connection failed\n");
    }
    else
    {
        char buffer[BUFFER_SIZE];
        memset(buffer,'a',BUFFER_SIZE);
        send(sockfd,buffer,BUFFER_SIZE,0);
        memset(buffer,'\0',BUFFER_SIZE);
        int ret=recv(sockfd,buffer,BUFFER_SIZE,0);
        printf("get %d bytes content:%s\n",ret,buffer);
    }
    close(sockfd);
    return 0;
}

sendfile

sendfile函数在2个文件描述符之间直接传递数据(完全在内核中操作),从而避免内核缓冲区和用户缓冲区之间的数据拷贝,效率很高,称之为零拷贝。

server.c

#include<sys/socket.h>
#include<netinet/in.h>
#include<arpa/inet.h>
#include<signal.h>
#include<unistd.h>
#include<stdlib.h>
#include<assert.h>
#include<stdio.h>
#include<errno.h>
#include<string.h>
#include<stdbool.h>
#include<libgen.h>
#include<sys/stat.h>
#include<fcntl.h>
#include<sys/sendfile.h>
const int BUFFER_SIZE=1024;
int main(int argc,char* argv[])
{
    if(argc<=3)
    {
        printf("usage: %s ip_address port_number 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);

    int sock=socket(PF_INET,SOCK_STREAM,0);
    assert(sock>=0);

    // 创建一个ipv4地址
    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  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
    {
        sendfile(connfd,filefd,NULL,stat_buf.st_size);
        close(connfd);
    }

    close(sock);
    return 0;
}

 

我用我的电脑telnet服务器,sendfile会把文件内容发送给客户端。

 

splice函数

splice函数用于在2个文件描述符之间移动数据,也是零拷贝。定义如下:

 

 

server.c

#define _GNU_SOURCE 1
#include<sys/socket.h>
#include<netinet/in.h>
#include<arpa/inet.h>
#include<signal.h>
#include<unistd.h>
#include<stdlib.h>
#include<assert.h>
#include<stdio.h>
#include<errno.h>
#include<string.h>
#include<stdbool.h>
#include<libgen.h>
#include<sys/stat.h>
#include<fcntl.h>
#include<sys/sendfile.h>
const int BUFFER_SIZE=1024;
int main(int argc,char* argv[])
{
    if(argc<=2)
    {
        printf("usage: %s ip_address port_number\n",basename(argv[0]));
        return 1;
    }
    const char* ip=argv[1];
    int port=atoi(argv[2]);

    int sock=socket(PF_INET,SOCK_STREAM,0);
    assert(sock>=0);

    // 创建一个ipv4地址
    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  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
    {
        int pipefd[2];
        assert(ret!=-1);
        ret=pipe(pipefd);//创建管道
        // 将connfd上流入的客户数据定向到管道中
        ret=splice(connfd,NULL,pipefd[1],NULL,32768,SPLICE_F_MORE | SPLICE_F_MOVE);
        assert(ret!=-1);
        // 将管道的输出定向到connfd客户连接文件描述符
        ret=splice(pipefd[0],NULL,connfd,NULL,32768,SPLICE_F_MORE | SPLICE_F_MOVE);
        assert(ret!=-1);
        close(connfd);
    }

    close(sock);
    return 0;
}

 client.c

#include<sys/socket.h>
#include<netinet/in.h>
#include<arpa/inet.h>
#include<signal.h>
#include<unistd.h>
#include<stdlib.h>
#include<assert.h>
#include<stdio.h>
#include<errno.h>
#include<string.h>
#include<stdbool.h>
#include<libgen.h>
const int BUFFER_SIZE=512;
int main(int argc,char* argv[])
{
    if(argc<=2)
    {
        printf("usage: %s ip_address port_number send_buffer_size\n",basename(argv[0]));
        return 1;
    }
    const char* ip=argv[1];
    int port=atoi(argv[2]);

    int sockfd=socket(PF_INET,SOCK_STREAM,0);
    assert(sockfd>=0);

    // 创建一个ipv4地址
    struct sockaddr_in server_address;
    bzero(&server_address,sizeof(server_address));
    server_address.sin_family=AF_INET;
    inet_pton(AF_INET,ip,&server_address.sin_addr);
    server_address.sin_port=htons(port);

    if(connect(sockfd,(struct sockaddr*)&server_address,sizeof(server_address))<0)
    {
        printf("connection failed\n");
    }
    else
    {
        char buffer[BUFFER_SIZE];
        memset(buffer,'a',BUFFER_SIZE);
        send(sockfd,buffer,BUFFER_SIZE,0);
        memset(buffer,'\0',BUFFER_SIZE);
        int ret=recv(sockfd,buffer,BUFFER_SIZE,0);
        printf("get %d bytes content:%s\n",ret,buffer);
    }
    close(sockfd);
    return 0;
}

fcntl函数

正如其名字(file control)描述那样,提供了对文件描述符的各种控制操作。另一个常见的控制文件描述符属性和行为的系统调用是ioctl,而且ioctl比fcntl能够执行更多的控制。但是对于控制文件描述符常用的属性和行为,fcntl函数是由POSIX规范指定的首选方法。定义如下:

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值