/*阻塞 和 非阻塞 是對於 網絡通訊而言的*/服務器
/*原先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 }