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 }