select的限制
1.一个进程能打开的最大文件描述符是有限的
2.FD_SETSIZE(fd_set)限制,select内部使用一个数据结构fd_set,它的容量最大不能超过FD_SETSIZE。
poll的限制
一个进程能打开的最大文件描述符是有限的
上面的进程能打开的最大文件描述符的个数可通过命令ulimit -n number更改,但也不是无限大,还受到系统所能打开的最大文件描述符个数的限制,这个大小和内存有关。
但是很肯定的说,这个只是个说明,你的内存不一定支持这么多。
select和poll的另一个主要的缺点是:
内核要遍历所有我们所感兴趣的文件描述符,直到找到发生事件的文件描述符,这样的话,当并发数增长的时候,内核要遍历的文件描述符的个数也随之增大,导致效率下降。这也就是epoll引入的原因。
在linux的网络编程中,很长的时间都在使用select来做事件触发。在linux新的内核中,有了一种替换它的机制,就是epoll。相比于select,epoll最大的好处在于它不会随着监听fd数目的增长而降低效率。因为在内核中的select实现中,它是采用轮询来处理的,轮询的fd数目越多,自然耗时越多。
epoll
epoll是Linux特有的I/O复用函数。它在实现和使用上与select、poll有很大差异。首先,epoll使用一组函数来完成任务,而不是单个函数。其次,epoll把用户关心的文件描述符上的事件放在内核里的一个事件表中,从而无需像select和poll那样每次调用都要重复传入文件描述符集或事件集。但epoll需要使用一个额外的文件描述符,来唯一标识内核中的这个事件表。这个文件描述符使用epoll_create函数来创建。
//通过拷贝的方式,每次循环都需要重复传入文件描述符集
nready=poll(client,maxi+1,-1);
//用户关心的文件描述符上的事件放在内核里的一个事件表中,通过epollfd标识
//这个事件表
nready=epoll_wait(epollfd,&*events.begin(),static_cast<int>(events.size()),-1);
epoll_create函数
int epoll_create(int size);
作用:
该函数生成一个epoll专用的文件描述符,返回的文件描述符将用作其它所有epoll系统调用的第一个参数,以指定要访问的内核事件表。
参数:
size参数现在并不起作用,只是给内核一个提示,告诉它事件表需要多大。(曾经表示在这个epoll fd上能关注的最大fd数)
epoll_ctl函数
int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event)
作用:
该函数用于控制某个epoll文件描述符上的事件,可以注册事件,修改事件,删除事件。
参数:
epfd:由 epoll_create 生成的epoll专用的文件描述符;
op:要进行的操作例如注册事件,可能的取值EPOLL_CTL_ADD 注册、EPOLL_CTL_MOD修改、EPOLL_CTL_DEL 删除
fd:关联的文件描述符,加入epoll_ctl进行管理
event:事件,对文件描述符所感兴趣的事件,指向epoll_event的指针;
返回值
如果调用成功返回0,不成功返回-1
说明
typedef union epoll_data {
void *ptr;
int fd;
__uint32_t u32;
__uint64_t u64;
} epoll_data_t;
struct epoll_event {
__uint32_t events; //感兴趣的事件是可读还是可写
epoll_data_t data; /* User data variable */这是epoll高效的地方,数据类型是上面的那个共用体
};
events可以是以下几个宏的集合:
- EPOLLIN:触发该事件,表示对应的文件描述符上有可读数据。(包括对端SOCKET正常关闭);
- EPOLLOUT:触发该事件,表示对应的文件描述符上可以写数据;
- EPOLLPRI:表示对应的文件描述符有紧急的数据可读(这里应该表示有带外数据到来);
- EPOLLERR:表示对应的文件描述符发生错误;
- EPOLLHUP:表示对应的文件描述符被挂断;
- EPOLLET: 将EPOLL设为边缘触发(Edge Triggered)模式,这是相对于水平触发(Level Triggered)来说的。
- EPOLLONESHOT:只监听一次事件,当监听完这次事件之后,如果还需要继续监听这个socket的话,需要再次把这个socket加入到EPOLL队列里。
epoll_wait函数
int epoll_wait(int epfd, struct epoll_event * events, int maxevents, int timeout);
说明:
epoll_wait函数如果检测到事件,就将所有就绪的事件从内核事件表(由epfd参数指定)中复制它的第二个参数events指向的数组中。这个数组只用于输出epoll_wait检测到的就绪事件,而不像select和poll的数组参数那样既用于传入用户注册的事件,又用于输出内核检测到的就绪事件。这就极大地提高了应用程序索引就绪文件描述符的效率。
参数:
epfd:由epoll_create 生成的epoll专用的文件描述符;
epoll_event:用于回传待处理事件的数组;
maxevents:每次能处理的事件数;
timeout:等待I/O事件发生的超时值;-1相当于阻塞,0相当于非阻塞。一般用-1即可返回发生事件数。
返回值:
如果函数调用成功,返回对应I/O上已准备好的文件描述符数目
epoll实现原理
https://blog.csdn.net/russell_tao/article/details/7160071
关键:
epoll是一种IO多路复用技术,可以非常高效的处理数以百万计的socket句柄,比起以前的select和poll效率高大发了。
首先要调用epoll_create建立一个epoll对象。参数size是内核保证能够正确处理的最大句柄数,多于这个最大数时内核可不保证效果。epoll_ctl可以操作上面建立的epoll,可以添加、移除socket句柄。epoll_wait在调用时,在给定的timeout时间内,当在监控的所有句柄中有事件发生时,就返回。
Epoll高效主要体现在以下三个方面:
①从上面的调用方式就可以看出epoll比select/poll的一个优势:select/poll每次调用都要传递所要监控的所有fd给select/poll系统调用(这意味着每次调用都要将fd列表从用户态拷贝到内核态,当fd数目很多时,这会造成低效)。而每次调用epoll_wait时(作用相当于调用select/poll),不需要再传递fd列表给内核,因为已经在epoll_ctl中将需要监控的fd告诉了内核(epoll_ctl不需要每次都拷贝所有的fd,只需要进行增量式操作)。所以,在调用epoll_create之后,内核已经在内核态开始准备数据结构存放要监控的fd了。每次epoll_ctl只是对这个数据结构进行简单的维护。
② 此外,内核使用了slab机制,为epoll提供了快速的数据结构:
在内核里,一切皆文件。所以,epoll向内核注册了一个文件系统,用于存储上述的被监控的fd。当你调用epoll_create时,就会在这个虚拟的epoll文件系统里创建一个file结点。当然这个file不是普通文件,它只服务于epoll。epoll在被内核初始化时(操作系统启动),同时会开辟出epoll自己的内核高速cache区,用于安置每一个我们想