IO多路转接之poll

目录

1. poll 的基本认识

2. poll 基于 select 的突破

3. poll() 系统调用

3.1. struct pollfd 结构

4. poll() 的 demo

5. poll 的总结


1. poll 的基本认识

poll 是一种多路转接的方案, 它的核心功能和 select 一模一样,我们知道 IO = 等待事件就绪 + 拷贝数据, 而它们只负责IO过程中的等待事件就绪

用户和内核通过 poll 想告诉对方:

  • 用户告诉内核 (调用 poll() 时):内核帮用户关心哪些文件描述符的哪些事件;
  • 内核告诉用户 (poll() 返回时):哪些文件描述符的哪些事件已经就绪;

2. poll 基于 select 的突破

因为 select 服务器有如下缺点:

  • 因为 select 服务器需要维护一个第三方数组,因此,select 服务器会充斥着大量的遍历操作 (时间复杂度O(N));
  • 我们知道 fd_set 是一个固定大小的位图,因此也就决定了 select 服务器所能监测的文件描述符的数量是有上限的;
  • 除开第一个参数,剩下的后四个参数,都是输入输出型参数,每调用一次 select,用户需要对这些参数进行重新设定;
  • 同时,也因为它们是输入输出型参数,即内核和用户都需要对其进行修改,因此,select 会进行频繁的用户到内核,内核到用户的数据拷贝;
  • 上面几个问题,也间接导致了 select 服务器的编码比较复杂。

因此,设计者们提出了 poll ,poll 基于 select 的突破:

  • poll 将输入型参数和输出型参数进行了分离:这也就意味着用户不用对参数进行重新设定, 这是其一; 同时,也因为输入参数和输出参数分离,poll 不会进行频繁的用户到内核的数据拷贝, 但是内核到用户的数据拷贝是不可少的,这是其二;
  • poll 没有最大文件描述符数量的限制:这里的文件描述符的数量由用户决定,poll 自身没有限制,只要服务器有能力承载更多的连接,poll 就可以监测更多的文件描述符。

3. poll() 系统调用

#include <poll.h>
int poll(struct pollfd *fds, nfds_t nfds, int timeout);

首先解释后两个参数。

timeout: timeout 代表超时时间,但这里的 timeout 是以毫秒 (ms) 为单位的。

  • 如果 timeout = 0, 代表 poll() 非阻塞等待;
  • 如果 timeout = -1, 代表 poll() 阻塞等待;
  • 如果 timeout > 0,比如 1000,那么代表,在1000毫秒内,阻塞等待,如果超时,没有事件就绪,那么 poll () 返回0。

nfds:代表 fds 这个数组的长度;

poll() 返回值:

  • 如果大于 0, 代表事件已就绪的文件描述符的个数;
  • 如果等于 0, time out,没有事件发生;
  • 如果小于 0, poll() error,可通过 errno 查看错误原因。

3.1. struct pollfd 结构

struct pollfd 结构如下:

/* Type used for the number of file descriptors.  */
typedef unsigned long int nfds_t;/* Data structure describing a polling request.  */
struct pollfd
{int fd;     /* File descriptor to poll.  */short int events;   /* Types of events poller cares about.  */short int revents;    /* Types of events that actually occurred.  */
}

int fd:

在使用 poll() 时,无论是用户告诉内核 ( 调用poll() ),还是内核告诉用户 ( poll() 返回时),它们都不会对文件描述符的值做修改,即这个值只要用户设定一次就好了。

  • 当用户告诉内核时,这里的 fd 就代表,内核需要关心这个文件描述符的某个IO事件;
  • 当内核告诉用户时,这里的 fd 就代表,这个文件描述符的某个IO事件已经就绪了。

光有一个 fd 不够,因为无论是用户告诉内核,还是内核告诉用户,都需要知道这个文件描述符所关心的IO事件是什么。

因此有了 events 和 revents;

  • events:代表请求事件。用户告诉内核,用户所关心的这个文件描述符的 IO 事件都在 events 里;
  • revents:代表返回事件。内核告诉用户,这个文件描述符上的哪些IO事件 (revents) 已经就绪了;

因此,可以看到, poll 是如何做到将输入型参数和输出型参数分离的呢? 

本质上是通过 events 和 revents 这两个参数做到的。

  • 用户告诉内核时,只会修改 events 这个数据,而不会对 revents 做任何修改;
  • 内核告诉用户时,只会修改 revents 这个数据,而不会对 events 做任何修改;

可是现在有一个问题, events 和 revents 的类型都是 short int 啊,而 short int 才2字节,怎样用 short int 来表示不同的IO事件呢?

这个问题,我们以前就遇到过,在学习基础IO的时候,我们学习的 open 系统调用,也使用了同样的方式,用一个 int 来表示不同的打开方式,那是怎样做到的呢?

用 short int 的不同比特位来表示不同的 IO 事件,具体如下:

      事 件                            描                述是否可作为输入是否可作为输出
    POLLIN          数据 ( 包括普通数据和优先数据 ) 可读         是          是
    POLLOUT          数据 ( 包括普通数据和优先数据 ) 可写                 是          是
    POLLERR                                  错误         否          是
    POLLPRI    高优先级数据可读,比如TCP的紧急数据(URG)         是          是

上面列举了一些标志位,但它们本质上都是宏,如下:

#define POLLIN    0x001   /* There is data to read.  */
#define POLLPRI   0x002   /* There is urgent data to read.  */
#define POLLOUT   0x004   /* Writing now will not block.  */
#define POLLERR   0x008   /* Error condition.  */

关于标志位就说到这里。

我们说过,poll 较于 select 解决了一个问题,poll 没有最大文件描述符数量的限制, 可是,我们发现,poll 的第一个参数是 struct pollfd *fds,这不就是一个数组吗,而数组是有范围的,那么为什么说 poll 没有最大文件描述符数量的限制呢?

  • 首先,select 是存在最大文件描述符数量的限制的,因为它受限于 fd_set 位图结构;
  • 而 poll 是不存在最大文件描述符数量限制的,它可以是 1024,也可以是 2048,甚至是4096,只要服务器有能力能够承载更多的连接, poll 就可以监测更多的文件描述符, 换言之,poll () 自身没有文件描述符数量的限制,实际上,文件描述符的数量是受用户和服务器的承受能力的限制,只要用户认为有必要且服务器有能力,poll() 就可以监视更多的文件描述符。

4. poll() 的 demo

demo 所需要的小组件,例如 Sock.hpp、Date.hpp、Log.hpp 在 IO多路转接之poll 文章中有。

声明:poll() demo 只处理读事件,即POLLIN,暂时不考虑POLLOUT;

实现思路:

  1. constructor:
    1.  创建套接字、监听、绑定;
    2.  动态申请数组 (struct pollfd* ),并初始化;
    3.  约定监听套接字为数组的第一个元素;
    4.  设置超时时间 (如果你愿意的话)。
  2. start:
    1. 因为 poll() 将输入参数 (events) 和 输出参数 (revents) 分离,故用户在轮询时不需要对数据进行重新设定;
    2. 直接调用 poll(), 通过返回值,确定不同的执行策略;
    3. 当 poll() return > 0 时,代表着有文件描述符的IO事件就绪,此时调用处理事件函数,handleEvent;
    4. 遍历整个数组,找到事件就绪的文件描述符,判断是监听套接字,还是服务套接字;
    5. 如果是监听套接字,调用accept,获取新连接,并将新连接 Load 到数组中 (如果可以的话);
    6. 如果是服务套接字,调用read/recv,拷贝数据。 注意:当read/recv返回0时,代表着对端关闭连接,服务端需要 close 该连接,并将这个套接字在数组中清除。
  3. 以上就是整体实现思路,在实现过程中,注意耦合度,适当解耦,提高代码的可读性。
#ifndef _POLL_SERVER_HPP_
#define _POLL_SERVER_HPP_#include "Log.hpp"
#include "Date.hpp"
#include "Sock.hpp"
#include <poll.h>
#include <sys/socket.h>
#include <sys/types.h>#define NUM 1024
#define FD_NONE -1namespace Xq
{class PollServer{public:PollServer(uint16_t port = 8080){_sock.Socket();_sock.Bind("", port);_sock.Listen();_nfds = NUM;_poll_fd = new struct pollfd[_nfds];// 初始化数组for(size_t i = 0; i < _nfds; ++i){_poll_fd[i].fd = FD_NONE;_poll_fd[i].events = _poll_fd[i].revents = 0;}// 约定监听套接字为数组的第一个元素_poll_fd[0].fd = _sock._sock;_poll_fd[0].events = POLLIN;_timeout = 1000;  // 以毫秒为单位LogMessage(DEBUG, "poll server init success");}void start(void){while(true){
#ifdef DEBUG_SHOWdebug_show();
#endifint n = poll(_poll_fd, _nfds, _timeout);if(n == 0){LogMessage(DEBUG, "time out...");}else if(n < 0){LogMessage(ERROR, "error: %d, error message: %s", errno, strerror(errno));}else{LogMessage(DEBUG, "ready IO enevt num: %d", n);handleEvent();}}}void handleEvent(void){for(nfds_t pos = 0; pos < _nfds; ++pos){// 用户不关心的文件描述符跳过if(_poll_fd[pos].fd == FD_NONE) continue;// 如果读事件就绪if(_poll_fd[pos].revents & POLLIN){// 如果是listen sock, accept 获取新连接if(_poll_fd[pos].fd == _sock._sock){Accepter();}// 如果是server sock, read/recv, 拷贝数据else{Recver(pos);}}// 读事件未就绪else{
#ifdef DEBUG_SHOWLogMessage(DEBUG, "%d file descriptor IO enent not ready", _poll_fd[pos].fd);
#endif}}}void Accepter(void){std::string client_ip;uint16_t client_port;int server_sock = _sock.Accept(client_ip, &client_port);if(server_sock < 0){LogMessage(ERROR, "error: %d, error message: %s", errno, strerror(errno));return ;}// accept successLogMessage(DEBUG, "get a new link, [%s:%d] server sock: %d",\client_ip.c_str(), client_port, server_sock);size_t pos = 1;for(; pos < _nfds; ++pos){if(_poll_fd[pos].fd == FD_NONE)break;}// 走到这里有两种情况// case 1: 数组已满, 将这个新连接关掉if(pos == _nfds){close(server_sock);LogMessage(WARNING, "poll array full");}// case 2: break 跳出循环, 添加即可else{// 将服务套接字添加到这个数组即可_poll_fd[pos].fd = server_sock;_poll_fd[pos].events = POLLIN;}}void Recver(int pos){char buffer[NUM] = {0};// 此时一定不会被阻塞ssize_t real_size = recv(_poll_fd[pos].fd, buffer, sizeof buffer - 1, 0);if(real_size < 0){LogMessage(ERROR, "errno: %d, errno message: %s", errno, strerror(errno));// 1. 关闭这个服务套接字close(_poll_fd[pos].fd);// 2. 将这个位置的数据还原_poll_fd[pos].events = _poll_fd[pos].revents = 0;_poll_fd[pos].fd = FD_NONE;}else if(real_size == 0){ LogMessage(DEBUG, "client close the link [fd : %d], me too...", _poll_fd[pos].fd);// 1. 关闭这个服务套接字close(_poll_fd[pos].fd);// 2. 将这个位置的数据还原_poll_fd[pos].events = _poll_fd[pos].revents = 0;_poll_fd[pos].fd = FD_NONE;}else {// recv successbuffer[real_size - 1] = 0;LogMessage(DEBUG, "[fd : %d] echo$ %s", _poll_fd[pos].fd, buffer);LogMessage(DEBUG, "server get a message of client success");}}void debug_show(void){std::cout << "fd_array[]: ";for(size_t i = 0; i < _nfds; ++i){if(_poll_fd[i].fd == FD_NONE) continue;else std::cout << _poll_fd[i].fd << " ";}std::cout << std::endl;}~PollServer(){// 释放我们动态申请的资源if(_poll_fd)delete[] _poll_fd;}private:Sock _sock;nfds_t _nfds;struct pollfd* _poll_fd;int _timeout;};
}
#endif

5. poll 的总结

poll 的优点:

  • IO效率高:因为 poll 服务器可以一次性等待多个套接字就绪,而IO过程 = 等待事件就绪 + 拷贝数据,而 poll 会将多个套接字的等待时间进行重叠,换言之,在单位时间内,poll 服务器等待的比重是比较低的,因此,它的IO效率就高;
  • 有适合 poll 的应用场景:当有大量的连接,但只有少量连接是活跃的。因为 poll 服务器是单进程的,因此,对于 poll 服务器的维护成本非常低 (不需要维护过多的执行流),哪怕有非常多的连接,poll 服务器的成本也微乎其微,即节省资源。
  • 输入输出参数分离:用户不需要对参数进行重复设定;
  • poll 没有最大文件描述符数量的限制:站在 poll 自身视角,它没有文件描述符的上限,只要服务器的资源足够,能够承载更多的连接,它就可以监测更多的文件描述符;

poll 的缺点:

  • poll 依旧需要遍历操作 (时间复杂度 O(N)): 无论是用户层面,还是内核层面,都需要对数组进行遍历, 特别是连接非常多的情况,此时 poll 就可能会高频的检测到IO事件就绪,进而导致高频的遍历操作,导致效率降低;
  • poll 需要内核到用户的拷贝:确切的说,因为输入输出参数分离,故用户不需要每次重新设定参数 (只需要第一次用户将数据拷贝给内核),而大部分都是内核将数据拷贝给用户,这是少不了的;

poll 最核心的缺点是第一点,即用户还是需要维护一个数组,无论是用户和内核,都需要对这个数组进行遍历操作。

那么 poll 的缺点如何解决呢? 因此我们需要学习 epoll (event poll) ,具体细节在下篇文章 IO多路转接之epoll 。

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

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

相关文章

论文略读:Window Attention is Bugged: How not to Interpolate Position Embeddings

iclr 2024 reviewer 打分 6666 窗口注意力、位置嵌入以及高分辨率微调是现代Transformer X CV 时代的核心概念。论文发现&#xff0c;将这些几乎无处不在的组件简单地结合在一起&#xff0c;可能会对性能产生不利影响问题很简单&#xff1a;在使用窗口注意力时对位置嵌入进行插…

华为再次布局新行业:合作伙伴已超前谋划,该领域将大有可为

华为布局新行业 华为向外界公布了一个重要信息&#xff1a;在过去的三年里&#xff0c;尽管受到美国的制裁&#xff0c;华为仍然成功地完成了超过13000个元器件的国产替代研发&#xff0c;以及4000多块电路板的迭代开发。 不仅在硬件领域取得了显著成就&#xff0c;在软件和生…

oracle 19c数据库W00n进程使用很多PGA内存资源的分析

今天&#xff0c;客户反馈测试环境的数据库PGA资源不足&#xff0c;报错ORA-04036: 实例使用的 PGA 内存超出 PGA_AGGREGATE_LIMIT&#xff1b;分析是多个W00n进程使用大量PGA-触发了BUG&#xff0c;对应解决办法就是打补丁。&#xff08;民间办法就是KILL进程、重启数据库&…

3d视图模型乱了怎么调?---模大狮模型网

在进行3D建模时&#xff0c;有时候您可能会遇到视图模型混乱的情况。这可能是由于模型结构问题、导入导出错误或编辑操作不当等原因造成的。混乱的模型不仅影响工作效率&#xff0c;还可能导致渲染结果不理想。本文将介绍六种有效的方法来调整混乱的3D视图模型&#xff0c;帮助…

【数据可视化包Matplotlib】Matplotlib基本绘图方法

目录 一、Matplotlib绘图的基本流程&#xff08;一&#xff09;最简单的绘图&#xff08;仅指定y的值&#xff09;&#xff08;二&#xff09;更一般的绘图&#xff08;同时指定x和y的值&#xff09;&#xff08;三&#xff09;增加更多的绘图元素 二、布局相关的对象——Figur…

Python 物联网入门指南(四)

原文&#xff1a;zh.annas-archive.org/md5/4fe4273add75ed738e70f3d05e428b06 译者&#xff1a;飞龙 协议&#xff1a;CC BY-NC-SA 4.0 第九章&#xff1a;构建光学字符识别的神经网络模块 本章介绍以下主题&#xff1a; 使用光学字符识别&#xff08;OCR&#xff09;系统 使…

多种方式打开SOLIDWORKS文件

在 SOLIDWORKS 中有多种打开文件的方法。一些最常用的方法包括双击文件资源管理器中的文件或拖放到 SOLIDWORKS 窗口中。当然&#xff0c;还有一种传统的方法&#xff0c;就是在SOLIDWORKS软件上方单击打开。 使用SOLIDWORKS“打开“命令 SOLIDWORKS 中的“打开“命令与任何其…

基于springboot实现在线考试系统设计【项目源码+论文说明】

基于springboot实现在线考试管理系统演示 摘要 随着信息技术在管理上越来越深入而广泛的应用&#xff0c;管理信息系统的实施在技术上已逐步成熟。本文介绍了基于JavaWeb技术的在线考试系统设计与实现的开发全过程。通过分析基于Java Web技术的在线考试系统设计与实现管理的不…

Python不可变序列类型--字符串

🥇作者简介:CSDN内容合伙人、新星计划第三季Python赛道Top1 🔥本文已收录于Python系列专栏: 零基础学Python 💬订阅专栏后可私信博主进入Python学习交流群,进群可领取Python视频教程以及Python相关电子书合集 私信未回可以加V:hacker0327 备注零基础学Python 订阅专…

ObjectMapper的具体介绍与使用

文章目录 声明一、前言二、ObjectMapper与JSONObject比较1、核心主要有三个部分&#xff1a;依赖包不同 2、ObjectMapper使用概述2.1、工程的pom.xml导包信息2.2、创建案例中的测试对象2.3、对象和JSON相互转化2.3.1、测试代码2.3.2、测试结果展示 2.4、集合和JSON像话转化2.4.…

Matlab|电价型负荷需求响应(考虑电价变化)

程序复现来源于《计及需求响应消纳风电的电-热综合能源系统经济调度 》第四章内容。 一、原理 需求响应的基本原理是需求侧根据电力市场价格和电网要求改变其负荷需求以 获取一定的利益回报。其中 PDR 可通过直观的电价变化信号引导用户调节用电方式&#xff0c; 从而达到优…

Qt for Android 开发环境

在搭建环境时开始感觉还挺顺利的&#xff0c;从 Qt 配置的环境里面看并没有什么问题&#xff0c;可真正编译程序的时候发现全是错误。 最开始的时候安装了 JDK21 最新版本&#xff0c;然后根据 JDK21 安装 ndk, build-tools, Platform-Tools 和 Gradle&#xff0c;但是不管这么…

零基础自学Python,啃透这五本书就够了!

选择合适的学习资源 在自学Python的前期&#xff0c;选择一本适合初学者的Python入门书籍或在线教程&#xff0c;从基础开始学习&#xff0c;好的入门书籍或在线教程会按照逻辑顺序组织知识&#xff0c;从基础概念开始&#xff0c;逐步引导你深入学习Python编程语言。这种系统…

如何在深度学习中调用CAME

1、介绍 CAME&#xff1a;一种以置信度为导向的策略&#xff0c;以减少现有内存高效优化器的不稳定性。基于此策略&#xff0c;我们提出CAME同时实现两个目标:传统自适应方法的快速收敛和内存高效方法的低内存使用。大量的实验证明了CAME在各种NLP任务(如BERT和GPT-2训练)中的…

必应bing竞价广告推广开户联系方式?

随着互联网广告市场的日益繁荣与细分&#xff0c;必应Bing作为全球重要的搜索引擎之一&#xff0c;在国内市场也逐渐展现出强大的潜力与吸引力。越来越多的企业开始关注并探索必应Bing搜索广告所带来的巨大商机。其中&#xff0c;云衔科技以其卓越的专业素养和全面的服务体系&a…

stable diffusion--小白学习步骤

1.看一下Unet网络的讲解_哔哩哔哩_bilibili&#xff0c;了解Unet网络 2.看一下【生成式AI】Diffusion Model 原理剖析 (1/4)_哔哩哔哩_bilibili&#xff0c;起码要看前3/6个视频 3.看一下超详细的扩散模型&#xff08;Diffusion Models&#xff09;原理代码 - 知乎 (zhihu.co…

鑫鹿助贷CRM系统:助力助贷行业实现智能商业转型

数字化时代&#xff0c;商业竞争愈发激烈&#xff0c;助贷行业如何把握商机、实现高效管理、打造高回报率的商业模式&#xff0c;成为了助贷行业老板们比较关注的问题&#xff0c;而鑫鹿助贷CRM管理系统&#xff0c;正是这场商业变革中的得力助手&#xff0c;系统功能完善&…

每帧纵享丝滑——ToDesk云电脑、网易云游戏、无影云评测分析及ComfyUI部署

目录 一、前言二、云电脑性能测评分析2.1、基本配置分析2.1.1、处理器方面2.1.2、显卡方面2.1.3、内存与存储方面2.1.4、软件功能方面 2.2、综合跑分评测 三、软件应用实测分析3.1、云电竞测评3.2、AIGC科研测评——ComfyUI部署3.2.1、下载与激活工作台3.2.2、加载模型与体验3.…

AGI的智力有可能在两年内超过人类水平

特斯拉CEO埃隆马斯克近日与挪威银行投资管理基金CEO坦根的访谈中表示&#xff0c;AGI的智力将在两年内可能超过人类智力&#xff0c;在未来五年内&#xff0c;AI的能力很可能超过所有人类。 马斯克透漏&#xff0c;去年人工智能发展过程中的主要制约因素是缺少高性能芯片&#…

基于springboot实现人事管理系统项目【项目源码+论文说明】

基于springboot实现人事管理系统演示 摘要 随着信息技术在管理上越来越深入而广泛的应用&#xff0c;作为学校以及一些培训机构&#xff0c;都在用信息化战术来部署线上学习以及线上考试&#xff0c;可以与线下的考试有机的结合在一起&#xff0c;实现基于vue的人事系统在技术…