/*阻塞 和 非阻塞 是对于 网络通讯而言的*/服务器
/*原先IO通讯在进行一些读写操做 或者 等待 客户机链接 这种,是阻塞的,必需要等到有数据被处理,当前线程才被释放*/网络
/*NIO 通讯 是将这个阻塞的过程 丢给了选择器,客户端和 服务器端 之间创建的通道,都会注册到 选择器上,而后用选择器 实时监控 咱们这些通道上的情况*/socket
/*当某一个通道上 某一个请求的事件 彻底准备就绪时,那么选择器才会将 这个任务 分配到服务器上的一个 或多个线程中*/性能
/*阻塞 与 非阻塞*/spa
传统的IO 流都是 阻塞式的。也就是说,当一个线程调用 read() 或 write() 时,该线程被阻塞,直到有一些数据被读取或写入,该线程在此期间不能执行其余任务线程
所以,在完成网络通讯进行IO操做时,因为线程会阻塞,因此 服务器必须为每一个客户端提供一个独立的线程进行处理 (这也是原来使用IO通讯的解决办法)rest
可是,当服务器须要处理大量客户端时,性能急剧降低code
Java NIO 是非阻塞式的。当线程从某通道进行读写数据时,若没有数据可用时,该线程能够进行其余任务。线程一般将非阻塞IO的空闲时间用于在其余通道上执行IO操做,因此单独的线程 能够管理 多个 输入和 输出通道。server
所以,NIO可让服务器端使用一个或有限几个线程来同时处理链接到服务器的全部客户端对象
/*NIO通讯非阻塞的缘由在于 选择器 的存在,若是不使用选择器,一样会出现阻塞的现象*/
关于NIO阻塞的演示:
1 public class TestBlockingNIO2 { 2 3 // 客户端 4 @Test 5 public void client() throws IOException { 6 //1.获取通道 7 SocketChannel socketChannel = SocketChannel.open(new InetSocketAddress("127.0.0.1", 8888)); 8 9 //2.分配指定大小的缓冲区 10 ByteBuffer buffer = ByteBuffer.allocate(1024); 11 12 //3.读取本地文件,并使用SocketChannel发送到服务器 13 FileChannel inChannel = FileChannel.open(Paths.get("1.jpg"), StandardOpenOption.READ); 14 while(inChannel.read(buffer) != -1) { 15 buffer.flip(); 16 socketChannel.write(buffer); 17 buffer.clear(); 18 } 19 20 //在这里服务端不知道 客户端数据 发没发完,线程就一直处于阻塞状态 21 //经过shutdownOutput 来告知服务器 我不发送数据了 22 23 //之因此上一个 程序 不用 shutdown 线程也能结束,多是由于上一个程序只须要向服务端发送数据,而不须要接收数据,可以判断出是否发送完了数据 24 socketChannel.shutdownOutput(); 25 26 //4.接收服务端传来的反馈 27 int length = 0; //这里指定一下 从 buffer 读取的长度,由于在这里buffer 中还带有了图片信息 28 while((length = socketChannel.read(buffer)) != -1) { 29 buffer.flip(); 30 System.out.println(new String(buffer.array(),0,length)); 31 buffer.clear(); 32 } 33 34 //4.关闭通道 35 inChannel.close(); 36 socketChannel.close(); 37 } 38 39 // 服务端 40 @Test 41 public void server() throws IOException { 42 //1.获取通道 43 ServerSocketChannel serverSocketChannel = ServerSocketChannel.open(); 44 45 //2.绑定链接端口号 46 serverSocketChannel.bind(new InetSocketAddress(8888)); 47 48 //3.获取客户端链接的通道 49 SocketChannel socketChannel = serverSocketChannel.accept(); 50 51 //4.分配指定大小的缓冲区 52 ByteBuffer buffer = ByteBuffer.allocate(1024); 53 54 //5.接收客户端发来的数据,并保存到本地 55 FileChannel outChannel = FileChannel.open(Paths.get("3.jpg"), StandardOpenOption.WRITE,StandardOpenOption.CREATE); 56 while(socketChannel.read(buffer) != -1) { 57 buffer.flip(); 58 outChannel.write(buffer); 59 buffer.clear(); 60 } 61 62 //6.发送反馈给客户端 63 buffer.put("服务端接收数据成功".getBytes()); 64 buffer.flip(); 65 socketChannel.write(buffer); 66 67 //6.关闭通道 68 outChannel.close(); 69 socketChannel.close(); 70 serverSocketChannel.close(); 71 72 } 73 74 }
/*选择器 (Selector)*/
选择器(Selector)是 SelectableChannle 对象的多路复用器,
/*Selector 能够同时 监控多个SelectableChannel 的 IO 情况*/,也就是说,
利用 /*Selector 可以使一个单独的线程管理多个 Channel */ selector 是 非阻塞的核心
/*选择器(Selector)的应用*/
1.建立 Selector :经过调用Selector.open() 方法建立一个 Selector
Selector selector = Selector.open(); //建立选择器
2.向选择器注册通道:SelectableChannel.register(Selector sel,int ops)
如:SelectionKey key = channel.register(selector,SelectionKey.OP_READ)
当调用 register (Selector sel,int ops) 为通道 注册选择器时,选择器对通道的监听事件,须要经过第二个参数 ops 指定
3.能够监听的事件类型(可以使用 SelectionKey 的四个常量表示):
读:SelectionKey.OP_READ (1)
写:SelectionKey.OP_WRITE (4)
链接:SelectionKey.OP_CONNECT (8)
接收:SelectionKey.OP_ACCEPT (16)
若注册时不止监听一个事件,则可使用 “位或” 操做符 (|)链接 : int interestSet = SelectionKey.OP_READ|SelectionKey.OP_WRITE
/*SocketChannel*/
Java NIO 中的 SocketChannel 是一个链接到TCP网络套接字的通道
操做步骤:打开SocketChannel 读写数据 关闭SocketChannel
/*ServerSocketChannel*/
Java NIO 中的 ServerSocketChannel 是一个 能够监听新进来的TCP链接的通道,就像标准IO 中的 ServerSocket同样
/*DatagramChannel*/
Java NIO 中的 DatagramChannel 是一个能收发UDP包的通道
使用选择器完成NIO的非阻塞式通讯:
1 /* 2 * 一:使用NIO完成网络通讯的三个核心: 3 * 4 *1.通道(Channel) :负责链接 5 * 6 * 7 *2.缓冲区(Buffer) :负责数据的存取 8 * 9 * 10 *3.选择器(Selector):监控SelectableChannel的IO情况 11 * 12 * */ 13 /*能够开启多个客户端,访问服务端,客户端的数据传递给服务端是非阻塞式的 14 * 最后的效果 相似于聊天室 15 * */ 16 public class TestNonBlockingNIO { 17 //客户端 18 @Test 19 public void client() throws IOException { 20 //1.获取通道 21 SocketChannel socketChannel = SocketChannel.open(new InetSocketAddress("127.0.0.1",8888)); 22 23 //2.切换非阻塞模式 24 socketChannel.configureBlocking(false); 25 26 //3.分配指定大小的缓冲区 27 ByteBuffer buffer = ByteBuffer.allocate(1024); 28 29 //4.发送数据到服务端 30 Scanner scanner = new Scanner(System.in); 31 while(scanner.hasNext()) { 32 String str = scanner.next(); 33 buffer.put((new Date().toString() + "\n" + str).getBytes()); 34 buffer.flip(); 35 socketChannel.write(buffer); 36 buffer.clear(); 37 } 38 39 /*buffer.put(new Date().toString().getBytes()); 40 buffer.flip(); 41 socketChannel.write(buffer); 42 buffer.clear();*/ 43 44 //5.关闭通道 45 socketChannel.close(); 46 } 47 48 //服务端 49 @Test 50 public void server() throws IOException { 51 //1.获取通道 52 ServerSocketChannel serverSocketChannel = ServerSocketChannel.open(); 53 54 //2.切换到非阻塞模式 55 serverSocketChannel.configureBlocking(false); 56 57 //3.绑定链接端口号 58 serverSocketChannel.bind(new InetSocketAddress(8888)); 59 60 //4.获取选择器 61 Selector selector = Selector.open(); 62 63 //5.将通道注册选择器 64 //经过SelectionKey 指定 这个 选择器 对 通道的监听事件 (这里是 accept)( SelectionKey.OP_ACCEPT) 65 //经过选择器监听的方式,只有等 客户端 链接 准备 就绪了,才会 accept 这个链接 66 serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT); 67 68 //6.轮询式的 获取选择器上已经 '准备就绪' 的事件 69 while(selector.select() > 0) { //这表明了 当前选择器 有准备就绪的 事件(第一次循环中由于这个选择器只监听了 accept,因此这个准备就绪的事件就是accept ) 70 71 //7.获取当前选择器中,全部注册的 ‘选择键(已就绪的监听事件)’ 72 Iterator<SelectionKey> iterator = selector.selectedKeys().iterator(); 73 while(iterator.hasNext()) { 74 //8.获取准备 “就绪的” 事件 75 SelectionKey sk = iterator.next(); 76 77 //9.判断具体是什么事件准备就绪 (是不是 accept 准备就绪) 78 if(sk.isAcceptable()) { 79 //10. 若是客户端链接 准备就绪,就使用accept 来接收 80 SocketChannel clientChannel = serverSocketChannel.accept(); 81 82 //11.切换到非阻塞模式 83 clientChannel.configureBlocking(false); 84 85 //12.将该客户端的通道注册到选择器上(由于要发送数据都服务器端,想要非阻塞式的,就要注册选择器) 86 clientChannel.register(selector, SelectionKey.OP_READ); 87 } else if(sk.isReadable()) { //第一次循环SelectionKey 中,是没有 read 的,SelectionKey尚未更新,//再一次 轮询式的 获取选择器上已经 '准备就绪' 的事件 后, 88 //13.获取当前 选择器 上 “读就绪” 状态的通道 //就能够 获取当前 选择器 上 “读就绪” 状态的通道 89 SocketChannel socketChannel = (SocketChannel) sk.channel(); 90 91 //14.读取客户端发来的数据 92 ByteBuffer buffer = ByteBuffer.allocate(1024); 93 94 //注:这里不能写 -1,只能写 > 0, 95 //可能由于会客户端一直会从控制台读取数据,而后发送给服务端,因此将通道中的数据读到缓冲区中时,由于可能一直有数据进来,因此不会返回 -1, 96 //若是写 != -1,会一直陷在循环中 ,必须写 > 0,肯定是有真实的数据过来的 97 98 while(socketChannel.read(buffer) > 0) { 99 buffer.flip(); 100 System.out.println(new String(buffer.array())); 101 buffer.clear(); 102 } 103 } 104 105 //15.取消选择键 SelectionKey,不取消,他就一直有效,SelectionKey 就没法更新 106 iterator.remove(); 107 } 108 } 109 } 110 }