nio入门

从JDK1.4开始,JDK提供了一套专门的类库支持非阻塞I/O,可以在java.nio包及其子包中找到相关的类和接口。由于这套API是新提供的I/O API,因此也叫New I/O,这就是JAVA NIO的由来。非阻塞IO API由3个主要部分组成:缓冲区(Buffers)、通道(Channels)和Selector

NIO服务端创建过程

  1. 打开ServerSocketChannel,用于监听客户端的连接,它是所有客户端连接的父管道

    1
    ServerSocketChannel acceptorSrv = ServerSocketChannel.open();
  2. 绑定监听端口,设置连接为非阻塞模式

    1
    2
    3
    acceptorSrv.socket().bind(new InetSocketAddress(InetAddress.getByName("IP"), port));

    acceptorSrv.configureBlocking(false);
  3. 创建Reactor线程,创建多路复用器并启动线程

    1
    2
    3
    Selector selector = Selector.open();

    new Thread(new ReactorTask()).start();
  4. 将ServerSocketChannel注册到Reactor线程的多路复用器Selector上,监听ACCEPT事件

    1
    SelectionKey key = acceptorSvr.register(selector, SelectionKey.OP_ACCEPT, ioHandler);
  5. 多路复用器在线程run方法的无限循环体内轮询准备就绪的Key

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    int num = selector.select();

    Set selectedKeys = selector.selectKeys();

    Iterator it = selectedKeys.iterator();

    while(it.hasNext()) {

    SelectionKey key = (SelectionKey) in.next();

    //...deal with I/O event...

    }
  6. 多路复用器监听到有新的客户端接入,处理新的接入请求,完成TCP三次握手,建立物理链路

    1
    SocketChannel channel = svrChannel.accept();
  7. 设置客户端链路的TCP参数

    1
    2
    3
    channel.configureBlocking(false);

    channel.socket().setReuseAddress(true);
  8. 将新接入的客户端连接注册到Reactor线程的多路复用器上,监听读操作

    1
    SelectionKey key = socketChannel.register(selector, SelectionKey.OP_READ, ioHandler);
  9. 异步读取客户端请求消息到缓冲区

    1
    int readNum = channel.read(receiveeBuffer);
  10. 对ByteBuffer进行编解码,如果有半包消息指针Reset,继续读取后续的报文,将解码成功的消息封装成Task,投递到业务线程池中,进行业务逻辑操作

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20

    Object message = null;

    while(buffer.hasRemain()) {

    byteBuffer.remark();

    message = decode(byteBuffer);

    if(message == null) {

    byteBuffer.reset();

    break;

    }

    messageLisk.add(message);

    }
  11. 将POJO对象encode成ByteBuffer,调用SocketChannel的异步write接口,将消息异步发送给客服端

    1
    socketChannel.write(buffer);