标签:开发 知识 tail 两种 3.1 属性 发送 att src
本文主要记录 Java 中 NIO 相关的基础知识点,以及基本的使用方式。
刚接触 Java 中的 I/O 时,使用的传统的 BIO 的 API。由于 BIO 设计的类实在太多,至今我仍然不能信手拈来的写出完成的 BIO 的代码。不过它基本的特点和分类,我还是记得一二的。
一个便于使用的的流对象的构建,一般都是由相对底层的流逐渐构建出相对高级的流。通常我都是用 BIO 来做一些本地文件的 I/O 操作 。在网络编程方面,BIO 因为其阻塞的原因,大家使用的都比较少,一般都使用 NIO,尤其是在服务端的网络开发。强大的 Netty 正是基于 Java NIO 的基础而开发出来的高性能框架,在学习 Netty 之前,很有必要去掌握 NIO 的基本使用。
NIO 相关的核心概念有 3 个,Channel、Buffer 和 Selector。
Java 中传统的 BIO 分为输入流和输出流,在同一个 Socket 连接或者文件的 IO 中,需要同时使用这两种流才能进行数据的交互。而 NIO 则使用了 Channel 的概念,可以对 Channel 进行双向操作。我们可以将数据写入到 Channel,也可以从 Channel 中读取数据。
Channel 的主要实现有以下 4 类:
从名称上看,就能知道这些类分别对应了文件、UDP、TCP(Client、Server)。
Buffer 也就是缓冲区。它负责将 Channel 中的数据取出来(读数据),或者将用户程序的数据放入 Channel(写数据)。如果将 Channel 比作是一架飞机及其航线,那么 Buffer 就是航站楼与飞机之
间的摆渡车。下图就是 Buffer 和 Channel 之间的交互:
Buffer 是用户程序与 Channel 进行数据交互的工具。ByteBuffer 是 NIO 中最底层的实现,在此基础上,还有 CharBuffer、DoubleBuffer 等。
ByteBuffer 实质上是维护了一个字节数组,它包含了一个几个特殊的属性:
具体怎么用,可以查询 JDK API,只要直到其他只属性即可。
在学习了 Channel 和 Buffer 之后,已经可以使用这两个类了进行阻塞式的 I/O 操作了。但是 Selector 才是 Java NIO 的核心优势点。只有 ScoketChannel 才能设置为非阻塞模式,所以 Selector 只能在网络 I/O 中才能使用。
Selector 的核心方法就是 select()方法,该方法会一直阻塞,直到注册在该选择器上的通道有用户所感兴趣的事件准备就绪了才会返回。这里面又涉及到 Selector 与 Channel 之间的映射,这个关系用 SelectionKey 来表示。在调用选择器的 select()方法前,用户可以使用 Channel 的 register()方法,将其注册到选择器上,同时表明用户对该通道的哪些操作感兴趣。注意,register()方法返回的就是 SelectionKey 对象。
1 public class Client { 2 private static final int REMOTE_PORT = 8888; 3 private static final String REMOTE_HOST = "127.0.0.1"; 4 private static final int BUFF_SIZE = 1024; 5 6 public static void main(String[] args) throws IOException { 7 Selector selector = Selector.open(); 8 SocketChannel socketChannel = 9 SocketChannel.open(new InetSocketAddress(REMOTE_HOST, REMOTE_PORT)); 10 //将套接字通道设置为非阻塞模式 11 socketChannel.configureBlocking(false); 12 //将通道注册到 Selector 中,第二个参数为该通道感兴趣的事件,此处为读事件 13 //注册方法会返回一个 SelectionKey 对象,它代表通道与选择器之间的映射关系 14 socketChannel.register(selector, 15 SelectionKey.OP_READ, ByteBuffer.allocateDirect(BUFF_SIZE)); 16 if (!socketChannel.isConnected()) { 17 socketChannel.finishConnect(); 18 } 19 for (; ; ) { 20 //选择器的 select()方法会阻塞到有通道所感兴趣的事件已经就绪 21 if (selector.select() > 0) { 22 //调用选择器的 selectedKeys() 方法会返回,本次所有就绪通道对应的 SelectionKey 集合 23 Iterator<SelectionKey> it = selector.selectedKeys().iterator(); 24 while (it.hasNext()) { 25 SelectionKey key = it.next(); 26 //通过迭代去删除掉本次将要处理的 key 27 //如果不删除,下次 select 还会返回该 key 28 it.remove(); 29 if (key.isReadable()) { 30 ByteBuffer bf = (ByteBuffer) key.attachment(); 31 bf.clear(); 32 SocketChannel sc = (SocketChannel) key.channel(); 33 int i = sc.read(bf); 34 if (i < 0) { 35 key.cancel(); 36 sc.close(); 37 return; 38 } 39 bf.flip(); 40 byte[] ret = new byte[bf.remaining()]; 41 bf.get(ret); 42 for (byte b : ret) { 43 System.out.print(b); 44 } 45 System.out.println(); 46 } 47 } 48 } 49 } 50 } 51 }
关于该示例的一些说明:
对于上面的第 3 点,之前遇到过一个问题,对端如果发现连接在指定的间隔内没有数据通讯,就会关闭掉连接,这个时候我们也需要关闭对应的通道。下图是抓包的结果:
1 public class Server { 2 3 private static final int PORT = 8888; 4 5 public static void main(String[] args) throws IOException { 6 Selector selector = Selector.open(); 7 ServerSocketChannel ssc = ServerSocketChannel.open(); 8 ssc.configureBlocking(false); 9 ssc.socket().bind(new InetSocketAddress(PORT)); 10 //对可连接事件感兴趣 11 ssc.register(selector, SelectionKey.OP_ACCEPT); 12 for (; ; ) { 13 if (selector.select() > 0) { 14 Iterator<SelectionKey> it = selector.selectedKeys().iterator(); 15 while (it.hasNext()) { 16 SelectionKey key = it.next(); 17 it.remove(); 18 if (key.isAcceptable()) { 19 //处理客户端的连接 20 SocketChannel sc = ssc.accept(); 21 sc.configureBlocking(false); 22 sc.register(selector, SelectionKey.OP_READ); 23 } 24 if (key.isReadable()) { 25 //通过该 key 处理对应的读事件 26 } 27 } 28 } 29 } 30 } 31 }
基于 Selector 的 NIO 模式,可以使用一个线程来处理大量的连接,优势十分明显。
以上只是 Java 中 NIO 的粗略介绍,仍需进一步熟悉各个 API 的使用方法。在熟悉 NIO 之后,下一步准备学习一下 Netty 框架的使用。
准备使用 Netty 的原因:
标签:开发 知识 tail 两种 3.1 属性 发送 att src
原文地址:https://www.cnblogs.com/magexi/p/9937430.html