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详解 - 人云思云