NIO与BIO
-
Blocking IO,阻塞型IO
-
NIO
No Blocking IO,非阻塞型IO
-
阻塞IO的弊端
在等待的过程中,什么事也做不了
-
非阻塞IO的好处
-
BIO是阻塞的,NIO是非阻塞的
-
区别二
BIO是面向流的,NIO是面向缓冲区的
-
用来存储数据
-
通道
用来建立连接和传输数据
-
选择器
方法名 | 说明 |
---|---|
static ByteBuffer allocate(长度) | 创建byte类型的缓冲区 |
static ByteBuffer wrap(byte[] array) | 创建一个有内容的byte类型缓冲区 |
代码示例
public class CreateByteBufferDemo1 { public static void main(String[] args) { //method1(); //method2(); ByteBuffer wrap = ByteBuffer.wrap("aaa".getBytes()); for (int i = 0; i < 3; i++) { System.out.println(wrap.get()); } } private static void method2() { byte [] bytes = {97,98,99}; ByteBuffer byteBuffer2 = ByteBuffer.wrap(bytes); //缓冲区的长度3 //缓冲区里面的内容就是字节数组的内容. for (int i = 0; i < 3; i++) { System.out.println(byteBuffer2.get()); } System.out.println(byteBuffer2.get()); } private static void method1() { ByteBuffer byteBuffer1 = ByteBuffer.allocate(5); //get for (int i = 0; i < 5; i++) { System.out.println(byteBuffer1.get()); } System.out.println(byteBuffer1.get()); } }
代码示例
public class ByteBufferDemo2 { public static void main(String[] args) { // int position() 当前要操作的索引 // int limit() 最多能操作到哪个索引 // int capacity() 缓冲区的总长度 ByteBuffer byteBuffer = ByteBuffer.allocate(10); System.out.println(byteBuffer.position());//0 System.out.println(byteBuffer.limit());//10 System.out.println(byteBuffer.capacity());//10 // put(byte b) 一次添加一个字节 // byteBuffer.put((byte) 97); // System.out.println(byteBuffer.position()); // System.out.println(byteBuffer.limit()); // System.out.println(byteBuffer.capacity()); // put(byte[] src) 一次添加一个字节数组 // byteBuffer.put("aaa".getBytes()); // System.out.println(byteBuffer.position());//3 // System.out.println(byteBuffer.limit());//10 // System.out.println(byteBuffer.capacity());//10 // position(int newPosition) 修改position // byteBuffer.position(1); // limit(int newLimit) 修改limit // byteBuffer.limit(5); // System.out.println(byteBuffer.position()); // System.out.println(byteBuffer.limit()); // System.out.println(byteBuffer.capacity()); // int remaining() 还有多少能操作 // boolean hasRemaining() 是否还有能操作的 byteBuffer.put("0123456789".getBytes()); System.out.println(byteBuffer.remaining()); System.out.println(byteBuffer.hasRemaining()); } }
方法名 | 介绍 |
---|---|
flip() | 切换读写模式(写à读) |
get() | 读一个字节 |
get(byte[] dst) | 读多个字节 |
get(int index) | 读指定索引的字节 |
rewind() | 将position设置为0,可以重复读 |
clear() | 数据读写完毕(读->写) |
array() | 将缓冲区转换成字节数组返回 |
代码示例
public class ByteBufferDemo3 { public static void main(String[] args) { ByteBuffer byteBuffer = ByteBuffer.allocate(10); byteBuffer.put("abc".getBytes()); // flip() 切换读写模式(写读) byteBuffer.flip(); // get() 读一个字节 // while(byteBuffer.limit() != byteBuffer.position()){ // System.out.println((char) byteBuffer.get()); // } for (int i = 0; i < byteBuffer.limit(); i++) { System.out.println((char) byteBuffer.get()); } // get(byte[] dst) 读多个字节 // byte [] bytes = new byte[byteBuffer.limit()]; // byteBuffer.get(bytes); // System.out.println(new String(bytes)); // get(int index) 读指定索引的字节 // System.out.println((char) byteBuffer.get(0)); // rewind() 将position设置为0,可以重复读 // byteBuffer.rewind(); // for (int i = 0; i < byteBuffer.limit(); i++) { // System.out.println((char) byteBuffer.get()); // } // clear() 数据读写完毕(读->写) byteBuffer.clear(); byteBuffer.put("qqq".getBytes()); // array() 将缓冲区转换成字节数组返回 byte[] bytes = byteBuffer.array(); System.out.println(new String(bytes)); } }
-
数据是从外面进入到缓冲区的,所以缓冲区在做读数据的操作。
-
需求:我要把数据从缓冲区中读出来。
数据是从缓冲区里面到外面的。所以缓冲区在做写数据的操作。
-
capacity:容量(长度) limit: 界限(最多能读/写到哪里) posotion:位置(读/写哪个索引)
-
获取缓冲区里面数据之前,需要调用flip方法
-
再次写数据之前,需要调用clear方法,
-
-
打开通道
-
指定IP和端口号
-
写出数据
-
释放资源
-
public class NIOClient { public static void main(String[] args) throws IOException { //1.打开通道 SocketChannel socketChannel = SocketChannel.open(); //2.指定IP和端口号 socketChannel.connect(new InetSocketAddress("127.0.0.1",10000)); //3.写出数据 ByteBuffer byteBuffer = ByteBuffer.wrap("一点寒毛先制".getBytes()); socketChannel.write(byteBuffer); //4.释放资源 socketChannel.close(); } }
-
-
服务端通道
只负责建立建立,不负责传递数据
-
客户端通道
建立建立并将数据传递给服务端
-
缓冲区
客户端发送的数据都在缓冲区中
-
服务端通道内部创建出来的客户端通道
相当于客户端通道的延伸用来传递数据
-
-
服务端实现步骤
-
打开一个服务端通道
-
绑定对应的端口号
-
通道默认是阻塞的,需要设置为非阻塞
-
此时没有门卫大爷,所以需要经常看一下有没有连接发过来没?
-
如果有客户端来连接了,则在服务端通道内部,再创建一个客户端通道,相当于是客户端通道的延伸
-
获取客户端传递过来的数据,并把数据放在byteBuffer1这个缓冲区中
-
给客户端回写数据
-
释放资源
-
public class NIOServer { public static void main(String[] args) throws IOException { // 1.打开一个服务端通道 ServerSocketChannel serverSocketChannel = ServerSocketChannel.open(); // 2.绑定对应的端口号 serverSocketChannel.bind(new InetSocketAddress(10000)); // 3.通道默认是阻塞的,需要设置为非阻塞 //如果传递true 表示通道设置为阻塞通道...默认值 //如果传递false 表示通道设置为非阻塞通道 serverSocketChannel.configureBlocking(false); // 4.此时没有门卫大爷,所以需要经常看一下有没有连接发过来没? while (true) { // 5.如果有客户端来连接了,则在服务端通道内部,再创建一个客户端通道,相当于是客户端通道的延伸 //此时已经设置了通道为非阻塞 //所以在调用方法的时候,如果有客户端来连接,那么会创建一个SocketChannel对象. //如果在调用方法的时候,没有客户端来连接,那么他会返回一个null SocketChannel socketChannel = serverSocketChannel.accept(); //System.out.println(socketChannel); if(socketChannel != null){ // 6.客户端将缓冲区通过通道传递给服务端,就到了这个延伸通道socketChannel里面 // 7.服务端创建一个空的缓冲区装数据并输出 ByteBuffer byteBuffer = ByteBuffer.allocate(1024); //获取传递过来的数据,并把他们放到byteBuffer缓冲区中. //返回值: //正数: 表示本次读到的有效字节个数. //0 : 表示本次没有读到有效字节. //-1 : 表示读到了末尾 int len = socketChannel.read(byteBuffer); System.out.println(new String(byteBuffer.array(),0,len)); //8.释放资源 socketChannel.close(); } } } }
-
实现步骤
-
打开通道
-
指定IP和端口号
-
写出数据
-
读取服务器写回的数据
-
释放资源
-
public class Clinet { public static void main(String[] args) throws IOException { // 1.打开通道 SocketChannel socketChannel = SocketChannel.open(); // 2.指定IP和端口号 socketChannel.connect(new InetSocketAddress("127.0.0.1",10000)); // 3.写出数据 ByteBuffer byteBuffer1 = ByteBuffer.wrap("吃俺老孙一棒棒".getBytes()); socketChannel.write(byteBuffer1); // 手动写入结束标记 socketChannel.shutdownOutput(); System.out.println("数据已经写给服务器"); // 4.读取服务器写回的数据 ByteBuffer byteBuffer2 = ByteBuffer.allocate(1024); int len; while((len = socketChannel.read(byteBuffer2)) != -1){ byteBuffer2.flip(); System.out.println(new String(byteBuffer2.array(),0,len)); byteBuffer2.clear(); } // 5.释放资源 socketChannel.close(); } }
-
实现步骤
-
打开一个服务端通道
-
绑定对应的端口号
-
通道默认是阻塞的,需要设置为非阻塞
-
此时没有门卫大爷,所以需要经常看一下有没有连接发过来没?
-
如果有客户端来连接了,则在服务端通道内部,再创建一个客户端通道,相当于是客户端通道的延伸
-
获取客户端传递过来的数据,并把数据放在byteBuffer1这个缓冲区中
-
给客户端回写数据
-
释放资源
-
public class Sever { public static void main(String[] args) throws IOException { // 1,打开一个服务端通道 ServerSocketChannel serverSocketChannel = ServerSocketChannel.open(); // 2,绑定对应的端口号 serverSocketChannel.bind(new InetSocketAddress(10000)); // 3,通道默认是阻塞的,需要设置为非阻塞 serverSocketChannel.configureBlocking(false); // 4,此时没有门卫大爷,所以需要经常看一下有没有连接发过来没? while(true){ // 5,如果有客户端来连接了,则在服务端通道内部,再创建一个客户端通道,相当于是客户端通道的延伸 SocketChannel socketChannel = serverSocketChannel.accept(); if(socketChannel != null){ System.out.println("此时有客户端来连接了"); // 6,获取客户端传递过来的数据,并把数据放在byteBuffer1这个缓冲区中 ByteBuffer byteBuffer1 = ByteBuffer.allocate(1024); //socketChannel.read(byteBuffer1); int len; //针对于缓冲区来讲 //如果 从添加数据 ----> 获取数据 flip //如果 从获取数据 ----> 添加数据 clear while((len = socketChannel.read(byteBuffer1)) != -1){ byteBuffer1.flip(); System.out.println(new String(byteBuffer1.array(),0,len)); byteBuffer1.clear(); } System.out.println("接收数据完毕,准备开始往客户端回写数据"); // 7,给客户端回写数据 ByteBuffer byteBuffer2 = ByteBuffer.wrap("哎哟,真疼啊!!!".getBytes()); socketChannel.write(byteBuffer2); // 8,释放资源 socketChannel.close(); } } } }
-
服务端内部获取的客户端通道在读取时,如果读取不到结束标记就会一直阻塞
-
解决方案
将服务端内部获取的客户端通道设置为非阻塞的
// 客户端 public class Clinet { public static void main(String[] args) throws IOException { SocketChannel socketChannel = SocketChannel.open(); socketChannel.connect(new InetSocketAddress("127.0.0.1",10000)); ByteBuffer byteBuffer1 = ByteBuffer.wrap("吃俺老孙一棒棒".getBytes()); socketChannel.write(byteBuffer1); System.out.println("数据已经写给服务器"); ByteBuffer byteBuffer2 = ByteBuffer.allocate(1024); int len; while((len = socketChannel.read(byteBuffer2)) != -1){ System.out.println("客户端接收回写数据"); byteBuffer2.flip(); System.out.println(new String(byteBuffer2.array(),0,len)); byteBuffer2.clear(); } socketChannel.close(); } } // 服务端 public class Sever { public static void main(String[] args) throws IOException { ServerSocketChannel serverSocketChannel = ServerSocketChannel.open(); serverSocketChannel.bind(new InetSocketAddress(10000)); serverSocketChannel.configureBlocking(false); while(true){ SocketChannel socketChannel = serverSocketChannel.accept(); if(socketChannel != null){ System.out.println("此时有客户端来连接了"); // 将服务端内部获取的客户端通道设置为非阻塞的 socketChannel.configureBlocking(false); //获取客户端传递过来的数据,并把数据放在byteBuffer1这个缓冲区中 ByteBuffer byteBuffer1 = ByteBuffer.allocate(1024); //socketChannel.read(byteBuffer1); int len; //针对于缓冲区来讲 //如果 从添加数据 ----> 获取数据 flip //如果 从获取数据 ----> 添加数据 clear while((len = socketChannel.read(byteBuffer1)) > 0){ System.out.println("服务端接收发送数据"); byteBuffer1.flip(); System.out.println(new String(byteBuffer1.array(),0,len)); byteBuffer1.clear(); } System.out.println("接收数据完毕,准备开始往客户端回写数据"); ByteBuffer byteBuffer2 = ByteBuffer.wrap("哎哟,真疼啊!!!".getBytes()); socketChannel.write(byteBuffer2); socketChannel.close(); } } } }
-
Selector
选择器对象
-
SelectionKey
绑定的key
-
SelectableChannel
能使用选择器的通道
-
SocketChannel
-
-
-
打开一个服务端通道(open)
-
绑定对应的端口号
-
通道默认是阻塞的,需要设置为非阻塞
-
打开一个选择器(门卫大爷)
-
将选择器绑定服务端通道,并监视服务端是否准备好
-
如果有客户端来连接了,大爷会遍历所有的服务端通道,谁准备好了,就让谁来连接 连接后,在服务端通道内部,再创建一个客户端延伸通道
-
代码实现
// 客户端 public class Clinet { public static void main(String[] args) throws IOException { SocketChannel socketChannel = SocketChannel.open(); socketChannel.connect(new InetSocketAddress("127.0.0.1",10000)); ByteBuffer byteBuffer1 = ByteBuffer.wrap("吃俺老孙一棒棒".getBytes()); socketChannel.write(byteBuffer1); System.out.println("数据已经写给服务器"); ByteBuffer byteBuffer2 = ByteBuffer.allocate(1024); int len; while((len = socketChannel.read(byteBuffer2)) != -1){ System.out.println("客户端接收回写数据"); byteBuffer2.flip(); System.out.println(new String(byteBuffer2.array(),0,len)); byteBuffer2.clear(); } socketChannel.close(); } } // 服务端 public class Server { public static void main(String[] args) throws IOException { //1.打开服务端通道 ServerSocketChannel serverSocketChannel = ServerSocketChannel.open(); //2.让这个通道绑定一个端口 serverSocketChannel.bind(new InetSocketAddress(10000)); //3.设置通道为非阻塞 serverSocketChannel.configureBlocking(false); //4.打开一个选择器 //Selector --- 选择器 // SelectionKey --- 绑定通道后返回那个令牌 // SelectableChannel --- 可以使用选择器的通道 Selector selector = Selector.open(); //5.绑定选择器和服务端通道 serverSocketChannel.register(selector,SelectionKey.OP_ACCEPT); while(true){ System.out.println("11"); //选择器会监视客户端通道的状态. //6.返回值就表示此时有多少个客户端来连接. int count = selector.select(); System.out.println("222"); if(count != 0){ System.out.println("有客户端来连接了"); //7.会遍历所有的服务端通道.看谁准备好了,谁准备好了,就让谁去连接. //获取所有服务端通道的令牌,并将它们都放到一个集合中,将集合返回. Set<SelectionKey> selectionKeys = selector.selectedKeys(); Iterator<SelectionKey> iterator = selectionKeys.iterator(); while(iterator.hasNext()){ //selectionKey 依次表示每一个服务端通道的令牌 SelectionKey selectionKey = iterator.next(); if(selectionKey.isAcceptable()){ //可以通过令牌来获取到了一个已经就绪的服务端通道 ServerSocketChannel ssc = (ServerSocketChannel) selectionKey.channel(); //客户端的延伸通道 SocketChannel socketChannel = ssc.accept(); //将客户端延伸通道设置为非阻塞的 socketChannel.configureBlocking(false); socketChannel.register(selector,SelectionKey.OP_READ); //当客户端来连接的时候,所有的步骤已经全部执行完毕. }else if(selectionKey.isReadable()){ //当前通道已经做好了读取的准备(延伸通道) SocketChannel socketChannel = (SocketChannel) selectionKey.channel(); ByteBuffer byteBuffer1 = ByteBuffer.allocate(1024); //socketChannel.read(byteBuffer1); int len; while((len = socketChannel.read(byteBuffer1)) > 0){ byteBuffer1.flip(); System.out.println(new String(byteBuffer1.array(),0,len)); byteBuffer1.clear(); } //给客户端的回写数据 socketChannel.write(ByteBuffer.wrap("哎哟喂好疼啊!!!".getBytes())); socketChannel.close(); } iterator.remove(); } } } } }