前言:
关于网上关于 NIO 和 Netty 的文章已经很多了,最近抽空我也来学习,希望大神们多多指教。
一、什么是 BIO、NIO、AIO
(1)BIO
同步并阻塞,服务器实现模式为一个连接一个线程,例子:小明开了一家餐厅,采用了客人(连接)与服务员(线程)一对一的服务,如下图,
当有客人来的时候,就会分配一名服务员给这客人,直到客人离去,该服务员才能去服务别的客人。
(2)NIO
同步非阻塞,服务器实现模式为一个请求一个线程。例子:小明的餐厅客人越来越多,本来是值得开心的事情,但是小明却苦恼了起来,因为服务员人数不能满足客人,导致很多客人没能得到服务员的帮助,这让客人很不满意,但是如果多招些服务员的话成本太高,于是小明决定采用服务员与客人一对多的服务,如下图,一个员工(多路复用器)观察每一位客人(连接),当发现某位客人需要帮助(I\O操作等),就让服务员(线程)去帮助这位客人。
(3)AIO
异步非阻塞,服务器实现模式为一个有效请求一个线程。例子:餐厅的生意越来越好,小明决定进购一批呼叫器,有了呼叫器后的服务如下
图,每张餐桌都有一个呼叫器,当客人有需要帮助时按下呼叫器,然后监控系统收到呼叫器传来的信号后就会提醒服务员哪张餐桌的客人需要
帮助。
二、NIO 类库简介
1. Channel
Channel 是一个通道,网络数据是通过 Channel 读取和写入的,它类似于流,但与流不同的是流是单向的,而通道是双向的,它可以读取数据和写入数据,并且读写可以同时进行。
Channel 的一些实现类:
- FileChannel :用于文件读写
- DatagramChannel :用于 UDP 读写
- SocketChannel :用于 TCP 读写
- ServerSocketChannel :可以监听到新连接进来的 TCP 连接
2. Buffer
Buffer 是一个缓冲区,内部用一个数组来存储,但是 Buffer 提供了很多方法来操作该缓冲区。
Buffer 的一些实现类 :
- ByteBuffer :字节缓冲区
- CharBuffer :字符缓冲区
- ShortBuffer :短整形缓冲区
- IntBuffer : 整形缓冲区
- LongBuffer :长整形缓冲区
- FloatBuffer :浮点型缓冲区
- DoubleBuffer : 双精度浮点型缓冲区
3. Selector
Selector 是一个选择器,它会轮询注册在其上的Channel,当发现某个 Channel 处于就绪状态,就会被 Selector 轮询出来,然后进行后续的操作。
三、Demo
(一)服务端的实现
import java.io.IOException; import java.net.InetSocketAddress; import java.nio.ByteBuffer; import java.nio.channels.SelectionKey; import java.nio.channels.Selector; import java.nio.channels.ServerSocketChannel; import java.nio.channels.SocketChannel; import java.util.Iterator; import java.util.Set; /** * Created by dzb on 2017/12/17. */ public class DemoServer { private static int port = 8080; // 选择器 private static Selector selector; public static void main (String[] arg) { try { // 打开 ServerSocketChannel, 监听客户端的连接 ServerSocketChannel serverSocketChannel = ServerSocketChannel.open(); // 绑定端口 serverSocketChannel.socket().bind(new InetSocketAddress(port), 1024); // 设置连接为非阻塞模式 serverSocketChannel.configureBlocking(false); // 创建选择器 selector = Selector.open(); // 将通道注册到选择器上面, 监听 Accept 事件 serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT); System.out.println("服务器已启动,端口为:" + port); run(); } catch (IOException e) { e.printStackTrace(); } } /** * 运行 */ public static void run () { while (true) { try { // 阻塞到至少有一个通道准备就绪,阻塞时间为 1秒 // selector.select() 如果使用这个方法的话则一直阻塞到至少有一个通道准备就绪 // 该方法会返回当前有多少通道准备就绪 selector.select(1000); // 获取准备就绪的Key Set<SelectionKey> selectorKeys = selector.selectedKeys(); Iterator<SelectionKey> it = selectorKeys.iterator(); SelectionKey key = null; // 遍历所有准备就绪的Key while (it.hasNext()) { key = it.next(); it.remove(); try { handleInput(key); } catch (IOException e) { // 客户端断开连接 // 关闭 key.cancel(); if (key.channel() != null) { key.channel().close(); } } } } catch (IOException e) { e.printStackTrace(); } } } /** * 进行操作 * @param key */ private static void handleInput(SelectionKey key) throws IOException { // 判断key是否有效 if (key.isValid()) { // 如果该事件是新链接进来的请求消息 if (key.isAcceptable()) { // 获取通道 ServerSocketChannel ssc = (ServerSocketChannel) key.channel(); // 获取读写 TCP 数据的SocketChannel SocketChannel sc = ssc.accept(); // 设置非阻塞 sc.configureBlocking(false); // 将 SocketChannel 注册到 Selector上面,监听 READ 读取事件 sc.register(selector, SelectionKey.OP_READ); } // 如果该事件是读取消息 if (key.isReadable()) { // 获取通道 SocketChannel sc = (SocketChannel) key.channel(); // 创建缓冲区 ByteBuffer buf = ByteBuffer.allocate(1024); // 将通道的数据读到缓冲区 int readBytes = sc.read(buf); if (readBytes > 0) { // 将Buffer从写模式切换到读模式 buf.flip(); byte[] bt = new byte[buf.remaining()]; buf.get(bt); String req = new String(bt, "UTF-8"); System.out.println("服务端收到客户端传来的消息是:" + req); write(sc); } else if (readBytes < 0){ // 关闭 key.cancel(); sc.close(); } } } } /** * 返回数据 * @param sc */ private static void write(SocketChannel sc) throws IOException { byte[] bt = "Hello Client".getBytes(); ByteBuffer buf = ByteBuffer.allocate(bt.length); buf.put(bt); buf.flip(); // 写出 sc.write(buf); } }
(二)客户端的实现
import java.io.IOException; import java.net.InetSocketAddress; import java.nio.ByteBuffer; import java.nio.channels.SelectionKey; import java.nio.channels.Selector; import java.nio.channels.SocketChannel; import java.util.Iterator; import java.util.Set; /** * Created by dzb on 2017/12/17. */ public class DemoClient { private static String host = "127.0.0.1"; private static int port = 8080; private static Selector selector; public static void main (String[] args) { try { // 打开 选择器 selector = Selector.open(); // 打开读写 TCP 数据的SocketChannel SocketChannel socketChannel = SocketChannel.open(); // 设置为非阻塞 socketChannel.configureBlocking(false); // 如果已经跟服务端连接上了则注册读取事件 if (socketChannel.connect(new InetSocketAddress(host, port))) { socketChannel.register(selector, SelectionKey.OP_READ); // 发送请求数据 write(socketChannel); } else { // 否则注册监听是否跟服务端连接上的事件 socketChannel.register(selector, SelectionKey.OP_CONNECT); } run (); } catch (IOException e) { e.printStackTrace(); } } /** * 运行 */ public static void run () { while (true) { try { // 阻塞到至少有一个通道准备就绪,阻塞时间为 1秒 selector.select(1000); // 获取准备就绪的Key Set<SelectionKey> selectionKeys = selector.selectedKeys(); Iterator<SelectionKey> it = selectionKeys.iterator(); SelectionKey key = null; while (it.hasNext()) { key = it.next(); it.remove(); try { handleInput(key); } catch (IOException e) { e.printStackTrace(); // 关闭 key.cancel(); if (key.channel() != null) { key.channel().close(); } } } } catch (IOException e) { e.printStackTrace(); } } } /** * 发送数据 * @param sc */ public static void write (SocketChannel sc) throws IOException { byte[] bt = "Hello Server".getBytes(); ByteBuffer buf = ByteBuffer.allocate(1024); buf.put(bt); buf.flip(); sc.write(buf); } /** * 进行操作 * @param key */ public static void handleInput (SelectionKey key) throws IOException { // 判断key是否有效 if (key.isValid()) { // 获取当前操作的 Channel SocketChannel sc = (SocketChannel) key.channel(); // 判断是否连接成功了 if (key.isConnectable()) { if (sc.finishConnect()) { sc.register(selector, SelectionKey.OP_READ); // 发送请求数据 write(sc); } else { System.exit(0); } } // 如果该事件是读取消息 if (key.isReadable()) { // 创建缓冲区 ByteBuffer buf = ByteBuffer.allocate(1024); // 将通道的数据读到缓冲区 int readBytes = sc.read(buf); if (readBytes > 0) { // 将Buffer从写模式切换到读模式 buf.flip(); byte[] bt = new byte[buf.remaining()]; buf.get(bt); String resp = new String(bt, "UTF-8"); System.out.println("客户端收到服务端传来的消息:" + resp); System.exit(0); } else if (readBytes < 0){ // 关闭 key.cancel(); sc.close(); } } } } }