IO多路复用

1. IO模型

IO模型是指四种不同的文件读写方式.

(1) 阻塞IO

阻塞IO是最常用,最简单,效率最低的一种IO模型.

  • 阻塞读

    如果有数据可读,则直接读取数据

    如果没有数据可读,则读会阻塞,直到读取到数据 或 出错才返回

  • 阻塞写

    如果有空间可供写入,则立即写入数据并返回

    如果空间已满在执行写入时,则写会阻塞,直到有空间 或 出错 才返回

(2) 非阻塞IO

可以防止进程或线程阻塞在IO操作上, 但是无法确保IO能够准确完成相应的操作.

  • 非阻塞读

    如果有数据可读,则直接读取数据并返回

    如果没有数据可读,也会立即返回 (0)

  • 非阻塞写

    如果有空间可供写入,则直接写入数据并返回

    如果没有空间可写,也会立即返回 (0)

    ==> 对于非阻塞IO来说,有时候为了能够读取/写入数据,可能需要进行轮询.

    例子 :

    ssize_t timedread(int fd,void * buf,int count ,int timeout)
    {
        while((ret == read(fd,buf,count) == 0)
        {
            usleep(1000);
            if(timeout-- <= 0)
            {
                break;
            }
        }
                
        return ret;
    }
    

    如何设置文件描述符 是 阻塞IO 还是 非阻塞IO ?

    ​ 阻塞IO 和 非阻塞IO 是由文件文件描述符的 O_NONBLOCK 标志来决定的

    ​ 如果在open时,flag参数中 位或 了 O_NONBLOCK 则文件描述符为非阻塞

    ​ 如果在open时, flag参数中没有O_NONBLOCK 的标志,则文件描述符默认(阻塞)

(3) 异步通知

在文件可读或可写的时候, 有驱动向系统发送一个SIGIO信号来提醒进行.

(4) IO多路复用

允许同时对多个阻塞IO进行控制操作

提前监视这些阻塞IO 是否 已经可读,可写或出错, 当条件满足后,再去执行相应的读写操作.

​ select poll epoll ==> 用来监视文件描述符的

2. select


       #include <sys/select.h>
    #include <sys/time.h>
    #include <sys/types.h>
       #include <unistd.h>
    
 			fd_set     : 文件描述符的集合. "类型"
                API函数接口 :
   							FD_SET(fd,fd_set);//把一个文件描述符fd加入到fd_set这个集合中去
   							FD_CLR(fd,fd_set);//把一个文件描述符fd从集合中fd_set中清掉
   							FD_ZERO(fd,fd_set);//把一个集合fd_set全部清掉
   							FD_ISSET(fd,fd_set);//判断文件描述符fd是否在fd_set这个集合中.
   
       int select(int nfds, fd_set *readfds, fd_set *writefds,fd_set *exceptfds, struct timeval *timeout);
			nfds    : 一般填你感兴趣的最大文件描述符 +1
			readfds : 要监听读的文件描述符的集合;
			writefds: 要监听写的文件描述符的集合;
			exceptfds : 要监听出错的文件描述符的集合;
					在调用前,调用者要填入你 "你感兴趣的文件描述符"
                    在函数返回后,三个集合中,"是已经就绪的文件描述符"
                   

			timeoutv:  超时时间设置.
              struct timeval 
              {
                  long    tv_sec;         /* seconds */
                     long    tv_usec;        /* microseconds */
           		 };
				在调用前, 调用者要填入的是 "设定的超时时间"
             在函数返回后, 这个timeout指向的是剩余时间.
             返回值 :
   				> 0 :表示已经就绪的文件描述符的个数.
                   = 0 : 表示超时.
                   < 0 : 表示出错啦
                       

   			select的实现是在内核中开辟了一个内核线程去实现 :
   				同时监听的 . "轮询" : 轮流询问
                while(不超时)
                {
                    先问一遍 [0 , 你感兴趣的文件描述符最大的那个)
                        //[0,nfds)
                    if(有文件就绪)
                    {
                        break;
                    }
                    
                    sleep t;
                }
			
		void FD_CLR(int fd, fd_set *set);
       	int  FD_ISSET(int fd, fd_set *set);
       	void FD_SET(int fd, fd_set *set);
       	void FD_ZERO(fd_set *set);
	

例子 :

  1. select 延时的效果
struct timeval timeout;
 timeout.tv_sec = 5;
tiemtout.tv_usec = 0;
  
select(100,NULL,NULL,NULL,&timeout);
   <==> sleep(5);
    <==> usleep();

3.poll

pool的功能和select类似, “监听多个文件描述符 , 是否就绪”.

只不过poll用一个结构体 struct poolfd 来描述 , ‘‘监听请求’’


       #include <poll.h>   
        struct pollfd 
        {
               int   fd;         //指定要监听的文件描述符
               short events;     //监听的事件
               						在linux内核中, 事件用bit-fields
               						POLLIN  : 可读的事件
                                         POLLOUT : 可写的事件
                                         POLLERR : 出错的事件
                                           ...: 
               						 POLLIN | POLLOUT
               short revents;    //已经就绪的事件: 是否已经可读
                                    if(revents & POLLIN)
                                    {
                                        可读的
                                    }   						
           };
   
   			监听一个文件描述符, 就需要一个 struct pollfd 的结构体
            监听多个文件描述符, 就需要多个 struct pollfd 的结构体
            ....


       int poll(struct pollfd *fds, nfds_t nfds, int timeout);
   			fds : 监听结构体 struct pollfd 的数组
         nfds :表示第一个参数 struct pollfd数组的元素个数
         timeout : 超时时间.单位 ms
            返回值 :
				> 0 表示就绪的文件个数
                   = 0 表示超时
                   < 0 出错啦
   

4.epoll : man epoll

(1) epoll_create : 创建一个监听文件的集合,这个函数的返回值是一个文件描述符

NAME
       epoll_create - open an epoll file descriptor

SYNOPSIS
       #include <sys/epoll.h>

       int epoll_create(int size);
			size : 已经被忽略了. 但是 > 0
            返回值 :
				成功 返回一个epoll的实例(对象),就是一个文件描述符
                失败 返回-1,同时errno被设置

创建一个epoll的实例, 用来监听多个其他的文件描述符

那么要监听的文件描述符, 如何加入这个监听的实例中去呢?

(2) epoll_ctl


       #include <sys/epoll.h>

    int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);
   			epfd : epoll监听集合的文件描述符, epoll_create 返回值
         op : 具体是何种操作, 常用的有如下 :
   				EPOLL_CTL_ADD :
					增加. 把一个要监听的文件描述符及监听事件
                    加入到epoll实例中去.
             EPOLL_CTL_DEL :
					从监听集合中删除一个文件描述符.
                EPOLL_CTL_MOD : modify
                    修改个月已经在监听集合中的文件描述符的监听事件.
         fd : 要监听或删除或修改的 文件描述符
            event : 要监听的事件的结构体的指针.
                在epoll中,用一个结构体 struct epoll_event
                    来描述一个要监听的事件.
                struct epoll_event 
                {
                    uint32_t     events;     
                    				要监听的事件标志, bit-fields
                                    可用的监听事件有如下 :
                    				    EPOLLIN    :可读的事件
                                        EPOOLOUT    : 可写的事件
                                        EPOLLRDHUP  : 这个事件 用来监听 流式套接字(tcp sockfd)
                                            对方是否已经关闭或关闭写
                                        RPOLLERR : 出错事件
                                        ...
                                  EPOLLET : Edge-Triggered 边缘触发
                                  LT : Level-Triggered
                                                只要有数据,就会不同的上报可读事件
                                                	EPOLLIN
                                                默认行为 : LT
                                   ET : Edge-Triggered
                                                有数据变化(数量),才报告可读事件
                                                
                                            例子 :
                    							一个文件里面有 2K数据
                                                  报告可读的事件.
                                                这个时候, 你读走了1K数据,文件剩余1K.
                                                    
                                                  LT : ---> 不停地上报可读事件
                                                  
                                                  ET : ----> 在数据有变化的情况下才会上报.
                    				
                    epoll_data_t data;        /* User data variable */
                    			//用来保存用户的一些数据
           	    };
   
   				typedef union epoll_data 
                {
                 void        *ptr;//用户的数据指针
                 int          fd;//文件描述符
                    uint32_t     u32;//
                    uint64_t     u64;//
           		} epoll_data_t;
   
   				返回值 :
   					成功 返回 0
                 失败 返回-1,同时errno被设置;

(3) epoll_wait : 用来等待监听事件的发生

NAME
       epoll_wait - wait for an I/O event on an epoll file descriptor

SYNOPSIS
       #include <sys/epoll.h>

       int epoll_wait(int epfd, struct epoll_event *events,
                      int maxevents, int timeout);
			 epfd : epoll实例
             events :结构体数据,用来保存已经就绪事件的信息的.
             maxevents : 表示第二个参数结构体数组最多可以保存多少个事件结构体.
             timeout :超时时间 , 单位 为 ms
             返回值 :
				> 0 已经就绪的文件描述符的个数
                = 0 表示超时了
                = -1 出错了

总结 :

  1. select 实现原理

    select函数在调用时,会从文件描述符 0 - nfds - 1 依次轮询遍历这些文件描述符在 readfds,writefds,exceptfds这是三个集合中存在是否就绪,如果没有就绪,则会在timeout指定的超时时间内一直循环遍历, 如果有就绪的,则在在当次轮询完会立即返回.

  2. poll实现原理

    poll是通过struct pollfd结构体数组的形式来指定并感兴趣的文件描述符是否就绪,该结构体中的 events成员用来指定感兴趣的事件,revents是用来存放以及就绪的事件,poll是通过指定的时间内,不断的去询问fds中的指定的文件描述符是否就绪

    ===> 单次轮询次数为nfds ,比select少

  3. epoll实现原理

    epoll是以对象的形式来描述一些你感兴趣点额文件描述符

    ==> epoll对象(感兴趣的文件描述符对象)

    epoll支持文件描述符数量很多的情况下,效率要比select/poll

    epoll支持 ET

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

int main

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值