为什么需要IO复用
以上一章阻塞式IO的情况为例子,假设此时客户端阻塞在标准输入上,同时server在另一端向client发送了FIN同步信号,这时客户端是读不到的,就不会针对该FIN回复一个ACK,server就要不断重试直到超时,导致此次半关闭失败。
IO复用的出现正是为了解决这个问题,同时监听多个描述符是否就绪或者出错。
I/O模型
阻塞式IO
非阻塞式IO
异步IO
IO复用
这个图片体现的并不够好
信号驱动式IO
select函数
/**
* maxfd1 所有描述符的最大值+1
* readset 读描述符集
* writeset 写描述符集
* exceptset 错误描述符集
* timeout 超时时间设置
* 描述符集是一个类似于按位操作的概念
*/
int select(int maxfd1,fd_set *readset,fd_set *writeset,fd_set *exceptset,const struct timeval timeout);
//还有几个重要的函数需要说明
// 这是对描述符集进行操作的。
void FD_ZERO(fd_set *fdset);
void FD_SET(int fd,fd_set *fdset);
void FD_CLR(int fd,fd_set *fdset);
void FD_ISSET(int fd,fd_set *fdset);
select返回情况
shutdown函数
/**
* howto:
* SHUT_RD; 读半关闭
* SHUT_WR; 写半关闭
* SHUT_RDWR; 等同于close了
*/
int shutdown(int sockfd,int howto)
TCP是一个全双工的链路,在这种情况下
假设请求9之后左端再无请求需要发送,此处面临两个选择:
- 保持连接,等待所有请求确认到达后关闭连接
- 关闭连接
先说2的方式,如果说9发送完以后就去关闭连接,那么缓冲区上还有一些正在传输的数据,那么这些数据将不再能得到正确的处理
一直保持连接直到所有请求的确认:这虽然是可行的,但这耦合了一些额外的不相关代码(处理请求的累计确认),且占用额外时间的socket通道。
所以说最好的方式就是采用shutdown的方式,采用半关闭的方式。
poll函数
struct pollfd{
int fd; // 待测试描述符
short events; // 待测试事件
short revents; // 返回后待测试描述符的状态
};
int poll(struct pollfd *fdarray,unsigned long nfds,int timeout)