NIO的理解

来源:互联网 发布:网络语言爸爸什么意思 编辑:程序博客网 时间:2024/03/28 21:25

1、简介

       新的输入/输出库是JKD1.4中引入的。NIO弥补了原来同步阻塞IO的不足,它在标准Java代码中提供了高速的、面向块的I/O.通过定义包含数据的类,以及通过以块的形式处理这些数据,NIO不用使用本机代码就可以利用低级优化,这是原来的IO包无法做到的。

下面是一些NIO的概念:

缓冲区Buffer

        Buffer是一个对象,它包含一些要写入或者要读出的数据。在NIO库中,所有数据都是用缓冲区处理的。在读取数据时,它是直接读到缓冲区中的;在写入数据时,写到缓冲区中。任何时候访问NIO中的数据,都是通过缓冲区进行操作。

       Buffer其实是一个数组,包括字节缓冲区、字符缓冲区等。

通道Channel

       Channel是一个通道,网络数据可以通过它读取和写入数据。这里要说一下它和传统IO中流的区别,流是单向的,它只是一个方向上的移动,它要么是InputStream的子类,要么是OutputStream的子类;而channel是双向的,可以同时进行读和写的操作,而不阻塞。

       channel大体上分为两类:用于网络读写的SelectableChannel和用于文件操作的FileChannel。

多路复用选择器Selector

      Selector是Java NIO编程的基础,它提供选择已经就绪的任务的能力。Selector会不断的轮询注册在其上面的Channel,只要channel上面有新的TCP连接接入,读和写事件,这个channel就处于就绪状态,它就会被Selector轮询出来,再通过SelectionKey获取就绪channel的集合,进行后续的IO操作。

       一个Selector可以同时轮询多个channel,它使用了epoll()代替传统的select实现,使得它没有最大连接句柄的限制,所以说只要开启一个线程来运行selector的轮询,就可以接入成千上万的客户端。

       下面我们结合代码看一下NIO的一个执行流程:

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

ServerSocketChannel   channelSvr=new ServerSocketChannel.open();
2、绑定监听端口,设置连接为非阻塞模式,代码如下:

channelSvr.socket().bind(new InetSocketAddress(InetAddress.getByName("IP"),port));channelSvr.configureBlocking(false);

3、创建Reactor线程,创建多路复用器并启动线程,代码如下:

Selector selector=Selector.open();New Thread(new ReactorTask()).start();

4、将ServerSocketChannel注册到Reactor线程的多路复用器Selector上,监听ACCEPT事件,代码如下:

SelectionKey key=accpetorSvr.register(selector,SelectionKey.OP_ACCEPT,ioHandler);

5、多路复用器在线程run方法的无限循环体内轮询准备就绪的Key,代码如下:

Set selectedKeys=selector.selectedKeys();Iterator it=selectedKeys.iterator();while(it.hashNext){SelectionKey key=(SelectionKey)it.next();}

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

Selector selector=Selector.open();New Thread(new ReactorTask()).start();

7、多路复用器在线程run方法的无限循环体内轮询准备就绪的Key,代码如下:

int num=selector.select();Set selectedKeys=selector.selectedKeys();Iterator it=selectedKeys.iterator();while(it.hashNext()){SelectionKey key=(SelectionKey)it.next();}

8、接收connect 事件进行处理,示例代码如下:

if(key.isConnectable())

9、判断连接结果,如果连接成功,注册读事件到多路复用器,示例代码如下:

if(channel.finishConnect());

10、注册读事件到多路复用器,示例代码如下:

clientChannel.register(selector,SelectionKey.OP_READ,ioHandler);

11、异步读客户端请求消息到缓冲区,示例代码如下:

int readNumer=channel.read(receivedBuffer);


12、对ByteBuffer 进行编码,如果有半包消息接收缓冲区Reset,继续读取后续的报文,将解码成功的消息封装成Task,投递到业务线程池中,进行业务逻辑编排示例代码如下:

Object message=null;while(buffer.hashRemain()){byteBuffer.mark();Object message=decode(byteBuffer);if(message==null){byteBuffer.reset();break;}}if(!byteBuffer.hashRemain())byteBuffer.clear();elsebyteBuffer.compact();if(messageList!=null&!messageList.isEmpty()){for(Object messageE:messageList)handlerTask(messageE);}

13、将POJO对象encode成ByteBuffer,调用SocketChannel的异步write接口,将消息异步发送给客户端,示例代码如下:

socketChannel.writer(buffer);



0 0