面试题答案
一键面试epoll在高并发场景下相较于select和poll的性能优势
- 连接数限制:
- select:单个进程能够监视的文件描述符的数量存在最大限制,通常是1024,在高并发场景下远远不够。
- poll:本质上和select没有区别,只是没有了文件描述符数量的限制,但随着文件描述符的增多,性能会急剧下降。
- epoll:没有文件描述符数量的限制,可以轻松处理大量连接,非常适合高并发场景。
- 事件通知方式:
- select:采用轮询的方式扫描文件描述符集合,时间复杂度为O(n),随着文件描述符数量的增加,效率会越来越低。
- poll:同样采用轮询方式,时间复杂度也是O(n)。
- epoll:采用回调机制,只有活跃的文件描述符才会被回调,时间复杂度为O(1),在高并发且连接活跃度较低的场景下,性能优势明显。
- 内存拷贝:
- select:每次调用select时,需要将用户态的文件描述符集合拷贝到内核态,返回时又要将结果从内核态拷贝回用户态,开销较大。
- poll:与select类似,也存在大量的内存拷贝操作。
- epoll:通过epoll_ctl注册文件描述符到内核态,之后内核维护这些信息,不需要每次都进行用户态和内核态之间的大量数据拷贝,减少了性能开销。
在代码实现中设置epoll参数以优化性能
- epoll创建:
int epollfd = epoll_create1(0); if (epollfd == -1) { perror("epoll_create1"); exit(EXIT_FAILURE); }
- 使用
epoll_create1
比epoll_create
更简洁,epoll_create1
的参数若为0表示与epoll_create
相同的行为。若设置为EPOLL_CLOEXEC
,可以使创建的epoll文件描述符在execve系统调用后自动关闭,防止文件描述符泄露。
- 使用
- 添加监控事件:
struct epoll_event event; event.data.fd = sockfd; event.events = EPOLLIN | EPOLLET; if (epoll_ctl(epollfd, EPOLL_CTL_ADD, sockfd, &event) == -1) { perror("epoll_ctl: add"); close(sockfd); close(epollfd); exit(EXIT_FAILURE); }
- 事件类型:
EPOLLIN
表示对应的文件描述符可以读(包括对端SOCKET正常关闭)。EPOLLOUT
表示对应的文件描述符可以写。EPOLLERR
表示对应的文件描述符发生错误。EPOLLHUP
表示对应的文件描述符被挂断。
- 触发模式:
- 水平触发(LT,默认):只要文件描述符对应的缓冲区还有未读数据,就会一直触发读事件;只要缓冲区还有可写空间,就会一直触发写事件。
- 边缘触发(ET):只有在文件描述符状态发生变化时才会触发事件,在高并发场景下可以减少事件触发次数,提高效率,但编程难度相对较高,需要确保一次性读完或写完数据。
- 事件类型:
- 等待事件:
struct epoll_event events[EPOLL_MAX_EVENTS]; int nfds = epoll_wait(epollfd, events, EPOLL_MAX_EVENTS, -1); if (nfds == -1) { perror("epoll_wait"); close(epollfd); exit(EXIT_FAILURE); }
- 事件数组:
events
数组用于存放epoll_wait返回的事件,EPOLL_MAX_EVENTS
定义了该数组的大小,应根据实际情况合理设置,既要避免过小导致事件丢失,又要避免过大浪费内存。 - 超时时间:
epoll_wait
的最后一个参数是超时时间,单位为毫秒。若设置为 -1,表示永久等待,直到有事件发生;若设置为0,则立即返回,不等待;设置为大于0的值,则等待指定的毫秒数,若超时仍无事件发生则返回。在实际应用中,应根据业务需求合理设置超时时间,避免过长等待导致响应不及时或过短等待导致无效轮询增加开销。
- 事件数组: