1.Java NIO 由以下几个核心部分组成:
- Channels(通道)
- Buffers(缓冲区)
- Selectors(选择器)
虽然Java NIO 中除此之外还有很多类和组件,Channel,Buffer 和 Selector 构成了核心的API。其它组件,如Pipe和FileLock,只不过是与三个核心组件共同使用的工具类。
(1)Channel 和 Buffer
基本上,所有的 IO操作在NIO 中都从一个Channel 开始。Channel 有点像 流。 数据可以从Channel读到Buffer中,也可以从Buffer 写到Channel中。这里有个图示:
Channel和Buffer有好几种类型。
下面是JAVA NIO中的一些主要Channel的实现:
- FileChannel
- DatagramChannel
- SocketChannel
- ServerSocketChannel
正如你所看到的,这些通道涵盖了UDP 和 TCP 网络IO,以及文件IO。
与这些类一起的有一些有趣的接口,但为简单起见,我尽量在概述中不提到它们。本教程其它章节与它们相关的地方我会进行解释。
以下是Java NIO里主要的Buffer实现:
- ByteBuffer
- CharBuffer
- DoubleBuffer
- FloatBuffer
- IntBuffer
- LongBuffer
- ShortBuffer
这些Buffer覆盖了你能通过IO发送的基本数据类型:byte, short, int, long, float, double 和 char。
Java NIO 还有个 MappedByteBuffer,用于表示内存映射文件, 我也不打算在概述中说明。
(2)Selector
Selector允许单线程处理多个 Channel。如果你的应用打开了多个连接(通道),但每个连接的流量都很低,使用Selector就会很方便。例如,在一个聊天服务器中。
这是在一个单线程中使用一个Selector处理3个Channel的图示:
要使用Selector,得向Selector注册Channel,然后调用它的select()方法。这个方法会一直阻塞到某个注册的通道有事件就绪。一旦这个方法返回,线程就可以处理这些事件,事件的例子有如新连接进来,数据接收等。
2. NIO使用代码示例:
java NIO服务端和客户端代码实现,为了更好地理解java NIO,下面贴出服务端和客户端的简单代码实现:
服务端:
1 package com.himi.demo2; 2 3 import java.io.IOException; 4 import java.net.InetSocketAddress; 5 import java.nio.ByteBuffer; 6 import java.nio.channels.SelectionKey; 7 import java.nio.channels.Selector; 8 import java.nio.channels.ServerSocketChannel; 9 import java.nio.channels.SocketChannel; 10 import java.util.Iterator; 11 12 /** 13 * NIO 服务端 14 * 15 * @author hebao 16 */ 17 public class NIOServer { 18 // 通道管理器 19 private Selector selector; 20 21 /** 22 * 获得一个ServerSocket通道,并对该通道做一些初始化的工作 23 * 24 * @param port 25 * 绑定的端口号 26 * @throws IOException 27 */ 28 public void initServer(int port) throws IOException { 29 // 获得一个ServerSocket通道 30 ServerSocketChannel serverChannel = ServerSocketChannel.open(); 31 // 设置通道为非阻塞 32 serverChannel.configureBlocking(false); 33 // 将该通道对应的ServerSocket绑定到port端口 34 serverChannel.socket().bind(new InetSocketAddress(port)); 35 // 获得一个通道管理器 36 this.selector = Selector.open(); 37 // 将通道管理器和该通道绑定,并为该通道注册SelectionKey.OP_ACCEPT事件,注册该事件后, 38 // 当该事件到达时,selector.select()会返回,如果该事件没到达selector.select()会一直阻塞。 39 serverChannel.register(selector, SelectionKey.OP_ACCEPT); 40 } 41 42 /** 43 * 采用轮询的方式监听selector上是否有需要处理的事件,如果有,则进行处理 44 * 45 * @throws IOException 46 */ 47 @SuppressWarnings("unchecked") 48 public void listen() throws IOException { 49 System.out.println("服务端启动成功!"); 50 // 轮询访问selector 51 while (true) { 52 // 当注册的事件到达时,方法返回;否则,该方法会一直阻塞 53 selector.select(); 54 // 获得selector中选中的项的迭代器,选中的项为注册的事件 55 Iterator ite = this.selector.selectedKeys().iterator(); 56 while (ite.hasNext()) { 57 SelectionKey key = (SelectionKey) ite.next(); 58 // 删除已选的key,以防重复处理 59 ite.remove(); 60 // 客户端请求连接事件 61 if (key.isAcceptable()) { 62 ServerSocketChannel server = (ServerSocketChannel) key.channel(); 63 // 获得和客户端连接的通道 64 SocketChannel channel = server.accept(); 65 // 设置成非阻塞 66 channel.configureBlocking(false); 67 68 // 在这里可以给客户端发送信息哦 69 channel.write(ByteBuffer.wrap(new String("向客户端发送了一条信息").getBytes())); 70 // 在和客户端连接成功之后,为了可以接收到客户端的信息,需要给通道设置读的权限。 71 channel.register(this.selector, SelectionKey.OP_READ); 72 73 // 获得了可读的事件 74 } else if (key.isReadable()) { 75 read(key); 76 } 77 78 } 79 80 } 81 } 82 83 /** 84 * 处理读取客户端发来的信息 的事件 85 * 86 * @param key 87 * @throws IOException 88 */ 89 public void read(SelectionKey key) throws IOException { 90 // 服务器可读取消息:得到事件发生的Socket通道 91 SocketChannel channel = (SocketChannel) key.channel(); 92 // 创建读取的缓冲区 93 ByteBuffer buffer = ByteBuffer.allocate(10); 94 channel.read(buffer); 95 byte[] data = buffer.array(); 96 String msg = new String(data).trim(); 97 System.out.println("服务端收到信息:" + msg); 98 ByteBuffer outBuffer = ByteBuffer.wrap(msg.getBytes()); 99 channel.write(outBuffer);// 将消息回送给客户端 100 } 101 102 /** 103 * 启动服务端测试 104 * 105 * @throws IOException 106 */ 107 public static void main(String[] args) throws IOException { 108 NIOServer server = new NIOServer(); 109 server.initServer(8000); 110 server.listen(); 111 } 112 113 }
客户端:
1 package com.himi.demo2; 2 3 import java.io.IOException; 4 import java.net.InetSocketAddress; 5 import java.nio.ByteBuffer; 6 import java.nio.channels.SelectionKey; 7 import java.nio.channels.Selector; 8 import java.nio.channels.SocketChannel; 9 import java.util.Iterator; 10 11 /** 12 * NIO客户端 13 * 14 * @author hebao 15 */ 16 public class NIOClient { 17 // 通道管理器 18 private Selector selector; 19 20 /** 21 * 获得一个Socket通道,并对该通道做一些初始化的工作 22 * 23 * @param ip 24 * 连接的服务器的ip 25 * @param port 26 * 连接的服务器的端口号 27 * @throws IOException 28 */ 29 public void initClient(String ip, int port) throws IOException { 30 // 获得一个Socket通道 31 SocketChannel channel = SocketChannel.open(); 32 // 设置通道为非阻塞 33 channel.configureBlocking(false); 34 // 获得一个通道管理器 35 this.selector = Selector.open(); 36 37 // 客户端连接服务器,其实方法执行并没有实现连接,需要在listen()方法中调 38 // 用channel.finishConnect();才能完成连接 39 channel.connect(new InetSocketAddress(ip, port)); 40 // 将通道管理器和该通道绑定,并为该通道注册SelectionKey.OP_CONNECT事件。 41 channel.register(selector, SelectionKey.OP_CONNECT); 42 } 43 44 /** 45 * 采用轮询的方式监听selector上是否有需要处理的事件,如果有,则进行处理 46 * 47 * @throws IOException 48 */ 49 @SuppressWarnings("unchecked") 50 public void listen() throws IOException { 51 // 轮询访问selector 52 while (true) { 53 selector.select(); 54 // 获得selector中选中的项的迭代器 55 Iterator ite = this.selector.selectedKeys().iterator(); 56 while (ite.hasNext()) { 57 SelectionKey key = (SelectionKey) ite.next(); 58 // 删除已选的key,以防重复处理 59 ite.remove(); 60 // 连接事件发生 61 if (key.isConnectable()) { 62 SocketChannel channel = (SocketChannel) key.channel(); 63 // 如果正在连接,则完成连接 64 if (channel.isConnectionPending()) { 65 channel.finishConnect(); 66 67 } 68 // 设置成非阻塞 69 channel.configureBlocking(false); 70 71 // 在这里可以给服务端发送信息哦 72 channel.write(ByteBuffer.wrap(new String("向服务端发送了一条信息").getBytes())); 73 // 在和服务端连接成功之后,为了可以接收到服务端的信息,需要给通道设置读的权限。 74 channel.register(this.selector, SelectionKey.OP_READ); 75 76 // 获得了可读的事件 77 } else if (key.isReadable()) { 78 read(key); 79 } 80 81 } 82 83 } 84 } 85 86 /** 87 * 处理读取服务端发来的信息 的事件 88 * 89 * @param key 90 * @throws IOException 91 */ 92 public void read(SelectionKey key) throws IOException { 93 // 和服务端的read方法一样 94 } 95 96 /** 97 * 启动客户端测试 98 * 99 * @throws IOException 100 */ 101 public static void main(String[] args) throws IOException { 102 NIOClient client = new NIOClient(); 103 client.initClient("localhost", 8000); 104 client.listen(); 105 } 106 107 }