1. 程式人生 > >Java-NIO(七):阻塞IO與非阻塞IO

Java-NIO(七):阻塞IO與非阻塞IO

  • 阻塞IO

  傳統的 IO 流都是阻塞式的。

  也就是說,當一個執行緒呼叫 read() 或 write()時,該執行緒被阻塞,直到有一些資料被讀取或寫入,該執行緒在此期間不能執行其他任務。

  因此,在完成網路通訊進行 IO 操作時,由於執行緒會阻塞,所以伺服器端必須為每個客戶端都提供一個獨立的執行緒進行處理,當伺服器端需要處理大量客戶端時,效能急劇下降。

  注意:在阻塞IO操作的過程中,用來提高程式的解決方案一般是使用多執行緒來處理,但是開闢執行緒也是比較耗費資源的。

 測試NIO阻塞模式:

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/**
 2      * 客戶端
 3      */
 4     @Test
 5     public void client() throws IOException {
 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     @Test
21     public void server() throws IOException {
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<SelectionKey> 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     }