I/O多路复用

136 阅读3分钟

select

/**
 * @param n 文件描述符中多少是有效位,就不用遍历完1024位
 * @param readfds 读文件描述符
 * @param writefds 写描述符
 * @param exceptfds 异常描述符
 * @param timeout 超时时间
 * returns 成功返回描述符的位数,超时返回0,失败返回-1
 */
int select (int n, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, struct timeval *timeout);

select监听描述符有最大限制,是一个1024的bitmap

poll

/**
 * @param fds pollfd结构体数组
 * @param nfds 数组长度
 * @param timeout 超时时间
 * returns 成功返回描述符的位数,超时返回0,失败返回-1
 */
int poll (struct pollfd *fds, unsigned int nfds, int timeout);

/**
 * events:
 * POLLIN:数据可读
 * POLLPRI:一些异常,tcp数据溢出,cgroup.events文件被修改或者伪master分组模式下slave的状态改变
 * POLLOUT:数据可写,写入过程仍然会阻塞(除非设置了O_NONBLOCK)
 * revents:
 * POLLERR: 错误,读取写描述符终端也会返回这个错误
 * POLLHUP:挂起,读取流时关闭了管道
 * POLLNVAL:无效请求,文件描述符没有打开
 */
struct pollfd {
    int fd; /* file descriptor */
    short events; /* requested events to watch */
    short revents; /* returned events witnessed */
};

仍然需要轮询fds来获取文件描述符,O(nfds)操作

epoll

epoll的API

1. 创建epoll

/**
 * @param size 告诉内核监听的数量
 * returns 返回一个epoll句柄(即一个文件描述符)
 */
int epoll_create(int size);

2. 控制epoll

/**
 * @param epfd 用epoll_create所创建的epoll句柄
 * @param op 表示对epoll监控描述符控制的动作
 *
 * EPOLL_CTL_ADD(注册新的fd到epfd)
 * EPOLL_CTL_MOD(修改已经注册的fd的监听事件)
 * EPOLL_CTL_DEL(epfd删除一个fd)
 *
 * @param fd 需要监听的文件描述符
 * @param event 告诉内核需要监听的事件
 *
 * @returns 成功返回0,失败返回-1, errno查看错误信息
 */
int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);

/**
 * events几个宏集合:
 * EPOLLIN :表示对应的文件描述符可以读(包括对端SOCKET正常关闭);
 * EPOLLOUT:表示对应的文件描述符可以写;
 * EPOLLPRI:表示对应的文件描述符有紧急的数据可读(这里应该表示有带外数据到来);
 * EPOLLERR:表示对应的文件描述符发生错误;
 * EPOLLHUP:表示对应的文件描述符被挂断;
 * EPOLLET: 将EPOLL设为边缘触发(Edge Triggered)模式,这是相对于水平触发(Level Triggered)来说的。
 * EPOLLONESHOT:只监听一次事件,当监听完这次事件之后,如果还需要继续监听这个socket的话,需要再次把这socket加入到EPOLL队列里
 */
struct epoll_event {
	__uint32_t events; /* epoll 事件 */
	epoll_data_t data; /* 用户传递的数据 */
}

typedef union epoll_data {
	void *ptr;
	int fd;
	uint32_t u32;
	uint64_t u64;
} epoll_data_t;

3. 等待epoll

/**
 *
 * @param epfd 用epoll_create所创建的epoll句柄
 * @param event 从内核得到的事件集合
 * @param maxevents 告知内核这个events有多大,
 * 注意: 值 不能大于创建epoll_create()时的size.
 * @param timeout 超时时间
 * -1: 永久阻塞
 * 0: 立即返回,非阻塞
 * >0: 指定微秒
 *
 * @returns 成功: 有多少文件描述符就绪,时间到时返回0
 * 失败: -1, errno 查看错误
 */
int epoll_wait(int epfd, struct epoll_event *events, int maxevents, int timeout);

epoll的触发模式

水平触发(Level Triggered)
水平模式同时支持阻塞和非阻塞socket,当epoll_wait检测到描述符事件发生并将此事件通知应用程序,应用程序可以不立即处理该事件。下次调用epoll_wait时,会再次响应应用程序并通知此事件。

边缘触发(Edge Triggered)
边缘模式只支持非阻塞模式,相比水平触发模式效率更高,当epoll_wait检测到描述符事件发生并将此事件通知应用程序,应用程序必须立即处理该事件。如果不处理,下次调用epoll_wait时,不会再次响应应用程序并通知此事件。

参考资料

深入理解Linux中网络I/O复用并发模型 - 刘丹冰
Linux IO模式及 select、poll、epoll详解 - 人云思云