• 第六章 高级I/O函数


    第六章 高级I/O函数


    6.1 pipe函数

    即管道函数,用于进程间的通信.

    #include<unistd.h>
    int pipe(int fd[2]); // fd:filedes
    	
    // 主要用于socket描述符.
    #include <sys/types.h>          /* See NOTES */
    #include <sys/socket.h>
    
    int socketpair(int domain, int type, int protocol, int sv[2]);
    //domain只能支持AF_UNIX.即本地协议族.
    
    • pipe函数只能创建单向管道,其中fd[0]用于读,fd[1]用于写.若实现双向则需要两个管道.

    • 默认情况下,pipe创建的这一对文件描述符是阻塞的.表现为:

      1)若write到一个满的管道,直到有空闲才执行.

      2)若read一个空的管道,直到有数据可读才执行.

    • 管道本身有容量限制,即最大可写大小,默认65536字节(自Linux 2.6.11).

    • socket有一个基础API,就是上面的socketpair函数,其可创建双向管道,双向既可读又可写.


    6.2 dup,dup2,dup3函数

    可用于把标准输入重定向到一个文件,或者把标准输出重定向到一个网络连接.

    #include <unistd.h>
    
    int dup(int oldfd);
    int dup2(int oldfd, int newfd);
    
    #define _GNU_SOURCE             /* See feature_test_macros(7) */
    #include <fcntl.h>              /* Obtain O_* constant definitions */
    #include <unistd.h>
    int dup3(int oldfd, int newfd, int flags);
    
    • dup函数返回的总是取系统当前可用的最小整数值.dup2/3返回的是第一个不小于newfd的整数值.

    • 通过dup和dup2函数创建的文件描述符并不继承原文件描述符的属性.(如close-on-exec,non-blocking).

    • dup3可以选择是否开启close-on-exec,non-blocking属性.

    例子:

    int connfd = accept(sock, (struct sockaddr *)&client, &client_addrlength);
    ...
    // 关闭标准输出
    close(STDOUT_FILENO);
    // 复制socket文件描述符connfd 
    dup(connfd);
    // 将输出到connfd中
    printf("abcd
    ");
    close(connfd);
    ...
    

    最终printf调用的输出将被客户端获得,而不是显示在服务端程序的终端上,这就是CGI服务器的基本工作原理.


    6.3 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);
    

    而iovec结构体为:

    struct iovec
    {
    	void  *iov_base;    /* Starting address */
    	size_t iov_len;     /* Number of bytes to transfer */
    };
    

    当Web服务器解析完一个HTTP请求之后,如果目标文档存在且客户端具有读取该文档的权限,那么就需要发送一个HTTP应答来传输该文档,这个文档包含一个状态行,多个头部字段,一个空行和文档内容.其中,前三部分的内容可能被Web服务器放置在一块内存中,而文档的内容通常被读入到另一块单独的内存.我们并不需要把这两部分拼接到一起再发送,而是可以使用writev函数将他们同时写出.

    例子:在Web服务器上的集中写,将服务上的一个文件传输给客户端.

    ...
    static const char *status_line[2] = {"200 OK","500 Internal server error"};
    ...
    // 取出连接.
    int connfd = accept(sock, (struct sockaddr *)&client, &client_addrlength);
    ...
    // 检测服务上文件是否存在.
    // 检测是否有权限.
    ...
    // 获取服务器上文件描述符.
    int fd = open(file_name, O_RDONLY);
    ...
    //下面部分内容将HTTP应答的状态行“Content-Length”头部字段和一个空行依次加入header_buf中
    char header_buf[BUFFER_SIZE];
    ret = snprintf(header_buf, BUFFER_SIZE - 1, "%s %s
    ","HTTP/1.1", status_line[0]);
    len += ret;
    ret = snprintf(header_buf + len, BUFFER_SIZE - 1 - len,"Content-Length: %lld
    ", (long long)file_stat.st_size);
    
    //利用writev将header_buf和file_buf的内容一并写出.
    struct iovec iv[2];
    iv[0].iov_base = header_buf;
    iv[0].iov_len  = strlen(header_buf);
    iv[1].iov_base = file_buf;
    iv[1].iov_len  = file_stat.st_size;
    ret = writev(connfd, iv, 2);
    

    以上代码关注的重点是HTTP应答的发送,省略了HTTP请求的接收及解析.


    6.4 sendfile函数

    在两个文件描述符之间直接传递数据,而且完全在内核之间操作,从而避免了缓冲区和用户缓冲区之间的数据拷贝,也称之为零拷贝.

    #include <sys/sendfile.h>
    
    ssize_t sendfile(int out_fd, int in_fd, off_t *offset, size_t count);
    
    • out_fd : 需要支持mmap的文件描述符.

    • in_fd : 在Linux 2.6.3之前必须是socket文件描述符,而在其之后可以是一个普通文件描述符.

    • sendfile函数可以说是一个专门为网络上传输文件而设计的.

    例子:将服务器上的一个文件传送给客户端.

    int filefd = open(file_name, O_RDONLY);
    assert( filefd > 0);
    struct stat stat_buf;
    fstat(filefd, &stat_buf);
    ...
    int connfd = accept(sock, (struct sockaddr *)&client, &client_addrlength);
    ...
    sendfile(connfd, filefd, NULL, stat_buf.st_size);
    close(connfd);
    ...
    

    可见没有为目标文件分配任何用户空间的缓存,也没有执行读取文件的操作.


    6.5 mmap 和 munmap函数

    mmap用于申请一段内存空间,可以将其作为进程间通信的共享内存,也可以将文件直接映射到其中.
    munmpa函数则释放由mmap创建的这段内存.

    #include <sys/mman.h>
    
    void *mmap(void *start,size_t length,int prot,int flags,int fd,off_t offsize);
    int munmap(void *start,size_t length);
    
    • start:若设置为NULL,则由系统自动分配一个地址.

    • prot:设置内存的访问权限.

    • flags:控制内存段内容被修改后的程序行为.

    • fd:是被映射文件对应的文件描述符,一般通过open获得.


    6.6 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);
    
    • fd_in 和 fd_out 至少有一个是管道文件描述符.

    • 若fd_in是管道文件描述符,则off_in必须为NULL.

    • off_in 和 off_out 用于设置具体的偏移位置.

    • flags控制数据如何移动.

    例子:将客户端的数据原样发送给客户端(回射服务).

    ...
    int connfd = accept(sock, (struct sockaddr *)&client, &client_addrlength);
    ...
    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, 326768, SPLICE_F_MORE | SPLICE_F_MOVE);
    assert(ret != -1);
    close(connfd);
    

    可见整个过程未执行send操作,因此未涉及用户空间和内核空间之间的数据拷贝.


    6.7 tee函数

    用于两个管道文件描述符之间复制数据,也是零拷贝.

    #define _GNU_SOURCE         /* See feature_test_macros(7) */
    #include <fcntl.h>
    
    ssize_t tee(int fd_in, int fd_out, size_t len, unsigned int flags);
    
    • fd_in 和 fd_out必须都是管道文件描述符.

    • 参数意义和splice相同.

    例子:同时输出数据到终端和文件描述符.

    int pipefd_stdout[2];
    int ret = pipe(pipefd_stdout);
    assert(ret != -1);
    
    int pipefd_file[2];
    ret = pipe(pipefd_file);
    assert(ret != -1);
    
    /*将标准输入内容输入管道 pipefd_stdout */
    ret = splice(STDIN_FILENO, NULL, pipefd_stdout[1], NULL,32678, SPLICE_F_MORE | SPLICE_F_MOVE);
    assert(ret != -1);
    
    /*将管道pipefd_stdout的输出复制到管道pipefd_file的输入端 */
    ret = tee(pipefd_stdout[0], pipefd_file[1], 32678, SPLICE_F_NONBLOCK);
    assert(ret != -1);
    
    /*将pipefd_file的输出定向到文件描述符file_fd上,从而将标准输入的内容写入文件*/
    ret = splice(pipefd_file[0], NULL, filefd, NULL,32678, SPLICE_F_MORE | SPLICE_F_MOVE);
    assert(ret != -1);
    
    /*将管道pipefd_stdout的输出定向到标准输出, 其内容和写入文件的内容完全一致*/
    ret = splice(pipefd_stdout[0], NULL, STDOUT_FILENO, NULL,32678, 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]);
    

    6.8 fcntl函数

    用于对文件描述符的各种控制操作,另一个是ioctl,它比fcntl能够执行更多控制.

    #include<fcntl.h>
    
    int fcntl(int fd , int cmd,...);
    

    例子:fcntl通常用来将一个文件描述符设置为非阻塞的.

    int setnonblocking (int fd){
    	// 获取文件描述符旧的状态标志.
    	int old_option = fcntl(fd,F_GETFL);
    	// 设置非阻塞标志.
    	int new_option = old_option | O_NONBLOCK;
    	fcntl(fd,F_SETFL,new_option);
    	// 返回文件描述符旧的状态标志,以便日后回复该状态标志.
    	return old_option;
    }
    

    关于第六章的总结

    • 学习了几个高级I/O函数,可能并不像Linux基础的IO函数那样常用,当通过例子可以看出来,在特定的情况下能表现出优异的性能.

    • 用于创建文件描述符的函数:pipe/dup.

    • 用于读写数据的函数,包括readv/writev,sendfile,mmap/munmap,splice,tee.

    • 用于控制I/O行为和属性的函数:fcntl.


    From

    Linux 高性能服务器编程 游双著 机械工业出版社

    MarkdownPad2

    Aaron-z/linux-server-high-performance

    2017/2/4 11:43:54

  • 相关阅读:
    数组
    字符对象的方法
    事件
    判断数据类型
    数据类型和变量
    语法
    快速入门
    JavaScript简介
    Spring init-method和destroy-method属性的使用
    spring3后提供了的context:property-placeholder/元素
  • 原文地址:https://www.cnblogs.com/leihui/p/6394442.html
Copyright © 2020-2023  润新知