面试题答案
一键面试LT(水平触发)和ET(边缘触发)模式的区别
- 触发条件:
- LT模式:当文件描述符(fd)上有数据可读(对于读事件)或者可写(对于写事件)时,epoll_wait会不断触发该事件,直到数据被处理完或者缓冲区满等情况。即只要条件满足,就会一直触发。
- ET模式:只有当fd状态发生变化时(从不可读变为可读,或者从不可写变为可写),epoll_wait才会触发事件。后续除非状态再次发生变化,否则不会重复触发。
- 数据读取:
- LT模式:应用程序可以按任意顺序和数量读取数据,即使一次没有读完,下次epoll_wait仍会触发读事件。
- ET模式:要求应用程序尽可能一次性读完数据,因为后续epoll_wait可能不会再次触发读事件,直到有新的数据到来。
- 缓冲区处理:
- LT模式:缓冲区有数据就触发,相对更“宽容”,对应用程序代码要求较低。
- ET模式:缓冲区状态变化才触发,更高效,但对应用程序代码要求较高,需要正确处理数据读取和缓冲区状态。
选择ET模式的场景
在高并发网络服务器场景下,当网络流量较大且对性能要求极高,希望减少不必要的系统调用开销时,选择ET模式。例如,在处理大量短连接请求的HTTP服务器或者处理海量实时数据传输的物联网服务器等场景中,ET模式可以通过减少事件触发次数,提高服务器处理效率。
避免“惊群”问题
- 使用epoll的EPOLLEXCLUSIVE标志:从Linux 4.5内核开始,epoll支持EPOLLEXCLUSIVE标志。当一个fd添加到epoll实例时设置该标志,在事件触发时,只有一个等待在该epoll实例上的进程会被唤醒,避免多个进程同时被唤醒竞争资源,从而解决“惊群”问题。
struct epoll_event ev; ev.events = EPOLLIN | EPOLLET | EPOLLEXCLUSIVE; ev.data.fd = sockfd; epoll_ctl(epollfd, EPOLL_CTL_ADD, sockfd, &ev);
- 使用锁机制:在应用层代码中,可以使用互斥锁(如pthread_mutex_t)来保护共享资源。当一个进程获取到锁后处理事件,其他进程等待锁释放,避免多个进程同时处理同一事件导致的竞争问题。
避免数据丢失问题
- 循环读取数据:在ET模式下,需要确保在事件触发后尽可能一次性读完数据。可以使用循环读取,直到
read
返回-1
且errno
为EAGAIN
或EWOULDBLOCK
,表示数据已读完或者缓冲区暂时无数据可读。char buffer[BUFFER_SIZE]; ssize_t nread; while ((nread = read(sockfd, buffer, sizeof(buffer))) > 0) { // 处理读取到的数据 } if (nread == -1 && (errno == EAGAIN || errno == EWOULDBLOCK)) { // 数据读完,继续处理其他事务 }
- 使用非阻塞I/O:将文件描述符设置为非阻塞模式,这样在读取或写入数据时,如果缓冲区没有数据或者已满,系统调用会立即返回,而不是阻塞等待。结合ET模式,可以更好地控制数据读取和写入过程,避免数据丢失。
int flags = fcntl(sockfd, F_GETFL, 0); fcntl(sockfd, F_SETFL, flags | O_NONBLOCK);