BIO与NIO

未来已来2018-11-25 11:12

此文已由作者赵计刚薪授权网易云社区发布。

欢迎访问网易云社区,了解更多网易技术产品运营经验。


一、BIO

1、服务端程序:

 View Code
  • 服务端使用8081端口打开服务,不断接入客户端请求,每接入一个请求,都创建一个线程来处理这个请求。
  • 处理逻辑:读取客户端传来的信息,之后想客户端写信息。

2、客户端程序:

 View Code
  • 客户端创建socket去连接服务端,之后想服务端写信息,并且读取服务端传来的信息。

 

服务端阻塞的几个点:

  • serverSocket.accept();//如果没有客户端接入,主线程阻塞在这里
  • 输入流InputStream的read操作也会阻塞:直到“有数据可读”或者“可用数据读取完毕”或者“发生异常”
  • 输出流OutputStream的write操作也会阻塞:直到“所有要发送的字节全部写入”或者“发生异常”

 

二、NIO

1、服务端程序

 View Code
  • nio三组件:
    • Buffer:用于存取数据,最主要的是ByteBuffer
      • position:下一个将被操作的字节位置
      • limit:在写模式下表示可以进行写的字节数,在读模式下表示可以进行读的字节数
      • capacity:Buffer的大小
    • Channel:用于传输数据,与Buffer相互配合
    • Selector:多路复用器。轮询注册在其上的Channel,当发现某个或者多个Channel处于“就绪状态”后(有新的TCP链接接入、读、写事件),从阻塞状态返回就绪的Channel的SelectionKey集合,之后进行IO操作。
  • selector.select():阻塞,直到有“就绪事件”发生或抛出异常

2、客户端程序

 View Code

 

附:ByteBuffer使用JVM堆内存和使用堆外内存的区别:(图片来自尚硅谷的NIO教程)


  • 使用堆内存:
    • 应用程序将数据写入用户地址空间(JVM)中,之后将用户地址空间中的数据拷贝到内核地址空间(操作系统)
  • 使用堆外内存:
    • 直接在物理内存开辟空间,应用程序直接将数据写入物理内存,之后操作系统将其写入硬盘 - “零拷贝”

 

三、BIO与NIO的比较

1、线程数

  • BIO:一个客户端连接就要使用一个服务端线程来进行处理
    • 可能会有大量的想爱你成处于休眠状态,只是等待输入或输出(阻塞)
    • 为每个线程分配调用栈,大约1M,服务端内存吃紧
    • 即使服务端内存很大,线程数太大会导致线程上下文切换浪费大量时间
  • NIO:一个服务端线程操作一个Selector,就可以处理成千上万的客户端连接

2、阻塞情况

  • BIO:读、写、接受连接都会发生阻塞
  • NIO:只有Selector.select()会阻塞,其实是等待Channel上的“就绪事件”

3、面向对象

  • BIO:面向流
  • NIO:面向Buffer

4、适合点

  • BIO:如果你有少量的连接使用非常高的带宽,一次发送大量的数据,也许典型的IO服务器实现可能非常契合
  • NIO:如果需要管理同时打开的成千上万个连接,这些连接每次只是发送少量的数据,例如聊天服务器,实现NIO的服务器可能是一个优势。

 

四、Reactor模型


主从模型:

  • 主线程池:
    • 全部由NIO线程组成,使用线程池是因为担心性能问题
    • 接收客户端连接请求,可能包含认证
    • 接收到客户端的连接请求并处理完成(比如认证)后,将创建出来的SocketChannel(查看上边的NIOServer类)注册到次线程池的某一条NIO线程上,之后这条NIO线程进行IO操作。
  • 次线程池:
    • 全部由NIO线程组成
    • 进行IO操作(编解码、业务逻辑等)