【计算机网络】poll | epoll

【计算机网络】poll | epoll第三种小于 0 等待失败返回 1 如 想要等待下标为 1 和 2 的文件描述符 但是下标为 2 的文件描述符根本不存在 就会等待失败

大家好,欢迎来到IT知识分享网。

1. poll

poll函数参数解析

输入 man poll

【计算机网络】poll | epoll

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

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


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


【计算机网络】poll | epoll

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

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


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


【计算机网络】poll | epoll

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

代码解析

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

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


【计算机网络】poll | epoll

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


【计算机网络】poll | epoll

当想要使用 数组当前元素表示对应的文件描述符时,需指向对应的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 解决了文件描述符 有上限的问题

【计算机网络】poll | epoll

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

2. epoll

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

认识接口

epoll_create

输入 man epoll_create

【计算机网络】poll | epoll

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

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

epoll_create 作用:创建出epoll模型


epoll_ctl

输入 man epoll_ctl

【计算机网络】poll | epoll

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

【计算机网络】poll | epoll

EPOLL_CTL_ADD 添加
EPOLL_CTL_MOD 修改
EPOLL_CTL_DEL 删除


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

【计算机网络】poll | epoll

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

epoll_wait

输入 man epoll_wait

【计算机网络】poll | epoll

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


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

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

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


【计算机网络】poll | epoll

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


基本原理

红黑树

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

【计算机网络】poll | epoll

点击查看:红黑树概念


【计算机网络】poll | epoll

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


【计算机网络】poll | epoll

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


【计算机网络】poll | epoll

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


就绪队列

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

【计算机网络】poll | epoll

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

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


【计算机网络】poll | epoll

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


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

【计算机网络】poll | epoll


【计算机网络】poll | epoll

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


【计算机网络】poll | epoll

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


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

工作模式

select poll epoll 基本的情况下:一旦有事件就绪,如果上层不取,底层会一直通知我事件就绪
这种情况,称为 LT 模式
LT—— Level Triggered 水平触发


还有一种 ET 模式
ET—— Edge Triggered 边缘触发
特点为 有效通知只有一次,数据从无到有,从有到多变化的时候,才有第二次通知


epoll默认处于 LT 模式

LT与ET的区别

故事1

【计算机网络】poll | epoll

假设在菜鸟驿站中有几个快递员,如:张三 李四 王五
小王 住在 宿舍楼的五楼,而且在淘宝上买了很多东西

驿站派出张三来 小王所在的宿舍楼 送快递
当张三到了宿舍楼下后,给小王打了电话通知下去取快递,
小王虽然回答回去取,但是因为正在打游戏所以并没有立刻下去取快递

张三等待了一会后,发现小王依旧没有下来,所以就又打了电话
可小王依旧在打游戏,所以只是敷衍说马上下来取


【计算机网络】poll | epoll

就这样墨迹了半天,小王终于下来取快递了
张三手里共有两个属于小王的快递,但小王表示身体不好,只能拿一个,一会再来拿另一个,就上楼了
可是小王上去,又打游戏去了,当张三再次打给小王电话时,又开始了敷衍 就是不下去取剩下的一个快递


【计算机网络】poll | epoll

李四手里也有属于小王的几个快递,只不过李四是先去另一栋楼送的快递
但李四返回时,发现张三还在小王宿舍楼下,李四就把小王的几个快递给了张三

李四又打电话通知小王,新增了几个快递
小王又墨迹了好半天,才叫室友一起下楼把快递全都取走


故事2

【计算机网络】poll | epoll

假设小王 喜欢打游戏,结果成天打,把键盘和鼠标搞坏了
所以小王就在淘宝买了一个键盘和鼠标

驿站派出李四来 小王所在的宿舍楼 送属于小王的两个快递
由于李四是老快递员,当李四到了宿舍楼下后
给小王打电话,并表示只打一次,要是不拿就不管了


【计算机网络】poll | epoll

没有办法,小王只好下楼去取,但是小王身体不行,只能拿走一个快递
可小王并不知道李四手中还有自己的一个快递,同样李四也不会告诉小王这件事情
李四就要下班了


【计算机网络】poll | epoll

李四在路上遇见张三,张三就想让李四把他手里小王的一个快递送一下
于是李四就又去了小王宿舍楼下
又开始通知小王一次,告诉小王 就打着一次电话,不来就走了
小王带着室友下楼来取快递,把快递全部拿走了
(李四刚开始只通知一次,当手中快递变多了时,才进行下一次通知)


张三和李四两人的派发快递的过程中
李四通知效率高(每次只打一个电话通知)
同时 IO效率也高(打一个电话,小王就下来取快递了)
倒逼上层,尽快取走数据

把张三的模式叫做LT模式
把李四的模式叫做ET模式

免责声明:本站所有文章内容,图片,视频等均是来源于用户投稿和互联网及文摘转载整编而成,不代表本站观点,不承担相关法律责任。其著作权各归其原作者或其出版社所有。如发现本站有涉嫌抄袭侵权/违法违规的内容,侵犯到您的权益,请在线联系站长,一经查实,本站将立刻删除。 本文来自网络,若有侵权,请联系删除,如若转载,请注明出处:https://haidsoft.com/158412.html

(0)
上一篇 2025-01-24 15:05
下一篇 2025-01-24 15:10

相关推荐

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

关注微信