目录
IO:input && Output
read && write
- 应用层read&&write的时候,本质就是把数据从用户层写给OS--本质就是拷贝函数
- 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
fcntl
一个文件描述符,默认都是阻塞IO
函数原型如下:
#include <unistd.h>#include <fcntl.h>int fcntl(int fd, int cmd, ... /* arg */ );
传入的cmd的值不同, 后面追加的参数也不相同。
- 复制一个现有的描述符(cmd=F_DUPFD).
- 获得/设置文件描述符标记(cmd=F_GETFD或F_SETFD).
- 获得/设置文件状态标记(cmd=F_GETFL或F_SETFL).
- 获得/设置异步I/O所有权(cmd=F_GETOWN或F_SETOWN).
- 获得/设置记录锁(cmd=F_GETLK,F_SETLK或F_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只负责等,一次可以等待多个fd
select函数原型
int select(int nfds, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, struct timeval *timeout)
返回值
- >0 有n个fd就绪了
- ==0 超时,没有错误,但是也没有fd就绪
- <0 等待出错
参数
- 参数nfds是需要监视的最大的文件描述符值+1;
- rdset,wrset,exset分别对应于需要检测的可读文件描述符的集合,可写文件描述符的集 合及异常文件描述符的集合;
- 参数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缺点:
- 因为使用位图存储fd,所以等待的fd的个数是有上限的,1024
- 输入输出型参数比较多,数据拷贝的频率比较高
- 输入输出型参数比较多,每次都要对关心的fd进行重置
- 使用第三方数组管理用户的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 */};
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);
- 它不同于select()是在监听事件时告诉内核要监听什么类型的事件, 而是在这里先注册要监听的事件类型.
- 第一个参数是epoll_create()的返回值(epoll的句柄).
- 第二个参数表示动作,用三个宏来表示.
- 第三个参数是需要监听的fd.
- 第四个参数是告诉内核需要监听什么事.
第二个参数的取值:
- EPOLL_CTL_ADD :注册新的fd到epfd中;
- EPOLL_CTL_MOD :修改已经注册的fd的监听事件;
- EPOLL_CTL_DEL :从epfd中删除一个fd
epoll的优势:
- 检测就绪O(1),获取就绪O(n)
- fd,event没有上限
- 返回值n,表示有几个fd就绪了,就绪事件是连续的,有返回值个,遍历数组即可
两种工作模式
- LT:水平触发 Level Triggered
- ET:边缘触发 Edge Triggered
epoll默认模式:LT模式,事件到来,但是上层不处理,高电平,一直有效
ET:数据或者连接,从无到有,从有到多,变化的时候,才会通知我们一次,倒逼程序员,每次通知,必须把本轮数据全部取走,循环读取,直到读取出错,fd默认是阻塞的,因此ET模式下,所有的fd必须是non_block的。
cmake
安装
sudo apt install -y cmake