JAVA NIO(四)阻塞式IO與非阻塞式IO

1.阻塞與非阻塞

IO模型:

由於進程是不可直接訪問外部設備的,所以只能調用內核去調用外部的設備(上下文切換),然後外部設備比如磁盤,讀出存儲在設備自身的數據傳送給內核緩衝區,內核緩衝區在copy數據到用戶進程的緩衝區。包含兩個步驟:一將數據讀到內核,二將數據從內核copy用戶地址空間(即應用程序)

① 阻塞與非阻塞:是針對於網絡通訊而言。應用程序在獲取網絡數據的時候,根據IO操作的就緒狀態來採取的不同方式。說白了是一種讀取或者寫入操作函數的實現方式。(即在服務端accept()時,若數據沒有準備就緒,則程序被阻塞)
(運行一個應用程序,就啓動了一個用戶進程,操作系統的核心是內核進程)
阻塞:應用程序在獲取網絡數據的時候,如果網絡傳輸數據很慢,那麼程序就一直等着,知道傳輸完畢爲止。
非阻塞:應用程序直接獲取數據無需等待。讀取或者寫入函數會立即返回一個狀態值。

阻塞IO模型:

在等待數據時,進程被掛起(就像線程中的wait()),知道數據就緒。


非阻塞IO模型

非阻塞是指在內核請求IO設備響應指令發出後,數據就開始準備,在此期間用戶進程沒有阻塞也就是沒有掛起,它一直在詢問或者check數據有沒有傳送到kernel buffer(內核)中,忙等…。但是第二個階段(數據從kernel buffer複製到用戶進程空間)依然是阻塞的。但這種IO模型會大量的佔用CPU的時間,效率很低效,很少使用。


IO多路複用

在內核請求IO設備響應指令發出後,數據就開始準備,在此期間用戶進程是阻塞的。數據從kernel buffer複製到用戶進程的過程也是阻塞的。但是和阻塞I/O所不同的是,它可以同時阻塞多個I/O操作,而且可以同時對多個讀操作,多個寫操作的I/O函數進行檢測,直到有數據可讀或可寫時,才真正調用I/O操作函數,也就是說一個線程可以響應多個請求。(在IO multiplexing Model中,實際中,對於每一個socket,一般都設置成爲non-blocking,但是,如下圖所示,整個用戶的process其實是一直被block的。只不過process是被select這個函數block,而不是被socket IO給block
弊端:
select 如果任何一個sock(I/O stream)出現了數據,select 僅僅會返回,但是並不會告訴你是那個sock上有數據,於是你只能自己一個一個的找,10幾個sock可能還好,要是幾萬的sock每次都找一遍,這個無謂的開銷就頗有海天盛筵的豪氣了。



② 選擇器

選擇器(Selector) 是 SelectableChannel 對象的多路複用器,Selector 可以同時監控多個 SelectableChannel 的 IO 狀況,也就是說,利用 Selector 可使一個單獨的線程管理多個 Channel。Selector 是非阻塞 IO 的核心。

(舉慄:比如說數據就緒,當有數據就緒服務端纔會進行accept();如圖所示)



當調用 register(Selector sel, int ops) 將通道註冊選擇器時,選擇器對通道的監聽事件,需要通過第二個參數 ops 指定。
可以監聽的事件類型(可使用 SelectionKey 的四個常量表示):
 讀 : SelectionKey.OP_READ (1)
 寫 : SelectionKey.OP_WRITE (4)
 連接 : SelectionKey.OP_CONNECT (8)
 接收 : SelectionKey.OP_ACCEPT (16)
 若註冊時不止監聽一個事件,則可以使用“位或”操作符連接。

ssChannel.register(selector, SelectionKey.OP_ACCEPT | SelectionKey.OP_CONNECT);
常用方法

③ SelectionKey
表示 SelectableChannel 和 Selector 之間的註冊關係。每次向選擇器註冊通道時就會選擇一個事件(選擇鍵)。選擇鍵包含兩個表示爲整數值的操作集。操作集的每一位都表示該鍵的通道所支持的一類可選擇操作。
常用方法

2.阻塞IO

package com.dason.nio2;

import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;
import java.nio.file.Paths;
import java.nio.file.StandardOpenOption;

import org.junit.Test;

/*
 * 一、使用 NIO 完成網絡通信的三個核心:
 * 
 * 1. 通道(Channel):負責連接
 * 		
 * 	   java.nio.channels.Channel 接口:
 * 			|--SelectableChannel
 * 				|--SocketChannel
 * 				|--ServerSocketChannel
 * 				|--DatagramChannel
 * 
 * 				|--Pipe.SinkChannel
 * 				|--Pipe.SourceChannel
 * 
 * 2. 緩衝區(Buffer):負責數據的存取
 * 
 * 3. 選擇器(Selector):是 SelectableChannel 的多路複用器。用於監控 SelectableChannel 的 IO 狀況
 * 
 */
public class TestBlockingNIO {

	//客戶端
	@Test
	public void client() throws IOException{
		//1. 獲取通道
		SocketChannel sChannel = SocketChannel.open(new InetSocketAddress("127.0.0.1", 9898));
		
		FileChannel inChannel = FileChannel.open(Paths.get("1.jpg"), StandardOpenOption.READ);
		
		//2. 分配指定大小的緩衝區
		ByteBuffer buf = ByteBuffer.allocate(1024);
		
		//3. 讀取本地文件,併發送到服務端
		while(inChannel.read(buf) != -1){
			buf.flip();
			sChannel.write(buf);
			buf.clear();
		}
		
		//4. 關閉通道
		inChannel.close();
		sChannel.close();
	}
	
	//服務端
	@Test
	public void server() throws IOException{
		//1. 獲取通道
		ServerSocketChannel ssChannel = ServerSocketChannel.open();
		
		FileChannel outChannel = FileChannel.open(Paths.get("2.jpg"), StandardOpenOption.WRITE, StandardOpenOption.CREATE);
		
		//2. 綁定連接
		ssChannel.bind(new InetSocketAddress(9898));
		
		//3. 獲取客戶端連接的通道
		SocketChannel sChannel = ssChannel.accept();
		
		//4. 分配指定大小的緩衝區
		ByteBuffer buf = ByteBuffer.allocate(1024);
		
		//5. 接收客戶端的數據,並保存到本地
		while(sChannel.read(buf) != -1){
			buf.flip();
			outChannel.write(buf);
			buf.clear();
		}
		
		//6. 關閉通道
		sChannel.close();
		outChannel.close();
		ssChannel.close();
		
	}
	
}

3.非阻塞IO

package com.dason.nio2;

import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;
import java.util.Date;
import java.util.Iterator;
import java.util.Scanner;

import org.junit.Test;

/*
 * 一、使用 NIO 完成網絡通信的三個核心:
 * 
 * 1. 通道(Channel):負責連接
 * 		
 * 	   java.nio.channels.Channel 接口:
 * 			|--SelectableChannel
 * 				|--SocketChannel
 * 				|--ServerSocketChannel
 * 				|--DatagramChannel
 * 
 * 				|--Pipe.SinkChannel
 * 				|--Pipe.SourceChannel
 * 
 * 2. 緩衝區(Buffer):負責數據的存取
 * 
 * 3. 選擇器(Selector):是 SelectableChannel 的多路複用器。用於監控 SelectableChannel 的 IO 狀況
 * 
 */
public class TestNonBlockingNIO {
	
	//客戶端
	@Test
	public void client() throws IOException{
		//1. 獲取通道
		SocketChannel sChannel = SocketChannel.open(new InetSocketAddress("127.0.0.1", 9898));
		
		//2. 切換非阻塞模式
		sChannel.configureBlocking(false);
		
		//3. 分配指定大小的緩衝區
		ByteBuffer buf = ByteBuffer.allocate(1024);
		
		//4. 發送數據給服務端
		Scanner scan = new Scanner(System.in);
		
		while(scan.hasNext()){
			String str = scan.next();
			buf.put((new Date().toString() + "\n" + str).getBytes());
			buf.flip();
			sChannel.write(buf);
			buf.clear();
		}
		
		//5. 關閉通道
		sChannel.close();
	}

	//服務端
	@Test
	public void server() throws IOException{
		//1. 獲取通道
		ServerSocketChannel ssChannel = ServerSocketChannel.open();
		
		//2. 切換非阻塞模式
		ssChannel.configureBlocking(false);
		
		//3. 綁定連接
		ssChannel.bind(new InetSocketAddress(9898));
		
		//4. 獲取選擇器
		Selector selector = Selector.open();
		
		//5. 將通道註冊到選擇器上, 並且指定“監聽接收事件”
		ssChannel.register(selector, SelectionKey.OP_ACCEPT);
		
		//6. 輪詢式的獲取選擇器上已經“準備就緒”的事件
		while(selector.select() > 0){
			
			//7. 獲取當前選擇器中所有註冊的“選擇鍵(已就緒的監聽事件)”
			Iterator<SelectionKey> it = selector.selectedKeys().iterator();
			
			while(it.hasNext()){
				//8. 獲取準備“就緒”的是事件
				SelectionKey sk = it.next();
				
				//9. 判斷具體是什麼事件準備就緒
				if(sk.isAcceptable()){
					//10. 若“接收就緒”,獲取客戶端連接
					SocketChannel sChannel = ssChannel.accept();
					
					//11. 切換非阻塞模式
					sChannel.configureBlocking(false);
					
					//12. 將該通道註冊到選擇器上
					sChannel.register(selector, SelectionKey.OP_READ);
				}else if(sk.isReadable()){
					//13. 獲取當前選擇器上“讀就緒”狀態的通道
					SocketChannel sChannel = (SocketChannel) sk.channel();
					
					//14. 讀取數據
					ByteBuffer buf = ByteBuffer.allocate(1024);
					
					int len = 0;
					while((len = sChannel.read(buf)) > 0 ){
						buf.flip();
						System.out.println(new String(buf.array(), 0, len));
						buf.clear();
					}
				}
				
				//15. 取消選擇鍵 SelectionKey
				it.remove();
			}
		}
	}
}
package com.dason.nio2;

import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.DatagramChannel;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.util.Date;
import java.util.Iterator;
import java.util.Scanner;

import org.junit.Test;

public class TestNonBlockingNIO2 {
	
	@Test
	public void send() throws IOException{
		DatagramChannel dc = DatagramChannel.open();
		
		dc.configureBlocking(false);
		
		ByteBuffer buf = ByteBuffer.allocate(1024);
		
		Scanner scan = new Scanner(System.in);
		
		while(scan.hasNext()){
			String str = scan.next();
			buf.put((new Date().toString() + ":\n" + str).getBytes());
			buf.flip();
			dc.send(buf, new InetSocketAddress("127.0.0.1", 9898));
			buf.clear();
		}
		
		dc.close();
	}
	
	@Test
	public void receive() throws IOException{
		DatagramChannel dc = DatagramChannel.open();
		
		dc.configureBlocking(false);
		
		dc.bind(new InetSocketAddress(9898));
		
		Selector selector = Selector.open();
		
		dc.register(selector, SelectionKey.OP_READ);
		
		while(selector.select() > 0){
			Iterator<SelectionKey> it = selector.selectedKeys().iterator();
			
			while(it.hasNext()){
				SelectionKey sk = it.next();
				
				if(sk.isReadable()){
					ByteBuffer buf = ByteBuffer.allocate(1024);
					
					dc.receive(buf);
					buf.flip();
					System.out.println(new String(buf.array(), 0, buf.limit()));
					buf.clear();
				}
			}
			
			it.remove();
		}
	}

}


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章