面试题:poll中 events 和 revents分开的原因是什么?

旅行   2024-11-05 08:00   广东  

欢迎关注本公众号,专注面试题拆解

分享一套视频课程:《C++实现百万并发服务器》 面试需要项目的可以找我获取,免费分享。 欢迎V:fb964919126


面试题:poll中 events 和 revents分开的原因是什么?


在系统编程中,I/O 多路复用是一个永恒的话题。从最早的 select 到现代的 epoll,每一代机制都在试图解决同样的问题:如何高效地监控多个文件描述符的状态。在这个演进过程中,poll 机制虽然不是最新的技术,但其中 events 和 revents 分离的设计思想却值得我们深入研究。

01

基本设计

struct pollfd {    int   fd;       // 文件描述符    short events;   // 要监视的事件(输入)    short revents;  // 实际发生的事件(输出)};

这种设计体现了关注点分离的原则。events 作为输入参数,由用户设置,表明用户对哪些事件感兴趣;revents 作为输出参数,由内核设置,表明实际发生了哪些事件。这种分离使得接口更加清晰,职责划分更加明确。


02

主要原因

2.1、 职责分离

void example_separation() {    struct pollfd fds[2];        // 1. events 由用户设置,表示关注什么事件    fds[0].events = POLLIN;   // 关注读事件    fds[1].events = POLLOUT;  // 关注写事件        // 2. revents 由内核设置,表示实际发生了什么事件    poll(fds, 2, -1);    // 现在 fds[0].revents 和 fds[1].revents 包含实际发生的事件}

这种分离设计有几个重要优点:

明确的输入输出:用户清楚地知道哪些是自己需要设置的,哪些是系统会返回的

安全性:防止用户错误地修改返回值

接口清晰:避免了参数的二义性

便于调试:可以清楚地看到用户请求和系统响应的差异

2.2、 重用性

void example_reuse() {    struct pollfd fds[1];        // 1. 只需要设置一次 events    fds[0].events = POLLIN | POLLOUT;        // 2. 可以重复使用    while(1) {        // 每次调用前清零 revents        fds[0].revents = 0;                poll(fds, 1, -1);        // 处理事件        // events 无需重新设置    }}

重用性的优势:

减少重复设置:events 只需要设置一次,避免了重复的初始化操作

提高性能:减少了不必要的内存写操作

代码简洁:避免了重复的初始化代码

降低错误风险:减少了重复设置可能带来的错误


2.3、 错误处理

void example_error() {    struct pollfd fds[1];        // 1. events 只设置关注的事件    fds[0].events = POLLIN;        poll(fds, 1, -1);        // 2. revents 可能包含错误标志,即使 events 中未设置    if (fds[0].revents & POLLERR) {        // 处理错误    }    if (fds[0].revents & POLLHUP) {        // 处理挂断    }    if (fds[0].revents & POLLNVAL) {        // 处理无效请求    }}

错误处理的优势:

即使用户没有请求错误通知,也能收到错误信息系统可以在 revents 中返回各种错误状态,错误标志不会与用户设置的 events 混淆。


03

与select的对比

void compare_with_select() {    // select 方式    fd_set readfds;    while(1) {        // 1. 每次都要重新设置        FD_ZERO(&readfds);        FD_SET(fd1, &readfds);        FD_SET(fd2, &readfds);                select(maxfd + 1, &readfds, NULL, NULL, NULL);        // readfds 被修改,下次循环需要重新设置    }        // poll 方式    struct pollfd fds[2];    // 只需要设置一次    fds[0].events = POLLIN;    fds[1].events = POLLIN;        while(1) {        fds[0].revents = 0;        fds[1].revents = 0;                poll(fds, 2, -1);        // events 保持不变,可以继续使用    }}

与 select 相比的优势:

更高效的重用:不需要每次重新设置所有文件描述符

更清晰的状态管理:输入和输出状态分离

更好的扩展性:没有文件描述符数量的固定限制

更低的系统开销:减少了数据复制和重置操作


这种设计不仅提高了代码的可维护性和可读性,还带来了性能上的优势。在实际应用中,这种设计使得 poll 比 select 更受欢迎,特别是在需要监控大量文件描述符的场景中。

04

会议总结

poll 中 events 和 revents 的分离机制,虽然是一个简单设计但是这种设计充分体现了"单一职责原则"和"关注点分离"的设计思想。

end



CppPlayer 



关注,回复【电子书】珍藏CPP电子书资料赠送

精彩文章合集

专题推荐

【专辑】计算机网络真题拆解
【专辑】大厂最新真题
【专辑】C/C++面试真题拆解

CppPlayer
一个专注面试题拆解的公众号
 最新文章