IT评测·应用市场-qidao123.com技术社区

标题: 【Linux高级IO(一)】明白五种IO模型 [打印本页]

作者: 宝塔山    时间: 2025-4-3 17:09
标题: 【Linux高级IO(一)】明白五种IO模型
目录
1、五种IO模型:
2、非阻塞IO
3、多路转接之select
3.1、明白select的执行过程


   1、应用层read&write的时候,本质把数据从用户写给OS ----  本质就是拷贝
  2、IO = 等待 + 拷贝(大部分时间在等待,比及有数据了才举行拷贝)
  要举行拷贝,必须先判断条件成立。  这个条件就是读写事件  读事件停当,就是读缓冲区有足够的数据可以读   写事件停当就是发送缓冲区中要有足够的空间
  什么叫做高效IO呢?   单元时间内,IO过程中,等的比重越小,IO服从就越高!
  实在险些所有提高IO服从的计谋,本质就是让等的比重变小。
  1、五种IO模型:

   五种IO模型:
1、张三:钓鱼的时候,专心钓鱼,不做任何其他的事,反面任何人说话。这叫阻塞式IO。
  2、李四:钓鱼的时候,在等鱼咬勾的时候还会一边看书。这是非阻塞轮询IO。
  3、王五:等鱼咬钩的时候,在鱼竿上面放一个铃铛,当鱼咬钩的时候,就会给他提醒。这是信号驱动式IO。
  4、赵六:他有100个鱼竿,全都放在水里,自己在岸边巡查,有鱼咬钩立马去检察。这是多路复用、多路转接式IO。
  5、田七:他是老板,他把钓鱼这件事委托给他的助理小王,田七只是钓鱼行为的发起者,他要的只是鱼。这是异步IO。
  阻塞IO  vs  非阻塞IO   它们的IO服从实在是雷同的,因为IO = 等待 + 拷贝 (等待的时间实在是一样的,只是等的方式差别)
同步IO  vs  异步IO     同步IO实在是当前这个人有没有参与IO,参与了就是同步,没参与就是异步。异步IO不参与IO只是发起IO,最后拿效果就行。 同步IO和线程同步实在没有任何关系。
因为多路复用式IO服从最高    我们后面重点阐明多路复用式IO和非阻塞IO
2、非阻塞IO

文件描述符本质就是一个数组下标,每一个数组下标指向的都是一个内核中的文件对象,文件对象中有文件的flags,用fcntl设置一个文件描述符的属性,实在就是设置这个文件在底层struct file中的标志位。
   #include <unistd.h>
  #include <fcntl.h>
  int fcntl(int  fd, int  cmd,... /* arg */ );
  

  将文件描述符设置为非阻塞,以后在利用read、write、recv、send...都是利用非阻塞的方式举行IO的。
   
  1. void SetNoBlock(int fd) {
  2. int fl = fcntl(fd, F_GETFL);  //获得指定文件描述符的标记位
  3. if (fl < 0)  //小于0,获取失败
  4. {
  5. perror("fcntl");
  6. return;
  7. }
  8. fcntl(fd, F_SETFL, fl | O_NONBLOCK);
  9. //为指定的文件描述符设置标记位   在老的标记位f1的基础上添加 O_NONBLOCK(非阻塞)
  10. }
复制代码
1、将文件描述符设置成为非阻塞,假如底层fd数据没有停当,recv、read、write、send返回值会以出错的形式返回(因为返回值大于0就是读到了,等于0就是关闭了)
  2、有两种情况: 我真的出错了、底层没有停当
  3、我要怎么区分这两种情况??? 通过errno错误码区分(11是底层数据没有停当)
  1. else if(errno == EWOULDBLOCK)
  2. {
  3.     cout << "0 fd data not ready, try again!" << endl;
  4. }
复制代码
  1. #include <fcntl.h>
  2. #include <unistd.h>
  3. #include<errno.h>
  4. #include <cstdlib>
  5. #include <iostream>
  6. using namespace std;
  7. //对指定的fd设置非阻塞
  8. void SetNonBlock(int fd) {
  9.     int fl = fcntl(fd, F_GETFL);
  10.     if (fl < 0) {
  11.         cerr << "fcntl error" << endl;
  12.         exit(1);
  13.     }
  14.     fcntl(fd, F_SETFL, fl | O_NONBLOCK);
  15. }
  16. int main() {
  17.     SetNonBlock(0);
  18.     while (1) {
  19.         char buffer[1024];
  20.         ssize_t s = read(0, buffer, sizeof(buffer) - 1);
  21.         if (s > 0) {
  22.             buffer[s] = 0;
  23.             cout << buffer << endl;
  24.         } else if (s == 0) {
  25.             cout << "读到文件结尾了" << endl;
  26.             break;
  27.         }
  28.         else
  29.         {
  30.             //1. 数据没用准备好 2. 真的出错了. 都以-1的返回值返回
  31.             // 数据没有准备好,不算出错. 需要区分这两种情况
  32.             if(errno == EWOULDBLOCK || errno == EAGAIN)
  33.             {
  34.                 cout<<"os底层数据还没就绪"<<endl;
  35.                 cout<<errno<<endl;
  36.             }
  37.             //被信号中断, 也不算read出错
  38.             else if(errno == EINTR)
  39.             {
  40.                 cout<<"IO interrupted by signal"<<endl;
  41.             }
  42.             else
  43.             {
  44.                 cout<<"read error"<<endl;
  45.                 break;
  46.             }
  47.         }
  48.         sleep(1);
  49.     }
  50. }
复制代码
3、多路转接之select

  IO = 等 + 拷贝
  select:只负责举行等待。一次可以等待多个fd
  停当事件通常分为可读事件,可写事件和非常事件
     #inclde <sys/select>
   int  select(int  nfds, fd_set *readfds, fd_set *writefds, fd_set  *exceptfds,   struct  timeval  *timeout);
   第一个参数:要等的最大的文件描述符+1
   
   第二个:关心读  fd_set是内核提供的一种数据类型,它是位图       输入输出型参数
   输入时:用户告诉内核,我给你一个或多个fd,你要帮我关心fd上面的读事件哦,假如读事件停当了,你要告诉我。
   输出时:内核告诉用户,用户你要我关心的多个fd,有哪些停当了,用户你赶紧读取吧。
   输入时:比特位的位置(从右向左数),表示文件描述符编号,比特位的内容,0/1,表示是否需要内核关心。
   输出时:比特位为0/为1,表示哪些用户关心的fd,上面的读事件已经停当了。
   fd_set是一张位图,让用户和内核传递fd是否停当的信息的。
   第三个: 关心写
   这就涉及到许多对位图修改的动作,系统提供了接口。
   

   fd_set类型参数, 输入你想要关心的fd聚集, 输出时, 此结构中存放, 已经事件停当的fd聚集. 比如你想要关心0~10号文件描述符的读事件, 函数返回时, 此聚集中可能只有1.3.5号fd被返回了, 也就是说只有1.3.5号fd的事件停当了
   第五个参数:
   struct  timeval:结构体 时间结构体:
   {5,0}:每隔5s,timeout一次。5s阻塞式等待,这5s没有文件描述符停当,就返回,再重新进入,重复(我们需要重复设置)。假如等待5s期间有文件描述符停当了,就会立刻返回
   
   timeval类型参数: 假如你设置阻塞时间为5秒, 但是等待了三秒后就有事件停当, 函数就返回了, 那么timeval类型参数的值会被设置成为2秒.
   他是输入输出参数,可能要举行周期的重复设置
   {0,0}:非阻塞等待。立马返回
   NULL:阻塞等待
   这个参数是输入输出型参数
   
   select返回值和错误码:
   
   大于0,有n个fd停当了
   等于0,超时,没有出错,但是也没有fd停当
   小于0,等待出错
   

    3.1、明白select的执行过程

  假如事件停当,上层不处置惩罚,select会一直关照你! 
select的缺点:
   1、等待的fd是有上限的
  2、输入输出型参数比较多,数据拷贝的频率比较高(用户到内核,内核到用户)
  3、输入输出型参数比较多,每次都要对关心的fd举行事件重置
  4、用户层,利用第三方数组管理用户的fd,用户层需要许多次遍历,内核中检测fd事件停当也要遍历
  1. #pragma once
  2. #include <iostream>
  3. #include <sys/select.h>
  4. #include <sys/time.h>
  5. #include "Socket.hpp"
  6. using namespace std;
  7. static const uint16_t defaultport = 8080;
  8. static const int fd_num_max = (sizeof(fd_set) * 8);
  9. int defaultfd = -1;
  10. class SelectServer
  11. {
  12. public:
  13.     SelectServer(uint16_t port = defaultport) : _port(port)
  14.     {
  15.         for(int i = 0; i < fd_num_max; i++)
  16.         {
  17.             fd_array[i] = defaultfd;
  18.             std::cout <<"fd_array[" << i << "]" << " : " << fd_array[i] << std::endl;
  19.         }
  20.     }
  21.     bool Init()
  22.     {
  23.         _listensock.Socket();
  24.         _listensock.Bind(_port);
  25.         _listensock.Listen();
  26.         return true;
  27.     }
  28.     void Accepter()
  29.     {
  30.         // 连接事件就绪了
  31.         std::string clientip;
  32.         uint16_t clientport = 0;
  33.         int sock = _listensock.Accept(&clientip, &clientport); // 获取连接不会阻塞在这里
  34.         if (sock < 0)
  35.         {
  36.             return;
  37.         }
  38.         lg(Info, "accept success, %s:%d", clientip.c_str(), clientport);
  39.         // 以前走到这里可以直接读取数据,可是,现在这里不可以
  40.         // 以前是多线程、多进程, 文件描述符其实是托管给执行流的 他阻塞是不影响的
  41.         // 我们现在是单进程,不能建立完连接,立马就进行读  如果不发,当前的进程就阻塞了
  42.         // select里面只有一个listen套接字    要将sock设置进select里  这样读文件描述符集里的文件描述符就会变得越来越多
  43.         // accept获取新连接, 不能直接读,因为不清楚是否就绪,selcet清楚是否就绪,所以要将新获取的连接 添加到辅助数组里
  44.         // select把数据处理完之后,下次循环时会重新再进行把文件描述符添加到rfds里,再交给select由他来监听
  45.         int pos = 1;
  46.         for (; pos < fd_num_max; pos++)
  47.         {
  48.             if (fd_array[pos] != default)
  49.                 continue; // 被占用
  50.             else
  51.                 break;
  52.         }
  53.         if (pos == fd_num_max)
  54.         {
  55.             // 全部被占用
  56.             lg(Warning, "server is full, close %d now!", sock);
  57.             close(sock);
  58.         }
  59.         else // 提前break说明由-1位置(即没有被占用)
  60.         {
  61.             fd_array[pos] = sock; // 新获取的连接往数组中添加
  62.         }
  63.     }
  64.     void Recver(int fd, int pos)
  65.     {
  66.         char buffer[1024];
  67.         ssize_t n = read(fd, buffer, sizeof(buffer) - 1);
  68.         if (n > 0)
  69.         {
  70.             buffer[n] = 0; // 把它当字符串
  71.             cout << "get a message: " << buffer << endl;
  72.         }
  73.         else if (n == 0) // 读失败
  74.         {
  75.             lg(Info, " client quit,me too, close fd is : ", fd);
  76.             close(fd);
  77.             fd_array[pos] = defaultfd; // 这里本质是从select中移除
  78.         }
  79.     }
  80.     void Dispatchar(fd_set &rfds) // 读事件就绪   //事件派发器
  81.     {
  82.         for (int i = 0; i < fd_num_max; i++)
  83.         {
  84.             int fd = fd_arry[i];
  85.             if (fd == dafaultfd)
  86.                 conitnue;
  87.             if (FD_ISSET(fd, &rfds)) // 判断listen套接字是否在集合里,即是否就绪
  88.             {
  89.                 if(fd == _listensock.Fd())
  90.                 {
  91.                     Accepter(); //连接管理器
  92.                 }
  93.                 else //不是listenfd
  94.                 {
  95.                     Recver(fd, i);
  96.                 }
  97.             }
  98.         }
  99.     }
  100.     void Start()
  101.     {
  102.         int listensock = _listensock.Fd();
  103.         fd_array[0] = listensock;
  104.         
  105.         for (;;)
  106.         {
  107.             Fd_set rfds;
  108.             Fd_ZERO(rfds);
  109.             int maxfd = fd_array[0];
  110.             for(int i = 0; i < fd_num_max; i++)
  111.             {
  112.                 if(fd_arry[i] == default)
  113.                 continue; //没有被设置过的
  114.                 Fd_SET(fd_array[i], &rfds); // 将文件描述符添加到集合里
  115.                 if(maxfd < fd_array[i])
  116.                 {
  117.                     maxfd = fd_array[i];
  118.                 }
  119.             }
  120.             // accept 的本质是检测listensocket上面有没有连接事件 即底层有三次握手 他一次只能等一个文件描述符 所以要交给select
  121.             // 新连接到来,对于select来讲就是读事件就绪
  122.             // 读文件描述符集,他是一个位图
  123.          
  124.            
  125.             struct timeval timeout = {5, 0}; //需要被重复设置 因为如果不重复设置就 会剩余的时间替换
  126.             //输入输出参数,每次从内核返回的时候值可能就被改过了 ,所以需要重复设置
  127.             int n = select(maxfd + 1, &rfds, nullptr, nullptr, &timeout); // 告诉OS关心这个文件描述符上的读事件
  128.             //select 是如果事件就绪,上层不处理,select会一直通知你
  129.             //select告诉你就绪了,接下来的一次读取,fd不会阻塞
  130.             switch(n)
  131.             {
  132.                 case 0:
  133.                 //等待超时,在等待期间任何事情都没有发生
  134.                     break;
  135.                 case -1:
  136.                     //异常
  137.                     break;
  138.                 default:
  139.                     //有事件就绪
  140.                     HandlerEvent(rfds);//就绪的事件在rfds里
  141.                     break;
  142.             }
  143.         }
  144.     }
  145.     ~SelectServer()
  146.     {
  147.         _listensock.Close();
  148.     }
  149. private:
  150.     Sock _listensock;
  151.     uint16_t _port;
  152.     int fd_array[fd_num_max]{defaultfd};//设置辅助数组
  153. };
复制代码
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。




欢迎光临 IT评测·应用市场-qidao123.com技术社区 (https://dis.qidao123.com/) Powered by Discuz! X3.4