个人主页Milestone-里程碑❄️个人专栏: 力扣hot100 CLinux心向往之行必能至一.多路复用 poll1.1 poll接口NAME poll, ppoll - wait for some event on a file descriptor SYNOPSIS #include poll.h int poll(struct pollfd *fds, nfds_t nfds, int timeout); #define _GNU_SOURCE /* See feature_test_macros(7) */ #include signal.h #include poll.h int ppoll(struct pollfd *fds, nfds_t nfds, const struct timespec *tmo_p, const sigset_t *sigmask);参数说明•fds是⼀个poll函数监听的结构列表. 每⼀个元素中, 包含了三部分内容: ⽂件描述符, 监听的事件集合, 返回的事件集合.•nfds表⽰fds数组的⻓度.•timeout表⽰poll函数的超时时间, 单位是毫秒(ms)events的取值,对于我们来说,注重看POLLIN和POLLOUT即可,一个可读,一个可写poll一次可以等待多个fd, fdevents有效:用户告诉内核,你帮我关心,fd上面的events事件poll成功返回时,fdevents有效:用户告诉内核,你要我关心的fd上的events事件,已经就绪了细节:1. poll输入和输出参数分离了,所以不用再poll进行重置了,提升效率2.poll等待的个数,没有上限,与文件描述符所处的数组是动态的对应其中对应fd0,不合法的,在内核中,不会关心这些fd的events返回结果•返回值⼩于0, 表⽰出错;•返回值等于0, 表⽰poll函数等待超时;•返回值⼤于0, 表⽰poll由于监听的⽂件描述符就绪⽽返回1.2 poll的使用大致用法与select差不多, 且需要注意的细节也差不多#pragma once #include iostream #include memory #include unistd.h #include sys/poll.h #include Socket.hpp #include Log.hpp using namespace SocketModule; using namespace LogModule; class PollServer { const static int size 4096; const static int defaultfd -1; public: PollServer(int port) : _listensock(std::make_uniqueTcpSocket()), _isrunning(false) { _listensock-BuildTcpSocketMethod(port); for (int i 0; i size; i) { _fds[i].fd defaultfd; _fds[i].events 0; _fds[i].revents 0; } _fds[0].fd _listensock-Fd(); _fds[0].events POLLIN; } void Start() { _isrunning true; while (_isrunning) { int timeout 1000; // 1000毫秒 1秒 int n poll(_fds, size, -1); // rfds: 0000 0000 switch (n) { case -1: LOG(LogLevel::ERROR) poll error; break; case 0: LOG(LogLevel::INFO) time out...; break; default: // 有事件就绪,就不仅仅是新连接到来了吧读事件就绪啊 LOG(LogLevel::DEBUG) 有事件就绪了..., n : n; Dispatcher(); // 处理就绪的事件啊 break; } } _isrunning false; } // 事件派发器 void Dispatcher() { // 就不仅仅是新连接到来了吧读事件就绪啊 // 指定的文件描述符在rfds里面就证明该fd就绪了 for (int i 0; i size; i) { if (_fds[i].fd defaultfd) continue; // fd合法不一定就绪 if (_fds[i].revents POLLIN) { // fd_array[i] 上面一定是读就绪了 // listensockfd 新连接到来也是读事件就绪啊 // sockfd 数据到来读事件就绪啊 if (_fds[i].fd _listensock-Fd()) { // listensockfd 新连接到来 Accepter(); } else { // 普通的读事件就绪 Recver(_fds[i].fd,i); } } // if (FD_ISSET(fd_array[i], wfds)) // { // // fd_array[i] 上面一定是读就绪了 // } } } // 链接管理器 void Accepter() { InetAddr client; int sockfd _listensock-Accept(client); // accept会不会阻塞? if (sockfd 0) { // 获取新链接到来成功, 然后呢能不能直接 // read/recv() sockfd是否读就绪我们不清楚 // 只有谁最清楚未来sockfd上是否有事件就绪select // 将新的sockfd托管给select // 如何托管? 将新的fd放入辅助数组 LOG(LogLevel::INFO) get a new link, sockfd: sockfd , client is: client.StringAddr(); int pos 0; for (; pos size; pos) { if (_fds[pos].fd defaultfd) break; } if (pos size) { LOG(LogLevel::WARNING) select server full; close(sockfd); } else { _fds[pos].fd sockfd; _fds[pos].eventsPOLLIN; } } } // IO处理器 void Recver(int fd, int pos) { char buffer[1024]; // 我在这里读取的时候会不会阻塞? ssize_t n recv(fd, buffer, sizeof(buffer) - 1, 0); // recv写的时候有bug吗 if (n 0) { buffer[n] 0; std::cout client say buffer std::endl; } else if (n 0) { LOG(LogLevel::INFO) clien quit...; // 必须先关闭再修改,反了就成为关闭-1了 close(fd); _fds[pos].fd defaultfd; } else { LOG(LogLevel::ERROR) recv error; // 必须先关闭再修改,反了就成为关闭-1了 close(fd); _fds[pos].fd defaultfd; } } void PrintFd() { std::cout _fd_array[]: ; for (int i 0; i size; i) { if (_fds[i].fd defaultfd) continue; std::cout _fds[i].fd ; } std::cout \r\n; } void Stop() { _isrunning false; } ~PollServer() { } private: std::unique_ptrSocket _listensock; bool _isrunning; struct pollfd _fds[size]; };1.3 poll的优点不同于select使⽤三个位图来表⽰三个fdset的⽅式poll使⽤⼀个pollfd的指针实现.•pollfd结构包含了要监视的event和发⽣的event不再使⽤select“参数-值”传递的⽅式. 接⼝使⽤⽐select更⽅便.•poll并没有最⼤数量限制 (但是数量过⼤后性能也是会下降)1.4 poll的缺点poll中监听的⽂件描述符数⽬增多时•和select函数⼀样poll返回后需要轮询pollfd来获取就绪的描述符.•每次调⽤poll都需要把⼤量的pollfd结构从⽤⼾态拷⻉到内核中.•同时连接的⼤量客⼾端在⼀时刻可能只有很少的处于就绪状态, 因此随着监视的描述符数量的增⻓, 其效率也会线性下降.
54 深入解析poll多路复用技术
发布时间:2026/5/20 4:29:44
个人主页Milestone-里程碑❄️个人专栏: 力扣hot100 CLinux心向往之行必能至一.多路复用 poll1.1 poll接口NAME poll, ppoll - wait for some event on a file descriptor SYNOPSIS #include poll.h int poll(struct pollfd *fds, nfds_t nfds, int timeout); #define _GNU_SOURCE /* See feature_test_macros(7) */ #include signal.h #include poll.h int ppoll(struct pollfd *fds, nfds_t nfds, const struct timespec *tmo_p, const sigset_t *sigmask);参数说明•fds是⼀个poll函数监听的结构列表. 每⼀个元素中, 包含了三部分内容: ⽂件描述符, 监听的事件集合, 返回的事件集合.•nfds表⽰fds数组的⻓度.•timeout表⽰poll函数的超时时间, 单位是毫秒(ms)events的取值,对于我们来说,注重看POLLIN和POLLOUT即可,一个可读,一个可写poll一次可以等待多个fd, fdevents有效:用户告诉内核,你帮我关心,fd上面的events事件poll成功返回时,fdevents有效:用户告诉内核,你要我关心的fd上的events事件,已经就绪了细节:1. poll输入和输出参数分离了,所以不用再poll进行重置了,提升效率2.poll等待的个数,没有上限,与文件描述符所处的数组是动态的对应其中对应fd0,不合法的,在内核中,不会关心这些fd的events返回结果•返回值⼩于0, 表⽰出错;•返回值等于0, 表⽰poll函数等待超时;•返回值⼤于0, 表⽰poll由于监听的⽂件描述符就绪⽽返回1.2 poll的使用大致用法与select差不多, 且需要注意的细节也差不多#pragma once #include iostream #include memory #include unistd.h #include sys/poll.h #include Socket.hpp #include Log.hpp using namespace SocketModule; using namespace LogModule; class PollServer { const static int size 4096; const static int defaultfd -1; public: PollServer(int port) : _listensock(std::make_uniqueTcpSocket()), _isrunning(false) { _listensock-BuildTcpSocketMethod(port); for (int i 0; i size; i) { _fds[i].fd defaultfd; _fds[i].events 0; _fds[i].revents 0; } _fds[0].fd _listensock-Fd(); _fds[0].events POLLIN; } void Start() { _isrunning true; while (_isrunning) { int timeout 1000; // 1000毫秒 1秒 int n poll(_fds, size, -1); // rfds: 0000 0000 switch (n) { case -1: LOG(LogLevel::ERROR) poll error; break; case 0: LOG(LogLevel::INFO) time out...; break; default: // 有事件就绪,就不仅仅是新连接到来了吧读事件就绪啊 LOG(LogLevel::DEBUG) 有事件就绪了..., n : n; Dispatcher(); // 处理就绪的事件啊 break; } } _isrunning false; } // 事件派发器 void Dispatcher() { // 就不仅仅是新连接到来了吧读事件就绪啊 // 指定的文件描述符在rfds里面就证明该fd就绪了 for (int i 0; i size; i) { if (_fds[i].fd defaultfd) continue; // fd合法不一定就绪 if (_fds[i].revents POLLIN) { // fd_array[i] 上面一定是读就绪了 // listensockfd 新连接到来也是读事件就绪啊 // sockfd 数据到来读事件就绪啊 if (_fds[i].fd _listensock-Fd()) { // listensockfd 新连接到来 Accepter(); } else { // 普通的读事件就绪 Recver(_fds[i].fd,i); } } // if (FD_ISSET(fd_array[i], wfds)) // { // // fd_array[i] 上面一定是读就绪了 // } } } // 链接管理器 void Accepter() { InetAddr client; int sockfd _listensock-Accept(client); // accept会不会阻塞? if (sockfd 0) { // 获取新链接到来成功, 然后呢能不能直接 // read/recv() sockfd是否读就绪我们不清楚 // 只有谁最清楚未来sockfd上是否有事件就绪select // 将新的sockfd托管给select // 如何托管? 将新的fd放入辅助数组 LOG(LogLevel::INFO) get a new link, sockfd: sockfd , client is: client.StringAddr(); int pos 0; for (; pos size; pos) { if (_fds[pos].fd defaultfd) break; } if (pos size) { LOG(LogLevel::WARNING) select server full; close(sockfd); } else { _fds[pos].fd sockfd; _fds[pos].eventsPOLLIN; } } } // IO处理器 void Recver(int fd, int pos) { char buffer[1024]; // 我在这里读取的时候会不会阻塞? ssize_t n recv(fd, buffer, sizeof(buffer) - 1, 0); // recv写的时候有bug吗 if (n 0) { buffer[n] 0; std::cout client say buffer std::endl; } else if (n 0) { LOG(LogLevel::INFO) clien quit...; // 必须先关闭再修改,反了就成为关闭-1了 close(fd); _fds[pos].fd defaultfd; } else { LOG(LogLevel::ERROR) recv error; // 必须先关闭再修改,反了就成为关闭-1了 close(fd); _fds[pos].fd defaultfd; } } void PrintFd() { std::cout _fd_array[]: ; for (int i 0; i size; i) { if (_fds[i].fd defaultfd) continue; std::cout _fds[i].fd ; } std::cout \r\n; } void Stop() { _isrunning false; } ~PollServer() { } private: std::unique_ptrSocket _listensock; bool _isrunning; struct pollfd _fds[size]; };1.3 poll的优点不同于select使⽤三个位图来表⽰三个fdset的⽅式poll使⽤⼀个pollfd的指针实现.•pollfd结构包含了要监视的event和发⽣的event不再使⽤select“参数-值”传递的⽅式. 接⼝使⽤⽐select更⽅便.•poll并没有最⼤数量限制 (但是数量过⼤后性能也是会下降)1.4 poll的缺点poll中监听的⽂件描述符数⽬增多时•和select函数⼀样poll返回后需要轮询pollfd来获取就绪的描述符.•每次调⽤poll都需要把⼤量的pollfd结构从⽤⼾态拷⻉到内核中.•同时连接的⼤量客⼾端在⼀时刻可能只有很少的处于就绪状态, 因此随着监视的描述符数量的增⻓, 其效率也会线性下降.