Linux高性能服务器编程 - 第6章 高级I/O函数

讨论与网络编程相关的三类高级I/O函数:

  1. 用于创建文件描述符的函数:pipe / dup / dup2
  2. 用于读写数据的函数:readv / writev , sendfile , splice , tee , mmap / munmap
  3. 用于控制I/O行为和属性的函数:fcntl

备注:根据个人理解,调整部分函数的介绍顺序

1.pipe

用于创建一个管道,以实现进程间通信。

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

通过pipe创建的两个文件描述符构建管道的两端:进程往fd[1]里写数据,往fd[0]读数据。可见,用一个pipe只能创建单向的数据流。若想实现双向数据传输,需要两个pipe。

默认情况下,这一对文件描述符是阻塞的。阻塞模式下,读写行为一致。非将一对文件描述符设置为非阻塞,读写会有不同的行为。

  • 读:若fd[1]的引用计数为0,则针对该管道的读端文件描述符fd[0]的读操作将返回0,即读到End of File
  • 写:若fd[0]的引用计数为0,则针对该管道的写端文件描述符fd[1]的写操作将引发SIGPIPE信号

管道内部传输的是字节流,和TCP字节流的概念相同。但二者有细微差别:

  • 管道字节流:容量受管道本身的限制,通常为65535字节,可通过fcntl改变其容量。
  • TCP字节流:受对端的接口通告窗口大小和本端的拥塞窗口的大小的限制。

除了创建两个管道来构建双向传输,socket基础API的socketpair可以方便的创建双向管道。

#include <sys/types.h>
#include <sys/socket.h>

int socketpair(int domain, int type, int protocol, int fd[2]);

2.dup / dup2

可通过dup / dup2实现把标准输入重定向到一个文件,或把标准输出重定向网络连接(比如CGI编程)。

#include <unistd.h>
int dup(int fd);
int dup2(int fd, int fd_min);

dup创建新文件描述符,该文件描述符和fd指向相同的文件、管道或网络连接,并且其返回的文件描述符总是取系统当前可用的最小整数值。
dup2与dup类似,不过它返回第一个不小于fd_min的整数值。

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

3.readv / writev

  • 分散读(scatter read):readv将文件描述符的数据读到分散的内存块中
  • 集中写(gather write):writev将多块分散的内存数据一并写入文件描述符中
#include <sys/uio.h>

ssize_t readv(int fd, const struct iovec* vector, int count);
sszie_t writev(int fd, const struct iovec* vector, int count);

4.sendfile

sendfile实现在两个文件描述符中直接传递数据,此过程完全在内核进行,省去了用户缓冲区与内核缓冲区之间的数据拷贝,效率很高,称为零拷贝。

#include <sys/sendfile.h>

//  offset是in_fd的偏移值
//  in_fd是一个支持类似mmap函数的文件描述符,即它必须指向真实文件,不能是socket和管道
//  out_fd必须是一个socket
sszie_t sendfile(int out_fd, int in_fd, off_t* offset, size_t count);

由以上注释可见,sendfile几乎是专门为在网络上传输文件而设计的。

5.splice

与sendfile一样,也是零拷贝。

#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必须为NULL。
  • 若fd_in不是管道文件描述符,那么off_in表示从输入数据流的off_in处开始读取数据。若off_in为NULL,表示从输入数据流的当前位置读入。

fd_out及off_out含义与fd_in及off_in类似。

6.tee

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

#include <fcntl.h>

ssize_t tee(int fd_in, int fd_out, size_t length, unsigned int flags);

7.mmap / munmap

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

#include <sys/mman.h>

//  start允许用户使用某个特定的地址作为这段内存的起始地址(亦即指定起始地址),如果它被设置成NULL,则系统自动分配一个地址
//  prot:内存段访问权限,可取PROT_READ, PROT_WRITE, PROT_EXEC, PROT_NONE
//  flags:控制内存段内容被修改后程序的程序的行为,可取MAP_SHARED, MAP_PRIVATE, MAP_ANONYMOUS, MAP_FIXED, MAP_HUGETLB。若取MAP_ANONYMOUS,则内存不映射至文件,此时fd和offset无意义。
void* mmap(void* start, size_t length, int prot, int flags, int fd, off_t offset);

int munmap(void* start, size_t length);

8.fcntl

顾名思义,fcntl用于控制文件描述符的属性和行为,另一个常见的用于控制文件描述符属性和行为的函数ioctl更具有普通性,控制范围更广。但fcntl是POSIX规范指定的用于专门控制文件描述符的首选。

#include <fcntl.h>

int fcntl(int fd, int cmd, …);

cmd可分为5类12项:

  • 复制文件描述符:F_DUPFD, FDUPFD_CLOEXEC
  • 获取和设置描述符的标志:F_GETFD, F_SETFD
  • 获取和设置描述符的状态标志:F_GETFL, FSETFL
  • 管理信号:F_GETOWN, F_SETOWN, F_GETSIG, F_SETSIG
  • 操作管道容量:F_GETPIPE_SZ, F_SETPIPE_SZ

此外,SIGIO和SIGURG与linux其他信号不同,它们必须与某个文件描述符关联方可使用:

  • 当被关联的文件描述符可读或可写时,系统将触发SIGIO信号
  • 当被关联的文件描述符(必须是socket)有带外数据时,系统将触发SIGURG信号

将信号与文件描述符关联的方法,就是使用fcntl函数为目标文件描述符指定宿主进程或进程组,由指定的宿主进程或进程组捕获这两个信号

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值