1、概述
什么是高级I/O函数
不是基础I/O函数的就是高级I/O函数,基础的I/O函数包括open
、read
等。
本章重点介绍什么?
高级I/O函数一般不常用,本章重点介绍与网络编程相关的,他们大致可分为三类:
- 用于创建文件描述符的函数,包括
pipe
、dup/dup2
函数。 - 用于读写数据的函数,包括
readv/writev
、sendfile
、mmap/munmap
、splice
和tee
函数。 - 用于控制I/O行为和属性的函数,包括
fcntl
函数
2、pipe
函数
pipe
函数创建一个管道,以实现进程通信。本书在13.4节讨论如何使用管道实现进程间的哦通信,这里只介绍基本使用方法和注意事项。pipe
函数定义如下:
#include<unistd.h>
int pipe(int fd[2]);
参数是一个长度为2的int
数组。
该函数成功返回0,并将 一对 打开的文件描述符值填充到传入的fd
数组中。其中fd[0]
和 fd[1]
分别是管道的两端。如果失败,返回-1并设置errno
。
注意事项
#include<sys/socket.h>
#include<sys/types.h>
int socketpair(int domain, int type, int protocol, int fd[2]);
2、dup
函数和dup2
函数
又时我们希望吧标准输入重定向到一个文件(类似于C++的ifstream
),或者把标准输出重定向到一个网络连接(比如CGI编程)。这可以通过下面的用于复制文件描述符的dup
或dup2
函数实现:
#include<unistd.h>
int dup(int file_descriptor);
int dup2(int file_descriptor_one, int file_descriptor_two);
dup
函数返回一个新的文件描述符,该新文件描述符和原有文件描述符file_descriptor
指向相同的 文件、管道或网络连接。
dup
返回的文件描述符总是取系统当前可用的最小的整数值。
3、readv
和 writev
函数
readv
函数将数据从文件描述符读到分散的内存块中,即 分散读;writev
函数则将多块分散的内存数据一并写入文件描述符中,即 集中写。他们的定义如下:
#inlcude<sys/uio.h>
ssize_t readv(int fd, const struct iovec* vector, int count);
ssize_t writev(int fd, const struct iovec* vector, int count);
参数说明:
fd
是被读取 或 写入的文件描述符。vector
参数的类型是iovec
结构体的数组(iovec是什么)。count
参数是vector
数组的长度,即 有多少内存数据需要从fd
读出或写入到fd
。
返回值:
readv
和 writev
在成功时返回读出 或 写入fd
的字节数,失败则返回-1并设置errno
。
这两个函数其实相当于简化版的recvmsg
和 sendmsg
:(复习一下)
#include<sys/socket.h>
ssize_t recvmsg(int sockfd, struct msghdr* msg, int flags);
ssize_t sendmsg(int sockfd, struct msghdr* msg, int flags);
4、sendfile
函数
sendfile
函数在两个文件描述符之间直接传递数据(完全在内核中操作),从而避免了内核缓冲区和用户缓冲区之间的数据拷贝,效率很高,这被称为零拷贝。sendfile
函数定义如下:
#include<sys/sendfile.h>
ssize_t sendfile(int out_fd, int in_fd, off_t* offset, size_t count);
参数说明:
in_fd
参数是输出其内容的文件描述符。其实就是输入流。in_fd
必须是一个支持类似mmap
函数的文件描述符,即他必须指向真实的文件,不能是socket
和管道。out_fd
参数是接收输出内容的文件描述符。out_fd
必须是一个socket
。offset
参数指定从读入文件流的哪个位置开始读,如果为空(NULL
),则使用读入文件流默认的起始位置。count
参数指定两个文件描述符之间传输的字节数。
由in_fd
和 out_fd
的限制可见,该函数几乎是专门为在网络上传输文件而设计的。
返回值说明:
sendfile
成功时返回传输的字节数,失败返回-1并设置errno
。
5、mmap
和 munmap
函数
这两个函数可用于进程间共享内存。
mmap
函数申请一段内存空间,我们可以将这段内存空间作为进程间通信的共享内存,也可以将文件直接映射到其中。munmap
函数则释放由mmap
创建的这段内存空间。他们的定义如下:
#include<sys/mman.h>
void* mmap(void* start, size_t length, int port, int flags, int fd, off_t offset);
int munmap(void* start, size_t length);
6、splice
函数
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
是输出其数据的文件描述符。就是输入流。off_in
的含义随fd_in
代表的文件类型而改变:
——若fd_in
是管道文件描述符,那么off_in
参数必须被设置为NULL
。
——若fd_in
不是管道文件描述符,那它指向输入数据流中开始读取数据的位置。若被置为NULL
,则表示从输入流的当前偏移位置读入。fd_out
/off_out
的含义与fd_in
/off_in
相同,不过用于输出数据流。len
指定移动数据的长度。flags
参数控制数据如何移动,他可以被设置为下标中的某些值的按位或。
返回值说明:
splice
函数调用成功时返回移动字节的数量。可能返回0,表示没有数据需要移动,这有可能发生在 从管道中读取数据而该管道中没有任何数据时。
失败时返回-1,并设置errno
。
7、tee
函数
tee
函数在两个管道文件描述符之间复制数据,也是零拷贝操作。他不消耗数据,拷贝后,源文件描述符上的数据仍然可以用于后续的读操作。函数原型如下:
#include<fcntl.h>
ssize_t tee(int fd_in, int fd_out, size_t len, unsigned int flags);
参数说明:
其实和splice
函数的参数意义相同,只不过输入流和输出流都必须是文件描述符。
fd_in
输入流fd_out
输出流
返回值:
成功返回在两个文件描述符之间复制的字节数,返回0表示没有复制任何数据。
失败时返回-1,并设置errno
。
8、fcntl
(file control)函数
顾名思义,该函数用来对文件描述符执行各种控制操作。还有一个常见的控制文件描述符属性和行为的系统调用是ioctl
,而且ioctl
比fcntl
能够执行更多的控制。但是,只控制文件描述符常用的属性和行为,fcntl
函数是首选方法。其定义如下:
#include<fcntl.h>
int fcntl(int fd, int cmd, ...);
fd
参数是被操作的文件描述符,cmd
参数指定执行何种类型的操作。根据操作类型的不同,该函数可能还需要第三个可选参数。fcntl
支持的常用操作及其参数如下表所示:
fcntl
函数成功时的返回值如上表中 最后一列所示,失败则返回-1并设置errno
。
在网络编程中,fcntl
函数通常用来将一个文件描述符设置为非阻塞的,如下面代码所示:
int set_nonblocking(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; 返回文件描述符旧的状态标志,以便日后恢复该状态
}