【Linux】高级IO

目录

IO:input && Output

五种IO模型概念

阻塞IO

非阻塞IO 

信号驱动IO 

异步IO

IO多路转接 

异步IO 

总结

非阻塞IO

fcntl

实现函数SetNoBlock

I/O多路转接之select

select

select函数原型

返回值

参数

参数struct timeval *timeout

fd_set 

fd_set *readfds

Select缺点:

I/O多路转接之Poll

I/O多路转接之ePoll 

epoll_create

epoll_wait 

 epoll_ctl

epoll的优势:

两种工作模式

cmake


IO:input && Output

read && write

  1. 应用层read&&write的时候,本质就是把数据从用户层写给OS--本质就是拷贝函数
  2. IO:等+拷贝(等读写事件就绪)‘

什么叫高效的IO呢?

单位时间内,IO过程中,等的比重越小,IO效率越高,因此提高IO效率,就是减少等的时间。

同步IO & 异步IO

同步IO有参与IO,异步IO不参与IO,只是发起IO,最后拿结果就行

五种IO模型概念

阻塞IO

阻塞IO:在内核将数据准备好之前,系统调用会一直等待,所有的套接字,默认都是阻塞方式

阻塞IO是最常见的IO模型

非阻塞IO 

        非阻塞IO: 如果内核还未将数据准备好, 系统调用仍然会直接返回, 并且返回EWOULDBLOCK错误码. 

        非阻塞IO往往需要程序员循环的方式反复尝试读写文件描述符, 这个过程称为轮询. 这对CPU来说是较大的浪费, 一般只有特定场景下才使用。

信号驱动IO 

信号驱动IO: 内核将数据准备好的时候, 使用SIGIO信号通知应用程序进行IO操作

 

异步IO

异步IO: 由内核在数据拷贝完成时, 通知应用程序(而信号驱动是告诉应用程序何时可以开始拷贝数据)

IO多路转接 

IO多路转接: 虽然从流程图上看起来和阻塞IO类似. 实际上最核心在于IO多路转接能够同时等待多个文件描述符的就绪状态

异步IO 

异步IO: 由内核在数据拷贝完成时, 通知应用程序(而信号驱动是告诉应用程序何时可以开始拷贝数据). 

总结

任何 IO 过程中 , 都包含两个步骤 . 第一是 等待 , 第二是 拷贝 . 而且在实际的应用场景中 , 等待消耗的时间往往都远远高于拷贝的时间. IO 更高效 , 最核心的办法就是让等待的时间尽量少 .

非阻塞IO

fcntl

一个文件描述符,默认都是阻塞IO

函数原型如下:

#include <unistd.h>
#include <fcntl.h>
int fcntl(int fd, int cmd, ... /* arg */ );

传入的cmd的值不同, 后面追加的参数也不相同。

fcntl函数有5种功能:
  • 复制一个现有的描述符(cmd=F_DUPFD.
  • 获得/设置文件描述符标记(cmd=F_GETFDF_SETFD).
  • 获得/设置文件状态标记(cmd=F_GETFLF_SETFL).
  • 获得/设置异步I/O所有权(cmd=F_GETOWNF_SETOWN).
  • 获得/设置记录锁(cmd=F_GETLK,F_SETLKF_SETLKW)

实现函数SetNoBlock

void SetNoBlock(int fd) {
        int fl = fcntl(fd, F_GETFL);
        if (fl < 0) {
                perror("fcntl");
                return;
        }
        fcntl(fd, F_SETFL, fl | O_NONBLOCK);
}
  • 使用F_GETFL将当前的文件描述符的属性取出来(这是一个位图).
  • 然后再使用F_SETFL将文件描述符设置回去. 设置回去的同时, 加上一个O_NONBLOCK参数.

设置成为非阻塞,如果底层fd数据没有就绪,recv/read/write/send,返回值会以出错的形式返回;

而返回值为出错的有两种情况:1.真的出错   2.底层未就绪

那么我们怎么进行区分呢,通过errno区分

if(errno==EWOULDBLOCK)

        //即为底层未就绪的情况

I/O多路转接之select

select

系统提供 select 函数来实现多路复用输入 / 输出模型 .
  • select系统调用是用来让我们的程序监视多个文件描述符的状态变化的;
  • 程序会停在select这里等待,直到被监视的文件描述符有一个或多个发生了状态改变

select只负责等,一次可以等待多个fd

select函数原型

select 的函数头文件 : #include <sys/select.h>
int select(int nfds, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, struct timeval *timeout)

返回值

  • >0  有n个fd就绪了
  • ==0 超时,没有错误,但是也没有fd就绪
  • <0   等待出错

参数

  1. 参数nfds是需要监视的最大的文件描述符值+1
  2. rdset,wrset,exset分别对应于需要检测的可读文件描述符的集合,可写文件描述符的集 合及异常文件描述符的集合;
  3. 参数timeout为结构timeval,用来设置select()的等待时间。

参数struct timeval *timeout

给sekect设置等待方式

struct timeval *timeout =[5,0]:每隔5S,timeout一次

                                     =[0,0]:立马返回,非阻塞的一种

                                     =NULL:阻塞等待

fd_set 

fd_set是内核提供的一种数据类型,它是位图 。

  • void FD_CLR(int fd, fd_set *set); // 用来清除描述词组set中相关fd 的位
  • int FD_ISSET(int fd, fd_set *set); // 用来测试描述词组set中相关fd 的位是否为真
  • void FD_SET(int fd, fd_set *set); // 用来设置描述词组set中相关fd的位
  • void FD_ZERO(fd_set *set); // 用来清除描述词组set的全部位

fd_set *readfds

  • 输入时,用户告诉内核,一个或者多个fd,如果fd上面的读事件就绪了,就通知用户
  • 输出时:内核告诉用户,用户你让我关心的几个fd中,有哪些已经就绪了,赶紧读取吧

因为fd_set 是位图,传入时 0000 1111 ,就代表要注意0,1,2,3四个文件描述符fd上面的读事件

返回时,0000 0100 就代表2号文件描述符上面的读事件就绪了

Select缺点:

  1. 因为使用位图存储fd,所以等待的fd的个数是有上限的,1024
  2. 输入输出型参数比较多,数据拷贝的频率比较高
  3. 输入输出型参数比较多,每次都要对关心的fd进行重置
  4. 使用第三方数组管理用户的fd,用户层需要很多次遍历,内核中检测fd事件就绪,也要遍历

I/O多路转接之Poll

Poll解决了Select的第二、三个缺点,最大的特点是将输入和输出事件进行了分离

Poll函数头文件 #include <poll.h>

接口

int poll(struct pollfd *fds, nfds_t nfds, int timeout);
//timeout的单位是毫秒
// pollfd 结构
struct pollfd {
        int fd; /* file descriptor */
        short events; /* requested events */
        short revents; /* returned events */
};
eventsrevents的取值:

 

Poll虽然还是一个数组,但是也解决了等待的fd上限问题,只不过上限被操作系统或者内存限制,poll本身无上限,还存在效率问题

I/O多路转接之ePoll 

epoll_create

#include <sys/epoll.h>

int epoll_create(int size);   //返回一个文件描述符

创建一个epoll的句柄.

  • 自从linux2.6.8之后,size参数是被忽略的.
  • 用完之后, 必须调用close()关闭

epoll_wait 

#include <sys/epoll.h>

int epoll_wait(int epfd, struct epoll_event * events, int maxevents, int timeout);
  • int epfd就是 epoll_create 返回的文件描述符
  • struct epoll_event * events   ,   int maxevents 返回已经就绪的fd和event
  • int timeout 单位毫秒,超时时间,设置为0为非阻塞等待
  • 返回值已经就绪的fd的个数
  • 上图中uint32_t events 以位图的形式,传递标志位
  • events 可以是以下几个宏的集合:
    EPOLLIN : 表示对应的文件描述符可以读 ( 包括对端 SOCKET 正常关闭 );
    EPOLLOUT : 表示对应的文件描述符可以写 ;
    EPOLLPRI : 表示对应的文件描述符有紧急的数据可读 ( 这里应该表示有带外数据到来 );
    EPOLLERR : 表示对应的文件描述符发生错误 ;
    EPOLLHUP : 表示对应的文件描述符被挂断 ;
    EPOLLET : EPOLL 设为边缘触发 (Edge Triggered) 模式 , 这是相对于水平触发 (Level Triggered) 来说的 .
    EPOLLONESHOT :只监听一次事件 , 当监听完这次事件之后 , 如果还需要继续监听这个 socket 的话 , 需要 再次把这个socket 加入到 EPOLL 队列里

 epoll_ctl

int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);

epoll 的事件注册函数 .
  • 它不同于select()是在监听事件时告诉内核要监听什么类型的事件, 而是在这里先注册要监听的事件类型.
  • 第一个参数是epoll_create()的返回值(epoll的句柄).
  • 第二个参数表示动作,用三个宏来表示.
  • 第三个参数是需要监听的fd.
  • 第四个参数是告诉内核需要监听什么事.

第二个参数的取值:

  • EPOLL_CTL_ADD :注册新的fdepfd中;
  • EPOLL_CTL_MOD :修改已经注册的fd的监听事件;
  • EPOLL_CTL_DEL :从epfd中删除一个fd

epoll的优势:

  1. 检测就绪O(1),获取就绪O(n)
  2. fd,event没有上限
  3. 返回值n,表示有几个fd就绪了,就绪事件是连续的,有返回值个,遍历数组即可

两种工作模式

  • LT:水平触发 Level Triggered
  • ET:边缘触发 Edge Triggered

epoll默认模式:LT模式,事件到来,但是上层不处理,高电平,一直有效 

ET:数据或者连接,从无到有,从有到多,变化的时候,才会通知我们一次,倒逼程序员,每次通知,必须把本轮数据全部取走,循环读取,直到读取出错,fd默认是阻塞的,因此ET模式下,所有的fd必须是non_block的。

cmake

安装

sudo apt install -y cmake

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值