IO复用
IO复用:使得程序能同时监听多个文件描述符
select:
select在一段指定的时间内,监听用户感兴趣的文件描述符的 读、写、异常事件。
select(int nfds,fd_set* readfds ,fd_set* writefds,fd_set* exceptfds,struct timeval* timeout);
当调用了select之后整个进程就会被block。同时kernel会监听select负责的所有的socket.如果
相关的时间就绪就会返回。
但是select只能处理 读、写、异常事件,不能处理更多的事件。
而且内核是对fd_set集合在线修改,下次调用不得不重置相应的集合。。。
poll:
int poll(struct pollfd* fds,....);
与select不同的是poll将 文件描述符和时间集合都定义在了 结构体中。并且注册事件和实际发生事件
被分开了。
struct pollfd
{
int fd;
short events; //注册事件
short revents; //实际发生事件
}
于是每次内核修改的是revents,无需重置pollfd了。
epoll:
epoll将用户关心的文件描述符放在内核的一个事件表中,从而无须每次重复传递。但需要一个额外的文件描述符
来标志内核中的时间表
epoll_create(int size) //创建一个事件表
epoll_ctl() //给文件符指定操作类型(注册xxx上的xxx事件...)
epoll_wait() //等待事件
三种方法差异:
select和poll都只能工作在相对低效的LT模式中,epoll则能在ET模式中并支持EPOLLONESHOT。
select和poll采用的轮询的方式,每次调用都要扫描整个事件表O(n),而epoll采用的是回调,如果检测到
就绪的,回调函数就将对应时间插入内核就绪队列中,最后直接拷贝出来O(1)。
如果触发频繁,epoll的效率不一定高。所以epoll适合连接数量多,活动少的情况。
LT和ET:
1.LT(电平模式):
当epoll_wait检测到事件发生,程序可以不立即处理,再次调用还会出现此事件,直到事件被处理。
2.ET(边沿触发):
当epoll_wait检测到事件发生,程序必需立即处理,因为再次调用不会出现。
EPOLLONESHOT事件:
即便使用ET,一个事件还是可能被多次触发。
比如一个线程读取完某个socket上的数据并进行处理时,socket又出现新的数据可读。。。
此时另一个线程会被唤醒来处理这个。
如果希望任意时刻一个socket只被一个线程处理,可以使用epoll的EPOLLONESHOT
参考:
linux高性能服务器编程