阻塞IO
传统的 IO 流都是阻塞式的。
也就是说,当一个线程调用 read() 或 write()时,该线程被阻塞,直到有一些数据被读取或写入,该线程在此期间不能执行其他任务。
因此,在完成网络通信进行 IO 操作时,由于线程会阻塞,所以服务器端必须为每个客户端都提供一个独立的线程进行处理,当服务器端需要处理大量客户端时,性能急剧下降。
注意:在阻塞IO操作的过程中,用来提高程序的解决方案一般是使用多线程来处理,但是开辟线程也是比较耗费资源的。
测试NIO阻塞模式:
1 package com.expgiga.NIO; 2 3 import java.io.IOException; 4 import java.net.InetSocketAddress; 5 import java.nio.ByteBuffer; 6 import java.nio.channels.FileChannel; 7 import java.nio.channels.ServerSocketChannel; 8 import java.nio.channels.SocketChannel; 9 import java.nio.file.Paths; 10 import java.nio.file.StandardOpenOption; 11 12 public class TestBlockingNIO { 13 14 15 //客户端 16 public void client() throws IOException { 17 //1.获取通道 18 19 SocketChannel sChannel = SocketChannel.open(new InetSocketAddress("123.0.0.1", 9898)); 20 21 FileChannel inChannel = FileChannel.open(Paths.get("1.jpg"), StandardOpenOption.READ); 22 //2.分配指定大小的缓冲区域 23 ByteBuffer buf = ByteBuffer.allocate(1024); 24 25 //3.读取本地文件,并发送到服务端去 26 while (inChannel.read(buf) != -1) { 27 buf.flip(); 28 sChannel.write(buf); 29 buf.clear(); 30 } 31 32 //4.关闭通道 33 inChannel.close(); 34 sChannel.close(); 35 } 36 37 38 //服务端 39 public void server() throws IOException { 40 //1.获取通道 41 ServerSocketChannel ssChannel = ServerSocketChannel.open(); 42 FileChannel outChannel = FileChannel.open(Paths.get("2.jpg"), StandardOpenOption.WRITE, StandardOpenOption.CREATE); 43 44 //2.绑定连接 45 ssChannel.bind(new InetSocketAddress(9898)); 46 47 //3.获取客户端连接的通道 48 SocketChannel sChannel = ssChannel.accept(); 49 50 //4.分配一个指定大小的缓冲区 51 ByteBuffer buf = ByteBuffer.allocate(1024); 52 53 //5.接受客户端的数据,保存到本地 54 while (sChannel.read(buf) != -1) { 55 buf.flip(); 56 outChannel.write(buf); 57 buf.clear(); 58 } 59 60 //6.关闭通道 61 sChannel.close(); 62 outChannel.close(); 63 ssChannel.close(); 64 65 } 66 67 }
1 @Test 2 public void client() throws IOException { 3 // 1、获取通道(channel) 4 SocketChannel socketChannel = SocketChannel.open(new InetSocketAddress("127.0.0.1", 9898)); 5 FileChannel inChannel = FileChannel.open(Paths.get("Java NIO.pdf"),StandardOpenOption.READ); 6 7 // 2、分配指定大小的缓冲区 8 ByteBuffer byteBuffer=ByteBuffer.allocate(1024); 9 10 // 3、读取本地文件,并写入发送channel 11 while (inChannel.read(byteBuffer)!=-1) { 12 byteBuffer.flip();// 切换到读模式 13 socketChannel.write(byteBuffer); 14 byteBuffer.clear();// 清空缓冲区 15 } 16 17 // 必须shutdown否则就没法切换到接收数据的模式 18 socketChannel.shutdownOutput(); 19 20 System.out.println("client waiting reading server response"); 21 // 接收服务端的数据 22 int length=0; 23 while((length=socketChannel.read(byteBuffer))!=-1){ 24 byteBuffer.flip(); 25 System.out.println(new String(byteBuffer.array(),0,length)); 26 byteBuffer.clear(); 27 } 28 29 System.out.println("end..."); 30 inChannel.close(); 31 socketChannel.close(); 32 } 33 34 @Test 35 public void server() throws IOException{ 36 // 1、获取通道 37 ServerSocketChannel serverSocketChannel = ServerSocketChannel.open(); 38 FileChannel outChannel=FileChannel.open(Paths.get("33.pdf"), StandardOpenOption.READ,StandardOpenOption.WRITE,StandardOpenOption.CREATE); 39 40 // 2、绑定连接 41 serverSocketChannel.bind(new InetSocketAddress(9898)); 42 // 3、获取客户端的连接 43 SocketChannel accept = serverSocketChannel.accept(); 44 45 // 4、分配指定大小的缓冲区 46 ByteBuffer byteBuffer= ByteBuffer.allocate(1024); 47 // 5、接收客户端的数据,并保存到本地 48 while (accept.read(byteBuffer)!=-1) { 49 byteBuffer.flip(); 50 outChannel.write(byteBuffer); 51 byteBuffer.clear(); 52 } 53 54 System.out.println("server print ..."); 55 56 byteBuffer.put("server success".getBytes()); 57 byteBuffer.flip();//切换到读模式 58 accept.write(byteBuffer); 59 60 // 6、关闭连接 61 accept.close(); 62 outChannel.close(); 63 serverSocketChannel.close(); 64 }
打印结果:
client waiting reading server response
server success
end…
非阻塞
Java NIO 是非阻塞模式的。
当线程从某通道进行读写数据时,若没有数据可用时,该线程可以进行其他任务。线程通常将非阻塞 IO 的空闲时间用于在其他通道上执行 IO 操作,所以单独的线程可以管理多个输入和输出通道。
因此, NIO 可以让服务器端使用一个或有限几个线程来同时处理连接到服务器端的所有客户端。
如何形成非阻塞IO(原理)
从上边的图中我们知道要构成NIO非阻塞模式,必须要引入Selector。那么,什么是Selector?
选择器(Selector)
选择器(Selector)是SelectableChannle对象的多路复用器,Selector可以同时监控多个SelectableChannel的IO状况,也就是说,利用Selector可以一个单独的线程管理多个Channel。Selector是非阻塞IO的核心。
使用NIO实现网络通信的三个核心
1、通道(channel):负责连接
java.nio.channels.Channel接口:
|–SelectableChannel
|–SocketChannel
|–ServerSocketChannel
|–DatagramChannel
|–Pipe.SinkChannel
|–Pipe.SourceChannel
2、缓冲区(Buffer):负责数据的存储
3、选择器(Selector):是SelectableChannel的多路复用器。用于监控SelectableChannel的IO状况。
阻塞IO示例
1 package com.expgiga.NIO; 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.Date; 11 import java.util.Iterator; 12 import java.util.Scanner; 13 30 public class TestNonBlockingNIO { 31 32 //客户端 33 public void client() throws IOException { 34 35 //1.获取通道 36 SocketChannel sChannel = SocketChannel.open(new InetSocketAddress("127.0.0.1", 9898)); 37 38 //2.切换成非阻塞模式 39 sChannel.configureBlocking(false); 40 41 //3.分配指定大小的缓冲区 42 ByteBuffer buf = ByteBuffer.allocate(1024); 43 44 //4.发送数据给服务端 45 Scanner scan = new Scanner(System.in); 46 47 while (scan.hasNext()) { 48 String str = scan.next(); 49 buf.put((new Date().toString() + "/n" + str).getBytes()); 50 buf.flip(); 51 sChannel.write(buf); 52 buf.clear(); 53 } 54 55 //5.关闭通道 56 sChannel.close(); 57 } 58 59 60 //服务端 61 public void server() throws IOException { 62 //1.获取通道 63 ServerSocketChannel ssChannel = ServerSocketChannel.open(); 64 65 //2.切换成非阻塞模式 66 ssChannel.configureBlocking(false); 67 68 //3.绑定连接 69 ssChannel.bind(new InetSocketAddress(9898)); 70 71 //4.获取选择器Selector 72 Selector selector = Selector.open(); 73 74 //5.将通道注册到选择器上,并且指定“监听接受事件” 75 ssChannel.register(selector, SelectionKey.OP_ACCEPT); 76 77 //6.轮询式获取选择器上已经准备就绪的事件 78 while (selector.select() > 0) { 79 //7.获取当前选择器中所有注册的选择键(已就绪的监听事件) 80 Iterator<SelectionKey> it = selector.selectedKeys().iterator(); 81 82 while (it.hasNext()) { 83 //8.获取准备就绪的事件 84 SelectionKey sk = it.next(); 85 86 //9.判断具体是什么事件准备就绪 87 if (sk.isAcceptable()) { 88 //10.若“接收就绪”,获取客户端连接 89 SocketChannel sChannel = ssChannel.accept(); 90 91 //11.切换成非阻塞的模式 92 sChannel.configureBlocking(false); 93 94 //12.将该通道注册到选择器上 95 sChannel.register(selector, SelectionKey.OP_READ); 96 } else if (sk.isReadable()) { 97 //13.获取当前选择器上的"读就绪"状态的通道 98 SocketChannel sChannel = (SocketChannel) sk.channel(); 99 100 //14.读取数据 101 ByteBuffer buf = ByteBuffer.allocate(1024); 102 103 int len = 0; 104 while ((len = sChannel.read(buf)) > 0) { 105 buf.flip(); 106 System.out.println(new String(buf.array(), 0, len)); 107 buf.clear(); 108 } 109 } 110 111 //15.取消选择键SelectionKey 112 it.remove(); 113 } 114 } 115 } 116 117 }
原创文章,作者:ItWorker,如若转载,请注明出处:https://blog.ytso.com/15742.html