【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很多时也很大。

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

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

相关文章

LC-将有序数组转换为二叉搜索树

LC-将有序数组转换为二叉搜索树 链接&#xff1a;https://leetcode.cn/problems/convert-sorted-array-to-binary-search-tree/description/ 描述&#xff1a; 给你一个整数数组 nums &#xff0c;其中元素已经按 升序 排列&#xff0c;请你将其转换为一棵 高度平衡 二叉搜索…

QT的mysql(数据库)最佳实践和常见问题解答

涉及到数据库&#xff0c;首先安利一个软件Navicat Premium&#xff0c;用来查询数据库很方便 QMysql驱动是Qt SQL模块使用的插件&#xff0c;用于与MySQL数据库进行通信。要编译QMysql驱动&#xff0c;您需要满足以下条件&#xff1a; 您需要安装MySQL的客户端库和开发头文件…

【LeetCode】字符串匹配

字符串匹配问题 常见的字符串匹配算法包括暴力匹配、 Knuth-Morris-Pratt 算法、Boyer-Moore算法、Sunday 算法等 解题 思路 1: 两个指针各指向两个字符串的起始位置,先判断,两个字符串的第一个字符是否相同,如果相同的话,则将两个指针往下移动,并且判断较短字符串的下一个字符…

el-table实现静态和动态合并单元格 以及内容显示的问题

实现效果图 <el-tablev-loading"loading":data"tableData"style"width: 100%":row-class-name"tableRowClassName"size"small"><el-table-column fixed label"序号" width"50"><el-tab…

[MySQL] — 数据类型和表的约束

目录 数据类型 数据类型分类 数值类型 tinyint类型 bit类型 小数类型 float decimal 字符串类型 char varchar char和varchar的区别 日期和时间类型 enum 和 set 表的约束 空属性 默认值 列描述 zeorfill 主键 创建表时在字段上指定主键 删除主键&#xff1a; 追…

Spring Bean的生命周期总结(包含面试题)

目录 一、Bean的初始化过程 1. 加载Spring Bean 2. 解析Bean的定义 3. Bean属性定义 4. BeanFactoryPostProcessor 扩展接口 5. 实例化Bean对象 6. Aware感知 7. 初始化方法 8. 后置处理 9. destroy 销毁 二、Bean的单例与多例模式 2.1 单例模式&#xff08;Sin…

Codeforces Round 893 (Div. 2)B题题解

文章目录 [The Walkway](https://codeforces.com/contest/1858/problem/B)问题建模问题分析1.分析所求2.如何快速计算每个商贩被去除后的饼干数量代码 The Walkway 问题建模 给定n个椅子&#xff0c;其中有m个位置存在商贩&#xff0c;在商贩处必须购买饼干吃&#xff0c;每隔…

由于找不到d3dx9_43.dll无法继续执行代码怎么解决

我们在安装PS&#xff0c;吃鸡等软件跟游戏的时候&#xff0c;有可能安装完之后出现以下问题&#xff08;特别是win7或者win8系统&#xff09; 错误&#xff1a; 打开PS或者吃鸡等游戏的时候出现这个错误&#xff1a;无法启动此程序&#xff0c;因为计算机中丢失d3dx9_43.dll。…

4. 软件开发的环境搭建

目录 1. 搭建环境 1.1 检查 JDK 1.2 检查 MySQL 数据库 1.3 检查 Maven 1.4 检查 GITEEGIT 1.5 安装插件 1.5.1 安装 Spring Boot Helper 1.5.2 安装 lombok 1.6 创建仓库 1.6.1 登录 GITEE 创建仓库并复制仓库地址 1.6.2 克隆到本地 1.7 创建工程 1.7.1 设置编码…

应用层自定义协议(组织数据的格式)

概念 在进行网络传输数据的时候&#xff0c;通常是将要传输的数据组织成一个字符串&#xff0c;再将字符串转换为一个字节流进行网络传输数据&#xff0c;而数据组织的格式是多种多样的&#xff0c;我们只需要保证&#xff0c;客户端和服务器对于字符串的组织和解析统一即可 现…

数据结构的树存储结构

数据结构的树存储结构 之前介绍的所有的数据结构都是线性存储结构。本章所介绍的树结构是一种非线性存储结构&#xff0c;存储的是具有“一对多”关系的数据元素的集合。 (A) (B) 图 1 树的示例 图 …

TDD(测试驱动开发)?

01、前言 很早之前&#xff0c;曾在网络上见到过 TDD 这 3 个大写的英文字母&#xff0c;它是 Test Driven Development 这三个单词的缩写&#xff0c;也就是“测试驱动开发”的意思——听起来很不错的一种理念。 其理念主要是确保两件事&#xff1a; 确保所有的需求都能被照…

corosync+pacemaker+nfs配置简单高可用

环境准备&#xff1a; 每个节点提供20G共享存储 web1192.168.134.176node7web2192.168.134.177node8 一、准备web环境&#xff08;两台web测试机都要准备&#xff09; yum install httpd -y echo " web test page ,ip is hostname -I." > /var/www/html/index…

利用logstash/filebeat/插件,将graylog日志传输到kafka中

1.graylog配置输出 在System-outputs&#xff0c;选择GELF Output&#xff0c;填写如下内容&#xff0c;其它选项默认 在要输出的Stream中&#xff0c;选择Manage Outputs 选择GELF Output&#xff0c;右边选择刚才创建好的test。 2.安装logstash&#xff0c;作为中间临时…

ROS机器人启动move base时代价地图概率性无法加载的原因及解决方法

最近&#xff0c;使用ROS机器人&#xff0c;在启动move_base 节点时&#xff0c;概率性会出现全局和局部代价地图不加载的问题&#xff0c;此时&#xff0c;发布目标点也无法启动路径规划。而且该问题有时候出现概率很低&#xff0c;比如启动10次&#xff0c;会有1次发送该情况…

进行 200 瓦太阳能 (PV) 模块设计以测量太阳能光伏阵列的电压、电流和功率、综合负荷频率和电压控制系统的方法研究(Simulink实现)

&#x1f4a5;&#x1f4a5;&#x1f49e;&#x1f49e;欢迎来到本博客❤️❤️&#x1f4a5;&#x1f4a5; &#x1f3c6;博主优势&#xff1a;&#x1f31e;&#x1f31e;&#x1f31e;博客内容尽量做到思维缜密&#xff0c;逻辑清晰&#xff0c;为了方便读者。 ⛳️座右铭&a…

构建可远程访问的企业内部论坛

文章目录 前言1.cpolar、PHPStudy2.Discuz3.打开PHPStudy&#xff0c;安装网页论坛所需软件4.进行网页运行环境的构建5.运行Discuz网页程序6.使用cpolar建立穿透内网的数据隧道&#xff0c;发布到公网7.对云端保留的空白数据隧道进行配置8.Discuz论坛搭建完毕 前言 企业在发展…

Beats:使用 Filebeat 将 golang 应用程序记录到 Elasticsearch - 8.x

毫无疑问&#xff0c;日志记录是任何应用程序最重要的方面之一。 当事情出错时&#xff08;而且确实会出错&#xff09;&#xff0c;我们需要知道发生了什么。 为了实现这一目标&#xff0c;我们可以设置 Filebeat 从我们的 golang 应用程序收集日志&#xff0c;然后将它们发送…

matlab RANSAC拟合多项式曲线

目录 一、功能概述1、算法概述2、主要函数3、参考文献二、代码实现三、结果展示四、参考链接本文由CSDN点云侠原创,原文链接。爬虫网站自重,把自己当个人。爬些不完整的误导别人有意思吗???? 一、功能概述 1、算法概述 使用RANSAC对点进行多项式拟合。

​《乡村振兴战略下传统村落文化旅游设计 》在2023年畅销榜排名465位

​《乡村振兴战略下传统村落文化旅游设计 》在2023年畅销榜排名465位