概念
I/O多路复用(multiplexing)的本质是通过一种机制(系统内核缓冲I/O数据),让单个进程可以监视多个文件描述符,一旦某个描述符就绪(一般是读就绪或写就绪),能够通知程序进行相应的读写操作。Linux支持I/O多路复用的系统调用有select、poll、epoll。
select
API
#include <sys/select.h>
#include <sys/time.h>
int select(int max_fd,
fd_set *readset,
fd_set *writeset,
fd_set *exceptset,
struct timeval *timeout)
FD_ZERO(int fd, fd_set* fds) //清空集合
FD_SET(int fd, fd_set* fds) //将给定的描述符加入集合
FD_ISSET(int fd, fd_set* fds) //将给定的描述符从文件中删除
FD_CLR(int fd, fd_set* fds) //判断指定描述符是否在集合中
流程
poll
API
#include <poll.h>
int poll(struct pollfd fds[], nfds_t nfds, int timeout);
typedef struct pollfd {
int fd; // 需要被检测或选择的文件描述符
short events; // 对文件描述符fd上感兴趣的事件
short revents; // 文件描述符fd上当前实际发生的事件
} pollfd_t;
事件
POLLIN 有数据可读
POLLRDNORM 有普通数据可读
POLLRDBAND 有优先数据可读
POLLPRI 有紧迫数据可读
POLLOUT 写数据不会导致阻塞
POLLWRNORM 写普通数据不会导致阻塞
POLLWRBAND 写优先数据不会导致阻塞
POLLMSGSIGPOLL 消息可用
当需要监听多个事件时,使用POLLIN | POLLRDNORM设置 events 域;当poll调用之后检测某事件是否发生时,fds[i].revents & POLLIN进行判断。
epoll
epoll在Linux2.6内核正式提出,是基于事件驱动的I/O方式,相对于select和poll来说,epoll没有描述符个数限制,使用一个文件描述符管理多个描述符,将用户关心的文件描述符的事件存放到内核的一个事件表中,这样在用户空间和内核空间的copy只需一次。优点如下:
- 没有最大并发连接的限制,能打开的fd上限远大于1024(1G的内存能监听约10万个端口)
- 采用回调的方式,效率提升。只有活跃可用的fd才会调用callback函数,也就是说 epoll 只管你“活跃”的连接,而跟连接总数无关,因此在实际的网络环境中,epoll的效率就会远远高于select和poll。
- 内存拷贝。使用mmap()文件映射内存来加速与内核空间的消息传递,减少复制开销。
epoll对文件描述符的操作有两种模式:LT(level trigger,水平触发)和ET(edge trigger)。
-
水平触发:默认工作模式,即当epoll_wait检测到某描述符事件就绪并通知应用程序时,应用程序可以不立即处理该事件;下次调用epoll_wait时,会再次通知此事件。
-
边缘触发:当epoll_wait检测到某描述符事件就绪并通知应用程序时,应用程序必须立即处理该事件。如果不处理,下次调用epoll_wait时,不会再次通知此事件。(直到你做了某些操作导致该描述符变成未就绪状态了,也就是说边缘触发只在状态由未就绪变为就绪时通知一次)。
ET模式很大程度上减少了epoll事件的触发次数,因此效率比LT模式下高。
API
#include <sys/epoll.h>
int epoll_create(int size);
int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);
int epoll_wait(int epfd, struct epoll_event * events, int maxevents, int timeout);
epoll_create
创建一个epoll句柄,参数size表明内核要监听的描述符数量。调用成功时返回一个epoll句柄描述符,失败时返回-1。
epoll_ctl
注册要监听的事件类型。四个参数解释如下:
- epfd表示epoll句柄;
- op表示fd操作类型:
- EPOLL_CTL_ADD(注册新的fd到epfd中), -
- EPOLL_CTL_MOD(修改已注册的fd的监听事件),
- EPOLL_CTL_DEL(从epfd中删除一个fd)
- fd是要监听的描述符;
- event表示要监听的事件
struct epoll_event {
__uint32_t events; /* Epoll events */
epoll_data_t data; /* User data variable */
};
typedef union epoll_data {
void *ptr;
int fd;
__uint32_t u32;
__uint64_t u64;
} epoll_data_t;
epoll_wait
等待事件的就绪,成功时返回就绪的事件数目,调用失败时返回 -1,等待超时返回 0。
- epfd是epoll句柄
- events表示从内核得到的就绪事件集合
- maxevents告诉内核events的大小
- timeout表示等待的超时事件
比较
应用
- 单线程 + epoll:redis
- 多线程 + epoll:nattyserver
- 多进程 + epoll:nginx
- 多核 + epoll:ntyco