NIO网络相关基础知识

前面的文章NIO基础知识介绍了Java NIO的一些基本的类及功能说明,Java NIO是用来替换java 传统IO的,NIO的一些新的特性在网络交互方面会更加的明显。java

Java 传统IO的弊端

    “基于JVM来实现每一个通道的轮询检查通道状态的方法是可行的,但仍然是有问题的,检查每一个通道是否就绪是至少须要一次系统调用,执行的代价是很是昂贵的。同时这种检查不是原子的。列表中的每一个通道在检查以后状态变成就绪,但须要等到下一次轮询以前JVM是没法感知的。最糟糕的是,JVM除了不断遍历列表以外将别无选择。JVM没法在某通道就绪时直接获得通知。
    这就是为何监控多个socket链接的传统的java方案是:为每一个socket建立一个线程并使线程能够再read()调用中阻塞,直到数据可用。这实际上将每一个阻塞在对应socket上的线程当作了socket事件监控器,并将JVM的线程调度当作了事件通知。可是线程的阻塞和JVM的线程调用都是为了这种目的而设计的。当线程数量增加失控时,JVM为了管理这些线程,致使程序性能下降。”--摘自《JAVA NIO》
上述两段话摘自《JAVA NIO》第四章-选择器中,主要阐述了java 传统NIO的基本实现和弊端。经过上述文字咱们也知道了,提升传统IO的性能能够从两方面入手:1.减小线程数量 2.实时获取IO事件react


I/O模型

传统I/O为了能实时获取I/O事件,因此才会给每一个socket链接分配一个线程用于监控socket事件,由于如何获取I/O事件通知是关键。所以调整JVM的I/O模型是提升I/O性能关键。
咱们来了解下I/O模型的类型:segmentfault

  1. 单线程阻塞I/O模型

    只能同时处理一个客户端请求,而且在I/O操做上是阻塞的,服务端线程会一直等待I/O操做完成,不会作其余的事情。服务端读取客户端数据时要等待客户端发送数据而且操做系统内核复制到用户进程中以后才解除阻塞状态;服务端写数据回客户端是要等待用户进程将数据写入内核并发送到客户端后才解除阻塞状态。单线程阻塞I/O模型没法同时处理多个链接,只能串行处理链接。整个运行过程都只有一个线程,服务端系统资源消耗较小,但并发能力低。安全


  2. 多线程阻塞I/O模型

    利用多线程机制为每一个客户端分配一个线程,每一个线程执行读取客户端的数据或数据成功写入客户端后才解除阻塞状态。支持多个客户端并发响应,处理能力获得提升。系统资源消耗较大,多线程之间会产生线程切换成本,同时为了实现线程安全引入线程同步机制导成程序结构复杂。网络


  3. 单线程非阻塞I/O模型

    在调用读写接口后当即返回,而不会进入阻塞状态;基于事件检测机制获取到事件发生,进行对应事件的I/O操做。
    事件检测方式:多线程

    • 应用程序遍历套接字的事件检测

      服务端程序会保存一个socket套接字链接列表,应用层线程对socket套接字列表轮询尝试读取或写入。若是尝试失败,则在下一个循环再继续尝试。查询每一个socket套接字都至少须要一次系统调用,并且没法当即获取socket套接字状态。同时应用程序除了要遍历socket套接字列表以外,还须要处理数据的拼接,实际会占用较多的CPU资源图片描述并发


    • 内核遍历套接字的事件检测

      将遍历socket列表的工做交给操做系统内核,应用层向发去操做系统内核请求读写列表。操做系统内核会遍历全部套接字并生成对应的可读列表readList和可写列表writeList返回给应用程序。应用程序获取到具体socket,对每一个socket进行相应的I/O操做。图片描述
      应用程序向内核请求读写列表,内核遍历全部的套接字并生成对应的可读列表readList和可写列表writeList。readList标明了每一个套接字是否可读,例如套接字二、套接字3的值为1表示可读,套接字1的值为0表示不可读;一样,writeList表示套接字是否可写。socket


    • 内核基于事件回调的事件检测

      遍历套接字列表是个效率比较低的方式,不管是在内核层仍是在应用层。操做系统是可以获取到I/O事件操做完成的事件,基于回调函数机制和操做系统的I/O操做控制实现事件检测机制。函数

      1. 基于回调机制的彻底套接字可读可写列表

        图片描述用可读列表readList和可写列表writeList标记读写事件,套接字的数量与readList和writeList两个列表的长度同样。readList元素为1表示可读,writeList元素为1表示可写。当客户端发送数据到服务端,内核完成从网卡复制数据调用回调函数将对应套接字readList中的元素置为1;若套接字已经作好写操做准备,内核会将套接字对应writeList中的元素置为1。应用程序发送请求读、写事件列表,内核会把包含readList和writeList的事件列表返回给应用程序,应用程序分别遍历列表,进而记性读写操做。性能


      2. 基于回调机制的部分套接字事件列表

        若是套接字数量变大,事件列表传输也是不小的开销。可让应用层告诉内核每一个套接字感兴趣的事件,当客户端发送数据过来时,内核完成从网卡复制后即调用回调函数将套接字相关信息封装成可读事件event放到事件列表中;一样,内核发现网卡可写时会将套接字相关信息封装成可写事件event添加到事件列表中,事件列表中只有处于ready状态的那部分套接字对应的事件。图片描述java NIO便是采用这种事件检测机制,在客户端链接大多数处于活跃的状况下,服务端只用一个线程一直循环处理这些链接,很好地利用了I/O操做的阻塞时间,提升了线程执行效率。


      3. 多线程非阻塞I/O模型

        单线程非阻塞I/O已经大大提升了机器的执行效率,在多核机器上为了更好的提升CPU的使用率能够引入多线程。引入reactor线程模型,每一个线程处理不一样类型的事件链接。


NIO网络通讯

java NIO性能较java传统IO有较大提高主要是在网络交互中,经过Selector来管理客户端的链接,应用程序将客户端socket链接注册到Selector对象上。当客户端socket链接有数据准备就绪或链接准备好写操做时,应用程序经过selector获取到对应socket通道(Channel),应用程序经过Channel对socket进行读写操做。
NIO中服务端一个线程能够同时与多个客户端链接进行读写交互;而传统IO则是服务端的一个线程只能同时处理一个客户端,I/O操做时,线程处于阻塞状态。
Selector是SelectableChannel的多路复用选择器,用于监控SelectableChannel的IO情况的。在Selector定义为SelectableChannel定义了四种事件类型:
SelectionKey.OP_READ        读事件
SelectionKey.OP_WRITE       写事件
SelectionKey.OP_CONNECT    接收到客户端的链接事件
SelectionKey.OP_ACCEPT      接收到客户端的请求事件
多个事件之间用“|”链接
当服务端线程注册事件发生时,操做系统内核会经过回调函数将该事件放到读写事件列表中。JVM经过Selector的select方法会获取到已经注册到Selector对象上的事件列表。若没有事件发生select方法会一直阻塞知道有注册的时间发生才会把发生的时间返回给Selector对象。

相关文章
相关标签/搜索