高效I/O函数简单整理(部分)

深入理解管道与文件描述符操作
本文详细阐述了管道的使用方式,包括管道的读写特性、容量限制、TCP与管道的区别,以及如何通过socketpair创建双向管道。同时介绍了dup和dup2函数在文件描述符复制时的不同之处,以及readv、writev、sendfile、mmap、munmap、splice和tee函数在高效数据传输方面的应用。文章最后展示了如何利用splice和tee函数实现一个简单的回射服务器。
PIPE函数
fd[0]只能用于从管道读出数据,fd[1]只能用于往管道写入数据

默认是阻塞的:
     read读空管道会被阻塞,write写满管道会被阻塞

如果管道写端fd[1]引用计数降至0,即没有进程会向管道写数据,则管道读端fd[0]read操作返回0,即读到EOF
反之,读端引用计数降为0,没进程需要从管道读,则管道写端write操作会失败,引发SIGPIPE信号。

管道是字节流传输,类TCP
但是对于TCP,写入数据量大小由对端接收通告窗口大小和本端拥塞窗口大小决定。
管道本身有一个容量限制,其规定如果应用不将数据从管道读走的话,该管道最多能被写入读少数据,我们可用fcntl来修改管道容量

此外,socketpair可以方便的创建双向管道
int socketpair(int domain, int type, int protocol, int fd[2]);
我们能在本地使用这个双向管道,与pipe不同的是,socketpair创建的这对文件描述符都是既可读也可写的

------------------------------------------------------------------------------------------------------------------------------

dup和dup2
要注意的是,通过dup和dup2创建的描述符并不继承原文件描述符的属性,比如close-on-exec和non-blocking等
dup函数创建一个新的文件描述符,这个文件描述符与参数指向相同的文件、管道或者网络连接

------------------------------------------------------------------------------------------------------------------------------

readv和writev
readv将数据从文件描述符读到分散的内存中,即分散读;
writev则将多块分散的数据一并写入文件描述符中,即集中写
//这里我们看一下什么是集中写
//之前第四章讨论过web服务器,如果目标文档存在且客户具有权限,那么服务器就发送HTTP应答。
//HTTP应答包含的内容可以分为两部分,1个状态行和几个头部字段,1个空行加上请求文档内容。
//前3部分可以被放在一块内存,文档内容另一块内存,这里我们演示将这两份内存集中发送。
#include <sys/types.h>
#include <fcntl.h>
#include <sys/stat.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <errno.h>
#include <stdio.h>

#define BUFSIZE 1024

static  char *status[2] = {"200 OK","500 internet server error"};

int main(int ac, char *av[])
{
    if(ac < 3)
    {
        fprintf(stderr, "Usage : %s ipaddress port filename\n", av[0]);
        exit(1);
    }

    char *ip = av[1];
    int port = atoi(av[2]);
    char *filename = av[3];
    int ret;

    struct sockaddr_in addr;
    bzero(&addr, sizeof(addr));
    addr.sin_family = AF_INET;
    addr.sin_port = htons(port);
    inet_pton(AF_INET, ip, &addr.sin_addr);

    int sock = socket(AF_INET, SOCK_STREAM, 0);
    if(sock < 0)
    {
        perror("socket error");
        exit(1);
    }

    ret = bind(sock, (struct sockaddr *)&addr, sizeof(addr));
    if(ret < 0)
    {
        perror("bind error");
        exit(1);
    }

    ret = listen(sock, 10);
    if(ret < 0)
    {
        perror("listen error");
        exit(1);
    }

    int connfd = accept(sock, NULL, NULL);
    if(connfd < 0)
    {
        perror("accept error");
        exit(1);
    }
    //以上内容可跳过
    else
    {
        //用于保存HTTP的状态行,头部字段,和空行
        char head_buf[BUFSIZE];
        //这块内存已用字节数
        int head_len = 0;
        memset(head_buf, '\0', BUFSIZE);
        //动态分配请求文档的大小
        char *file_buf;
        //获取目标文档的信息
        struct stat file_stat;
        //判断最终是否成功获得了目标文档
        int valid = 1;
        //目标文档存在否?
        if(stat(filename, &file_stat) < 0)
        {
            valid = 0;
        }
        else
        {
            //目标文档是否为文件夹?
            if(S_ISDIR(file_stat.st_mode))
            {
                valid = 0;
            }
            //目标文档对客户来说是否有读的权限?
            else if(file_stat.st_mode & S_IROTH)
            {
                int fd = open(filename, O_RDONLY);
                file_buf = malloc(file_stat.st_size+1);
                memset(file_buf, '\0', file_stat.st_size+1);
                //读取目标文档到缓存中
                if(read(fd, file_buf, file_stat.st_size) < 0)
                {    
                    valid = 0;
                }
            }
            else
            {
                valid = 0;
            }
        }

        //如果成功读到了目标文档
        if(valid == 1)
        {
            ret = snprintf(head_buf, BUFSIZE-1, "%s %s\r\n", "HTTP/1.1", status[0]);
            head_len += ret;

            ret = snprintf(head_buf+head_len, BUFSIZE-head_len-1, "Content-Length:%d\r\n", file_stat.st_size);
            head_len += ret;
            
            ret = snprintf(head_buf+head_len, BUFSIZE-head_len-1, "%s", "/r/n" );

            //将两块内存集中写
            struct iovec iv[2];
            iv[0].iov_base = head_buf;
            iv[0].iov_len = sizeof(head_buf);
            iv[1].iov_base = file_buf;
            iv[1].iov_len = file_stat.st_size;
            ret = writev(connfd, iv, 2);
        }
        else
        {
            ret = snprintf(head_buf, BUFSIZE-1, "%s %s\r\n", "HTTP/1.1", status[1]);
            head_len += ret;

            ret = snprintf(head_buf+head_len, BUFSIZE-head_len-1, "%s", "/r/n" );
            send(connfd, head_buf, strlen(head_buf), 0);
        }
        close(connfd);
        free(file_buf);
    }
    close(sock);
    return 0;
}




------------------------------------------------------------------------------------------------------------------------------

sendfile函数
是在两个文件描述符之间直接传递数据(完全在内核中操作),从而避免了用户缓冲区和内核缓冲区之间的数据拷贝,效率很高
in_fd是待读出内容的文件描述符,out_fd是待写入内容的文件描述符
in_fd必须是一个支持类似mmap函数的文件描述符,它必须指向真实的文件,不能是socket和管道;
然而out_fd必须是一个socket
因此, sendfile几乎似乎专门为在网络上传输文件而设计的
比如在一个服务器中这样使用:
     sendfile(connfd, filefd, NULL, stat.st_size);
stat.st_size是通过fstat或stat得到的关于该文件的属性,从而获得该文件的长度; connfd为客户套接字; filefd为要读的文件的套接字;NULL表示没有偏移,即默认的起始位置
操作效率明显比较高

------------------------------------------------------------------------------------------------------------------------------

mmap和munmap

前者用于申请一段内存空间,后者用于释放该内存空间


mmap函数中prot设置内存段的访问权限:
     PROT_READ        
     PROT_WRITE
     PROT_EXEC        //内存段可执行
     PROT_NONE        //不能被访问


------------------------------------------------------------------------------------------------------------------------------

splice函数
用于在两个文件描述符之间移动数据,也是零拷贝操作
如果fd_in为管道文件描述符,那么off_in 必须设置为NULL; 如果fd_in不是管道描述符,那么可指定off偏移量,此时若为NULL,表示从输入数据流的当前偏移位置读入。

在使用这个函数时,fd_in和fd_out必须至少有一个是管道文件描述符


比如我们使用splice实现一个回射服务器:

int pipefd[2];
pipe(pipefd);
splice(connfd, NULL, pipefd[1] , NULL, 32768, SPLICE_F_MORE | SPLICE_F_MOVE);
splice(pipefd[0], NULL, connfd , NULL, 32768, SPLICE_F_MORE | SPLICE_F_MOVE);
close(connfd);



整个过程没有执行recv/send操作,因此也未涉及用户空间和内核空间的数据拷贝

------------------------------------------------------------------------------------------------------------------------------

tee函数
在两个管道文件描述符之间复制数据,也是零拷贝操作
它不消耗数据,因此源文件描述符上的数据仍可用于后续的操作
fd_in和fd_out必须是管道描述符
这里使用tee和splice实现linux中的tee程序

//大意就是用tee函数从一个管道将数据送到另一个管道而不消耗源管道中的数据
splice(STDIN_FILENO, NULL, pipe_stdout[1], NULL, 32768, SPLICE_F_MORE | SPLICE_F_MOVE);
tee(pipe_stdout[0], pipe_file[1], 32768, SPLICE_F_NONBLOCK);
splice(pipe_file[0], NULL, fd, NULL, 32768, SPLICE_F_MORE | SPLICE_F_MOVE);
splice(pipe_stdout[0], NULL, STDOUT_FILENO, NULL, 32768, SPLICE_F_MORE | SPLICE_F_MOVE);


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值