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\n", errno );
 5     }
 6     else
 7     {
 8         close( STDOUT_FILENO );
 9         dup( connfd );
10         printf( "abcd\n" );
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\n", 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 }

 

posted on 2015-12-09 13:21  已停更  阅读(271)  评论(0编辑  收藏  举报