一文理解BIO/NIO/AIO

1.什么是BIO?什么是NIO?什么是AIO?
BIO是同步阻塞式IO,一个连接对应一个线程.每当有一个客户端请求连接,都要在服务端创建一个线程来服务这个客户端(因为这时候会阻塞,不能够接受其他客户端的连接请求),如果有很多客户端,就会对应成千上万个服务端线程,这会导致服务端负载过高,甚至卡死.
在这里插入图片描述
NIO是同步非阻塞IO,一个IO请求对应一个线程,并且IO请求结束后会释放.
一个客户端对应一个channel,多路复用器selector会轮询channel,当有IO请求过来的时候,selector才会去创建工作线程与buffer,工作线程会通过buffer从channel中读取IO请求并进行处理,处理完成后再通过buffer将数据返回给channel,当IO请求读写完毕后会释放这个线程到线程池中.
在这里插入图片描述
AIO是异步非阻塞IO,当有IO请求时会通知服务器端进行相应处理.不需要一个线程去轮询所有IO操作状态的改变.

BIO、NIO、AIO适用场景
BIO方式适用于连接数目比较小且固定的架构,这种方式对服务器资源要求比较高,并发局限于应用中,JDK1.4以前的唯一选择,但程序直观简单易理解。
NIO方式适用于连接数目多且连接比较短(轻操作)的架构,比如聊天服务器,并发局限于应用中,编程比较复杂,JDK1.4开始支持。
AIO方式适用于连接数目多且连接比较长(重操作)的架构,比如相册服务器,充分调用OS参与并发操作,编程比较复杂,JDK7开始支持。

2.BIO有什么缺陷
有多少个连接请求就要创建多少个线程,会导致服务器负载过高,甚至卡死.

3.针对C10K这样的需求,NIO靠什么解决问题
C10K即client 10000,指同时连接到服务器的客户端数量超过10000个的环境.
一个客户端对应一个channel,NIO通过一个线程:多路复用器selector去轮询channels,当有IO请求的时候,select会创建一个工作线程和buffer进行处理,工作线程会通过buffer读取channel中的IO请求,处理完之后,会通过buffer将结果返回给channel,并且释放工作线程到线程池中.

4.多路复用操作系统函数select()工作原理
Select(max+1, &rset, &wset, &eset, time).
第一个参数: 最大socket文件描述符+1,表示需要轮询监控的socket文件描述符数量
第二个参数: 读文件描述符集合(不需监控传NULL)
第三个参数: 写文件描述符集合(不需监控传NULL)
第四个参数: 异常文件描述符集合(不需监控传NULL)
第五个参数: 超时时间.为NULL表示会一直轮询是否有就绪的socket文件描述符.
1.timeout=NULL(阻塞:select将一直被阻塞,直到某个文件描述符上发生了事件)
2.timeout所指向的结构设为非零时间(等待固定时间:如果在指定的时间段里有事件发生或者时间耗尽,函数均返回)
3.timeout所指向的结构,时间设为0(非阻塞:仅检测描述符集合的状态,然后立即返回,并不等待外部事件的发生)
fdset文件描述符集是使用bitmap位图实现,bitmap大小默认是1024位.
当调用select()的时候会涉及到用户态和内核态的切换,将需要检查的socket文件描述符集合(fd_set)拷贝到内核态中,检查socket文件描述符状态,是否有读就绪/写就绪/异常,如果有就绪状态的socket文件描述符,会在fdset中将其置位为1,当检查完一遍之后,如果存在就绪的socket文件描述符,则返回,然后遍历文件描述符,查看哪一个是就绪的,对就绪状态的文件描述符进行读/写等操作.
select存在的缺点:
1)每次调用select,都需要将文件描述符集合拷贝到内核态,如果fd很多,则开销会比较大
2)每次调用select都需要在内核态中遍历传递进来的fd是否就绪
3)select默认支持同时监控的文件描述符个数为1024,比较小.

5.Select()默认监听socket文件描述符数量为什么是1024?
因为fd_set结构是bitmap位图结构,bitmap默认是1024位.(当前也可以修改,不过需要重新编译操作系统内核…)

6.select第一遍O(N)未发现就绪socket文件描述符,后续在某个socket文件描述符就绪后,select是如何感知的?是不停轮询吗?
不是轮询.
socket有三块核心区域:读缓存、写缓存、等待队列.
select函数在第一遍轮询中没有发现就绪状态的socket文件描述符,就会将当前线程的引用追加到当前线程监控的每一个socket文件描述符对象的等待队列中,挂起当前线程,select函数不再运行.
当socket有数据需要读/写的时候,就会将数据导入到读缓存/写缓存中,然后检查socket的等待队列看是否有等待者,如果有的话,就把等待着的所有线程移动到工作队列,就又有机会获取到cpu时间片,当获取到cpu时间片后,再次执行select函数,轮询检查是否有就绪的socket文件描述符,并对就绪的socket文件描述符打上标记,返回,然后检查哪一个socket文件描述符就绪,进行相应的读/写操作.

7.Select()和poll()的主要区别是什么?
select使用的bitmap表示需要检查的socket文件描述符集合.
Poll在用户态使用的pollfds数组表示需要检查的socket文件描述符集合,在内核态将数组转换为链表.使用数组结构就可以让线程监听的socket文件描述符超过1024个这个限制.
pollfd结构有三个参数:
fd: socket文件描述符
events: 事件类型,读POLLIN,写POLLOUT,还有其他…
revents: 默认值为0,当读就绪时置为POLLIN,写就绪时置为POLLOUT.

8.为什么会有epoll()这个技术,它产生的背景是什么?
poll()解决了socket文件描述符限制的问题,但是仍然存在以下两个问题:
1)每次调用select/poll都要将文件描述符集合拷贝到内核态
2)每次调用select/poll都需要在内核态轮询遍历每一个socket文件描述符是否就绪
epoll()就是为了解决这两个问题的

9.epoll函数的工作原理是什么?
epoll提供了三个函数: epoll_create、epoll_ctl、epoll_wait.
epoll_create: 创建一个eventpoll对象,返回这个对象的文件描述符epfd.可以使用该epfd增删查改其监视的epoll实例即eventpoll对象的信息.eventpoll的结构有三块重要的区域,一块是用来存放需要监听的文件描述符兴趣列表,一块存放就绪的socket文件描述符信息链表,还有一块存放等待队列,保存调用epoll_wait的线程.

epoll_ctl: 根据epfd增删查改内核空间上eventepoll对象的信息.向epoll实例注册的所有文件描述符统称为epoll的兴趣列表,底层使用红黑树实现.

epoll_wait: 传入epfd,返回该eventpoll对象的就绪链表大小.-1表示异常,0表示没有就绪,大于0表示有几个就绪了.
int epoll_wait(int epfd, struct epoll_event * events, int maxevents, int timeout);

10.eventpoll对象的就绪链表数据是如何维护的?
当调用epoll_ctl函数将需要监控的socket文件描述符放入到eventpoll的兴趣列表时,内核程序也会把当前eventpoll对象追加到socket文件描述符的等待队列中,当socket有数据需要读写时,会将该数据放入到读/写缓存区中,再去检查socket的等待队列,当发现socket的等待队列中是eventpoll对象引用时,就会根据该eventpoll对象引用,将当前socket的引用追加到eventpoll就绪链表的末尾,然后检查eventpoll对象的等待队列,如果有进程,就把该进程转移到工作队列中,获取到cpu时间片后,调用epoll_wait函数,返回该eventpoll对象的就绪链表大小.

11.epoll_wait函数返回一个就绪列表的大小,没有表示出那个socket文件描述符是就绪的,那么获取就绪的socket文件描述符是怎么实现的呢?
调用epoll_wait函数的时候会传入一个epoll_event对象数组指针,在epoll_wait函数返回之前会将就绪的socket文件描述符信息拷贝到这个指针指向的对象数组中.

12.epoll_wait可不可以设置为非堵塞的呢?
可以.epoll_wait函数有一个参数表示堵塞时间长度,如果参数设置为0表示不管有没有就绪的socket文件描述符都会立即返回;如果参数设置为-1,则表示无限期阻塞,直到有socket文件描述符就绪.

13.eventpoll对象中存放需要检查的socket文件描述符信息是采用什么结构?为什么?
红黑树
因为红黑树的增删查改比较稳定,时间复杂度为O(logN),调整时旋转次数不超过3次.

14.LT和ET
LT 模式即 Level Triggered 工作模式,水平触发,默认,水平触发只要socket读缓冲区或者写缓冲区中有数据就会触发监控其socket文件描述符的eventpoll对象,进而触发调用epoll_wait方法的线程,然后调用epoll_wait方法并返回。默认情况下为LT触发模式。
LT的优点:易于编码,未读完的数据下次还能继续读,不易遗漏
LT的缺点:在并发量高的时候,epoll_wait返回的就绪队列比较大,遍历比较耗时。因此LT适用于并发量小的情况

ET 模式即 Edge Triggered 工作模式,边沿触发,缓冲区剩余未读尽/未写完的数据不会导致epoll_wait返回,新的事件满足才会导致epoll_wait返回,边缘触发只有数据到来才触发,不管缓存区中是否还有数据。
ET的优点:并发量大的时候,就绪队列要比LT小得多,效率更高
ET的缺点:难以编码,需要一次读完,有时会遗漏

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

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值