高级IO--select

高级IO:

IO概念 / 类型 / 处理流程 / 多路转接IO的实现模型

四种典型IO:

阻塞IO, 非阻塞IO, 信号驱动IO,异步IO

IO过程:

发起IO调用,等待IO条件就绪, 进行数据拷贝

 阻塞IO:发起IO调用,若IO就绪条件不满足,则一直等待

优点:流程简单,一个IO完成后进行下一个
缺点:无法充分利用资源,效率较为低下

 非阻塞IO:发起IO条件,若IO就绪条件不满足在,则报错返回

优点:相较于阻塞IO,对资源利用较为充分,可以利用就绪等待时间干点其他事情或者发起另一个IO调用
缺点:相较于阻塞IO,流程稍微复杂,IO调用需要循环发起(总不能因为当前没有就绪就不进行IO),IO不够实时

 信号驱动IO:定义IO就绪信号处理方式,在处理方式中进行IO请求,进程可以一直干其他事情,等到收到IO就绪信号的时候,会打断进程当前操作去处理IO

优点:相较于非阻塞IO,更加实时,资源利用更加充分
缺点:流程更加复杂,需要定义信号处理,既有主控流程又有信号处理流程,涉及到信号是否可靠的问题

 异步IO:IO顺序不确定,IO过程由系统完成,不自己进行。
  定义IO完成信号处理方式自定义,发起异步IO调用,告诉系统要完成什么功能,剩下的IO功能完全由系统完成,完成后通知信号通知进程

优点:对资源利用最为充分,以最高效率进行任务处理
缺点:资源消耗比较高,流程最为复杂

阻塞与非阻塞的区别:常用于讨论调用函数是否阻塞,表示这个函数无法立即完成功能时,是否立即报错返回

阻塞:为了完成一个功能,发起调用,若不具备完成功能的条件,则调用一直等待
非阻塞:为了完成一个功能,发起调用,若不具备完成功能的条件,则报错返回

同步与异步的区别:通常用于讨论功能的完成方式,表示一个功能是否是顺序化且由自己来完成的

同步:功能完成的流程通常是顺序化的,并且是进程自身完成的。
异步:功能完成的流程通常是不确定的,并且功能不是由进程自身完成的,由系统完成的。

异步的种类:异步阻塞 ----等着别人完成功能 / 异步非阻塞----不等待别人完成功能

同步好还是异步好:

同步流程简单,同一时间占用资源少
异步处理效率高,同一时间占用资源多

 多路转接IO:主要用于进行了大量的IO就绪时间监控,能够让我们的进程只针对就绪的IO进行IO操作

就绪事件:IO的就绪
 可读事件:一个描述符当前是否有数据可读
 可写事件:一个描述符当前是否可以写入数据
 异常事件:一个描述符是否发送了某些异常

只对就绪的描述符进行IO操作有什么好处?----避免阻塞,并且提高效率

1.在默认的socket中,例如tcp服务端只能于一个客户端的socket通信一次,因为我们不知道哪个为客户端新建的socket有数据到来或者监听socket有新连接,有可能就会对没有新连接到来的监听socket进行accept操作而阻塞或者对没有数据到来的普通socket进行recv阻塞
2.在tcp服务端中,将所有的socket设置为非阻塞,若没有数据到来,则立即报错返回,进行下一个描述符的操作,这种操作中,有一个不好的地方,对没有就绪时间的描述符进行了操作,降低了处理效率

如何实现多路转接IO:
 操作系统提供了三种模型:select模型,poll模型,epoll模型

select模型:

使用流程,接口介绍以及原理理解:

1.用户定义自己想要监控的事件的描述符集合(就绪IO事件有三种,但并不是每一个描述符都要监控所有事件),例如定义可读事件的描述符集合,将哪些描述符添加到这个集合中,就表示要对这个描述符监控可读事件,因此想要监控什么事件就定义什么集合

1.定义指定事件的集合 / 2.初始化集合 / 3.将需要监控这个事件的描述符添加到这个集合中
集合:fd_set结构体,结构体中只有一个成员,就是一个数组----作为位图使用。
向这个集合中添加一个描述符,描述符就是一个数字,添加描述符其实就是这个数字对应的bit置1,表示添加到集合中。
这个数组中有多少个bit或者说select最多能监控多少描述符,取决于_FD_SELECT,默认是1024.
void FD_ZERO(fd_set* set); -----清空指定描述符集合;
void FD_SET(int fd, fd_set* set);----将fd描述符添加到set集合中
2.发起调用,将集合中数据拷贝到内核中进行监控,监控采用轮询遍历判断方式进行。
int select(int ndfs, fd_set* readfds, fd_set* writefds, fd_set* exceptfds, struct timeval* timeout);
ndfs:所有集合中最大的那个描述符数值+1,为了减少内核中遍历次数
readfds:可读事件集合
writefds:可写事件集合
exceptfds:异常事件集合
timeout: select是一个默认阻塞操作(struct timeval{ tv_sec; // 秒 tv_usec //微秒), 若timeout = NULL 表示永久阻塞,直到有描述符就绪在返回,若timeout中的数据为0,则表示非阻塞,没有就绪则立即返回,若timeout有数据,若指定时间内没有描述符就绪则超时报错返回
返回值 : 返回值小于0,表示监控出错,返回值等于0,表示没有描述符就绪,返回值大于0表示就绪的描述符个数
3. select调用返回后,进程遍历了哪些描述符还在集合中,就可以知道哪些描述符就绪了哪些事件,进而进行对应操作。
其他操作:void FD_CLR(int fd, fd_set* set);----从set集合中移除fd描述符;
     int FD_ISSET(int fd, fd_set* set);----判断fd描述符是否在集合中

 1 #include <stdio.h>
 2 #include<unistd.h>
 3 #include<stdlib.h>
 4 #include<sys/select.h>
 5 
 6 int main(int argc, char* argv){
 7     struct timeval tv;
 8     while(1){
 9         //select(最大文件描述符+1, 可读事件集合, 可写事件集合, 异常事件集合, 超时等待事件);
 10         fd_set set;
 11         FD_ZERO(&set);
 12         FD_SET(0, &set);
 13         tv.tv_sec = 3;
 14         tv.tv_usec = 0;
 15         printf("Start monitoring\n");
 16         int ret = select(0+1, &set, NULL, NULL, &tv);//select返回时会删除集合中没有就绪的描述符
 17         if(ret < 0){
 18             perror("select error");
 19             return -1;
 20         }
 21         else if(ret == 0){                                                          
 22             perror("monitoring timeout");         
 23             continue;                
 24         }        
 25         printf("Descriptor ready or timeout waiting\n");
 26         if(FD_ISSET(0, &set)){                                                                                                                                                       
 27             printf("Start reading data\n");
 28             char buf[1024] = {0};     
 29             int ret = read(0, buf, 1023);
 30             if(ret < 0){  31                 perror("read error\n");                          
 32                 return -1;                                     
 33             }                                           
 34             printf("buf:%s\n", buf);
 35         }                                             
 36     }                                       
 37     return 0;                                                                                                  
 38 }    

封装一个select类

#include <cstdio>
#include <vector>
#include <sys/select.h>
#include "tcpsocket.hpp"

#define MAX_TIMEOUT 3000
class Select{
    public:
	        Select():_maxfd(-1){
            FD_ZERO(&_rfds);//初始化清空集合
        }//初始化操作
        bool Add(TcpSocket &sock) {
            int fd = sock.GetFd();
            FD_SET(fd, &_rfds);
            _maxfd = _maxfd > fd ? _maxfd : fd;//每次修改集合都要重新判断最大描述符
            return true;
        }//将sock中的描述符添加到保存集合中
        bool Del(TcpSocket &sock) {
            int fd = sock.GetFd();
            FD_CLR(fd, &_rfds);
            for (int i = _maxfd; i >= 0; i--) {
                if (FD_ISSET(i, &_rfds)) {//移除之后。从后往前判断第一个还在集合中的就是最大的
                    _maxfd = i;
                    break;
                }
            }
            return true;
        }//从保存集合中移除这个描述符,不再监控这个描述符
		//int select(int ndfs, fd_set* readfds, fd_set* writefds, fd_set* exceptfds, struct timeval* timeout);
        bool Wait(std::vector<TcpSocket> *list, int outtime = MAX_TIMEOUT) {
            struct timeval tv;
            tv.tv_sec = outtime / 1000;//outtime以毫秒为单位
            tv.tv_usec = (outtime % 1000) * 1000; //计算剩余的微妙
            fd_set set;
            set = _rfds;//不能直接使用_rfds,因为一旦_rfds被修改就需要外部重新添加描述符了
            int ret = select(_maxfd+1, &set, NULL, NULL, &tv);
            if (ret < 0) {
                perror("select error");
                return false;
            }if (ret == 0) {
                printf("wait timeout\n");
                return true;
            }
            for (int i = 0; i < _maxfd+1; i++) {
                if (FD_ISSET(i, &set)) {
                    //还在集合中证明是就绪的,因为没有额外保存描述符,因此只能从0开始判断
                    TcpSocket sock;
                    sock.SetFd(i);
                    list->push_back(sock);
                }
            }
            return true;
        }//进行监控,并且直接向外提供就绪的TcpSocket
    private:
        //保存要监控可读事件的描述符---每次监控使用的集合都是这个集合的复制版(select会修改集合)
        fd_set _rfds; 
        //每次监控都需要输入的最大描述符
        int _maxfd;
};

实现一个tcp并发服务器

//多路转发模型的并发服务器
#include<iostream>
#include"select.hpp"

int main(int argc, char* argv[]){
  if(argc != 3){
    std::cout << "Usage:./main ip port\n";
    return -1;
  }
  std::string ip = argv[1];
  uint16_t port = std::stoi(argv[2]);
  TcpSocket lst_sock;
  CHECK_RET(lst_sock.Socket());
  CHECK_RET(lst_sock.Bind(ip, port));
  CHECK_RET(lst_sock.Listen());

  Select s;
  s.Add(lst_sock);
  while(1){
    std::vector<TcpSocket> list;
    bool ret = s.Wait(&list);
    if(ret == false){
      continue;
    }
    for(auto sock : list){
      if(sock.GetFd() == lst_sock.GetFd()){
        //就绪的描述符和监听套接字描述符,表示需要获取新连接
        TcpSocket new_sock;
        ret = lst_sock.Accept(&new_sock);
        if(ret == false){
          continue;
        }
        s.Add(new_sock);
      }else{
        //就绪的描述符不是监听套接字,就是通信套接字,则进行recv
        std::string buf;
        ret = sock.Recv(&buf);
        if(ret == false){
          sock.Close();
          continue;
        }
        std::cout << "client say: " << buf << std::endl;
        std::cout << "sever say: ";
        std::cin >> buf;
        ret = sock.Send(buf);
        if(ret == false){
          sock.Close();
		      s.Del(sock);
		      continue;
        }
      }
    }
  }
  lst_sock.Close();
  return 0;
}

select优缺点分析:

1.select所能够监控的描述符数量有最大上限,取决于宏__FD_SIZE,默认是1024
2.select进行监控的原理,是在内核中进行轮询遍历,性能会随着描述符的增多而下降
3.select返回时移除集合中未就绪的描述符,每次监控都需要重新添加描述符,重新拷贝到内核
4.select只能返回就绪的描述符集合,无法直接返回就绪的描述符,需要用户进行遍历判断哪个描述符还在集合中才能确定是否就绪

1.select遵循posix标准,可以跨平台移植
2.select的超时等待时间设置,可以精细到微妙

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
基于YOLOv9实现工业布匹缺陷(破洞、污渍)检测系统python源码+详细运行教程+训练好的模型+评估 【使用教程】 一、环境配置 1、建议下载anaconda和pycharm 在anaconda中配置好环境,然后直接导入到pycharm中,在pycharm中运行项目 anaconda和pycharm安装及环境配置参考网上博客,有很多博主介绍 2、在anacodna中安装requirements.txt中的软件包 命令为:pip install -r requirements.txt 或者改成清华源后再执行以上命令,这样安装要快一些 软件包都安装成功后才算成功 3、安装好软件包后,把anaconda中对应的python导入到pycharm中即可(不难,参考网上博客) 二、环境配置好后,开始训练(也可以训练自己数据集) 1、数据集准备 需要准备yolo格式的目标检测数据集,如果不清楚yolo数据集格式,或者有其他数据训练需求,请看博主yolo格式各种数据集集合链接:https://blog.csdn.net/DeepLearning_/article/details/127276492 里面涵盖了上百种yolo数据集,且在不断更新,基本都是实际项目使用。来自于网上收集、实际场景采集制作等,自己使用labelimg标注工具标注的。数据集质量绝对有保证! 本项目所使用的数据集,见csdn该资源下载页面中的介绍栏,里面有对应的下载链接,下载后可直接使用。 2、数据准备好,开始修改配置文件 参考代码中data文件夹下的banana_ripe.yaml,可以自己新建一个不同名称的yaml文件 train:训练集的图片路径 val:验证集的图片路径 names: 0: very-ripe 类别1 1: immature 类别2 2: mid-ripe 类别3 格式按照banana_ripe.yaml照葫芦画瓢就行,不需要过多参考网上的 3、修改train_dual.py中的配置参数,开始训练模型 方式一: 修改点: a.--weights参数,填入'yolov9-s.pt',博主训练的是yolov9-s,根据自己需求可自定义 b.--cfg参数,填入 models/detect/yolov9-c.yaml c.--data参数,填入data/banana_ripe.yaml,可自定义自己的yaml路径 d.--hyp参数,填入hyp.scratch-high.yaml e.--epochs参数,填入100或者200都行,根据自己的数据集可改 f.--batch-size参数,根据自己的电脑性能(显存大小)自定义修改 g.--device参数,一张显卡的话,就填0。没显卡,使用cpu训练,就填cpu h.--close-mosaic参数,填入15 以上修改好,直接pycharm中运行train_dual.py开始训练 方式二: 命令行方式,在pycharm中的终端窗口输入如下命令,可根据自己情况修改参数 官方示例:python train_dual.py --workers 8 --device 0 --batch 16 --data data/coco.yaml --img 640 --cfg models/detect/yolov9-c.yaml --weights '' --name yolov9-c --hyp hyp.scratch-high.yaml --min-items 0 --epochs 500 --close-mosaic 15 训练完会在runs/train文件下生成对应的训练文件及模型,后续测试可以拿来用。 三、测试 1、训练完,测试 修改detect_dual.py中的参数 --weights,改成上面训练得到的best.pt对应的路径 --source,需要测试的数据图片存放的位置,代码中的test_imgs --conf-thres,置信度阈值,自定义修改 --iou-thres,iou阈值,自定义修改 其他默认即可 pycharm中运行detect_dual.py 在runs/detect文件夹下存放检测结果图片或者视频 【特别说明】 *项目内容完全原创,请勿对项目进行外传,或者进行违法等商业行为! 【备注】 1、该资源内项目代码都经过测试运行成功,功能ok的情况下才上传的,请放心下载使用!有问题请及时沟通交流。 2、适用人群:计算机相关专业(如计科、信息安全、数据科学与大数据技术、人工智能、通信、物联网、自动化、电子信息等)在校学生、专业老师或者企业员工下载使用。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值