java十分钟速懂知识点——NIO

1、引子

    nio是java的IO框架里边十分重要的一部份内容,其最核心的就是提供了非阻塞IO的处理方式,最典型的应用场景就是处理网络链接。不少同窗提起nio都能提及一二,可是细究其背后的原理、思想每每就开始背书,说来讲去都是那么几句,其中很多人并不见的真的很理解。本人以前就属于此类,看了不少书和博客,可是大多数都只是讲了三件套和怎么使用,不多会很细致的讲背后的思想,那本次咱们就来扒一扒吧。
    不少博客描述nio都是这么说的:基于Reactor模式实现的多路非阻塞高性能的网络IO。那么咱们就从这个定义来分析,其中两个关键点:多路非阻塞和Reactor模式。(原本想把高性能也算进去,可是后来想一想这个应该算前二者的结果)下边咱们来分别搞懂这两块。html

2、网络IO模型

    多路非阻塞其实准确的名字叫作IO多路复用模型,其是linux五种网络模型之一,也是当前网络编程最常使用的模型之一。至于详细的介绍请参考博客:高性能IO模型浅析(这个里边只给出了4中,没有信号驱动IO,但讲的很赞,特别是图),这里仅做简要介绍和对比:java

  • 阻塞IO:java中老的bio即是这种模式,在接到事件(数据到达、数据拷贝完成等)前程序需阻塞等待。优势是编码简单,缺点是效率低,处理程序阻塞会致使cpu利用率很低。
  • 非阻塞IO:在未接到事件时处理程序一直主动轮询,这样处理程序无需阻塞,能够在轮询间歇去干别的,可是轮询会形成重复请求,一样浪费资源。之前java中实现的的伪异步模式就是采用这种思想。
  • IO复用模型:增长了对socket的事件监听器(selector),从而把处理程序和对应的socket事件解耦,所用的socket链接都注册在监听器,在等待阶段只有监听器会阻塞,处理线程从监听器获取事件对socket链接处理便可,并且一个处理线程能够对应多个链接(前两种通常都是一个socket链接起一个线程,这就是为何叫复用),有点是节省资源,因为处理程序可以被多个链接复用,所以少数的线程就能处理大量链接。缺点一样由于复用,若是是大量费时处理的链接(如大量链接上传大文件),很容易形成线程占满而致使新链接失败。
  • 信号驱动IO模型:在数据准别阶段无需阻塞,只需向系统注册一个信号,在数据准备好后,系统会响应该信号。该模型依赖于系统实现,并且信号通讯使用比较麻烦,所以java中未有对应实现。
  • 异步IO:与信号驱动IO很相似,并且在数据拷贝阶段(指数据从系统缓冲区拷贝至程序本身的缓冲区,其余模型改阶段程序都须要阻塞等待)一样能够异步处理。有点没必要多说,效率很高,缺点是依赖系统底层实现。目前不少语言都提供该模型的实现,jdk1.7以后一样在concurrent包中提供了。

    对比以上五种模型能够知道,IO复用模型从效率和实现成本综合而言目前是比较好的选择,这就是java基于该模型实现nio的根本缘由。上边提到了IO复用模型的实现思想,其实这种思想在其余语言中早已实现(如C++中听说流弊哄哄超10w行代码的ACE,自适配通讯环境,就采用了该模型),而且提出了一个叫Reactor的设计模式。react

3、Reactor模

    Reactor模式,翻译过来叫作反引器模式,其目的是在事件驱动的应用中,将一个请求的可以分离而且调度给应用程序。我相信大多数人都没看明白前一句的意思(书仍是要背的),说白了就是对于一个请求的多个事件(如链接、读写等),通过这种模式的处理,可以区分出来,而且分别交给对应的处理模块处理。废话很少说,来看下一个简图:
linux

     能够看到Reactor模式中组件有acceptor、dispatcher和handler(这里只是拿一种实现作个例子,真实的实现各有不一样),其中 acceptor中注册了各种事件,当链接有新的事件过来时,其会将事件交给dispatcher进行分发;dispatcher绑定了事件和对应处理程序handler的映射关系,当接到新事件时其会把事件分发到对应handler;而handler 负责处理对应事件,这块就是咱们的业务层了。
    从该模式咱们能够发现,对于 acceptor、dispatcher咱们每每只须要一个线程做为入口便可,由于其并不会有耗时处理,效率很高,而handler则根据须要起几个线程便可(多数时候使用一个线程池实现),这正是IO复用模型 望的效果。
     下边咱们会介绍NIO是如何实现该模式的,在此以前先介绍一下框架,其实除了NIO以外,基于JVM实现的还有其余Reactor框架,正好最近OSC牵头翻译了对应文档,有兴趣的能够看下: Reactor 指南

4、NIO

    NIO的细节就很少讲了,这里只介绍下三件套:
  • channel:管道,能够看作对流的封装,有点像pipe,不过其是全双工的。其好处是屏蔽了底层细节,不用关心流对应的是文件仍是网络,也不用关心链接怎么处理的,并且全双工,不用考虑输入流或输出流,你只用使用buffer对其进行读写就好了。
  • buffer:channel的好基友,底层就是个字节数组,不一样的是对其进行了封装,不只提供了对基本类型的支持,并且内部维持了读写位置(postion、limit、capacity、mark等),还提供了便捷的方法(clear、flip)。对channel的读写必须经过buffer。
  • selector:这个很少说了,若是前边认真看基本上就明白干啥的,就是Reactor模式中Acceptor的实现。

    再来看个简图吧: 编程

    基本上和Reactor能对应上,少了个dispatcher,这是因为jdk自己提供的nio比较基本,dispatcher通常都由咱们本身实现,而在我理解中,mina、netty这些框架很重要的一方面也是提供了该部分的实现。
设计模式

5、一个例子

    从《netty权威指南》上抄了个例子以及配图,并且代码没有客户端的,你们能够瞄一眼吧(为何没有?由于已经快一点了,我不想写了......):
服务器端时序图:数组

客户端时序图: 服务器

服务器端代码:
网络

package com.gj.netty.nio;

import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.ServerSocketChannel;
import java.util.Iterator;
import java.util.Set;

/**
 * Created by guojing on 2015/6/7.
 */
public class MultiplexerTimerServer implements Runnable {

    private Selector selector;
    private ServerSocketChannel servChannel;
    private volatile boolean stop;

    public MultiplexerTimerServer(int port) {
        try {
            selector = Selector.open(); //新建多路复用selector
            servChannel = ServerSocketChannel.open();   //新建channel
            servChannel.configureBlocking(false);  //设置非阻塞
            servChannel.socket().bind(new InetSocketAddress(port),1024); //端口、块大小
            servChannel.register(selector, SelectionKey.OP_ACCEPT);
            System.out.println("TimeServer is start, port:" + port);
        } catch (IOException e) {
            e.printStackTrace();
        }

    }

    public void run() {
        while (!stop){
            try {
                selector.select(1000);
                Set<SelectionKey> keys = selector.selectedKeys();
                Iterator<SelectionKey> ketIt = keys.iterator();
                SelectionKey key = null;
                while (ketIt.hasNext()){
                    key = ketIt.next();
                    ketIt.remove();
                    //处理对应key事件
                    handler(key);
                }
            } catch (IOException e) {
                e.printStackTrace();
            }

        }
    }

    private void handler(SelectionKey key){
        //根据key去除channel作对应处理
    }
}
View Code

6、最后一点啰嗦

    我想若是这会儿还有人记得标题必定会骂我了,丫的十分钟个屁啊,认真看完至少待半个小时。这个我只能说若是你以前已经理解了,那么画个10分钟瞟一眼无所谓的,若是之前没理解,若是本文能让你有了更好的理解,那么花多少时间更无所谓了,要知道懂了java的nio是量的积累,了解了其背后的思想和原理是质的积累。并且,我明明计划半小时写完的,这会已经2个多小时过去了......框架

相关文章
相关标签/搜索