Modern C++中poll()/epoll()/select()惯用实现及ifstream事件监听查询
Modern C++ 针对IO多路复用的惯用方案
针对传统select()/poll()/epoll()这类IO多路复用接口,目前Modern C++生态已经有非常成熟的通用惯用方案:
- 首选方案是使用Asio库:C23已将Asio纳入标准库为
std::asio,低于C23版本可以用Boost.Asio,二者接口基本兼容。Asio本身就是对不同平台底层IO多路复用接口的高层封装,会在编译期自动选择对应操作系统的最优实现(Linux下用epoll、BSD/macOS下用kqueue、Windows下用IOCP),完全符合Modern C++的RAII、泛型、异步编程范式,几乎没有额外抽象开销,是目前网络/IO开发的行业标准选择。 - 如果你有定制场景需要自行封装底层接口:可以用RAII类封装多路复用实例的生命周期,用
std::function存储事件回调,结合C++20协程避免回调地狱,核心逻辑示例如下:
// 极简epoll封装示例 class EpollInstance { int fd_; public: EpollInstance() : fd_(epoll_create1(0)) { if (fd_ < 0) throw std::runtime_error("epoll create failed"); } ~EpollInstance() { close(fd_); } // 禁止拷贝,允许移动 EpollInstance(const EpollInstance&) = delete; EpollInstance& operator=(const EpollInstance&) = delete; EpollInstance(EpollInstance&&) = default; EpollInstance& operator=(EpollInstance&&) = default; void add_fd(int target_fd, uint32_t events, std::function<void()> callback) { // 存储回调、注册epoll事件逻辑 } void run(size_t max_events = 64) { auto events = std::make_unique<epoll_event[]>(max_events); while(true) { int n = epoll_wait(fd_, events.get(), max_events, -1); for(int i=0; i<n; i++) { // 触发对应回调逻辑 } } } };
除非有极特殊的性能/定制需求,否则不推荐自行裸写底层多路复用逻辑,Asio的通用性和稳定性远高于自定义实现。
ifstream事件等待监听的实现方法
标准库std::ifstream本身没有提供原生的事件监听接口,且其和操作系统底层文件句柄的绑定属于实现定义行为,需要分场景处理:
- 如果你监听的是普通磁盘文件:绝大多数操作系统的IO多路复用接口都会认为普通磁盘文件永远处于可读/可写状态,直接注册到epoll/poll/select是没有效果的。这种场景下可以用异步IO方案实现事件通知:如果用Asio可以直接调用其异步文件读接口,提交读请求后绑定回调,读操作完成后会自动触发通知;也可以用
std::async提交读任务,用std::future等待结果。 - 如果你监听的是特殊文件(管道、终端输入、套接字等支持多路复用的文件):可以先通过标准库实现提供的扩展接口拿到
ifstream对应的底层文件描述符,比如glibc实现下可以用ifstream.rdbuf()->fd()获取fd,再把这个fd注册到Asio的io_context或者你自己的多路复用实例中监听对应事件,事件触发后再调用ifstream的读接口即可。
注意:拿到的文件描述符不要手动关闭,其生命周期由
ifstream的RAII机制管理;监听期间不要在其他线程同时调用ifstream的读写接口,避免内部状态混乱。
内容的提问来源于stack exchange,提问作者Felipe Balbi
相关产品推荐
相关产品推荐

