BIO(IO)与NIO——NIO

278 阅读5分钟

区别

从工作原理上来讲

  • BIO是面向流的
  • NIO是面向缓冲区和channel的

BIO在处理客户端请求的时候是采用阻塞式的方式(请求来了在超时时间内如果没有得到结果就一直等在那里)另开了一个线程出来为客户端服务,有多少个客户端就有多少个线程,而线程在计算机中又是一个十分珍贵的资源,一个线程最起码占用1M的内存空间,也就是说一个1G的内存撑死了只能处理1000个请求,这显然是十分浪费资源的。而NIO是面向缓冲区的,一个线程中可以负责多个客户端发出的请求,这无疑大大增加了线程的利用率。 这里再说明一下阻塞非阻塞和同步异步相关的概念 比如说你去商场买衣服

  • 同步和异步关注的是消息通信机制 同步:你跟老板说我要买李宁的运动外套,老板就去找,找到了就直接给你 异步:你跟老板说我要买李宁的运动外套,老板找到了就打电话通知你
  • 阻塞和非阻塞关注的是程序在等待调用结果(消息,返回值)时的状态 阻塞:你跟老板说我要买李宁的运动外套,在老板没有给出一个结果(有或者没有)之前,你(程序)啥事情都不干,这对你来说就是阻塞的 非阻塞:你跟老板说我要买李宁的运动外套,你(程序)不在这家店傻等老板给你找外套,而是去超市逛逛,这对你来说就是非阻塞的

说明:在上面的场景中,同步异步是老板通知你的方式,至于期间你要干什么(去超市还是在这傻等)老板不关心;而阻塞非阻塞中,是你(程序)来决定在老板找东西的时候你是选择傻等还是要去超市逛逛。所以阻塞,非阻塞同步,异步排列组合后可以有四种情况 这里使用一个场景来形象地说明,假设你和你女朋友约好了要去逛街,你要到她家门口等着,而你女朋友还在卧室化妆,这个时候,女朋友和你之间可能会有这样几种情况

  1. 同步阻塞:你就站在门口啥都不干一动不动,等你女朋友化完妆一开门就出发逛街。
  2. 同步非阻塞:你敲开隔壁小姐姐的家门,在隔壁小姐姐家玩一会儿就去敲你女朋友的家门问问她有没有好,如果没有好就再去隔壁小姐姐家再玩一会儿,然后再去敲你女朋友家的门问问她有没有好......直到你女朋友准备好了,然后出发逛街。
  3. 异步阻塞:你和你女朋友约定好了,如果你女朋友化好妆,就打你的电话通知你,然后你女朋友开始化妆,你就站在她家门口啥都不干,就是傻等电话,直到你女朋友打你电话说弄完了,你们就出发逛街。
  4. 异步非阻塞:你和你女朋友约定好了,如果你女朋友化好妆,就打你的电话通知你,然后你女朋友开始化妆,然后你就撬开了隔壁小姐姐家的房门,和小姐姐玩,直到你女朋友打给你说可以走了,你才去和女朋友出发逛街。

以上几种情况第三种很少用得到,第四种情况用的比较多。

NIO的工作流程

在NIO中有三个重要的组件:

  • selector----------------------选择器
  • channel----------------------通道
  • buffer-------------------------缓冲区 SocketServerChannel:是负责绑定端口号和建立连接 SocketChannel:ServerSocketChannel.accept()
  1. 服务器启动,产生一个ServerSocketChannel,ServerSocketChannel向Selector关注客户端连接事件(op_accept)。服务器就位
  2. 客户端A向服务器发起连接,selector会产生一个客户端连接事件通知服务器端的ServerSocketChannel和客户端进行三次握手建立连接。
  3. 连接建立后服务端会启动一个SocketChannelA,而SocketChannelA会向Selector关注读事件(op_read)。
  4. 客户端数据准备就位后,开始向服务端发送数据,这时候Selector会产生一个读事件,这时候Selector发现这个读事件是被SocketChannelA所关注的,于是就通知SocketChannelA并传递数据过去。
  5. SocketChannelA接收到数据后,会首先将数据写入Buffer(SocketChannel.read())再由服务端处理程序去读取Buffer中的数据(Buffer.read())并返回客户端需要的数据(Buffer.write())。
  6. SocketChannelA从Buffer中获取到服务端给客户端的最终数据,然后将数据发送给客户端(SocketChannel.write())。 一个完整的客户端到服务器,服务端反数据给客户端的NIO流程大致就这样结束了,下面是一张简图: nio流程简图.png 在这一通流程中有一个很重要的角色就是Buffer,它实际上是一块普通的内存,兼顾着读和写的两种操作,具体是怎么实现的呢,看下图: image.png 在buffer处于写的状态的时候上图中position就是当前数据写入的位置,position一开始是指向顶部,每写一段就下移一格,capacity是指buffer的大小,limit指向底部。 在buffer处于读的状态的时候limit会指向原先写的position的位置,而position指向顶部,capacity依旧是buffer最大的位置。 切换读写状态的方法是调用flip()