epoll就是为了 处理大批量句柄而改进的poll,相比与select,poll最大的好处在于它不会随着坚挺fd的数目增长而效率降低。因为在内核中的select是采用轮询来处理的,轮询fd的数目越多,自然耗时越多,并且slelct的监听数目有限(虽然可以通过头文件来改变,但并不治本)
一.epoll的相关系统调用
epoll只有三个简单地接口 分别为epoll_creat,epoll_ctl,epoll_wait
(1)int epoll_creat(int size)
创建一个epoll句柄,当创建好一个epoll句柄后,它就会占一个 fd值,所以在使用完以后要调用close()函数关闭,否则fd可能被耗尽
(2)int epoll_ctl(int epfd,int op,int fd,struct epoll_event* event)
epoll的事件注册函数,它不同于 select函数在监听事件的时候告诉内核要监听什么类型的事件,而是 在 这里先注册要监听的事件类型
第一个参数是epoll_creat的返回值
第二个参数表示动作,用三个宏来表示
EPOLL_CTL_ADD:注册新的fd到epfd中
EPOLL_CTL_MOD;修改已经注册到的fd的监听事件
EPOLL_CTL_DEL:从epfd中删除一个fd
第三个参数 是 要监听 的fd
第四个参数 告诉内核 需要监听什么 事件
event可以是以下几个 宏的集合
EPOLLIN:表示 对应的文件描述符可以读(包括对socket正常关闭)
EPOLLOUT:表示 对应的文件描述符可以写
EPOLLPRI:表示对应的文件描述符有紧急的数据可读
EPOLLERR:对应的文件描述符发生错误
EPOLLET:将EPOLL设置为 边沿除法模式 ,这是相对于水平触发而言
EPOLLONESET:只监听一次事件,如果之后还需要监听的话,需要再次把这个socket键入到EPOLL队列里
(3)int epoll_wait(int epfd,struct epoll_event* events,int maxevents,int timeout)
收集epoll监控的事件中已经发生的事件参数event是分配好的epoll_event结构体数组。epoll把发生的事件赋值到events数组中 (events不可以是空指针,内核只负责把数据复制到数组中,但不会帮用户开辟内存),maxevents告诉这个内核有多大这个maxevents的值不能大于创建的epoll_ctreat()时的size,阐述timeout是超时事件(毫秒 ,0立即返回,-1将不确定,也就是永久阻塞)如果函数调用成功,返回对应已准备好的文件描述符数目,如果返回0表示超时
二. epoll工作原理
epoll只告诉那些就绪的文件描述符,而且当你调用epoll_wait()获得就绪文件描述符的时候,返回的不是实际的文件描述符,而是一个代表就绪描述符数量值,你只是需要去epoll指定的一个数组 中一次获得相应数量的文件描述符即可。
另一个本质的改进在于epoll采用基于 事件的就绪通知方式,在select/poll中进程只有在调用一定的方法之后,内核才对所有监视的文件描述符进行扫描,而epoll先通过采用epoll_ctl()来注册一个文件描述符,当进程调用epoll_wait()时便得到通知。
三. epoll的工作方式-水平触发(LT)-边沿触发(ET)
LT(level triggered)是epoll缺省的工作方式,并且同时支持block和no-block socket.在这种做法中,内核告诉你一个文件描述符是否就绪了,然后你可以对这个就绪的fd进行IO操作。如果你不作任何操作,内核还是会继续通知你 的,所以,这种模式编程出错误可能性要小一点。传统的select/poll都是这种模型的代表.
ET (edge-triggered)是高速工作方式,只支持no-block socket,它效率要比LT更高。ET与LT的区别在于,当一个新的事件到来时,ET模式下当然可以从epoll_wait调用中获取到这个事件,可是如果这次没有把这个事件对应的套接字缓冲区处理完,在这个套接字中没有新的事件再次到来时,在ET模式下是无法再次从epoll_wait调用中获取这个事件的。而LT模式正好相反,只要一个事件对应的套接字缓冲区还有数据,就总能从epoll_wait中获取这个事件。
因此,LT模式下开发基于epoll的应用要简单些,不太容易出错。而在ET模式下事件发生时,如果没有彻底地将缓冲区数据处理完,则会导致缓冲区中的用户请求得不到响应
四.epoll的优点
(1)支持一个进程打开大数目的socket描述符(FD)
select所能够打开的fd是有限的一般为1024,对于那些支持上万条链接数目的服务器来说 太少了,epoll就没有这个限制 ,它所支持的FD上限是最大可以打开文件数目这个树种子一般大于2048;
(2)IO效率不随FD的数目 增加而线性下降
传统的select/poll致命弱点是当拥有一个很大的集合的时候,每次都用都会轮询扫描 集合 ,导致效率下降,但是epoll只对活跃 的socket进行操作--因为在内核中epoll根据每个fd上面的callback函数实现的,只有活跃的函数 才会 调用 callback函数
五.linux下epoll如何处理百万句柄
(1)首先调用epoll_creat建立一个epoll对象,参数 size是内核保证能够正确处理的最大句柄数
(2)epoll_ctl操作上面建立好的epoll,例如让刚建立的socket加入到epoll中让其监控,或者把epoll正在监控的某个socke句柄移除epoll
(3)epoll_wait在给定的timeout时间内当监控的所有句柄发审核变化时就返回用户态进程(毫秒 ,0立即返回,-1将不确定,也就是永久阻塞)如果函数调用成功,返回对应已准备好的文件描述符数目,如果返回0表示超时
从上面调用方式可以看出epoll比select/poll的优越之处:因为 后者每次调用时都会给你返回所要监控所有socket给select/poll系统 调用 ,这意味着需要将用户态的socket列表 拷贝到内核态,如果数以万计的句柄会导致每次都要copy几十几百KB的内存到内核态,非常低效,但epoll_wait不用传递socket句柄给内核,因为内核已经在 epoll_ctl中拿到了要监控的句柄列表
下面是代码部分
1.创建监听套接字,设置端口服用127.0.0.1 指定簇为 IPv4,指定端口号并 转化为 网络字节序列,指定ip地址并转化为 网络 字节序列
2.调用epoll_creat得到一个epoll模型,判断若创建成功,则调用epoll_ctl函数注册要监听事件的类型 ,本代码中设置的监听事件类型为EPOLLIN:表示 对应的文件描述符可以读
epoll的事件注册函数,它不同于 select函数在监听事件的时候告诉内核要监听什么类型的事件,而是 在 这里先注册要监听的事件类型
第一个参数是epoll_creat的返回值
第二个参数表示动作,用三个宏来表示
EPOLL_CTL_ADD:注册新的fd到epfd中
EPOLL_CTL_MOD;修改已经注册到的fd的监听事件
EPOLL_CTL_DEL:从epfd中删除一个fd
第三个参数 是 要监听 的fd
第四个参数 告诉内核 需要监听什么 事件
event可以是以下几个 宏的集合
EPOLLIN:表示 对应的文件描述符可以读(包括对socket正常关闭)
EPOLLOUT:表示 对应的文件描述符可以写
EPOLLPRI:表示对应的文件描述符有紧急的数据可读
EPOLLERR:对应的文件描述符发生错误
EPOLLET:将EPOLL设置为 边沿除法模式 ,这是相对于水平触发而言
EPOLLONESET:只监听一次事件,如果之后还需要监听的话,需要再次把这个socket键入到EPOLL队列里
3.调用epoll_wait函数收集epoll监控的事件中已经发生的事件,参数event是分配好的epoll_event结构体数组。epoll把发生的事件赋值到events数组中 (events不可以是空指针,内核只负责把数据复制到数组中,但不会帮用户开辟内存),maxevents告诉这个内核有多大这个maxevents的值不能大于创建的epoll_ctreat()时的size,
阐述timeout是超时事件(毫秒 ,0立即返回,-1将不确定,也就是永久阻塞)如果函数调用成功,返回对应已准备好的文件描述符数目,如果返回0表示超时
判断它是否为 监听套接字,如果是并且就绪 则对它进行接收,并且以边沿出让的方式读入 ,若是其他套接字 ,则对他进行同样的 接收