1 概述
与Socket类和ServerSocket,NIO也提供了SocketChannel和ServerSocketChannel两种不同的套接字通道实现。这两种新增的通道都支持阻塞和非阻塞两种模式。阻塞模式使用简单,但性能和可靠性都不好,非阻塞模式则正好相反。一般来说,低负载、地并发的应用程序可以选择同步阻塞I/O降低变成复杂度;对于高负载、高并发的网络应用,需要使用NIO的非阻塞模型进行开发。
2 NIO类库简介
新的输入/输出(NIO)库是在JDK 1.4中引入的。NIO弥补了原来同步阻塞I/O的不足,它在标准java代码中提供了高速、面向块的I/O。通过定义包含数据的类,以及通过以块的形式处理这些数据,NIO不用使用本机代码就可以利用低级优化,这是原来的I/O无法做到的。
2.1 缓冲区Buffer
首先介绍缓冲区(Buffer)的概念。Buffer是一个对象,它包含一些要写入或者要读出的数据。在NIO类库中加入Buffer对象,体现了新库与原I/O的一个重要区别。在面向流的I/O中,可以将数据直接写入或者将数据直接读到Stream对象中。
在NIO库中,所有数据都是用缓冲区处理的。在读取数据时,它是直接读到缓冲区中;在写入数据时,也是写入到缓冲区中。任何使用访问NIO中的数据,都是通过缓冲区进行操作。
缓冲区实质上是一个数组。通常它是一个字节数组(ByteBuffer),也可以使用其他类型的数据。但是一个缓冲区不仅仅是一个数组,缓冲区提供了对数据的结构化访问以及维护读写位置等信息。
最常用的缓冲区是ByteBuffer,一个ByteBuffer提供了一组功能用于操作byte数据。除了ByteBuffer,还有其他的一些 缓冲区,实际上,每一种java基本类型(除了boolean类型)都对应有一种缓冲区,具体如下:
1、ByteBuffer:字节缓冲区
2、CharBuffer:字符缓冲区
3、ShortBuffer:短整型缓冲区
4、IntBuffer:整型缓冲区
5、LongBuffer:长整型缓冲区
6、FloatBuffer:浮点型缓冲区
7、DoubleBuffer:双精度浮点型缓冲区
缓冲区的类图继承关系如下:

每一个Buffer类都是Buffer接口的一个子实例。除了ByteBuffer,每一个Buffer类都有完全一样的操作,只是它们所处理的数据类型不一样。因为大多数标准I/O操作都是用ByteBuffer,所以它在具有一般缓冲区的操作之外还提供了一些特有的操作,以方便网络读写。
2.2 通道Channel
Channel是一个通道,就像自来水管一样,网络数据通过Channel读取和写入。通道与流的不同至于在于通道时双向的,流只是在一个方向上移动(一个流必须是InputStream或OutputStream的子类),而通道可用于读、写或者二者同时进行。
因为Channel是全双工的,所以它可以比流更好的映射底层操作系统的API。特别是在UNIX网络编程模型中,底层操作系统的通道全都是全双攻的,同时支持读写操作。
Channel的类图继承关系如下:

自顶向下看,前三层主要是Channel接口,用于定义它的功能,后面是一些具体的功能类(抽象类)。从图中可以看出,实际上Channel分为两大类:用于王立国读写的SelectableChannel和用于文件操作的FileChannel。
2.3 多路复用器Selector
多路复用器Selector,它是Java NIO编程的基础,熟练地掌握Selector对于NIO编程至关重要。多路复用器提供选择已经就绪的任务的能力。简单来说,Selector会不断地轮询注册在其上的Channel,如果某个Channel上面发生读写事件,这个Channel就处于就绪状态,会被Selector轮询出来,然后通过SelectionKey可以获取就绪Channel的集合,记性后续的I/O操作。
一个多路复用器Selector可以同时轮询多个Channel,由于JDK使用了epoll代替传统的select实现,所以它并没有最大连接句柄1024/2048的限制。这样就意味着只需要一个Selector线程负责Selector的轮询,就可以接入成千上万个客户端。
3 NIO服务端序列图
NIO服务端通信序列图如下:

第一步:打开ServerSocketChannel,用于监听客户端的连接,它是所有客户端连接的父管道,示例代码如下:
ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
第二步:绑定监听端口,设置连接为非阻塞模型,示例代码如下:
int port = 8080;
serverSocketChannel.socket().bind(new InetSocketAddress(InetAddress.getByName("IP"), port));
serverSocketChannel.configureBlocking(false);
第三步:创建Reactor线程,创建多路复用器并启动线程,示例代码如下:
//创建Reactor线程
new Thread(new ReactorTask()).start();
//创建多路复用器
Selector selector = Selector.open();
第四步:将ServerSocketChannel注册到Reactor线程的多路复用器Selector上,监听ACCEPT事件,示例代码如下:
SelectionKey key = serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT, ioHandler);
第五步:多路复用器在线程run()方法的无限循环体内轮询准备就绪的key,示例代码如下:
int num = selector.select();
Set<SelectionKey> selectededKeys = selector.selectedKeys();
Iterator<SelectionKey> iterator = selectededKeys.iterator();
while(iterator.hasNext()){
SelectionKey key = (SelectionKey) iterator.next();
//do something
}
第六步:多路复用器监听到有新的客户端接入,处理新的接入请求,完成TCP三次握手,建立物理链路,示例代码如下:
SocketChannel channel = serverSocketChannel.accept();
第七步:设置客户端链路为非阻塞模式,示例代码如下:
channel.configureBlocking(false);
channel.socket().setReuseAddress(true);

最低0.47元/天 解锁文章
1149

被折叠的 条评论
为什么被折叠?



