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

【Linux】IO多路转接——poll接口

目录

poll初识

poll函数

poll服务器

poll的优点

poll的缺点


poll初识

poll也是系统提供的一个多路转接接口。

  • poll系统调用也可以让我们的程序同时监视多个文件描述符上的事件是否就绪,和select的定位是一样的,适用场景也是一样的。

poll函数

poll函数

poll函数的函数原型如下:

int poll(struct pollfd *fds, nfds_t nfds, int timeout);

参数说明:

  • fds:一个poll函数监视的结构列表,每一个元素包含三部分内容:文件描述符、监视的事件集合、就绪的事件集合。
  • nfds:表示fds数组的长度。
  • timeout:表示poll函数的超时时间,单位是毫秒(ms)。

参数timeout的取值:

  • -1:poll调用后进行阻塞等待,直到被监视的某个文件描述符上的某个事件就绪。
  • 0:poll调用后进行非阻塞等待,无论被监视的文件描述符上的事件是否就绪,poll检测后都会立即返回。
  • 特定的时间值:poll调用后在指定的时间内进行阻塞等待,如果被监视的文件描述符上一直没有事件就绪,则在该时间后poll进行超时返回。

返回值说明:

  • 如果函数调用成功,则返回有事件就绪的文件描述符个数。
  • 如果timeout时间耗尽,则返回0。
  • 如果函数调用失败,则返回-1,同时错误码会被设置。

poll调用失败时,错误码可能被设置为:

  • EFAULT:fds数组不包含在调用程序的地址空间中。
  • EINTR:此调用被信号所中断。
  • EINVAL:nfds值超过RLIMIT_NOFILE值。
  • ENOMEM:核心内存不足。

struct pollfd结构

struct pollfd结构当中包含三个成员:

  • fd:特定的文件描述符,若设置为负值则忽略events字段并且revents字段返回0。
  • events:需要监视该文件描述符上的哪些事件。
  • revents:poll函数返回时告知用户该文件描述符上的哪些事件已经就绪。

events和revents的取值:

这些取值实际都是以宏的方式进行定义的,它们的二进制序列当中有且只有一个比特位是1,且为1的比特位是各不相同的。

  • 因此在调用poll函数之前,可以通过或运算符将要监视的事件添加到events成员当中。
  • 在poll函数返回后,可以通过与运算符检测revents成员中是否包含特定事件,以得知对应文件描述符的特定事件是否就绪。

poll服务器

poll的工作流程和select是基本类似的,这里我们也实现一个简单poll服务器,该服务器也只是读取客户端发来的数据并进行打印。

PollServer类

PollServer类当中也只需要包含监听套接字和端口号两个成员变量,在poll服务器绑定时直接将IP地址设置为INADDR_ANY尽即可。

  • 在构造PollServer对象时,需要指明poll服务器的端口号,当然也可以在初始化poll服务器的时候指明。
  • 在初始化poll服务器的时候调用Socket类当中的函数,依次进行套接字的创建、绑定和监听即可,这里的Socket类和之前实现的一模一样。
  • 在析构函数中可以选择调用close函数将监听套接字进行关闭,但实际也可以不进行该动作,因为服务器运行后一般是不退出的。

代码如下:

#pragma once#include "socket.hpp"
#include <poll.h>#define BACK_LOG 5class PollServer{
private:int _listen_sock; //监听套接字int _port; //端口号
public:PollServer(int port): _port(port){}void InitPollServer(){_listen_sock = Socket::SocketCreate();Socket::SocketBind(_listen_sock, _port);Socket::SocketListen(_listen_sock, BACK_LOG);}~PollServer(){if (_listen_sock >= 0){close(_listen_sock);}}
};

 运行服务器

服务器初始化完毕后就可以开始运行了,而poll服务器要做的就是不断调用poll函数,当事件就绪时对应执行某种动作即可。

  • 首先,在poll服务器开始死循环调用poll函数之前,需要定义一个fds数组,该数组当中的每个位置都是一个struct pollfd结构,后续调用poll函数时会作为参数进行传入。先将fds数组当中每个位置初始化为无效,并将监听套接字添加到fds数组当中,表示服务器刚开始运行时只需要监视监听套接字的读事件。
  • 此后,poll服务器就不断调用poll函数监视读事件是否就绪。如果poll函数的返回值大于0,则说明poll函数调用成功,此时已经有文件描述符的读事件就绪,接下来就应该对就绪事件进行处理。如果poll函数的返回值等于0,则说明timeout时间耗尽,此时直接准备进行下一次poll调用即可。如果poll函数的返回值为-1,则说明poll调用失败,此时也让服务器准备进行下一次poll调用,但实际应该进一步判断错误码,根据错误码来判断是否应该继续调用poll函数。
     

代码如下:

#pragma once#include "socket.hpp"
#include <poll.h>#define BACK_LOG 5
#define NUM 1024
#define DFL_FD - 1class PollServer{
private:int _listen_sock; //监听套接字int _port; //端口号
public:void Run(){struct pollfd fds[NUM];ClearPollfds(fds, NUM, DFL_FD); //清空数组中的所有位置SetPollfds(fds, NUM, _listen_sock); //将监听套接字添加到数组中,并关心其读事件for (;;){switch (poll(fds, NUM, -1)){case 0:std::cout << "timeout..." << std::endl;break;case -1:std::cerr << "poll error" << std::endl;break;default://正常的事件处理//std::cout<<"有事件发生..."<<std::endl;HandlerEvent(fds, NUM);break;}}}
private:void ClearPollfds(struct pollfd fds[], int num, int default_fd){for (int i = 0; i < num; i++){fds[i].fd = default_fd;fds[i].events = 0;fds[i].revents = 0;}}bool SetPollfds(struct pollfd fds[], int num, int fd){for (int i = 0; i < num; i++){if (fds[i].fd == DFL_FD){ //该位置没有被使用fds[i].fd = fd;fds[i].events |= POLLIN; //添加读事件到events当中return true;}}return false; //fds数组已满}
};

事件处理

当poll检测到有文件描述符的读事件就绪,就会在其对应的struct pollfd结构中的revents成员中添加读事件并返回,接下来poll服务器就应该对就绪事件进行处理了,事件处理过程如下:

  • 首先遍历fds数组中的每个struct pollfd结构,如果该结构当中的fd有效,且revents当中包含读事件,则说明该文件描述符的读事件就绪,接下来就需要进一步判断该文件描述符是监听套接字还是与客户端建立的套接字。
  • 如果是监听套接字的读事件就绪,则调用accept函数将底层建立好的连接获取上来,并将获取到的套接字添加到fds数组当中,表示下一次调用poll函数时需要监视该套接字的读事件。
  • 如果是与客户端建立的连接对应的读事件就绪,则调用read函数读取客户端发来的数据,并将读取到的数据在服务器端进行打印。
  • 如果在调用read函数时发现客户端将连接关闭或read函数调用失败,则poll服务器也直接关闭对应的连接,并将该连接对应的文件描述符从fds数组当中清除,表示下一次调用poll函数时无需再监视该套接字的读事件。

代码如下:

#pragma once#include "socket.hpp"
#include <poll.h>#define BACK_LOG 5
#define NUM 1024
#define DFL_FD - 1class PollServer{
private:int _listen_sock; //监听套接字int _port; //端口号
public:void HandlerEvent(struct pollfd fds[], int num){for (int i = 0; i < num; i++){if (fds[i].fd == DFL_FD){ //跳过无效的位置continue;}if (fds[i].fd == _listen_sock&&fds[i].revents&POLLIN){ //连接事件就绪struct sockaddr_in peer;memset(&peer, 0, sizeof(peer));socklen_t len = sizeof(peer);int sock = accept(_listen_sock, (struct sockaddr*)&peer, &len);if (sock < 0){ //获取连接失败std::cerr << "accept error" << std::endl;continue;}std::string peer_ip = inet_ntoa(peer.sin_addr);int peer_port = ntohs(peer.sin_port);std::cout << "get a new link[" << peer_ip << ":" << peer_port << "]" << std::endl;if (!SetPollfds(fds, NUM, sock)){ //将获取到的套接字添加到fds数组中,并关心其读事件close(sock);std::cout << "poll server is full, close fd: " << sock << std::endl;}}else if (fds[i].revents&POLLIN){ //读事件就绪char buffer[1024];ssize_t size = read(fds[i].fd, buffer, sizeof(buffer)-1);if (size > 0){ //读取成功buffer[size] = '\0';std::cout << "echo# " << buffer << std::endl;}else if (size == 0){ //对端连接关闭std::cout << "client quit" << std::endl;close(fds[i].fd);UnSetPollfds(fds, i); //将该文件描述符从fds数组中清除}else{std::cerr << "read error" << std::endl;close(fds[i].fd);UnSetPollfds(fds, i); //将该文件描述符从fds数组中清除}}}}
private:bool SetPollfds(struct pollfd fds[], int num, int fd){for (int i = 0; i < num; i++){if (fds[i].fd == DFL_FD){ //该位置没有被使用fds[i].fd = fd;fds[i].events |= POLLIN; //添加读事件到events当中return true;}}return false; //fds数组已满}void UnSetPollfds(struct pollfd fds[], int pos){fds[pos].fd = DFL_FD;fds[pos].events = 0;fds[pos].revents = 0;}
};

说明一下:

  • 因为这里将fds数组的大小是固定设置的,因此在将新获取连接对应的文件描述符添加到fds数组时,可能会因为fds数组已满而添加失败,这时poll服务器只能将刚刚获取上来的连接对应的套接字进行关闭。

poll服务器测试

运行poll服务器时也需要先实例化出一个PollServer对象,对poll服务器进行初始化后就可以运行服务器了。

代码如下:

#include "poll_server.hpp"
#include <string>static void Usage(std::string proc)
{std::cerr << "Usage: " << proc << " port" << std::endl;
}int main(int argc, char* argv[])
{if (argc != 2){Usage(argv[0]);exit(1);}int port = atoi(argv[1]);PollServer* svr = new PollServer(port);svr->InitPollServer();svr->Run();return 0;
}

因为我们编写的poll服务器在调用poll函数时,将timeout的值设置成了-1,因此运行服务器后如果没有客户端发来连接请求,那么服务器就会在调用poll函数后进行阻塞等待。

当我们用telnet工具连接poll服务器后,poll服务器调用的poll函数在检测到监听套接字的读事件就绪后就会调用accept获取建立好的连接,并打印输出客户端的IP和端口号,此时客户端发来的数据也能够成功被poll服务器收到并进行打印输出。

此外,poll服务器也是一个单进程服务器,但是它也可以同时为多个客户端提供服务。

当服务器端检测到客户端退出后,也会关闭对应的连接,并将对应的套接字从fds数组当中清除。

poll的优点

  • struct pollfd结构当中包含了events和revents,相当于将select的输入输出型参数进行分离,因此在每次调用poll之前,不需要像select一样重新对参数进行设置。
  • poll可监控的文件描述符数量没有限制。
  • 当然,poll也可以同时等待多个文件描述符,能够提高IO的效率。

说明一下:

  • 虽然代码中将fds数组的元素个数定义为1024,但fds数组的大小是可以继续增大的,poll函数能够帮你监视多少个文件描述符是由传入poll函数的第二个参数决定的。
  • 而fd_set类型只有1024个比特位,因此select函数最多只能监视1024个文件描述符。

poll的缺点

  • 和select函数一样,当poll返回后,需要遍历fds数组来获取就绪的文件描述符。
  • 每次调用poll,都需要把大量的struct pollfd结构从用户态拷贝到内核态,这个开销也会随着poll监视的文件描述符数目的增多而增大。
  • 同时每次调用poll都需要在内核遍历传递进来的所有fd,这个开销在fd很多时也很大。

相关文章:

【Linux】IO多路转接——poll接口

目录 poll初识 poll函数 poll服务器 poll的优点 poll的缺点 poll初识 poll也是系统提供的一个多路转接接口。 poll系统调用也可以让我们的程序同时监视多个文件描述符上的事件是否就绪&#xff0c;和select的定位是一样的&#xff0c;适用场景也是一样的。 poll函数 po…...

系统架构设计师---OSI七层协议

目录 OSI七层协议 各层主要功能和详细说明 Internet协议的主要协议及其层次关系...

Next.js - Route Groups(路由组)

路由组的作用 在应用程序目录中&#xff0c;嵌套文件夹通常会映射到 URL 路径。不过&#xff0c;您可以将文件夹标记为路由组&#xff0c;以防止该文件夹包含在路由的 URL 路径中。 这样就可以在不影响 URL 路径结构的情况下&#xff0c;将路由段和项目文件组织到逻辑组中。 …...

musl libc ldso 动态加载研究笔记:01

前言 musl 是一个轻量级的标准C库&#xff0c;建立在系统调用之上&#xff0c;可以认为是【用户态】的C 库&#xff0c;与 glibc 或者 uClibc 属于同一类。 基于 musl 的 gcc 工具链包括交叉编译工具链&#xff0c;可以用于编译 Linux 或者其他的操作系统&#xff0c;如当前 L…...

2023 年 4 款适用于安卓手机的最佳 PDF 转 Word 转换器

尝试在 Android 上将 PDF 文档转换为 Word 文件&#xff1f;好吧&#xff0c;您可能会发现要让它发挥作用几乎是不可能的&#xff0c;至少在没有任何额外工具的情况下是这样。Web 上有用于此类转换的选项&#xff0c;但本地不一定会发生任何情况&#xff08;可能除了一个应用程…...

前端:运用html+css+jquery.js实现截图游戏

前端:运用htmlcssjquery.js实现截图游戏 1. 前言2. 实现原理3. 参考代码和运行结果 1. 前言 最近在刷手机视频时&#xff0c;总是能刷到一个这样的视频&#xff0c;视频上是一个截图游戏&#xff0c;当图片上的某个片段正好在图片的正确位置时&#xff0c;暂停视频&#xff0c;…...

Maven之JDK编译问题

IDEA Maven 默认使用 JDK 1.5 编译问题 IDEA 在「调用」maven 时&#xff0c;IDEA 默认都会采用 JDK 1.5 编译&#xff0c;不管你安装的 JDK 版本是 JDK 7 还是 JDK 8 或者更高。这样一来非常不方便&#xff0c;尤其是时不时使用 JDK 7/8 的新特性时。如果使用新特性&#xff…...

开发测试框架一 - 创建springboot工程及基础操作

一、创建及运行方式 1. 从官网导入&#xff1a; 注意&#xff1a;由于我的java版本是1.8&#xff1b;所以选中了spring2.7.14&#xff1b;如果你的java版本是9及以上&#xff0c;选中spring3相关的同时Java 版本也要对应起来 2. 创建第一个get请求 创建Controller package及…...

【IMX6ULL驱动开发学习】08.马达驱动实战:驱动编写、手动注册平台设备和设备树添加节点信息

目录 一、使用设备树 1.1 修改设备树流程 二、手动创建平台设备 三、总结&#xff08;附驱动程序&#xff09; 前情提要&#xff1a;​​​​​​​【IMX6ULL驱动开发学习】07.驱动程序分离的思想之平台总线设备驱动模型和设备树_阿龙还在写代码的博客-CSDN博客 手动注册…...

直方图均衡化和自适应直方图均衡化

前言&#xff1a; Hello大家好&#xff0c;我是Dream。 均衡化是数字图像处理中常用的一种技术&#xff0c;用于增强图像的视觉效果和对比度。&#xff0c;今天我们将实现对同一张图像的直方图均衡化和自适应直方图均衡化处理&#xff0c;学习一下两者的的基本原理和实现过程&a…...

京东门详一码多端探索与实践 | 京东云技术团队

本文主要讲述京东门详业务在支撑过程中遇到的困境&#xff0c;面对问题我们在效率提升、质量保障等方向的探索和实践&#xff0c;在此将实践过程中问题解决的思路和方案与大家一起分享&#xff0c;也希望能给大家带来一些新的启发 一、背景 1.1、京东门详介绍 1.1.1、京东门…...

数据挖掘 | 零代码采集房源数据,支持自动翻页、数据排重等

1 前言 城市规划、商业选址等应用场景中经常会对地区房价、地域价值进行数据分析&#xff0c;其中地区楼盘房价是分析数据中重要的信息参考点&#xff0c;一些互联网网站上汇聚了大量房源信息&#xff0c;通过收集此类数据&#xff0c;能够对地区房价的分析提供参考依据。 如何…...

迪米特法则

迪米特法则&#xff0c;也称为最少知识原则&#xff08;Law of Demeter&#xff09;&#xff0c;是面向对象设计中的一个原则&#xff0c;旨在降低对象之间的耦合性&#xff0c;提高系统的可维护性和可扩展性。该原则强调一个类不应该直接与其它不相关的类相互交互&#xff0c;…...

云积天赫|AIGC+营销的排头兵

AIGC生成式人工智能&#xff0c;正逐渐成为人们关注的焦点。AIGC的出现&#xff0c;标志着人工智能已经进入了一个全新的时代。AIGC的出现&#xff0c;也为营销行业带来了新的活力。那么企业该怎么利用这次AIGC浪潮&#xff0c;成为AIGC营销的排头兵呢&#xff1f;      “…...

Oracle 数据库备份

1、使用管理员账号创建对应的directory目录 登录数据库 sqlplus / as sysdba 创建directory create or replace directory dumpdir as F:\container; 2、给用户赋予使用该目录的权限 grant read,write on directory dumpdir to Scott; 查看创建的目录位置 select * fro…...

【C++】模板template

&#x1f525;&#x1f525; 欢迎来到小林的博客&#xff01;&#xff01;       &#x1f6f0;️博客主页&#xff1a;✈️林 子       &#x1f6f0;️博客专栏&#xff1a;✈️ C       &#x1f6f0;️社区 :✈️ 进步学堂       &#x1f6f0;️欢…...

智能工厂:适应不断变化的制造世界

制造业已经从过去传统的装配线工艺流程中走了很长一段路。随着技术的进步和工业 4.0 的兴起&#xff0c;制造业正在迅速发展&#xff0c;以满足现代世界不断变化的需求。近年来出现的一个关键概念就是“智能工厂”。在这篇文章中&#xff0c;我们将探讨什么是智能工厂、它是如何…...

大数据课程I3——Kafka的消息流与索引机制

文章作者邮箱:yugongshiye@sina.cn 地址:广东惠州 ▲ 本章节目的 ⚪ 掌握Kafka的消息流处理; ⚪ 掌握Kafka的索引机制; ⚪ 掌握Kafka的消息系统语义; 一、Kafka消息流处理 1. Producer 写入消息 流程说明: 1. producer 要向Kafka生产消息,需要先通过…...

LVGL学习笔记 28 - 键盘keyboard

目录 1. 设置关联文本框 2. 设置模式 2.1 LV_KEYBOARD_MODE_TEXT_LOWER 2.2 LV_KEYBOARD_MODE_TEXT_UPPER 2.3 LV_KEYBOARD_MODE_SPECIAL 2.4 LV_KEYBOARD_MODE_NUMBER 2.5 LV_KEYBOARD_MODE_USER_1 ~ LV_KEYBOARD_MODE_USER_4 3. 使能弹窗模式 4. 更改按键布局 5. 事…...

【Microsoft 支持】【数据库-MySql】当您尝试从大于 5000 的 TCP 端口连接时收到错误 WSAENOBUFS (10055)

​ 一、转载原文 When you try to connect from TCP ports greater than 5000 you receive the error ‘WSAENOBUFS (10055)’ Symptoms If you try to set up TCP connections from ports that are greater than 5000, the local computer responds with the following WSAE…...

RestClient

什么是RestClient RestClient 是 Elasticsearch 官方提供的 Java 低级 REST 客户端&#xff0c;它允许HTTP与Elasticsearch 集群通信&#xff0c;而无需处理 JSON 序列化/反序列化等底层细节。它是 Elasticsearch Java API 客户端的基础。 RestClient 主要特点 轻量级&#xff…...

接口测试中缓存处理策略

在接口测试中&#xff0c;缓存处理策略是一个关键环节&#xff0c;直接影响测试结果的准确性和可靠性。合理的缓存处理策略能够确保测试环境的一致性&#xff0c;避免因缓存数据导致的测试偏差。以下是接口测试中常见的缓存处理策略及其详细说明&#xff1a; 一、缓存处理的核…...

<6>-MySQL表的增删查改

目录 一&#xff0c;create&#xff08;创建表&#xff09; 二&#xff0c;retrieve&#xff08;查询表&#xff09; 1&#xff0c;select列 2&#xff0c;where条件 三&#xff0c;update&#xff08;更新表&#xff09; 四&#xff0c;delete&#xff08;删除表&#xf…...

黑马Mybatis

Mybatis 表现层&#xff1a;页面展示 业务层&#xff1a;逻辑处理 持久层&#xff1a;持久数据化保存 在这里插入图片描述 Mybatis快速入门 ![在这里插入图片描述](https://i-blog.csdnimg.cn/direct/6501c2109c4442118ceb6014725e48e4.png //logback.xml <?xml ver…...

shell脚本--常见案例

1、自动备份文件或目录 2、批量重命名文件 3、查找并删除指定名称的文件&#xff1a; 4、批量删除文件 5、查找并替换文件内容 6、批量创建文件 7、创建文件夹并移动文件 8、在文件夹中查找文件...

Oracle查询表空间大小

1 查询数据库中所有的表空间以及表空间所占空间的大小 SELECTtablespace_name,sum( bytes ) / 1024 / 1024 FROMdba_data_files GROUP BYtablespace_name; 2 Oracle查询表空间大小及每个表所占空间的大小 SELECTtablespace_name,file_id,file_name,round( bytes / ( 1024 …...

Mybatis逆向工程,动态创建实体类、条件扩展类、Mapper接口、Mapper.xml映射文件

今天呢&#xff0c;博主的学习进度也是步入了Java Mybatis 框架&#xff0c;目前正在逐步杨帆旗航。 那么接下来就给大家出一期有关 Mybatis 逆向工程的教学&#xff0c;希望能对大家有所帮助&#xff0c;也特别欢迎大家指点不足之处&#xff0c;小生很乐意接受正确的建议&…...

SCAU期末笔记 - 数据分析与数据挖掘题库解析

这门怎么题库答案不全啊日 来简单学一下子来 一、选择题&#xff08;可多选&#xff09; 将原始数据进行集成、变换、维度规约、数值规约是在以下哪个步骤的任务?(C) A. 频繁模式挖掘 B.分类和预测 C.数据预处理 D.数据流挖掘 A. 频繁模式挖掘&#xff1a;专注于发现数据中…...

页面渲染流程与性能优化

页面渲染流程与性能优化详解&#xff08;完整版&#xff09; 一、现代浏览器渲染流程&#xff08;详细说明&#xff09; 1. 构建DOM树 浏览器接收到HTML文档后&#xff0c;会逐步解析并构建DOM&#xff08;Document Object Model&#xff09;树。具体过程如下&#xff1a; (…...

C# SqlSugar:依赖注入与仓储模式实践

C# SqlSugar&#xff1a;依赖注入与仓储模式实践 在 C# 的应用开发中&#xff0c;数据库操作是必不可少的环节。为了让数据访问层更加简洁、高效且易于维护&#xff0c;许多开发者会选择成熟的 ORM&#xff08;对象关系映射&#xff09;框架&#xff0c;SqlSugar 就是其中备受…...