阻塞IO
传统的 IO 流都是阻塞式的。
也就是说,当一个线程调用 read() 或 write()时,该线程被阻塞,直到有一些数据被读取或写入,该线程在此期间不能执行其他任务。
因此,在完成网络通信进行 IO 操作时,由于线程会阻塞,所以服务器端必须为每个客户端都提供一个独立的线程进行处理,当服务器端需要处理大量客户端时,性能急剧下降。
注意:在阻塞IO操作的过程中,用来提高程序的解决方案一般是使用多线程来处理,但是开辟线程也是比较耗费资源的。
测试NIO阻塞模式:
1 @Test2 public void client() throwsIOException {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 @Test35 public void server() throwsIOException{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 /**
2 * 客户端3 */
4 @Test5 public void client() throwsIOException {6 //1、获取通道(channel)
7 SocketChannel socketChannel = SocketChannel.open(new InetSocketAddress("127.0.0.1", 9898));8 //2、切换成非阻塞模式
9 socketChannel.configureBlocking(false);10
11 //3、分配指定大小的缓冲区
12 ByteBuffer byteBuffer = ByteBuffer.allocate(1024);13 byteBuffer.put("你可理论上的 。。。".getBytes());14 byteBuffer.flip();15 socketChannel.write(byteBuffer);16
17 socketChannel.close();18 }19
20 @Test21 public void server() throwsIOException {22 //1、获取通道
23 ServerSocketChannel serverSocketChannel =ServerSocketChannel.open();24 //2.设置为非阻塞
25 serverSocketChannel.configureBlocking(false);26 //3、绑定连接
27 serverSocketChannel.bind(new InetSocketAddress(9898));28
29 //4、获取Selector选择器
30 Selector selector =Selector.open();31
32 //5、将通道注册到选择器上,并制定监听事件为:“接收”事件
33 serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);34
35 //6、采用轮询的方式获取选择器上“准备就绪”的任务
36 while (selector.select() > 0) {37 //7、获取当前选择器中所有注册的选择键(“已经准备就绪的事件”)
38 Iterator selectedKeys =selector.selectedKeys().iterator();39 while(selectedKeys.hasNext()) {40 //8、获取“准备就绪”的时间
41 SelectionKey selectedKey =selectedKeys.next();42
43 //9、判断key是具体的什么事件
44 if(selectedKey.isAcceptable()) {45 //10、若接受的事件是“接收就绪”事件,就获取客户端连接
46 SocketChannel socketChannel =serverSocketChannel.accept();47 //11、切换为非阻塞模式
48 socketChannel.configureBlocking(false);49 //12、将该通道注册到selector选择器上
50 socketChannel.register(selector, SelectionKey.OP_READ);51 } else if(selectedKey.isReadable()) {52 //13、获取该选择器上的“读就绪”状态的通道
53 SocketChannel socketChannel =(SocketChannel) selectedKey.channel();54
55 //14、读取数据
56 ByteBuffer byteBuffer = ByteBuffer.allocate(1024);57 int length = 0;58 while ((length = socketChannel.read(byteBuffer)) != -1) {59 byteBuffer.flip();60 System.out.println(new String(byteBuffer.array(), 0, length));61 byteBuffer.clear();62 }63 socketChannel.close();64 }65
66 //15、移除选择键
67 selectedKeys.remove();68 }69 }70
71 //7、关闭连接
72 serverSocketChannel.close();73 }