NIO
一、概述
1.用于进行数据传输的
2.BIO - BlockingIO - 同步式阻塞式IO
3.NIO - NewIO - NonBlockingIO - 同步式非阻塞式IO - JDK1.4以上 - 就是专门为了应对高并发场景
4.NIO的组件:Buffer、Channel、Selector
5.NIO框架:Netty、Mina、Grizzly
6.AIO 在JDK1.8以上 - AsynchronousID :异步式非阻塞IO
AIO的整体设计和NIO基本类似,只是多了异步改进,以及事件驱动方式做了改变,也因此成为NIO.2
二、BIO的缺点
1.单项流:需要创建大量的流对象,内存会被大量的占用
2.一对一连接:每一个客户端都需要有一个服务端的线程来处理;
如果当并发量大的时候,服务器端会产生大量的线程,如果线程数量过多会导致服务器的崩溃
3.客户端连接之后即使不产生任何的操作也会占用着服务器端的线程,会导致服务器端资源的浪费
三、Buffer - 缓冲区
1.在NIO中用于进行数据的存储
2.底层基于数组来进行存储的,存储的元素类型是基本类型,
针对除了boolean以外的基本类型都提供了具体的子类,但是最常用的是ByteBuffer
3.重要位置:capacity >= limit >= pmosition >= mark
a. capacity:容量位。用于标记缓冲区的容量,指定之后就不可变
b. limit:限制位。用于标记操作位所能达到的最大尺度,默认在缓冲区的最后一位
c. position:操作位。用于指向要操作的位置,默认在第0位
d. mark:标记位。用于标记数据的位置,一般用于进行校验。在Java中,mark默认是不启用
4.重要操作
a. flip:反转缓冲区。将limit挪动到position上,然后将position归零,mark置位-1
b. clear:清空缓冲区。回归缓冲区最开始状态
c. reset:重置缓冲区。将position挪到mark上
e. rewind:重绕缓冲区。将postion归零,将mark置位-1
四、Channel - 通道
1.用于进行数据的传输
2.双向通道:可以进行数据的双向传输
3.Channel默认是阻塞的,需要手动设置为非阻塞
4.通道是面向缓冲区进行操作的
5..文件:FileChannel
UDP:DatagramChannel
TCP:SocketChannel、ServerSocketChannel
五、Selector - 多路复用选择器
1.用于进行通道的选择的
2.选择器上注册的通道需要为非阻塞的
3.在选择通道的时候,是基于事件驱动机制来完成,即通道身上必须有对应的事件才会被处理
服务端代码:
package com.apple.selector; 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; /** * This is Description * * @author apple * @date 2020/06/01 */ public class Server { public static void main(String[] args) throws IOException { //开启服务器端的通道 ServerSocketChannel ssc = ServerSocketChannel.open(); //绑定端口 ssc.bind(new InetSocketAddress(8070)); //设置为非阻塞的 ssc.configureBlocking(false); //开启选择器 Selector selc = Selector.open(); //将通道注册到选择器上 ssc.register(selc, SelectionKey.OP_ACCEPT); while (true) { //选择,选择出已经注册的通道 selc.select(); //获取这次选择出来的通道的事件 Set<SelectionKey> keys = selc.selectedKeys(); Iterator<SelectionKey> it = keys.iterator(); while (it.hasNext()) { SelectionKey key = it.next(); //接收 if (key.isAcceptable()) { //获取通道 ServerSocketChannel sscx = (ServerSocketChannel) key.channel(); //接收连接 SocketChannel sc = sscx.accept(); sc.configureBlocking(false); sc.register(selc, SelectionKey.OP_WRITE | SelectionKey.OP_READ); } //读 if (key.isReadable()) { //获取通道 SocketChannel sc = (SocketChannel) key.channel(); //读取数据 ByteBuffer dst = ByteBuffer.allocate(1024); sc.read(dst); System.out.println(new String(dst.array(), 0, dst.position())); //如果在同一个通道上注册,就会将之前注册的事件给注册 //key.interestOps()获取这个通道上的所有事件 //key.interestOps() - SelectionKey.OP_READ 表示在这个通道中所有事件列表中将READ事件去除 //实际上就是注销掉READ事件 sc.register(selc, key.interestOps() - SelectionKey.OP_READ); //方法二:异或: 1^1=0,1^0=1 相同为0,不同为1 sc.register(selc, key.interestOps() ^ SelectionKey.OP_READ); } //写 if (key.isWritable()) { //获取通道 SocketChannel sc = (SocketChannel) key.channel(); //向客户端响应 sc.write(ByteBuffer.wrap("hi client".getBytes())); //注销掉write事件 //异或: 1^1=0,1^0=1 相同为0,不同为1 sc.register(selc, key.interestOps() ^ SelectionKey.OP_WRITE); } //最后移除通道 it.remove(); } } } }
客户端代码:
package com.apple.selector; import java.io.IOException; import java.net.InetSocketAddress; import java.nio.ByteBuffer; import java.nio.channels.SocketChannel; /** * This is Description * * @author apple * @date 2020/06/01 */ public class Client { public static void main(String[] args) throws IOException { SocketChannel sc = SocketChannel.open(); sc.connect(new InetSocketAddress("127.0.0.1", 8070)); System.out.println("连接已经建立"); //ByteBuffer.wrap 用于 知道 传输数据的大小 的场景下使用 // ByteBuffer.allocate(size) 用于 不确定 传输数据的大小 的场景下使用 //例如:服务端在接收客户端的请求时,是不知道有多少数据传输过来的, //同样,客户端接收服务端的响应时,也不知道多少数据传输过来 sc.write(ByteBuffer.wrap("hello server".getBytes())); ByteBuffer dst = ByteBuffer.allocate(1024); sc.read(dst); System.out.println(new String(dst.array(), 0, dst.position())); } }