I/O复用

IO复用:select和poll函数

需求

当一个TCP客户同时处理两个输入:标准输入和套接字时,由于客户端阻塞于标准输入,此时服务端进程可能会被杀死。那是因为此时虽然服务端给客户端发了一个FIN,但由于客户端正在阻塞于标准输入,没看到EOF。这样的进程需要一种能力,告知内核一旦发现一个或者多个IO条件准备就绪的话,内核就要通知进程;

使用场景

  • 客户处理多个描述符(网络套接字和交互式输入),必须使用;
  • 既要处理监听套接字又要处理已连接套接字;
  • 既要处理TCP又要处理UDP;
  • 处理多个协议;

模型描述

IO多路复用模型是单个线程,通过追踪每个IO流(socket)的状态,从而达到管理多个IO流。有了这个模型,我们可以通过select或poll来具体实现这个模型,这样我们只会阻塞在这些函数的调用上,而不会阻塞在真正的IO上。

select

1
2
3
#include <sys/select.h>
#include <sys/time.h>
int select(int maxfdpl, fd_set* readset, fd_set* writeset, fd_set* exceptset, const struct timeval& timeout);
  • timeout:告诉内核等待指定描述符中任意一个就绪的最长等待时间;
  • 中间三个指定我们让内核测试读写和异常的文件描述符;可以通过宏来设置;
  • maxfdpl则是指定待测试的文件描述符个数,选取的值是待测试的文件描述符加一;

描述符的就绪条件

  • 可读
    • 该套接字的接收缓冲区中的字节数大于等于套接字接收缓冲区低水位标记的当前大小。TCP和UDP中默认为1;
    • 该套接字是一个监听套接字,且连接数不为0;
    • 该连接的读半部关闭(即接收了FIN的TCP连接);
    • 有一个套接字错误在等待处理;
  • 可写
    • 该套接字的发送缓冲区中的字节数大于等于套接字发送缓冲区低水位标记的当前大小。TCP和UDP中默认为2048;
    • 写半部关闭;
    • 使用非阻塞connect的套接字已经建立了连接,或者connect失败;
    • 有一个套接字错误待处理;

poll

1
2
3
4
5
6
7
8
#include<poll.h>
int poll(struct pollfd* fdarray, unsigned long nfds, int timeout);

struct pollfd{
int fd;
short events;//指定测试条件
short revents;//返回描述符的状态
};

以下条件引起poll返回特定的revent:

  • 所有正规TCP和UDP数据都被认为普通数据;
  • TCP的带外数据被认为是优先级带数据;
  • TCP连接的读半部关闭,即接收了对端的FIN,普通数据;
  • TCP存在错误时可以认为是普通数据,也可以是错误;
  • 在监听套接字上有新的连接可用,可认为是普通数据或者错误;
  • 非阻塞connect的完成;

poll识别三类数据:normal、priority band和high priority

结论

IO复用模型中最常用的函数是select,我们告知select函数就读写和异常三种条件所关心的描述符、最长等待时间和最大描述符号。缺点是单个进程能够监管的文件描述符数量较少,一般为1024;

poll函数提供类似于select的功能,不过能为流设备提供额外信息。

目前来说,select函数会更加频繁。