网上很多关于EPOLL的介绍,或者翻译man手册,或者找一些国外的文章,还有一些很好得心得分享。但是看完总有一种模糊的感觉,尤其是关于 ET LT 两种模式,今天自己做一些例子仔细体会下。
一:概念
边沿触发(edge triggered),就是在事务的两个状态交替的边沿触发,对socket来讲就是的新的数据来的时候触发,如果是上一次的数据,你没有收完 则不会再次提醒。除非有新的数据到来。
电平触发(levle triggered),所谓电平触发只要是事务的某一状态出现就触发,对sockets来讲就是只有内核缓冲区中有数据,就会触发。而不管这数据是你上次没收完,还是新来数据。
(以上摘自互联网)
理解:上面提到的状态及交替,以socket接收到新的数据为分割,如果接到了新的数据,则认为交替了,没有收到新数据则认为是上一个状态未结束。
代码场景:
- 1:ET模式,得到可读事件,但是不去read数据,看epoll_wait以后是否有事件提醒。
2:LT模式,得到可读事件,但是不去read数据,看epoll_wait以后是否有事件提醒。
- 1:ET模式,没有及时读取数据,再次有新数据到来,能不能继续读取上次的数据。
验证结果:ET模式中,接收到新数据,给数据提醒,无论读不读数据,读取多少,都只提醒一次。如果没有及时读取,则下次有新数据到来,则读取上次数据。
LT模式中,接收到新数据,给数据提醒,只要不读完数据,一直提醒。
二:如何处理ET模式场景
解决此问题有两种方法:
1.采用非阻塞函数,将数据收完后,再次调用epoll_wait(),对应epoll man手册的描述为
i with non-blocking file descriptors
ii by going to wait for an event only after read(2) or write(2) return EAGAIN or EWOULDBLOCK
2.在接收数据后,不管有没有收完,都调用 epoll_ctl() with EPOLL_CTL_MOD,这样相当于重新设置事件,就可以收到数据。
(以上摘自互联网)
理解:如果只提醒一次,那我们就应该在用户态的代码中,"尽量完整" 并且 "尽量减少IO操作中CPU的等待" 得完成内核缓冲区的数据的读取。所以方法1中,第一步置为非阻塞,第二步中的"EAGAIN or EWOULDBLOCK"要做处理,但是没必要非等到返回这两个东西。
附代码片段:
conn->recv_ready=1; do{ for(;;){ if(conn->recv_ready==0){ break; } printf("recv starting ...... "); ret=read(conn->fd,buffer,len); if(ret>0){ if(ret<(ssize_t)len){ conn->recv_ready=0; } buffer+=ret; conn->recv_bytes+=(ssize_t)ret; } if(ret==0){ printf("recv done...... "); return; } if(ret<0){ if(errno==EINTR){ printf("recv on sd %d not ready eintr ",conn->fd); continue; }else if(errno==EAGAIN||errno==EWOULDBLOCK){ printf("recv on sd %d not ready eagain ",conn->fd); conn->recv_ready=0; return; }else{ printf("recv on sd %d error ",conn->fd); conn->recv_ready = 0; return; } } } }while(conn->recv_ready);