一、简介
java传统I/0中一个核心的概念是流(Stream),是面向流的编程。而且数据的传输是以字节为单位的。这一块相信大家相对会比较熟悉,就不做详细介绍了。java传统的I/O又叫做阻塞的I/O,这只要是相对于网络编程来说的。
java 在1.4引入了NIO。NIO中拥有3个核心概念:Selector,Channel与Buffer。而与传统I/O不同的是,NIO是面向块来编程的,也就是数据的传输的单位并不是字节流,而是基于字节数组,也就是上面提到的Buffer。
二、为什么要引入NIO?
Java既然在1.4引入了NIO,那肯定是传统的IO存在很多的不足。
java传统IO的不足:
- 数据的传输是以字节为单位的。然而底层操作系统都支持块(block)来传输了。这种以字节为单位的传输势必会造成一定的性能影响。此外,它不能前后移动流中的数据。如果需要前后移动从流中读取的数据,需要先将它缓存到一个缓冲区,不够灵活。
- 传统的IO是阻塞的。当一个线程调用read() 或 write()时,该线程被阻塞,直到有一些数据被读取,或数据完全写入。该线程在此期间不能再干任何事情了。
- 基于传统的IO的网络编程,每有一个客户端发起链接,服务端就会起一个线程来进行IO处理。当客户端很多时,服务端就会产生大量的线程,而且这些线程并不会一直处于运行状态,这样就会产生了大量的资源浪费。还有服务端能够分配的线程资源有限,不可能无限的分配线程资源来处理客户端的链接。再者大量的线程上下文的切换也会带来大量的开销影响新能。
基于以上这些原因,java引入了NIO。
三、Java NIO
Java NIO主要由三部分组成:Selector,Channel与Buffer。
1. Buffer
Buffer本身就是一块内存,底层实现上它实际上是一个数组,数组的读写都是通过Buffer来实现的。
除了数组之外,Buffer还提供了对于数据的结构化访问方式,并且可以追踪到系统的读写过程。
Java中的7中原生数据类型都有各自对应的Buffer类型,如IntBuffer,LongBuffer,CharBuffer,ByteBuffer等等,并没有BooleanBuffer类型。
关于Nio Buffer中的3个重要状态属性的含义:position,limit与capacity。position,limit与capacity存在以下关系:
0 <= mark <= position <= limit <= capacity
Buffer提供的一些API 方法,底层实现使用的就是这几个状态属性指针。API就不具体介绍了,感兴趣的可自行查看Java API。
有一个特别值得注意的是,Buffer在进行读写模式切换时,必须要调用它的flip方法。至于为什么,这个就需要理解上面提到的几个变量的含义了。
2. Channel
Channel指的是可以向其写入数据或是从中读取数据的对象,它类似于java.io中最为核心的一个概念是流(Stream)。只不过传统IO中流(stream)要么是输入流(只能从中读取数据),要么是输出流(只能向其写出数据),不可能既是输入流又是输出流。而Channel则不同,它是一种全双工的,既可以从中读取数据,也可以向其写出数据。
并且,所有数据的读写都是通过Buffer来进行的,永远不会出现直接向Channel写入数据的情况,或是直接从Channel读取数据的情况。
可以这么理解,Channel就是用来传输数据的,而Buffer就是Channel传输数据的载体。
3. Selector
该组件主要会被网络编程而用到。Selector是Java的非阻塞IO实现的关键。Selector在进行select操作时,操作系统会找出已经准备好的IO进行处理,没有准备好的不处理也不会产生阻塞。所以只需要一个线程,不断循环的使用Selector对注册其上的Channel进行select操作,对准备好的IO进行处理。这样就实现了一种非阻塞的IO。并且在这种模式下,服务端只需要一个线程就能处理多个客户端发起的连接。这也就解决了前面说的传统IO在连接的客户端太多而产生服务端线程过多的问题。
下面有两个原理图,分别讲述了传统IO和NIO的网络编程模型。
传统IO网络编程模型图:
NIO网络编程模型图
下面在给出NIO网络编程服务端代码(客户端类似):
public class NioTest {
public static void main(String[] args) throws Exception {
int[] ports = new int[5];
ports[0] = 5000;
ports[1] = 5001;
ports[2] = 5002;
ports[3] = 5003;
ports[4] = 5004;
Selector selector = Selector.open();
for (int i = 0; i < ports.length; i++) {
ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
// 配置非阻塞的方式
serverSocketChannel.configureBlocking(false);
ServerSocket serverSocket = serverSocketChannel.socket();
InetSocketAddress address = new InetSocketAddress(ports[i]);
serverSocket.bind(address);
// 通道注册到Selector上
serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);
System.out.println("监听端口: " + ports[i]);
}
while (true) {
int numbers = selector.select();
System.out.println("numbers: " + numbers);
if (numbers <= 0)
continue;
Set<SelectionKey> selectionKeys = selector.selectedKeys();
System.out.println("selectedKeys: " + selectionKeys);
Iterator<SelectionKey> iter = selectionKeys.iterator();
while (iter.hasNext()) {
SelectionKey selectionKey = iter.next();
// 事件是accept
if (selectionKey.isAcceptable()) {
ServerSocketChannel serverSocketChannel = (ServerSocketChannel) selectionKey.channel();
SocketChannel socketChannel = serverSocketChannel.accept();
socketChannel.configureBlocking(false);
// 连接好了,将这个读通道注册到Selector上
socketChannel.register(selector, SelectionKey.OP_READ);
System.out.println("获得客户端连接: " + socketChannel);
}
if (selectionKey.isReadable()) {
SocketChannel socketChannel = (SocketChannel) selectionKey.channel();
int bytesRead = 0;
while (true) {
ByteBuffer byteBuffer = ByteBuffer.allocate(512);
byteBuffer.clear();
int read = socketChannel.read(byteBuffer);
if (read <= 0) {
break;
}
byteBuffer.flip();
socketChannel.write(byteBuffer);
bytesRead += read;
}
System.out.println("读取: " + bytesRead + ", 来自于: " + socketChannel);
}
iter.remove();
}
}
}
}
public class NioTest {
public static void main(String[] args) throws Exception {
int[] ports = new int[5];
ports[0] = 5000;
ports[1] = 5001;
ports[2] = 5002;
ports[3] = 5003;
ports[4] = 5004;
Selector selector = Selector.open();
for (int i = 0; i < ports.length; i++) {
ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
// 配置非阻塞的方式
serverSocketChannel.configureBlocking(false);
ServerSocket serverSocket = serverSocketChannel.socket();
InetSocketAddress address = new InetSocketAddress(ports[i]);
serverSocket.bind(address);
// 通道注册到Selector上
serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);
System.out.println("监听端口: " + ports[i]);
}
while (true) {
int numbers = selector.select();
System.out.println("numbers: " + numbers);
if (numbers <= 0)
continue;
Set<SelectionKey> selectionKeys = selector.selectedKeys();
System.out.println("selectedKeys: " + selectionKeys);
Iterator<SelectionKey> iter = selectionKeys.iterator();
while (iter.hasNext()) {
SelectionKey selectionKey = iter.next();
// 事件是accept
if (selectionKey.isAcceptable()) {
ServerSocketChannel serverSocketChannel = (ServerSocketChannel) selectionKey.channel();
SocketChannel socketChannel = serverSocketChannel.accept();
socketChannel.configureBlocking(false);
// 连接好了,将这个读通道注册到Selector上
socketChannel.register(selector, SelectionKey.OP_READ);
System.out.println("获得客户端连接: " + socketChannel);
}
if (selectionKey.isReadable()) {
SocketChannel socketChannel = (SocketChannel) selectionKey.channel();
int bytesRead = 0;
while (true) {
ByteBuffer byteBuffer = ByteBuffer.allocate(512);
byteBuffer.clear();
int read = socketChannel.read(byteBuffer);
if (read <= 0) {
break;
}
byteBuffer.flip();
socketChannel.write(byteBuffer);
bytesRead += read;
}
System.out.println("读取: " + bytesRead + ", 来自于: " + socketChannel);
}
iter.remove();
}
}
}
}