用大白话来说一下BIO\NIO与I\O复用技术

BIO大家都不陌生,不熟悉的同学只需要了解,BIO处理socket时一个socket对应着一个线程来处理,而且BIO的线程处理socket时是阻塞的,之前的博客中也说到,阻塞意味着在socket里没数据进来的时候会线程会挂起来。

BIO用这种方式去处理socket,是因为在操作多个socket的时候,要一直保持有线程在做处理,才能知道当前的socket有没有数据进来,而没有数据线程没办法只能挂起来,而不能去做别的事情。这严重的导致了资源的浪费。

NIO的出现,是的操作IO的时候,操作系统可以通知线程,当前的socket有没有数据到来,在NIO模式下调用read方法时,若发现没有数据到来,会直接返回-1,被设置状态为未获取,再用while循环不断的去尝试获取数据(这里明显看出是同步非阻塞的)。

NIO的操作很明显比BIO要好的多,BIO挂起线程将会导致线程不断的挂起唤醒,内核态与用户态之间不断的切换,同时也会造成线程资源的浪费。

而NIO本身也有很大的缺点:NIO虽然不会挂起线程,但是会不断的轮询,尝试获取数据,导致CPU资源的浪费。而且这里NIO尝试获取数据,没有数据到来返回-1的过程,是对socket文件描述符的不断尝试得到的,也就意味着要不停的读取文件描述符,造成大量上下文的切换。这个时候I/O多路复用出现了。

I/O多路复用是一种操作系统的机制,意思是说程序会注册一组socket的文件描述符给操作系统,让操作系统去监听这些文件描述符是否有数据到了,有数据到就通知程序做处理。

这里可以看出来,NIO与IO复用其实是分开来的,NIO保证了一旦有数据,程序总是可以立刻做处理,不用挂起;IO复用保证了一旦有数据进入内核态,操作系统可以立刻监听到是哪个socket的数据到了,并通知线程做处理。所以一个是程序层面的,一个是系统层面的,两者配合使用使得到内核态的数据处理的快,切不耗费太多的系统资源。

NIO这里用的是轮询的方式做的非阻塞,系统的IO复用是如何实现的呢?

I/O复用有三种方式:select,poll,epoll:

select:select函数需要接收三个文件描述符数组,分别用于监听读,写,异常。调用select后,程序会Block住,直到一个事件发生了,或者等到最大1秒钟(tv定义了这个时间长度)就返回。然后依次遍历所有注册的socket,等待有事件到来。

缺点:select用于监听的数组,最大长度不得超过1024,这对高并发来说,是不可接受的;为了使用select要构造三个数组在内存中,每次调用select都要把这三个数组复制到内核态由操作系统使用;每次都要遍历所有的文件描述符,性能很差。

poll:poll相对于select有一定的优化,poll中文件描述符数组的大小不再有限制了,用链表数据结构存储文件描述符,而且文件描述符不再有三个,而是用一个pollfd数组;但每次同样要轮询所有的数组中fd才能返回。

epoll:在select和poll中,每次都需要把文件描述符从用户态复制到内核态去遍历,而epoll不同,epoll是先在内核建立一个由文件描述符指向的表,每次要监听的文件描述符有事件发生,就在表里标记一下(有状态的),调用epoll_wait时也只是将表里已经标记的文件描述符返回即可。可以看出,epoll代价相比较每次都对所有文件描述符做一次遍历的poll,select来说要好的多。

epoll分为水平触发与边沿触发:水平触发指的是,每次只要表里有状态为有数据的文件描述符,这些有数据的fd总是会在epoll_wait时被返回;边沿触发关系的是事件,边沿触发需要事件驱动,当文件描述符有新的事件产生,就返回。

水平触发相当于跑的比较快的poll,但边沿触发不同,边沿触发把处理数据的控制权交给了开发者,开发者可以设置一些事件,比如http请求,开发者可以在读完header的时候停下来,然后作出判断是否要做下一步处理。编程难度更大。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值