当前位置: 首页 > news >正文

54 深入解析poll多路复用技术

🔥个人主页:Milestone-里程碑

❄️个人专栏: <<力扣hot100>> <<C++>><<Linux>>

🌟心向往之行必能至

一.多路复用 poll

1.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, fd&&events有效:用户告诉内核,你帮我关心,fd上面的events事件

poll成功返回时,fd&&events有效:用户告诉内核,你要我关心的fd上的events事件,已经就绪了

细节:

1. poll输入和输出参数分离了,所以不用再poll进行重置了,提升效率

2.poll等待的个数,没有上限,与文件描述符所处的数组是动态的对应

其中对应fd<0,不合法的,在内核中,不会关心这些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_unique<TcpSocket>()), _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].events=POLLIN; } } } // 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_ptr<Socket> _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结构从⽤⼾态拷⻉到内核中.
同时连接的⼤量客⼾端在⼀时刻可能只有很少的处于就绪状态, 因此随着监视的描述符数量的增
⻓, 其效率也会线性下降.
http://www.cnnetsun.cn/news/2464678.html

相关文章:

  • MySQL进阶
  • 【软考中级备考日记|系统集成项目管理工程师Day11:项目资源管理核心精讲\+团队建设冲突解决\+20道专项必刷题(带解析)】
  • 数据不会说谎:园区智能化带来的五个变化
  • PLINK实战:用--genome参数搞定GWAS数据中的“亲戚”排查(附pihat阈值选择心得)
  • 【Perplexity行业分析搜索终极指南】:2024年全球Top 5垂直领域实战数据+3大避坑红线
  • 临床决策倒计时:Perplexity医生信息搜索如何将循证检索从15分钟压缩至22秒?
  • 【原创】智询管理系统操作说明
  • 从伺服报警到产线停机:一个EtherCAT状态机跳变引发的故障诊断实录
  • GIS技巧100例23-ArcGIS像元统计实战:从月度栅格到年度气候指标
  • 从‘老王’到动态数据:C# Winform中Label控件如何优雅地绑定和更新显示内容
  • 实测 DeepSeek-V4 接入 Hermes:一句话爬取几十个网页,真的丝滑!
  • 技术动态 | 大模型驱动情报领域知识图谱构建新范式:ERC-KG方法精确率高达94.32% - 解放军网络空间部队信工大等
  • 基于双CNN架构的实时神经信号处理与FPGA实现
  • 5分钟快速合并B站缓存视频:m4s-converter终极使用指南
  • 半导体设备ETF(159516.SZ)单日大涨5.05%,规模超257亿领跑行业
  • IL‑4、IL-13:调控嗜酸性粒细胞与肥大细胞活化的关键细胞因子
  • Swift学习笔记29-数据库SQlite
  • CodeWave项目导出实战:从云端到本地的完整避坑指南(含数据库配置与端口冲突解决)
  • Kubernetes Ingress Controller 深度解析:从入门到精通
  • OpenCV实战:用Triangle和Maxentropy算法搞定文档扫描与OCR预处理
  • 【独家首发】Gemini Ultra未公开API限流机制曝光:3类高频报错代码对应的真实QPS阈值与绕过方案
  • Rust内存安全:所有权、借用与生命周期深度解析
  • 从光伏MPPT到手机快充:拆解Boost电路在不同场景下的Matlab建模核心差异
  • 深入解析Arm Cortex-A53 Cache架构:从原理到多核一致性与性能优化实践
  • ARM PMU性能监控原理与缓存优化实战
  • 为什么你的Gemini Gmail智能回复总在关键邮件失效?——从LLM token截断到上下文窗口压缩的底层归因分析
  • 苹果app上架卡审核的底层逻辑(经验分享)
  • Spring Cloud Gateway配置HTTPS后,微服务调用报NotSslRecordException?一个配置项帮你搞定
  • 手把手教你无损转换:把老电脑的Legacy启动盘改成UEFI+GPT(附DiskGenius详细操作图)
  • C# CAD二次开发实战:掌握Editor类核心选择方法,实现高效范围选择