Epoll常见面试问题,LT和ET区别?将socket设为非阻塞?正确的读写方式?关于epoll的数据结构?epoll与select、poll的对比?

epoll——常见面试问题

epoll——常见面试问题

常问面试问题

1、Linux epoll ET模式下 EPOLLOUT和EPOLLIN触发场景?

1、EPOLLOUT事件:

EPOLLOUT事件只有在连接时触发一次,表示可写,其他时候想要触发,那你要先准备好下面条件:

  • 1.某次write,写满了发送缓冲区,返回错误码为EAGAIN。

  • 2.对端读取了一些数据,又重新可写了,此时会触发EPOLLOUT。

简单地说:EPOLLOUT事件只有在不可写到可写的转变时刻,才会触发一次,所以叫边缘触发,这叫法没错的!

其实,如果你真的想强制触发一次,也是有办法的,直接调用epoll_ctl重新设置一下event就可以了,event跟原来的设置一模一样都行(但必须包含EPOLLOUT),关键是重新设置,就会马上触发一次EPOLLOUT事件。

2、EPOLLIN事件:

EPOLLIN事件则只有当对端有数据写入时才会触发,所以触发一次后需要不断读取所有数据直到读完EAGAIN为止。否则剩下的数据只有在下次对端有写入时才能一起取出来了。

现在明白为什么说epoll必须要求异步socket了吧?如果同步socket,而且要求读完所有数据,那么最终就会在堵死在阻塞里。

2、epoll的ET模式下,正确的读写方式

  • 读: 只要可读, 就一直读,直到返回0,或者 errno = EAGAIN或者EWOULDBLOCK
  • 写:只要可写, 就一直写,直到数据发送完,或者 errno = EAGAIN或者EWOULDBLOCK

从字面上看, 意思是:

  • EAGAIN: 再试一次
  • EWOULDBLOCK: 如果这是一个阻塞socket, 操作将被block
正确的读:
while ((byte_read = read(fd, buf + n, BUFSIZ-1)) > 0) {
    n += byte_read;//计算读取的字节
}
if (nread == -1 && errno != EAGAIN) {
    perror("read error");
}


正确的写:
int nwrite, data_size = strlen(buf);  
n = data_size;  
while (n > 0) {  
    nwrite = write(fd, buf + data_size - n, n);  
    if (nwrite < n) {  
        if (nwrite == -1 && errno != EAGAIN) {  
            perror("write error");  
        }  
        break;  
    }  
    n -= nwrite;  
}

3、epoll的水平触发和边沿触发区别

(1)水平触发LT:缺省的工作方式(epoll默认的设置),并且同时支持block和no-block socket.

  • 接受缓冲区不为空,对应fd一直处于“读就绪”状态;发送缓冲区不满,对应fd一直处于“写就绪”状态(关于这里的缓冲区:读多少就少多少,写多少就多多少)。所以水平触发,只要数据没读完,那么在epoll_wait中,就一直认为该fd处于就绪状态。
  • 采用LT模式的文件描述符,当epoll_wait检测到其上有事件发生并将此事件通知应用程序后,应用程序可以不立即处理此事件,当下一次调用epoll_wait是,epoll_wait还会将此事件通告应用程序。
  • 如果系统中有大量你不需要读写的就绪文件描述符,而它们每次都会返回,这样会大大降低处理程序检索自己关心的就绪文件描述符的效率

(2)边沿触发ET:只支持no-block socket

如果套接口被设置成阻塞模式,服务器就会一直阻塞在accept调用上,直到其他某个客户建立一个新的连接为止。但是在此期间,服务器单纯地阻塞在accept 调用上,就绪队列中的其他描述符都得不到处理。

  • 当fd对应的读缓冲区从”有“变为了”无“时,对应fd才处于”读就绪“。当fd对应的写缓冲区从”无“变为了”有“时,对应fd才处于”写就绪“。注意:一定是在”有“和”无“之间变化才能触发
  • 当epoll_wait检测到其上有事件发生并将此通知应用程序后,应用程序必须立即处理该事件,因为后续的epoll_wait调用将不在向应用程序通知这一事件。
  • ET模式降低了同意epoll事件被触发的次数,效率比LT模式高。

4、使用epoll时需要将socket设为非阻塞吗?

只有边沿触发才必须设置为非阻塞。

边沿触发的问题:

  • 1、sockfd 的边缘触发,高并发时,如果没有一次处理全部请求,则会出现客户端连接不上的问题。不需要讨论 sockfd 是否阻塞,因为epoll_wait() 返回的必定是已经就绪的连接,所以不管是阻塞还是非阻塞,accept() 都会立即返回。
  • 2、阻塞 connfd 的边缘触发,如果不一次性读取一个事件上的数据,会干扰下一个事件,所以必须在读取数据的外部套一层循环,这样才能完整的处理数据。但是外层套循环之后会导致另外一个问题:处理完数据之后,程序会一直卡在 recv() 函数上,因为是阻塞 IO,如果没数据可读,它会一直等在那里,直到有数据可读。但是这个时候,如果用另一个客户端去连接服务器,服务器就不能受理这个新的客户端了。
  • 3、非阻塞 connfd 的边缘触发,和阻塞版本一样,必须在读取数据的外部套一层循环,这样才能完整的处理数据。因为非阻塞 IO 如果没有数据可读时,会立即返回,并设置 errno。这里我们根据 EAGAIN 和 EWOULDBLOCK 来判断数据是否全部读取完毕了,如果读取完毕,就会正常退出循环了。

总结一下:

  • 1、对于监听的 sockfd,最好使用水平触发模式,边缘触发模式会导致高并发情况下,有的客户端会连接不上。如果非要使用边缘触发,可以用 while 来循环 accept()。
  • 2、对于读写的 connfd,水平触发模式下,阻塞和非阻塞效果都一样,建议设置非阻塞。
  • 3、对于读写的 connfd,边缘触发模式下,必须使用非阻塞 IO,并要求一次性地完整读写全部数据。

所以在用EPOLL的时候,我们都用fcntl将描述符置为非阻塞吧,皆大欢喜。

5、ET模式下的accept问题

请思考以下一种场景:在某一时刻,有多个连接同时到达,服务器的 TCP 就绪队列瞬间积累多个就绪连接,由于是边缘触发模式,epoll 只会通知一次,accept 只处理一个连接,导致 TCP 就绪队列中剩下的连接都得不到处理。在这种情形下,我们应该如何有效的处理呢?

解决的方法是:解决办法是用 while 循环抱住 accept 调用,处理完 TCP 就绪队列中的所有连接后再退出循环。如何知道是否处理完就绪队列中的所有连接呢? accept 返回 -1 并且 errno 设置为 EAGAIN 就表示所有连接都处理完。

6、epoll涉及的数据结构?epoll与select、poll的对比?
篇幅过长,参考上一篇博客,便于清楚理解

  • 3
    点赞
  • 16
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值