【计算机网络】poll | epoll

news2024/11/26 11:29:35

文章目录

  • 1. poll
    • poll函数参数解析
    • 代码解析
      • PollServer代码
    • poll 特点
  • 2. epoll
    • 认识接口
      • epoll_create
      • epoll_ctl
      • epoll_wait
    • 基本原理
      • 红黑树
      • 就绪队列

1. poll

poll函数参数解析

输入 man poll

poll的第一个参数是文件描述符
poll的第二个参数为 等待的多个文件描述符(fd)数字层面 最大的+1

poll函数中的最后一个参数 timeout 是一个 纯输入型参数,单位是毫秒
若 timeout 为-1,则表示永久阻塞,直到文件描述符就绪
若 timeout为0,则表示 非阻塞
若timeout 大于0,则表示 在timeout事件以内 以阻塞等待,超时则进行非阻塞等待


poll的返回值的含义与select 相同
第一种 大于0表示有几个文件描述符 是就绪的
第二种 等于0进入timeout状态 ,即 5s以内没有任何一个文件描述符 就绪
第三种 小于0等待失败 返回-1如:想要等待下标为1 和2的文件描述符,但是下标为2的文件描述符根本不存在,就会等待失败


在pollfd 结构体 中
fd 表示 文件描述符
events: 用户告诉内核,需要关心那些文件描述符上的那些事件
revents :内核会告诉用户,关心的那些文件描述符上的那些事件已经就绪

poll将 输入参数 和输出参数进行分离


poll就有对应的事件
常用的有
POLLIN 表示 有数据可以读
POLLOUT 表示 当前写的时候不会被阻塞


POLLIN 表示第一个比特位为1
POLLOUT 表示 第三个比特位为1

代码解析

主要将第一个初始版本的select代码进行修改

由于poll 自带结构体,内部包含
fd (文件描述符)
events (用户告诉操作系统 那些文件描述符上的事件需要关心)
revents (操作系统告诉用户 关心的那些文件描述符上的事件已经就绪)


此时的fdaaray作为结构体指针,可以通过该指针 去指向 pollfd结构体成员


当想要使用 数组当前元素表示对应的文件描述符时,需指向对应的fd成员

想要表示 (用户告诉操作系统 那些文件描述符上的事件需要关心)
需要通过指针去指向对应的成员 events
想要表示 (操作系统告诉用户 关心的那些文件描述符上的事件已经就绪)
需要通过指针去指向对应的成员 revents

PollServer代码

#include<iostream>
#include<string>
#include<sys/poll.h>
#include<cstring>
#include"Sock.hpp"
#include"Log.hpp"
#include"Err.hpp"
using namespace std;


  const static int gport=8888;    

  const static int N=4096;

  const static short defaultevent=0;

typedef pollfd type_t;

class PollServer
{
 public:
      PollServer(uint16_t port=gport)
      :port_(port),fdarray_(nullptr)
      {}

      void InitServer()//初始化
      {
           listensock_.Socket();//创建套接字
           listensock_.Bind(port_);//绑定
           listensock_.Listen();//设置监听状态
           
           fdarray_=new type_t[N];
           //对fdarray数组进行初始化
           for(int i=0;i<N;i++)
           {
             fdarray_[i].fd= defaultfd;
             fdarray_[i].events= defaultevent;
             fdarray_[i].revents=defaultevent;
           }
      }


     void Accepter()//获取新连接的动作
     {
             //这里再使用accept 就不会阻塞了
             //listen套接字底层一定有就绪的事件 即连接已经到来了
             string clientip;
             uint16_t  clientport;
            int sock=listensock_.Accept(&clientip,&clientport);//获取客户端IP和端口号
            if(sock<0)
            {
              return;
            }
            
            //当得到对应新连接的sock套接字,是不能进行read/recv
            //并不知道sock上的数据是否就绪的
            //所以需要将sock交给select,由select进行管理
            logMessage(Debug,"[%s:%d],sock:%d",clientip.c_str(),clientport,sock );
             //只需把新获取的sock 添加到 数组中
             int pos=1;
             for(;pos<N;pos++)
             {
                if(fdarray_[pos].fd==defaultfd)//说明没有被占用
                {
                   break;
                }
             }
             if(pos>=N)//整个数组中的位置全被占用了
             {
              //由于fdarray_是动态开辟空间的,所以可以动态扩容

              //若扩容失败,则close
                close(sock);
                logMessage(Warning,"sockfd[] array full");
             }
             else //找到了对应的位置
             {
                fdarray_[pos].fd=sock;
                fdarray_[pos].events=POLLIN;
                fdarray_[pos].revents=defaultevent;
             }
     }
     
      void  HandlerEvent()//处理就绪事件
      { 
             for(int i=0;i<N;i++)
             {
              int fd=fdarray_[i].fd;
              int revent= fdarray_[i].revents;

               if( (fd==defaultfd)&&(revent &POLLIN))//读事件就绪
               { 
                  continue;
               }
               //合法fd

               //若套接字为listensock套接字,并且读事件就绪
               if(fd==listensock_.Fd() &&(revent &POLLIN))
               {
                  Accepter();
               }
               //若套接字不是listensock套接字,并且读事件就绪 即普通的读取数据就绪
                else if ((fd != listensock_.Fd()) && (revent &POLLIN)) 
               {
                   char buffer[1024];
                   ssize_t s=recv(fd,buffer,sizeof(buffer)-1,0);
                   //读取不会被阻塞
                   if(s>0)//读取成功
                   {
                     buffer[s-1]=0;
                     cout<<"client# "<<buffer<<endl;

                     //发送回去 也要被select管理
                      string echo=buffer ;
                      echo+= "[select server echo ]";
                      send(fd,echo.c_str(),echo.size(),0);//发送消息 将echo内的数据 交给fd
                   }
                   else 
                   {
                     if(s==0)//读到文件结尾
                     {
                       logMessage(Info,"client quit...,fdarray_[i] -> defaultfd:%d->%d",fd,defaultfd);
                     }
                     else //读取失败 
                     {
                       logMessage(Warning,"recv error,client quit...,fdarray_[i] -> defaultfd:%d->%d",fd,defaultfd);
                     }  
                      close(fd);
                      fdarray_[i].fd=defaultfd;
                      fdarray_[i].events=defaultevent;
                      fdarray_[i].revents=defaultevent;
                   }   
                } 
             } 
      }
      

        void DebugPrint()
       {
         cout<<"fdarray_[]:"<<endl;
         for(int i=0;i<N;i++)
         {
          if(fdarray_[i].fd==defaultfd)
          {
            continue;
          }
          cout<<fdarray_[i].fd<<" ";
         }
         cout<<"\n";
       }

      void Start() //启动
      {
        //在网络中,新连接到来被当作 读事件就绪
        //对应不同的事件就绪,做出不同的动作
        
        fdarray_[0].fd=listensock_.Fd();
        fdarray_[0].events=POLLIN;//数据可读
        while(true) 
        {
          int timeout= -1;//永久阻塞  
            int n= poll(fdarray_,N,timeout);
            //timeout 设为nullptr后,全部为阻塞等待

            switch(n)
            {
              case 0:   //表示没有任何一个文件描述符就绪 
               logMessage(Debug,"timeout,%d: %s",errno,strerror(errno));
               break;

             case -1:  //等待失败 返回-1
              logMessage(Warning,"%d: %s",errno,strerror(errno));
              break;

             default:  //大于0 ,则表示成功 返回有多少文件描述符就绪
                logMessage(Debug,"有一个就绪事件发生了:%d",n);
                 HandlerEvent();//处理就绪事件
                 DebugPrint();//打印数组内容
               break;
            }
        }
      }
       
      ~PollServer()
       {
        listensock_.Close();
        if(fdarray_)
        {
          delete[]fdarray_;
        }
       }

 private:
     uint16_t port_;//端口号
      Sock listensock_;//创建Sock对象
      type_t* fdarray_;//自己定义一个数组,与位图大小相同,来进行已经获得的sock进行管理
};

poll 特点

poll 就相当于在 select 的基础上进行优化
poll自带结构体,只需将读写 异常 放入 events 事件即可

poll 跟 select 一样 也是以数组的形式 传递多个文件描述符,传进去后,需要操作系统继续遍历

  • 每次调用poll,都需要把fd集合从用户态拷贝到内核态,在fd很多时开销会很大
    (每次都需要用户需要告诉内核,那些文件描述符的那些事件需要关心)
  • 每次调用poll,都需要在内核遍历传递过来的所有fd,在fd很多时 开销会很大
  • (每次都需要内核需要告诉用户,关心的文件文件描述符上的那些事件就绪)

poll 解决了文件描述符 有上限的问题

(定义的数组是在堆上开辟的,若空间满了,还可以动态扩容)
select由于定义的是一个固定长度的数组大小,当到达整个数组长度时,就只能打印信息

2. epoll

epoll 是为处理大批句柄而作改进的poll

认识接口

epoll_create

输入 man epoll_create

参数size可以被忽略,但是必须大于0

返回值 :
若返回epoll文件描述符,则表示返回成功
若返回-1,则表示返回失败

epoll_create 作用:创建出epoll模型


epoll_ctl

输入 man epoll_ctl

第一个参数 epfd 为 epoll_create 的返回值
第二个参数 op 表示你想作什么样的操作
一般常见设置为三个值

EPOLL_CTL_ADD 添加
EPOLL_CTL_MOD 修改
EPOLL_CTL_DEL 删除


第三个参数 fd 表示 哪一个文件描述符

最后一个参数 event 表示关心什么事件
events 表示 输入
fd表示 输入时 表示那些文件描述符上的什么样事件要关心
epoll_ctl 作用: 用户告诉内核,帮我关心 增加/修改/删除那个文件描述符上的那一个事件

epoll_wait

输入 man epoll_wait

返回值含义 与select和poll相同
第一种 大于0表示有几个文件描述符 是就绪的
第二种 等于0进入timeout状态 ,即 5s以内没有任何一个文件描述符 就绪
第三种 小于0等待失败 返回-1如:想要等待下标为1 和2的文件描述符,但是下标为2的文件描述符根本不存在,就会等待失败


第一个参数 epfd v 为 epoll_create的返回值
最后一个参数 timeout 与poll中含义相同

第二个参数 events 为 返回的就绪事件
第三个参数 maxevents为 epoll模型的最大个数

epoll_wait作用:内核告诉 用户 那些文件描述符上的那些事件就绪


与poll的宏基本一致
主要使用 EPOLLIN 和 EPOLLOUT
EPOLLIN 表示 有数据可以读
EPOLLOUT 表示 当前写的时候不会被阻塞


基本原理

红黑树

创建epoll时,在底层就会创建一颗红黑树
使用红黑树 使用户告诉操作系统 来关心 增加/修改/删除那个文件描述符上的那一个事件

点击查看:红黑树概念


红黑树的节点假设为 sruct rb_node
内部包含 文件描述符fd 和 对应事件 event


eopll_ctl 本质 为 通过epoll模型来对红黑树操作
向红黑树中新增 删除 修改 某一个节点
而每一个节点 都对应的是文件描述符和对应的事件
epoll_ctl 用来对红黑树 进行增删改 操作


在内核中,一个结构体对象,既可以属于结构A,又可以属于结构B
所以struct rb_node 既可以属于红黑树,又可以属于其他结构


就绪队列

创建epoll时,同时也会创建一个就绪队列

当特定的文件描述符上有对应的事件发生了,就可以将对应已经发生事件的节点 链入就绪队列中
(所以struct rb_node 既可以属于红黑树,又可以属于就绪队列)

就绪队列中只保存已经准备好的文件描述符上的对应事件


作为就绪队列的节点,需要包含文件描述符fd 以及 revent (操作系统告诉用户 关心的文件描述符的那些事件就绪)


epoll_wait 以事件复杂度为O(1)的方式,检测有没有事件就绪 即检测就绪队列是否为空


数据就绪 形成节点放入就绪队列中 ,将红黑树中节点关系 也添加到就绪队列中
这样一个结构体对象就可以既属于红黑树 ,又属于就绪队列了


整体称为 epoll
当调用 epoll_create 时,就是创建epoll模型


epoll避免使用 遍历,而是通过回调函数的方式,将就绪的文件描述符加入 就绪队列中
epoll_wait 返回直接访问 就绪队列 就知道那些文件描述符就绪

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

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

相关文章

Apache Ranger:(一)安装部署

1.Ranger简介 Apache Ranger提供一个集中式安全管理框架, 并解决授权和审计。它可以对Hadoop生态的组件如HDFS、Yarn、Hive、Hbase等进行细粒度的数据访问控制。通过操作Ranger控制台,管理员可以轻松的通过配置策略来控制用户访问权限。 说白了就是管理大多数框架的授权问题。 …

微信小程序抓包

https://github.com/water-kid/WeChatOpenDevTools 抓包工具 第一次安装成功了&#xff0c;公众号能抓&#xff0c;&#xff0c;小程序报错&#xff0c;&#xff0c;卸载后安装不起了 方法二&#xff1a; 将version.dll 放入 微信所在目录 E:\Program Files\Tencent\WeChat\[…

SpringBoot 如何使用 Ehcache 作为缓存

使用Spring Boot Sleuth进行分布式跟踪 在现代分布式应用程序中&#xff0c;跟踪请求和了解应用程序的性能是至关重要的。Spring Boot Sleuth是一个分布式跟踪解决方案&#xff0c;它可以帮助您在分布式系统中跟踪请求并分析性能问题。本文将介绍如何在Spring Boot应用程序中使…

全息投影技术服务公司【盟云全息】收入急剧下降,存在风险

来源&#xff1a;猛兽财经 作者&#xff1a;猛兽财经 全息投影技术服务公司【盟云全息】MicroCloud Hologram(HOLO)的股价表现今年以来异常出色&#xff0c;年初至今已经上涨了334%以上&#xff0c;猛兽财经将在本文中分析MicroCloud Hologram股价上涨的原因&#xff0c;以及它…

Qt QGridLayout和QFormLayout案例分析

QGridLayout和QFormLayout是Qt中常用的布局管理器&#xff0c;可以用于在应用程序中设置控件的位置和大小。 QGridLayout网格布局(栅格布局) QGridLayout是一个网格布局管理器&#xff0c;可以将控件放置在一个二维网格中。在QGridLayout中&#xff0c;控件可以跨越多个行和列…

2023年中国涂料助剂市场发展历程及趋势分析:中高端助剂市场规模将会迎来新的增长[图]

涂料助剂又称油漆辅料&#xff0c;系配制涂料的辅助材料&#xff0c;能改进涂料性能&#xff0c;促进涂膜形成。种类很多&#xff0c;包括催干剂、增韧剂、乳化剂、增稠剂、颜料分散剂、消泡剂、流平剂、抗结皮剂、消光剂、光稳定剂、防霉剂、抗静电剂等&#xff0c;其中用量最…

2023年中国临床质谱检测行业市场规模及行业竞争格局分析[图]

临床质谱检测是指质谱技术在临床检验中的应用&#xff0c;主要涉及临床生化检验、临床免疫学检验、临床微生物检验以及临床分子生物诊断等多方面&#xff0c;质谱诊断比传统诊断技术更具灵敏性、特异性和准确性&#xff0c;且具有高通量、高效率和低成本的优势。临床质谱技术可…

Allegro 17.2如何直接更新元件封装?

想必很多从事电子设计的小伙伴&#xff0c;都有这样的经历&#xff1a;有些时候原理图和PCB设计是由不同的工程师负责&#xff0c;然后偶尔需要在没有原理图的情况下直接对PCB作品进行操作&#xff0c;如更新元件封装等操作&#xff0c;这种环节不仅费时费力&#xff0c;效率贼…

Table ‘mysql.proc‘ doesn‘t exist

使用workbench 同步model 報錯 "Table ‘mysql.proc‘ doesn‘t exist" 爲什麽會出現這個錯誤&#xff1f; 原因&#xff1a;误删了mysql数据库 解决办法如下&#xff1a; 1、在服务列表里找到mysql&#xff0c;停止服务 2、把mysql文件夹下的data文件夹备份&…

微信小程序--》从模块小程序项目案例23.10.09

配置导航栏 导航栏是小程序的门户&#xff0c;用户进来第一眼看到的便是导航栏&#xff0c;其起着对当前小程序主题的概括。而我们 新建的小程序 时&#xff0c;第一步变开始配置导航栏。如下&#xff1a; 配置tabBar 因为配置tabBar需要借助字体图标&#xff0c;我这里平常喜…

uniapp快速入门系列(3)- CSS技巧与布局

章节三&#xff1a;CSS技巧与布局 1. uniapp中的样式编写2. 常见布局技巧与实例解析2.1 水平居中布局2.2 垂直居中布局2.3 等高布局2.4 响应式布局 3. CSS动画与过渡效果 在uniapp中&#xff0c;我们使用CSS来设置页面的样式和布局。本章将介绍一些在uniapp中常用的CSS技巧和布…

区块链技术在金融领域的应用场景

区块链技术在金融领域具有广泛的应用场景&#xff0c;它可以提供更安全、透明、高效和可信的金融服务。以下是一些区块链金融的主要应用场景&#xff0c;希望对大家有所帮助。北京木奇移动技术有限公司&#xff0c;专业的软件外包开发公司&#xff0c;欢迎交流合作。 1.数字货币…

多媒体播放软件 Infuse mac中文特点介绍

Infuse mac是一款多媒体播放器应用&#xff0c;它支持播放多种格式的视频文件、音频文件和图片文件&#xff0c;并且可以通过AIrPlay将媒体内容投放到其他设备上。Infuse还支持在线视频流媒体播放和本地网络共享&#xff0c;用户可以通过它来访问家庭网络上的媒体文件。 Infuse…

【elasticsearch】elasticsearch8.0.1使用rpm包安装并启用TLS

背景 公司的业务需要在加密的情况下使用&#xff0c;为此&#xff0c;研究测试了一下es8是如何启用TLS的。以下是测试使用过程。 x-pack了解 在 Elasticsearch 7.11.0 版本及更高版本中&#xff0c;X-Pack 功能在默认情况下已经整合到 Elastic Stack 的各个组件中&#xff0…

黑盒测试方法:原理+实战

目录 一、如何设计测试用例 二、黑盒测试常用方法 1、基于需求进行测试用例的设计 2、等价类 3、边界值 4、判定表分析法&#xff08;因果分析法&#xff09; 5、正交表 6、场景设计法 三、案例补充 1、使用Fiddler模拟弱网 2、针对一个接口该如何测试 一、如何设计测试…

低成本开发一款苹果ios安卓apk双端的APP应用制作用什么语言研发浅谈一些低代码平台

这不是很懂代码的同学们还是比较多的吧&#xff0c;公司想开发一款app不想成本增加太大&#xff0c;不知道怎么找团队做事情&#xff0c;找什么语言的呢&#xff1f;都是最后能组成一个app但是不知道从哪里下手可以看看兄弟我的这篇文章哈&#xff0c;虽然不是很厉害&#xff0…

区块链技术-比特币数据结构

背景 随着近几年区块链技术的迅速发展&#xff0c;越来越多的行业正在将区块链技术应用到实际中去。例如&#xff0c;金融、物流、交易所等行业都开始尝试使用区块链技术来替代传统技术。伴随着区块链迅速发展的期间&#xff0c;诞生了比特币&#xff08;BTC&#xff09;、以太…

深度学习DAY1:神经网络NN;二元分类

深度学习笔记 DAY1 深度学习基本知识 1.神经网络 1.1 单一神经元 所有神经元将房屋大小size作为输入x,计算线性方程&#xff0c;结果取max&#xff08;0&#xff0c;y&#xff09;,输出预测房价y ReLU函数&#xff08;线性整流函数&#xff09;–max&#xff08;0&#xf…

二叉树的层序遍历 --力扣

&#x1f388;个人主页:&#x1f388; :✨✨✨初阶牛✨✨✨ &#x1f43b;强烈推荐优质专栏: &#x1f354;&#x1f35f;&#x1f32f;C的世界(持续更新中) &#x1f43b;推荐专栏1: &#x1f354;&#x1f35f;&#x1f32f;C语言初阶 &#x1f43b;推荐专栏2: &#x1f354;…

vue3中动态设置ref,如表格中使用级联选择器

需求&#xff1a;vue3中有一个表格&#xff0c;表格里是表单项&#xff0c;用户可以输入或选择&#xff0c;有一项是一个级联选择器&#xff0c;需要使用到ref&#xff0c;然后通过getCheckedNodes()方法获取到这一项选择的数据。 数据源是一个级联选择器&#xff0c;如果这个表…