【计算机网络】非阻塞IO——poll实现多路转接

🔥个人主页🔥:孤寂大仙V
🌈收录专栏🌈:计算机网络
🌹往期回顾🌹:【计算机网络】非阻塞IO——select实现多路转接
🔖流水不争,争的是滔滔不息


一、poll实现多路转接

在网络编程或多路 IO 编程中,我们经常需要同时监听多个文件描述符(fd),比如多个客户端的 socket 连接。这时,poll 就登场了。
poll 是 Linux 提供的一种 IO 多路复用机制,用于监听多个 fd 上的读写等事件,一旦有就绪,立刻通知我们处理。

poll的参数

int poll(struct pollfd fds[], nfds_t nfds, int timeout);
  1. 参数struct pollfd fds[]是关心的文件描述符数组,每一个元素代表一个要监听的 fd 及其感兴趣的事件和返回的事件。
struct pollfd {int fd;         // 要监听的文件描述符short events;   // 感兴趣的事件(由你设置)short revents;  // 实际返回的事件(由内核设置)
};
  1. 参数 nfds_t nfds这个是 fds[] 数组里有效元素的数量,简单说就是你监听几个文件描述符就写几。
  2. int timeout
    单位是 毫秒(ms),表示阻塞多久。timeout > 0:等待指定毫秒数后返回、timeout == 0:立即返回(非阻塞)、timeout < 0:永远阻塞,直到有事件发生。

函数返回值

  • 0:就绪的文件描述符数量
  • 0:超时,没有任何事件发生
  • <0:出错(比如信号中断)

常用事件(events/revents)取值

POLLIN     // 有数据可读
POLLOUT    // 可以写数据而不会阻塞
POLLERR    // 错误(由 revents 设置)
POLLHUP    // 对端关闭连接
POLLNVAL   // 描述符非法

poll的缺点

  1. 每次调用都要传入整个 fd 列表
    poll 的 pollfd 是个数组,内核不会保存状态,每次都得重新传。
    如果你有上千个连接,那每次 poll() 都会把这上千个 fd 重新复制到内核 → 代价大。
  2. 线性扫描效率低
    poll 返回的是“多少个 fd 就绪”,但你要线性扫描整个数组找出来。
    比如你监听 1000 个连接,但只有 1 个可读,你得从 0 扫到 999 才找到它。
  3. fd 数量仍有限制
    虽然 poll 相比 select 不再限制 1024 个,但:
    它还是受限于内核最大文件描述符数量(ulimit -n,比如 65535)
    每个 pollfd 占用空间较大,更不适合极大并发
  4. 无事件通知机制
    poll 只能“等和扫”,没有像 epoll 的 EPOLLONESHOT、EPOLLET(边缘触发)那种高级机制。
    没法在事件处理完后说“我暂时不关注这个 fd 了”。

二、poll实现非阻塞服务器

#pragma once
#include "Common.hpp"
#include "Log.hpp"
#include "Socket.hpp"
#include <sys/poll.h>using namespace std;
using namespace LogModule;
using namespace SocketModule;class PollServer
{const static int size = 4096;const static int defaultfd = -1;public:PollServer(int port): _isrunning(false), _listensockfd(make_unique<TcpSocket>()){_listensockfd->BuildTcpSocketServer(port); // 构造TCP服务器for (int i = 0; i < size; i++){_fds[i].fd = defaultfd;_fds[i].events = 0;_fds[i].revents = 0;}_fds[0].fd = _listensockfd->FD();_fds[0].events = POLLIN;}void Start() // 服务器启动{int timeout = 1000; // 1000毫秒_isrunning = true;while (true){int n = poll(_fds, size, timeout); // 多路转接只关系读事件switch (n){case -1:LOG(LogLevel::ERROR) << "poll error"; // 异常break;case 0:LOG(LogLevel::WARNING) << "poll timeout"; // 超时default:LOG(LogLevel::INFO) << "事件就绪"; // 读事件就绪Dispatcher();                      // 派发break;}}}void Dispatcher() // 事件派发{for (int i = 0; i < size; i++){if (_fds[i].fd == defaultfd) // 跳过continue;if (_fds[i].revents & POLLIN) // 是读就绪{if (_fds[i].fd == _listensockfd->FD()){// listen 套接字Accept();}else{// 普通  套接字Recv(i);}}}}void Accept(){InetAddr client;int sockfd = _listensockfd->AcceptOrDie(&client);LOG(LogLevel::DEBUG) << "accept a new client" << client.StringAddr();int pos = 0;for (; pos < size; pos++){if (_fds[pos].fd == defaultfd)break; // 数组中找到空位}if (pos == size){LOG(LogLevel::WARNING) << "poll server full";close(sockfd);}else{_fds[pos].fd = sockfd;_fds[pos].events = POLLIN;_fds[pos].revents = 0;}}void Recv(int pos) // 读数据{char buffer[1024];ssize_t n = recv(_fds[pos].fd, buffer, sizeof(buffer) - 1, 0); // 收信息if (n > 0){buffer[n] = 0;cout << "client say@ " << buffer << endl;}else if (n == 0) // 客户端退出{LOG(LogLevel::INFO) << "client quit";_fds[pos].fd = defaultfd;_fds[pos].events = 0;_fds[pos].revents = 0;close(_fds[pos].fd);}else // 出现错误 异常{LOG(LogLevel::FATAL) << "recv error";_fds[pos].fd = defaultfd;_fds[pos].events = 0;_fds[pos].revents = 0;close(_fds[pos].fd);}}~PollServer(){}private:unique_ptr<Socket> _listensockfd;bool _isrunning;struct pollfd _fds[size];
};

构造

//私有成员变量
private:unique_ptr<Socket> _listensockfd;bool _isrunning;struct pollfd _fds[size];
//构造PollServer(int port): _isrunning(false), _listensockfd(make_unique<TcpSocket>()){_listensockfd->BuildTcpSocketServer(port); // 构造TCP服务器for (int i = 0; i < size; i++){_fds[i].fd = defaultfd;_fds[i].events = 0;_fds[i].revents = 0;}_fds[0].fd = _listensockfd->FD();_fds[0].events = POLLIN;}

私有成员变量中,把关心文件描述符的数组开好,构造服务器的时候遍历这个数组,把要监听的文件描述符设置进去,把要关系的状态设置为关系读事件。


服务器启动

void Start() // 服务器启动{int timeout = 1000; // 1000毫秒_isrunning = true;while (true){int n = poll(_fds, size, timeout); // 多路转接只关系读事件switch (n){case -1:LOG(LogLevel::ERROR) << "poll error"; // 异常break;case 0:LOG(LogLevel::WARNING) << "poll timeout"; // 超时default:LOG(LogLevel::INFO) << "事件就绪"; // 读事件就绪Dispatcher();                      // 派发break;}}}

用poll函数,进行多路转接,事件就绪就派发任务。


事件派发

void Dispatcher() // 事件派发{for (int i = 0; i < size; i++){if (_fds[i].fd == defaultfd) // 跳过continue;if (_fds[i].revents & POLLIN) // 是读就绪{if (_fds[i].fd == _listensockfd->FD()){// listen 套接字Accept();}else{// 普通  套接字Recv(i);}}}}

对文件描述符数组,进行遍历。如果不是合法位置(没放文件描述符)就跳过这个位置。如果是读就绪然后判断是监听套接字合适普通套接字。

收到客户端的连接accept

void Accept(){InetAddr client;int sockfd = _listensockfd->AcceptOrDie(&client);LOG(LogLevel::DEBUG) << "accept a new client" << client.StringAddr();int pos = 0;for (; pos < size; pos++){if (_fds[pos].fd == defaultfd)break; // 数组中找到空位}if (pos == size){LOG(LogLevel::WARNING) << "poll server full";close(sockfd);}else{_fds[pos].fd = sockfd;_fds[pos].events = POLLIN;_fds[pos].revents = 0;}}

主要是收到客户端的连接,创建了accept的套接字,要把这个套接字放到数组中。在文件描述符数组中找到空位,把这个acceptfd设置进去。


读数据

void Recv(int pos) // 读数据{char buffer[1024];ssize_t n = recv(_fds[pos].fd, buffer, sizeof(buffer) - 1, 0); // 收信息if (n > 0){buffer[n] = 0;cout << "client say@ " << buffer << endl;}else if (n == 0) // 客户端退出{LOG(LogLevel::INFO) << "client quit";_fds[pos].fd = defaultfd;_fds[pos].events = 0;_fds[pos].revents = 0;close(_fds[pos].fd);}else // 出现错误 异常{LOG(LogLevel::FATAL) << "recv error";_fds[pos].fd = defaultfd;_fds[pos].events = 0;_fds[pos].revents = 0;close(_fds[pos].fd);}

这时候,读数据已经是非阻塞的了,客户端退出和异常要把文件描述符数组的内容清空,然后关闭文件描述符。


在这里插入图片描述
源码:poll多路转接

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.pswp.cn/web/82747.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

vscode使用系列之快速生成html模板

一.欢迎来到我的酒馆 vscode&#xff0c;yyds! 目录 一.欢迎来到我的酒馆二.vscode下载安装1.关于vscode你需要知道2.开始下载安装 三.vscode快速创建html模板 二.vscode下载安装 1.关于vscode你需要知道 Q&#xff1a;为什么使用vscode? A&#xff1a;使用vscode写…

【C/C++】不同防止头文件重复包含的措施

文章目录 #pragma once vs #ifndef 文件宏1 原理层面区别&#xff08;core&#xff09;2 关键区别与优缺点分析3 总结与最佳实践 #pragma once vs #ifndef 文件宏 在 C/C 中&#xff0c;#pragma once 和传统的文件宏守卫 (#ifndef HEADER_H #define HEADER_H ... #endif) 都用…

java-springboot文件上传校验之只允许上传excel文件,且检查不能是脚本或者有害文件或可行性文件

四重验证机制&#xff1a; 文件扩展名检查&#xff08;.xlsx/.xls&#xff09;MIME类型检查文件魔数验证&#xff08;真实文件类型&#xff09;可执行文件特征检测 防御措施&#xff1a; 使用try-with-resources确保流关闭限制文件大小防止DoS攻击使用Apache POI的FileMagic进…

不确定性分析在LEAP能源-环境系统建模中的整合与应用

本内容突出与实例结合&#xff0c;紧密结合国家能源统计制度及《省级温室气体排放编制指南》&#xff0c;深入浅出地介绍针对不同级别研究对象时如何根据数据结构、可获取性、研究目的&#xff0c;构建合适的能源生产、转换、消费、温室气体排放&#xff08;以碳排放为主&#…

高性能分布式消息队列系统(四)

八、客户端模块的实现 客户端实现的总体框架 在 RabbitMQ 中&#xff0c;应用层提供消息服务的核心实体是 信道&#xff08;Channel&#xff09;。 用户想要与消息队列服务器交互时&#xff0c;通常不会直接操作底层的 TCP 连接&#xff0c;而是通过信道来进行各种消息的发布…

QPair 类说明

QPair 类说明 QPair 是一个模板类&#xff0c;用于存储一对数据项。 头文件&#xff1a; cpp #include <QPair> qmake 配置&#xff1a; QT core 所有成员列表&#xff08;包括继承成员&#xff09; 公共类型 类型定义说明first_type第一个元素的类型&#xff…

4.大语言模型预备数学知识

大语言模型预备数学知识 复习一下在大语言模型中用到的矩阵和向量的运算&#xff0c;及概率统计和神经网络中常用概念。 矩阵的运算 矩阵 矩阵加减法 条件&#xff1a;行数列数相同的矩阵才能做矩阵加减法 数值与矩阵的乘除法 矩阵乘法 条件&#xff1a;矩阵A的列数 矩阵…

uniapp 设置手机不息屏

在使用 UniApp 开发应用时&#xff0c;有时需要在设备长时间未操作时实现息屏保护功能&#xff0c;以节省电量和保护屏幕。以下是如何在 UniApp 中实现这一功能的步骤。 示例一 // 保持屏幕常亮 uni.setKeepScreenOn({keepScreenOn: true });// 监听应用进入后台事件 uni.onH…

智能推荐系统:协同过滤与深度学习结合

智能推荐系统&#xff1a;协同过滤与深度学习结合 系统化学习人工智能网站&#xff08;收藏&#xff09;&#xff1a;https://www.captainbed.cn/flu 文章目录 智能推荐系统&#xff1a;协同过滤与深度学习结合摘要引言技术原理对比1. 协同过滤算法&#xff1a;基于相似性的推…

使用Python和OpenCV实现图像识别与目标检测

在计算机视觉领域&#xff0c;图像识别和目标检测是两个非常重要的任务。图像识别是指识别图像中的内容&#xff0c;例如判断一张图片中是否包含某个特定物体&#xff1b;目标检测则是在图像中定位并识别多个物体的位置和类别。OpenCV是一个功能强大的开源计算机视觉库&#xf…

《基于Apache Flink的流处理》笔记

思维导图 1-3 章 4-7章 8-11 章 参考资料 源码&#xff1a; https://github.com/streaming-with-flink 博客 https://flink.apache.org/bloghttps://www.ververica.com/blog 聚会及会议 https://flink-forward.orghttps://www.meetup.com/topics/apache-flink https://n…

LLaMA-Factory 微调 Qwen2-VL 进行人脸情感识别(二)

在上一篇文章中,我们详细介绍了如何使用LLaMA-Factory框架对Qwen2-VL大模型进行微调,以实现人脸情感识别的功能。本篇文章将聚焦于微调完成后,如何调用这个模型进行人脸情感识别的具体代码实现,包括详细的步骤和注释。 模型调用步骤 环境准备:确保安装了必要的Python库。…

Splash动态渲染技术全解析:从基础到企业级应用(2025最新版)

引言 在Web 3.0时代&#xff0c;87%的网站采用JavaScript动态渲染技术。传统爬虫难以应对Ajax加载、SPA应用等场景&#xff0c;Splash作为专业的JavaScript渲染服务&#xff0c;凭借​​Lua脚本控制​​和​​异步处理能力​​&#xff0c;已成为动态数据抓取的核心工具。本文…

【应用】Ghost Dance:利用惯性动捕构建虚拟舞伴

Ghost Dance是葡萄牙大学的一个研究项目&#xff0c;研究方向是探索人与人之间的联系&#xff0c;以及如何通过虚拟舞伴重现这种联系。项目负责人Cecilia和Rui利用惯性动捕创造出具有流畅动作的虚拟舞伴&#xff0c;让现实中的舞者也能与之共舞。 挑战&#xff1a;Ghost Danc…

广目软件GM DC Monitor

广目&#xff08;北京&#xff09;软件有限公司成立于2024年&#xff0c;技术和研发团队均来自于一家具有近10年监控系统研发的企业。广目的技术团队一共实施了9家政府单位、1家股份制银行、1家芯片制造企业的数据中心监控预警项目。这11家政企单位由2家正部级、1家副部级、6家…

12-Oracle 23ai Vector 使用ONNX模型生成向量嵌入

一、Oracle 23ai Vector Embeddings 核心概念​ 向量嵌入&#xff08;Vector Embeddings&#xff09;​​ -- 将非结构化数据&#xff08;文本/图像&#xff09;转换为数值向量 - - 捕获数据的语义含义而非原始内容 - 示例&#xff1a;"数据库" → [0.24, -0.78, 0.5…

用 NGINX 构建高效 POP3 代理`ngx_mail_pop3_module`

一、模块定位与作用 协议代理 ngx_mail_pop3_module 让 NGINX 能够充当 POP3 代理&#xff1a;客户端与后端 POP3 服务器之间的所有请求均转发到 NGINX&#xff0c;由 NGINX 负责与后端会话逻辑。认证方式控制 通过 pop3_auth 指令指定允许客户端使用的 POP3 认证方法&#xf…

每日算法 -【Swift 算法】三数之和

Swift&#xff5c;三数之和&#xff08;3Sum&#xff09;详细题解 注释 拓展&#xff08;LeetCode 15&#xff09; ✨题目描述 给你一个包含 n 个整数的数组 nums&#xff0c;判断 nums 中是否存在三个元素 a, b, c&#xff0c;使得 a b c 0。请你找出所有和为 0 且不重…

服务器磁盘空间被Docker容器日志占满处理方法

事发场景&#xff1a; 原本正常的服务停止运行了&#xff0c;查看时MQTT服务链接失败&#xff0c;查看对应的容器服务发现是EMQX镜像停止运行了&#xff0c;重启也是也报错无法正常运行&#xff0c;报错如下图&#xff1a; 报错日志中连续出现两个"no space left on devi…

令牌桶 滑动窗口->限流 分布式信号量->限并发的原理 lua脚本分析介绍

文章目录 前言限流限制并发的实际理解限流令牌桶代码实现结果分析令牌桶lua的模拟实现原理总结&#xff1a; 滑动窗口代码实现结果分析lua脚本原理解析 限并发分布式信号量代码实现结果分析lua脚本实现原理 双注解去实现限流 并发结果分析&#xff1a; 实际业务去理解体会统一注…