五中I/O模型

目前Linux系统中提供了5种IO处理模型
1.阻塞IO
2.非阻塞IO
3.IO多路复用
4.信号驱动IO
5.异步IO

内核(kernel)利用文件描述符(file descriptor)来访问文件。文件描述符是非负整数。打开现存文件或新建文件时,内核会返回一个文件描述符。读写文件也需要使用文件描述符来指定待读写的文件。

阻塞IO(BIO)

当我们发起一次IO操作后一直等待成功或失败之后才返回,在这期间程序不能做其它的事情。阻塞IO操作只能对单个文件描述符进行操作。接受一个读写请求后,就无法接受其他请求,串行化的执行IO操作。也可以多个线程,一个读写请求就开一个线程,线程之间切换很浪费时间。

非阻塞IO(NIO)

服务端接受一个IO请求后,先放进fds集合(存放文件描述符的集合)里,轮询这个集合看看有没有就绪的(就是已经建立好网络连接的)IO的思想实现非阻塞,但也会浪费不必要的CPU资源。一次也只能对单个描述符进行操作。

IO多路复用

IO多路复用解决的本质问题是在用更少的资源完成更多的事

单线程或者单进程环境下IO多路复用都可以同时检测多个文件描述符是否可以执行IO操作,有select、poll、epoll三种机制。实现方式:首先都会对一组文件描述符进行相关事件的注册,然后阻塞等待某些事件的发生或等待超时

常见软件的IO多路复用方案
redis: Linux下 epoll(level-triggered),没有epoll用select

nginx: Linux下 epoll(edge-triggered),没有epoll用select

select和poll都是状态持续通知(level-triggered)的机制,且不可改变,只要文件描述符中有IO操作可以进行,那么select和poll都会返回以通知程序。

select

select将监听的文件描述符分为三组,每一组监听不同的需要进行的IO操作。readfds是需要进行操作的文件描述符,writefds是需要进行操作的文件描述符,exceptfds是需要进行异常事件处理的文件描述符。这三个参数可以用NULL来表示对应的事件不需要监听。

当select返回时,每组文件描述符会被select过滤,只留下可以进行对应IO操作的文件描述符。

poll

和select用三组文件描述符不同的是,poll只有一个pollfd数组,数组中的每个元素都表示一个需要监听IO操作事件的文件描述符。

poll vs select

1.poll传参对用户更友好,不需要分开三组(select)传入参数。
2.poll会比select性能稍好些,因为select是每个bit位都检测,假设有个值为1000的文件描述符,select会从第一位开始检测一直到第1000个bit位。但poll检测的是一个数组
3.支持select的系统更多,兼容更强大.
4.select提供精度更高(到microsecond)的超时时间,而poll只提供到毫秒的精度。

select和epoll都需要把文件描述符的集合从用户态拷贝到内核态,这个开销在fd很多时会很大。且对socket扫描时都是是线性扫描,采用轮询的方法,效率较低(高并发时)

epoll vs poll&select

1.**select&poll是O(N)的复杂度,epoll是O(1)**在需要同时监听的文件描述符数量增加时,都用epoll。

2.epoll内部用一个文件描述符挂载需要监听的文件描述符,这个epoll的文件描述符可以在多个线程/进程共享,所以epoll的使用场景要比select&poll要多。

在这里插入图片描述
10、epoll函数接口
#include <sys/epoll.h>

// 数据结构
// 每一个epoll对象都有一个独立的eventpoll结构体
// 用于存放通过epoll_ctl方法向epoll对象中添加进来的事件
// epoll_wait检查是否有事件发生时,只需要检查eventpoll对象中的rdlist双链表中是否有epitem元素即可
struct eventpoll {
/红黑树的根节点,这颗树中存储着所有添加到epoll中的需要监控的事件/
struct rb_root rbr;
/双链表中则存放着将要通过epoll_wait返回给用户的满足条件的事件/
struct list_head rdlist;
};

// API

int epoll_create(int size); // 内核中间加一个 ep 对象,把所有需要监听的 socket 都放到 ep 对象中
int epoll_ctl(int epfd, int op, int fd, struct epoll_event event); // epoll_ctl 负责把 socket 增加、删除到内核红黑树
int epoll_wait(int epfd, struct epoll_event * events, int maxevents, int timeout);// epoll_wait 负责检测可读队列,没有可读 socket 则阻塞进程
11、epoll使用示例
int main(int argc, char
argv[])
{
/*

  • 在这里进行一些初始化的操作,
  • 比如初始化数据和socket等。
    */
// 内核中创建ep对象
epfd=epoll_create(256);
// 需要监听的socket放到ep中
epoll_ctl(epfd,EPOLL_CTL_ADD,listenfd,&ev);

while(1) {
  // 阻塞获取
  nfds = epoll_wait(epfd,events,20,0);
  for(i=0;i<nfds;++i) {
      if(events[i].data.fd==listenfd) {
          // 这里处理accept事件
          connfd = accept(listenfd);
          // 接收新连接写到内核对象中
          epoll_ctl(epfd,EPOLL_CTL_ADD,connfd,&ev);
      } else if (events[i].events&EPOLLIN) {
          // 这里处理read事件
          read(sockfd, BUF, MAXLINE);
          //读完后准备写
          epoll_ctl(epfd,EPOLL_CTL_MOD,sockfd,&ev);
      } else if(events[i].events&EPOLLOUT) {
          // 这里处理write事件
          write(sockfd, BUF, n);
          //写完后准备读
          epoll_ctl(epfd,EPOLL_CTL_MOD,sockfd,&ev);
      }
  }
}
return 0;

}
12、epoll缺点
epoll只能工作在linux下

13、epoll LT 与 ET模式的区别
epoll有EPOLLLT和EPOLLET两种触发模式,LT是默认的模式,ET是“高速”模式。

LT模式下,只要这个fd还有数据可读,每次 epoll_wait都会返回它的事件,提醒用户程序去操作

ET模式下,它只会提示一次,直到下次再有数据流入之前都不会再提示了,无论fd中是否还有数据可读。所以在ET模式下,read一个fd的时候一定要把它的buffer读完,或者遇到EAGAIN错误

14、epoll应用
redis

nginx

信号驱动IO

信号驱动IO是利用信号机制,让内核告知应用程序文件描述符的相关事件。但信号驱动IO在网络编程的时候通常很少用到,因为在网络环境中,和socket相关的读写事件太多了。

异步IO

异步IO和信号驱动IO差不多,但它比信号驱动IO可以多做一步:相比信号驱动IO需要在程序中完成数据从用户态到内核态(或反方向)的拷贝,异步IO可以把拷贝这一步也帮我们完成之后才通知应用程序(异步,不会阻塞当前程序)。我们使用 aio_read 来读,aio_write 写。

同步IO vs 异步IO

1.同步IO指的是程序会一直阻塞到IO操作如read、write完成
2.异步IO指的是IO操作不会阻塞当前程序的继续执行

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值