select、poll、epoll都是IO多路复用的机制,但是他们的机制有很大的区别
1、select
select机制刚开始的时候,需要把fd_set从用户空间拷贝到内核空间,并且检测的fd数是有限制的,由FD_SETSIZE设置,一般是1024。
检测的时候,根据timeout,遍历fd_set表,把活跃的fd(可读写或者错误),拷贝到用户空间,
再在用户空间依次处理相关的fd。
这个机制是linux内核很早的版本,epool是根据select,pool基础上优化的,缺点比较多。
缺点:
1)每次调用select的时候需要把fd_set从用户空间拷贝到内存空间,比较耗性能。
2)wait时,需要遍历所有的fd,消耗比较大。
3)select支持的文件数大小了,默认只有1024,如果需要增大,得修改宏FD_SETSIZE值,并编译内核(麻烦,并且fd_set中的文件数多的话,每次遍历的成本就很大)。
2. pool
poll的实现和select非常相似,只是描述fd集合的方式不同,poll使用pollfd结构而不是select的fd_set结构,其他的都差不多。
3. epool
epool是select和poll的改进版本,
* 先是使用int epoll_create(int size)在内存中创建一个指定size大小的事件空间,
* 再使用int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);事件注册函数,注册新的fd到epfd的epool对象空间中,并指明event(可读写啊等等),注意:在注册新事件fd的过程中,也再内核中断处理程序里注册fd对应的回调函数callback,告诉内核,一旦这个fd中断了,就把它放到ready队列里面去。
* 再使用int epoll_wait(int epfd, struct epoll_event * events, int maxevents, int timeout);在epool对象对应的ready队列里取就绪的fd,并使用内存映射mmap拷贝到用户空间。
* 再在用户空间依次处理相关的fd。
优点:
1)支持一个进程打开大数目的socket描述符
select 一个进程打开FD是有限制的,由FD_SETSIZE设置,默认值是1024。epool可以打开的FD数可以很大,一般1GB的内存有10万多的FD数,具体数目可以cat
/proc/sys/fs/file-max查看。
2) IO效率不随FD数目增加而线性下降
3) 使用mmap加速内核与用户空间的消息传递
epool的使用:
epoll的相关系统调用
epoll只有epoll_create,epoll_ctl,epoll_wait 3个系统调用。
1. int epoll_create(int size);
创建一个epoll的句柄。自从linux2.6.8之后,size参数是被忽略的。需要注意的是,当创建好epoll句柄后,它就是会占用一个fd值,在linux下如果查看/proc/进程id/fd/,是能够看到这个fd的,所以在使用完epoll后,必须调用close()关闭,否则可能导致fd被耗尽。
2. int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);
epoll的事件注册函数,它不同于select()是在监听事件时告诉内核要监听什么类型的事件,而是在这里先注册要监听的事件类型。
第一个参数是epoll_create()的返回值。
第二个参数表示动作,用三个宏来表示:
EPOLL_CTL_ADD:注册新的fd到epfd中;
EPOLL_CTL_MOD:修改已经注册的fd的监听事件;
EPOLL_CTL_DEL:从epfd中删除一个fd;
第三个参数是需要监听的fd。
第四个参数是告诉内核需要监听什么事,struct epoll_event结构如下:
//保存触发事件的某个文件描述符相关的数据(与具体使用方式有关)
typedef union epoll_data {
void *ptr;
int fd;
__uint32_t u32;
__uint64_t u64;
} epoll_data_t;
//感兴趣的事件和被触发的事件
struct epoll_event {
__uint32_t events; /* Epoll events */
epoll_data_t data; /* User data variable */
};
events可以是以下几个宏的集合:
EPOLLIN :表示对应的文件描述符可以读(包括对端SOCKET正常关闭);
EPOLLOUT:表示对应的文件描述符可以写;
EPOLLPRI:表示对应的文件描述符有紧急的数据可读(这里应该表示有带外数据到来);
EPOLLERR:表示对应的文件描述符发生错误;
EPOLLHUP:表示对应的文件描述符被挂断;
EPOLLET: 将EPOLL设为边缘触发(Edge Triggered)模式,这是相对于水平触发(Level Triggered)来说的。
EPOLLONESHOT:只监听一次事件,当监听完这次事件之后,如果还需要继续监听这个socket的话,需要再次把这个socket加入到EPOLL队列里
3. int epoll_wait(int epfd, struct epoll_event * events, int maxevents, int timeout);
收集在epoll监控的事件中已经发送的事件。参数events是分配好的epoll_event结构体数组,epoll将会把发生的事件赋值到events数组中(events不可以是空指针,内核只负责把数据复制到这个events数组中,不会去帮助我们在用户态中分配内存)。maxevents告之内核这个events有多大,这个 maxevents的值不能大于创建epoll_create()时的size,参数timeout是超时时间(毫秒,0会立即返回,-1将不确定,也有说法说是永久阻塞)。如果函数调用成功,返回对应I/O上已准备好的文件描述符数目,如返回0表示已超时。
用例
写了一个类,封装了epool的使用接口:
#include <sys/epoll.h> class FDEvent { int evfd; struct epoll_event *events; int maxfds; bool nonblocking; int epet; public: typedef int EventType; FDEvent(int max_client_num, bool is_nonblocking):maxfds(max_client_num),nonblocking(is_nonblocking) { if ((evfd = epoll_create(maxfds)) == -1) { perror("epoll_create error!"); exit(1); } if (fcntl(evfd, F_SETFD, FD_CLOEXEC) == -1) { perror("epoll_create error!"); exit(1); } events = new epoll_event[max_client_num]; epet = 0;//nonblocking?EPOLLET:0; } ~FDEvent() { delete[] events; } int wait(int timeout) { return epoll_wait(evfd, events, maxfds, timeout); } int add(int fd, bool init = false) { epoll_event ev; if (init) { ev.events = EPOLLIN; } else { ev.events = EPOLLIN|epet; } ev.data.u64 = 0; ev.data.fd = fd; if (epoll_ctl(evfd, EPOLL_CTL_ADD, fd, &ev) == -1) { perror("epoll_add error!"); return -1; } return 0; } int del(int fd) { epoll_event ev; ev.data.fd = fd; if (epoll_ctl(evfd, EPOLL_CTL_DEL, fd, &ev) == -1) { perror("epoll_del error!"); return -1; } return 0; } pair<int, EventType> get(uint32_t i) { return make_pair(events[i].data.fd, events[i].events); } bool isEventIn(EventType t) { return t&EPOLLIN; } }; 使用: int n = fde.wait(-1); for (int i=0; i<n; i++) { pair<int, FDEvent::EventType> p = fde.get(i); if (p.first == listenfd) { int clientfd = accept(listenfd, (struct sockaddr *)&remote_addr, &adsize); fde.add(clientfd); } else if (fde.isEventIn(p.second)) { fde.del(p.first); } }
参考文章:
1. http://blog.csdn.net/xiajun07061225/article/details/9250579#(这个比较系统的讲epool)
2. http://watter1985.iteye.com/blog/1614039(这个是select, pool和epool源码剖析)
3. http://www.cnblogs.com/Anker/p/3265058.html (这个总结的很好)