zoukankan      html  css  js  c++  java
  • 6 高级IO函数

    6.1 pipe函数

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

    1 #include<unistd.h>
    2 int pipe(int fd[2]);

    参数包含两个文件描述符fd[0]和fd[1],往fd[1]写入的数据可以从fd[0]读出

    默认情况下这对文件描述符都是阻塞的。如果用read调用读取一个空管道,read将会阻塞直到有数据可读为止;write类似。

    如果将fd[0]fd[1]都设置为非阻塞,则read和write会有不同行为。如果fd[1]引用计数减少到0,即没有任何进程需要往管道写入数据,则对fd[0]的read操作返回0,即读到了EOF;反之,如果fd[0]引用计数减少至0,则对fd[1]的write操作将失败,并引发SIGPIPE信号。

    管道内部传输的数据是字节流,这和TCP字节流的概念相同。从Linux 2.6.11开始,管道容量的大小是默认65536字节。可以使用fcntl修改。

    socketpair函数可以方便的创建双向管道:

    1 #include<sys/types.h>
    2 #include<sys/socket.h>
    3 int socketpair(int domain, int type, int protocol, int fd[2]);

    6.2 dup函数和dup2函数 

    复制文件描述符,它们经常用来重定向进程的stdin、stdout和stderr。文件描述符是与打开文件或者数据流 相关联的整数, 0、1、2 是系统保留的三个文件描述符,分别对应标准输入、标准输出、标准错误 

    1 #include<unistd.h>
    2 int dup(int file_descriptor);
    3 int dup2(int file_descriptor_one, int file_descriptor_two);

    利用函数dup,我们可以复制一个描述符。传给该函数一个既有的描述符,它就会返回一个新的描述符,这个新的描述符是传给它的描述符的拷贝。这意味着,这两个描述符共享同一个数据结构。

    dup2函数跟dup函数相似,但dup2函数允许调用者规定一个有效描述符和目标描述符的id。dup2函数成功返回时,目标描述符(dup2函数的第二个参数)将变成源描述符(dup2函数的第一个参数)的复制品,换句话说,两个文件描述符现在都指向同一个文件,并且是函数第一个参数指向的文件。

    通过dup和dup2创建的文件描述符并不继承原来的文件描述符的属性,比如close-on-exec和non-blocking等。

    例子:CGI服务器的基本原理

     1    int connfd = accept( sock, ( struct sockaddr* )&client, &client_addrlength );
     2     if ( connfd < 0 )
     3     {
     4         printf( "errno is: %d
    ", errno );
     5     }
     6     else
     7     {
     8         close( STDOUT_FILENO );
     9         dup( connfd );
    10         printf( "abcd
    " );
    11         close( connfd );
    12     }

    关闭标准输出文件描述符,dup总返回系统最小的可用文件描述符,所以实际返回值是1,这样就将所连接的socket文件描述符connfd定位到标书输出,printf调用的输出将被客户端获得,这就是CGI服务器的基本原理

    6.3 readv函数和writev函数 

    读指从文件描述符读到内存,写指从内存写入文件描述符

    readv函数将数据从文件描述符读到分散的内存块中,即分散读;writev函数则将多块分散的内存数据一并写入文件描述符中,即集中写。

    1 #include<sysy/uio.h>
    2 ssize_t readv(int fd, const struct iovec* vector, int count);
    3 ssize_t writev(int fd, const struct iovec* vector, int count);
    4 struct iovec {
    5     void *iov_base; //starting address
    6     size_t iov_len; //number of bytes to transfer
    7 };

    例子:web服务器对客户端的HTTP请求响应,HTTP应答包含1个状态行,多个头部字段,1个空行和文档内容,这些可能不在一起,可以用writev写入一个文件描述符

    1 struct iovec iv[2];
    2 iv[ 0 ].iov_base = header_buf;
    3 iv[ 0 ].iov_len = strlen( header_buf );
    4 iv[ 1 ].iov_base = file_buf;
    5 iv[ 1 ].iov_len = file_stat.st_size;
    6 ret = writev( connfd, iv, 2 );

    6.4 sendfile函数

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

    1 #include <sys/sendfile.h>
    2 ssize_t sendfile(int out_fd, int in_fd, off_t* offset, size_t count);

    in_fd必须是一个支持类似mmap函数的文件描述符,即它必须指向真实的文件,不能是socket和管道;

    而out_fd则必须是一个socket,由此可见,sendfile几乎是专门为在网络上传输文件设计的。

    offset:读入位置

    count:传输字节数

    6.5 mmap函数和munmap函数

    mmap函数用于申请一段内存空间。我们可以将这段内存作为进程间通信的共享内存,也可以将文件直接映射到其中。munmap释放这段内存空间。

    1 #include <sys/mman.h>
    2 void* mmap(void *start, size_t length, int prot, int flags, int fd, off_t offset);
    3 int munmap(void *start, size_t length);

    start可以指定这段内存的起始地址,当为NULL时,系统自动分配一个地址。

    prot参数用来设置访问权限。按位或:

    1 PROT_READ        //内存可读
    2 PROT_WRITE    //内存可写
    3 PROT_EXEC    //内存段可执行
    4 PROT_NONE    //内存段不能被访问

    flags参数控制内存段修改后的行为

    6.6 splice函数

    用于在两个文件描述符之间移动数据,也是零拷贝操作

    1 #include <fcntl.h>
    2 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和fd_out必须至少有一个是管道文件描述符,splice函数调用成功时返回移动字节的数量

    如果fd_in是管道描述符,那么off_in必须为NULL。若不是管道,则标示从什么位置读取,fd_out类似

    flags参数:

    1 SPLICE_F_MOVE    //按整页内存移动数据,只是给内核一个提示,没有实际效果。
    2 SPLICE_F_NONBLOCK    //实际效果还会受到文件描述符本身的阻塞状态影响。
    3 SPLICE_F_MORE    //给内核一个提示:后续的splice调用将读取更多数据。
    4 SPLICE_F_GIFT    //没有效果    

    例子:使用splice函数实现的echo服务器

     1     int connfd = accept( sock, ( struct sockaddr* )&client, &client_addrlength );
     2     if ( connfd < 0 )
     3     {
     4         printf( "errno is: %d
    ", errno );
     5     }
     6     else
     7     {
     8         int pipefd[2];
     9         assert( ret != -1 );
    10         ret = pipe( pipefd );
    11         ret = splice( connfd, NULL, pipefd[1], NULL, 32768, SPLICE_F_MORE | SPLICE_F_MOVE );     //从connfd流入的客户端数据定向到管道
    12         assert( ret != -1 );
    13         ret = splice( pipefd[0], NULL, connfd, NULL, 32768, SPLICE_F_MORE | SPLICE_F_MOVE );    //将管道输出定向到connfd客户端连接的文件描述符
    14         assert( ret != -1 );
    15         close( connfd );
    16     }

    6.7 tee函数

    在两个管道文件描述符之间复制数据,也是零拷贝。它不消耗数据,因此源文件描述符上的数据仍然可以用于后续的读操作

    1 #include <fcntl.h>
    2 ssize_t tee(int fd_in, int fd_out, size_t len, unsigned int flags);

    6.8 fcntl函数

    提供了对文件描述符的各种控制操作

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

    在网络编程中,fcntl函数通常用来将一个文件描述符设置为非阻塞的 重要常用

    1 #include <fcntl.h>
    2 int fcntl(int fd, int cmd, ...);
    3 int setnonblocking(int fd)
    4 {
    5     int old_option = fcntl( fd ,F_GETFD );    //获取文件描述符旧状态标志
    6     int new_option = odl_option | O_NONBLOCK;    //设置非阻塞标志
    7     funcl( fd, F_SETFL, new_option);
    8     return old_option;    //返回旧状态以便日后恢复
    9 }
  • 相关阅读:
    定义全局时间过滤器
    vue局部过滤器和全局过滤器
    vue-ref指令
    vue进行代码排序
    vue-通过name进行数据过滤
    将vue文档下载到本地预览
    由于vue的for循环id并不严谨,提高id严谨性
    vue指令v-for报错:Elements in iteration expect to have 'v-bind:key' directives.eslint-plugin-vue
    FlowPortal BPM历史版本升级说明
    102从 Outlook 中将电子邮件、联系人和日历导出到 .pst 文件
  • 原文地址:https://www.cnblogs.com/raichen/p/5031117.html
Copyright © 2011-2022 走看看