【网络】高级IO(select||poll||epoll)

基础引入

  1. 应用层read&&write的时候,把数据从用户层写到操作系统,本质是拷贝函数。
  2. read时候如果缓冲区没有数据,那么就要等待数据才能读取,因此IO=等待+拷贝,要进行拷贝,必须等待读写事件就绪。
  3. 高效IO指单位时间内,IO过程中,等待的比重小,IO效率高。

五种IO模型

  • 同步阻塞IO(Blocking IO):这是一种传统的IO模型。在这种模型中,用户空间线程是主动发起IO请求的一方,而内核空间是被动接受方。用户空间程序需要等待内核IO操作彻底完成后,才返回到用户空间执行用户的操作。
  • 同步非阻塞IO(Non-blocking IO):在这种模型中,用户程序不需要等待内核IO操作完成后,内核会立即返回给用户一个状态值。这样,用户空间无需等到内核IO操作彻底完成,可以立即返回用户空间执行用户的操作,处于非阻塞的状态。
  • IO多路复用(IO Multiplexing):这种模型有时也称为异步阻塞IO。它是经典的Reactor设计模式,允许一个线程同时处理多个IO请求。这种模型可以提高系统的吞吐量,因为它减少了线程的上下文切换次数。
  • 异步IO(Asynchronous IO):异步IO是一种更高级的IO模型,它将IO操作与用户请求分离开来。当用户发出IO请求时,系统会立即返回一个结果(通常是一个表示操作正在进行的标识符),而不会等待IO操作真正完成。当IO操作完成后,系统会通过某种方式(如回调函数)通知用户程序。
  • 信号驱动IO(Signal-Driven I/O):这种模型允许用户进程收到一个信号(通常是SIGIO)来通知其某个IO操作已经完成。在信号驱动IO模型中,当用户线程发起一个IO请求时,它不需要等待这个请求完成,而是可以继续执行其他任务。当内核完成IO请求时,它会发送一个信号给用户线程,通知它IO操作已经完成。

select

函数解析

int select(int nfds, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, struct timeval *timeout);
  • nfds:需要监视的文件描述符中,最大的文件描述符值+1。
  • readfds:输入输出型参数,调用时用户告知内核需要监视哪些文件描述符的读事件是否就绪,返回时内核告知用户哪些文件描述符的读事件已经就绪。
  • writefds:输入输出型参数,调用时用户告知内核需要监视哪些文件描述符的写事件是否就绪,返回时内核告知用户哪些文件描述符的写事件已经就绪。
  • exceptfds:输入输出型参数,调用时用户告知内核需要监视哪些文件描述符的异常事件是否就绪,返回时内核告知用户哪些文件描述符的异常事件已经就绪。
  • timeout:输入输出型参数,调用时由用户设置select的等待时间,返回时表示timeout的剩余时间。

select函数运行时将rfds(bitmap)拷贝到内核态来判断事件是否就绪,如果没数据就阻塞等待,当事件就绪,会将rfds置位(可能有多个位),变为输出型参数,再进行后续逻辑

实现一个select_server

#pragma once#include<iostream>
#include<sys/select.h>
#include<sys/time.h>
#include<unistd.h>
#include"Socket.hpp"static const uint16_t defaultport=8080;
static const int fd_num_max=(sizeof(fd_set)*8);
int defaultfd=-1;class SelectServer
{
public:SelectServer(uint16_t port=defaultport):_port(port){for(int i=0;i<fd_num_max;i++){fd_array[i]=defaultfd;}}bool Init(){_listensock.Sock();_listensock.Bind(_port);_listensock.Listen();return true;}// void HandlerEvent(fd_set& fds)// {//     for (int i = 0; i < fd_num_max; i++)//     {//         int fd=fd_array[i];//         if(fd==defaultfd)//             continue;//         if (FD_ISSET(fd, &fds))//判断当前fd是否在fd集合里,如果在表明事件就绪//         {//             // 如果listensock在fds位图中,表明链接就绪,下面的逻辑用于获取新链接加入fd_array中//             if (_listensock.Fd() == fd)//             {//                 std::string clientip;//                 uint16_t clientport = 0;//                 int sock = _listensock.Accept(&clientip, &clientport); // 不会阻塞在这里//                 if (sock < 0)//                     continue;//                 lg(Info, "accept success,%s,%d,sockfd:%d", clientip.c_str(), clientport, _listensock.Fd());//                 // sock->fd_array[]//                 int pos = 1;//                 for (; pos < fd_num_max; pos++)//                 {//                     if (fd_array[pos] != defaultfd)//                         continue;//                     else//                         break;//                 }//                 if (pos == fd_num_max)//                 {//                     lg(Warning, "server is full,close %d now", sock);//                     close(sock);//                 }//                 else//                 {//                     fd_array[pos] = sock;//                     //TODO//                 }//             }//             else//如果不是listensock,那么就是读事件就绪的文件描述符//             {//                 char buffer[1024];//                 ssize_t n=read(fd,buffer,sizeof buffer-1);//?bug//                 if(n>0)//                 {//                     buffer[n]=0;//                     std::cout<<"get a message: "<<buffer<<std::endl;//                 }//                 else if(n==0)//                 {//                     lg(Info,"clinet quit,close fd is: ",fd);//                     close(fd);//                     fd_array[i]=defaultfd;//                 }//             }//         }//     }// }void Accepter(){// 我们的连接事件就绪了std::string clientip;uint16_t clientport = 0;int sock = _listensock.Accept(&clientip, &clientport); // 会不会阻塞在这里?不会if (sock < 0) return;lg(Info, "accept success, %s: %d, sock fd: %d", clientip.c_str(), clientport, sock);// sock -> fd_array[]int pos = 1;for (; pos < fd_num_max; pos++) // 第二个循环{if (fd_array[pos] != defaultfd)continue;elsebreak;}if (pos == fd_num_max){lg(Warning, "server is full, close %d now!", sock);close(sock);}else{fd_array[pos] = sock;//PrintFd();// TODO}}void Recver(int fd, int pos){// demochar buffer[1024];ssize_t n = read(fd, buffer, sizeof(buffer) - 1); // bug?if (n > 0){buffer[n] = 0;std::cout << "get a messge: " << buffer << std::endl;}else if (n == 0){lg(Info, "client quit, me too, close fd is : %d", fd);close(fd);fd_array[pos] = defaultfd; // 这里本质是从select中移除}else{lg(Warning, "recv error: fd is : %d", fd);close(fd);fd_array[pos] = defaultfd; // 这里本质是从select中移除}}void Dispatcher(fd_set &rfds){for (int i = 0; i < fd_num_max; i++) // 这是第三个循环{int fd = fd_array[i];if (fd == defaultfd)continue;if (FD_ISSET(fd, &rfds)){if (fd == _listensock.Fd()){Accepter(); // 连接管理器}else // non listenfd{Recver(fd, i);}}}}void Start(){int listensock=_listensock.Fd();fd_array[0]=listensock;while(true){fd_set rfds;FD_ZERO(&rfds);int maxfd=fd_array[0];for(int i=0;i<fd_num_max;i++){if(fd_array[i]==defaultfd)continue;FD_SET(fd_array[i],&rfds);if(maxfd<fd_array[i]){maxfd=fd_array[i];lg(Info,"max fd update,maxfd is:%d",maxfd);}}//sleep(1);struct timeval timeout={0,0};//如果事件就绪,上层不处理,select会一直通知//select告诉你就绪,接下来的一次读取,不会被阻塞int n=select(maxfd+1,&rfds,nullptr,nullptr,nullptr);switch(n){case 0://std::cout<<"time out,timeout: "<<timeout.tv_sec<<std::endl;break;case -1:std::cerr<<"select error"<<std::endl;break;default://有事件就绪了std::cout<<"get a new link"<<std::endl;Dispatcher(rfds);break;}}}~SelectServer(){_listensock.Close();}
private:Socket _listensock;uint16_t _port;int fd_array[fd_num_max];
};

缺点

fd有上限,输入输出型参数比较多,数据拷贝频率比较高,每次都要重置fd_set(不能重用),管理第三方数组的fd需要用户层多次遍历较繁杂,用户态到内核态数据拷贝的开销

poll

函数

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

struct pollfd

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

简易实现

pollfd pollfds[5];Socket lissock;lissock.Sock();lissock.Bind(8080);lissock.Listen();for(int i=0;i<5;i++){std::string clientip;uint16_t clientport;pollfds[i].fd=lissock.Accept(&clientip,&clientport);pollfds[i].events=POLLIN;}sleep(1);while(true){poll(pollfds,5,3000);for(int i=0;i<5;i++){if(pollfds[i].revents&POLLIN){char buffer[1024];read(pollfds[i].fd,buffer,sizeof buffer-1);pollfds[i].revents=0;}}}

epoll

底层原理

  • epoll_create先在文件系统中创建epoll的struct file,并分配epfd给用户,同时在内核中创建RBTree用来存储以后epoll_ctr传来的socket,监听并维护这些fd,此外还要建立一个list用于存储就绪事件。
  • 当epoll_wait调用时,仅仅观察这个list链表里有没有数据即可。有数据就返回,没有数据就sleep,等到timeout时间到后即使链表没数据也返回。所以,epoll_wait非常高效。
  • list链表的维护:当我们执行epoll_ctl时,除了把socket放到epoll文件系统里file对象epoll_event维护的红黑树上之外,还会给内核中断处理程序注册一个回调函数,告诉内核,如果这个句柄的中断到了,就把它放到准备就绪list链表里。所以,当一个socket上有数据到了,内核在把网卡上的数据copy到内核中后就来把socket插入到就绪链表里了。
  • epoll相比于select并不是在所有情况下都要高效,例如在如果有少于1024个文件描述符监听,且大多数socket都是出于活跃繁忙的状态,这种情况下,select要比epoll更为高效,因为epoll会有更多次的系统调用,用户态和内核态会有更加频繁的切换。

优势

1.检测就绪O(1),获取就绪O(n)
2.fd,event没有上限
3.wait返回值n,表示有几个fd就绪了,且就绪事件是连续的,拷贝到events数组头部n个位置。

LT和ET

Level Trigger

  • 只要底层数据就绪就会一直通知用户
  • 由于在LT工作模式下,只要底层有事件就绪就会一直通知用户,因此当epoll检测到底层读事件就绪时,可以不立即进行处理,或者只处理一部分,因为只要底层数据没有处理完,下一次epoll还会通知用户事件就绪。
  • select和poll其实就是工作是LT模式下的。
  • 支持阻塞读写和非阻塞读写。

Edge Trigger

  • 只有底层就绪事件数量由无到有或由有到多发生变化的时候,epoll才会通知用户.
  • 由于在ET工作模式下,只有底层就绪事件无到有或由有到多发生变化的时候才会通知用户,因此当epoll检测到底层读事件就绪时,必须立即进行处理,而且必须全部处理完毕,因为有可能此后底层再也没有事件就绪,那么epoll就再也不会通知用户进行事件处理,此时没有处理完的数据就相当于丢失了。
  • ET工作模式下epoll通知用户的次数一般比LT少,因此ET的性能一般比LT性能更高,Nginx就是默认采用ET模式使用epoll的。
  • 只支持非阻塞的读写,当底层读事件就绪时,循环调用recv函数进行读取,直到某次调用recv读取时,实际读取到的字节数小于期望读取的字节数,则说明本次底层数据已经读取完毕了.
  • 但有可能最后一次调用recv读取时,刚好实际读取的字节数和期望读取的字节数相等,但此时底层数据也恰好读取完毕了,如果我们再调用recv函数进行读取,那么recv就会因为底层没有数据而被阻塞住。
  • 而这里的阻塞是非常严重的,就比如我们这里写的服务器都是单进程的服务器,如果recv被阻塞住,并且此后该数据再也不就绪,那么就相当于我们的服务器挂掉了,因此在ET工作模式下循环调用recv函数进行读取时,必须将对应的文件描述符设置为非阻塞状态。

设置文件描述符为非阻塞

#include<unistd.h>
#include<fcntl.h>
#include<cstdlib>
void SetNonBlock(int sock)
{int flag=fcntl(sock,F_GETFL);if(flag<0)exit(1);fcntl(sock,F_SETFL,flag|O_NONBLOCK); 
}

对比

ET的通知效率更高,ET的IO效率更高。
比如listenfd,接受缓冲区 可能存放多个客户端连接请求的信息,这时候要使用水平触发(LT),因为accept每次只能处理一个,需要多次触发。如果用边沿触发(ET)可能会漏掉一些连接。

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

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

相关文章

成长之路Flutter中的TextField组件

TextField组件本身具备多种属性&#xff0c;支持很多参数设置来实现不同样式效果。 TextField组件可直接上手使用&#xff0c;但默认样式和输入规则并不一定是需求开发中想要的&#xff08;实话说默认样式并不好看&#xff09;。下面就通过Flutter TextField组件属性介绍来自定…

Android15 Beta更新速览

Android15 Beta更新速览 前台服务变更 前台服务使应用保持活动状态&#xff0c;以便它们可以执行关键且对用户可见的操作&#xff0c;通常以牺牲电池寿命为代价。在 Android 15 Beta 2 中&#xff0c;dataSync 和 mediaProcessing 前台服务类型现在具有约 6 小时的超时时间&a…

青春旅行家:大学生旅游创业的新星

在青春的岁月里&#xff0c;我们怀揣着梦想&#xff0c;渴望探索世界的每一个角落。对于普通高校的大学生而言&#xff0c;毕业季不仅是人生中的一次重要转折&#xff0c;更是实现梦想、放飞自我的绝佳时机。在这个特殊的时刻&#xff0c;一群年轻的大学生创业者凭借他们的智慧…

内网安全--隧道技术-CS上线本地

免责声明:本文仅做技术交流与学习...请勿非法搞破坏... ---隧道技术:硬刚网络协议,(你不让我走我偏走!) 解决不出网协议上线的问题&#xff08;利用出网协议进行封装出网&#xff09; 代理协议&#xff1a; SOCKS4/5 代理软件&#xff1a; SocksCap Proxifier ProxyChains(…

YOLOv8改进 | 融合模块 | 用Resblock+CBAM卷积替换Conv【轻量化网络】

💡💡💡本专栏所有程序均经过测试,可成功执行💡💡💡 在目标检测领域内,尽管YOLO系列的算法傲视群雄,但在某些方面仍然存在改进的空间。在YOLOv8提取特征的时候,由于卷积的缘故,会导致很多信息的丢失。而凯明大神的神作resnet可以减少信息的丢失。本文给大家带…

磁盘管理以及文件系统08

1、为什么要对磁盘进行分区&#xff1f; 业务层面&#xff1a;为满足一定的需求所是做的特定操作。 2、硬盘是什么&#xff0c;以及硬盘的作用 硬盘&#xff1a;计算机的存储设备&#xff0c;一个或者多个带磁性的盘组成的&#xff0c;可以在盘片上进行数据的读写。硬盘的最…

LVGL圆弧、线条、图片、色环、按钮矩阵、文本区域、键盘部件

目录 LVGL圆弧部件 LVGL线条部件 LVGL图片部件 LVGL色环部件 LVGL按钮矩阵部件 LVGL文本区域部件 LVGL键盘部件 LVGL圆弧部件 圆弧部件以弧形滑动的形式来调节、显示某个参数的值。 圆弧部件组成部分&#xff1a; 背景弧&#xff08;LV_PART_MAIN&#xff09; 前景弧&am…

Spring AOP 切面按照一定规则切片并行查询Mapper并返回

需求&#xff1a; 有时候我们在查询mapper层时&#xff0c;有时候可能由于入参数据过大或者查询的范围较大&#xff0c;导致查询性能较慢&#xff0c;此时 我们需要将原本的查询按照一定规则将查询范围进行切面&#xff0c;然后分片查询&#xff0c;最后将查询结果进行组装合并…

MySQL入门学习-查询进阶.CASE

CASE 表达式是一种在 SQL 中用于进行条件判断和分支执行的功能。它可以根据不同的条件返回不同的结果&#xff0c;类似于编程语言中的 if-else 语句。 一、CASE 表达式有两种主要形式&#xff1a;简单 CASE 表达式和搜索 CASE 表达式。 1、简单 CASE 表达式&#xff1a; CAS…

浏览器提示网站不安全怎么办?有什么解决办法吗?

当你在浏览器中访问一个网站时&#xff0c;如果看到提示说该网站不安全&#xff0c;这通常是由于网站没有使用SSL证书或者SSL证书存在问题。SSL证书在这里扮演着非常关键的角色&#xff0c;下面我会详细解释它的作用以及如何解决这类不安全提示。 SSL证书的作用&#xff1a; 1…

tkinter实现窗口嵌入桌面

在桌面插件例如日历&#xff0c;便签或桌面宠物等等应用&#xff0c;通常希望能够将软件的窗口钉在桌面上&#xff0c;同时又不影响打开的其他窗口&#xff08;即不是置顶&#xff0c;而是常驻与桌面&#xff0c;即使用wind也不会将其窗口关闭&#xff09;。许多桌面美化类软件…

【CTF Web】CTFShow web11 Writeup(RCE+PHP+代码审计)

web11 1 阿呆听完自己菜死了&#xff0c;自己呆了。决定修好漏洞&#xff0c;绝对不能让自己再菜死了。 解法 可知 flag 在 config.php。 <?php # flag in config.php include("config.php"); if(isset($_GET[c])){$c $_GET[c];if(!preg_match("/system…

Vue3学习-组件之各种传参方式

props // 父界面传子界面 <childrenModu name"123" :getName"getName" /> // 父界面接收子界面数据 function getName(val:string){console.log(val) } // 子界面触发父界面函数 <button click"getName(11)"></button> // 子…

WorldSpace下的合批策略与ScreenSpace有什么区别

1&#xff09;WorldSpace下的合批策略与ScreenSpace有什么区别 2&#xff09;在iOS上用Metal取代OpenGL的多么 3&#xff09;在动画蓝图中将两个或多个动画同时融合到同一个网格 4&#xff09;Mipmap如何限定层级 这是第387篇UWA技术知识分享的推送&#xff0c;精选了UWA社区的…

java面对对象编程-多态

介绍 方法的多态 多态是在继承&#xff0c;重载&#xff0c;重写的基础上实现的 我们可以看看这个代码 package b;public class main_ {public static void main(String[] args) { // graduate granew graduate(); // gra.cry();//这个时候&#xff0c;子类的cry方法就重写…

安全面试中的一个基础问题:你如何在数据库中存储密码?

3分钟讲解。 上周的面试故事 职位&#xff1a;初级安全工程师&#xff0c;刚毕业。 开始面试。 我&#xff1a;“这里你提到对数据安全有很好的理解。你能举例说明哪些方面的数据安全吗&#xff1f;” A&#xff1a;“当然。例如&#xff0c;当我们构建一个系统时&#xff0c;会…

结合Django和Vue.js构建现代Web应用

文章目录 1. 创建Django项目2. 配置Django后端3. 创建Vue.js前端4. 连接Django和Vue.js5. 构建和部署 在现代Web开发中&#xff0c;结合后端框架和前端框架是非常常见的&#xff0c;其中Django作为一种流行的Python后端框架&#xff0c;而Vue.js则是一种灵活强大的前端框架。本…

Spring Boot 中使用 Redis 和 Lua 脚本实现一个延时队列

效率工具 推荐一个程序员的常用工具网站&#xff0c;效率加倍嘎嘎好用&#xff1a;程序员常用工具 云服务器 云服务器限时免费领&#xff1a;轻量服务器2核4G腾讯云&#xff1a;2核2G4M云服务器新老同享99元/年&#xff0c;续费同价阿里云&#xff1a;2核2G3M的ECS服务器只需99…

仿冒、钓鱼、入侵……警惕邮件安全这些“坑”

为了保证用户对电子邮箱系统的安全使用&#xff0c;保证个人的隐私和财产的安全&#xff0c;我们呼吁每个人都要加强自己的网络安全意识&#xff0c;在对电子邮件进行处理的时候&#xff0c;要对钓鱼邮件进行认真的识别&#xff0c;同时还需要设定一个客户的密码来保证你的邮箱…

【Unity实战】Mirror/UNET中SyncVar和SyncList需要注意的点

SyncVar和SyncList在Unity开发中喜闻乐见&#xff0c;常用于脚本中字段的同步。 但也时常会出现修改了但是没同步的问题。 故本人根据过往踩的坑进行了以下总结&#xff1a; 1. 尽量不要用它进行类的同步 在Unity中&#xff0c;[SyncVar] 特性通常用于同步Unity网络游戏中基…