1. 程式人生 > >非阻塞式通訊詳解

非阻塞式通訊詳解

用ServerSocket 和Socket編寫伺服器和客戶端程式的時候,其在執行過程中往往是阻塞的。例如SeverSocket中的accept()方法,假如沒有客戶連線就一直處於阻塞狀態。JDK1.4

後引入非阻塞式通訊機制,伺服器程式接收客戶連線,客戶程式建立與伺服器的連線以及伺服器程式和客戶程式收發資料的操作都可以按照非阻塞的方式進行,伺服器只需建立一個執行緒,就能完成同時與多個客戶通訊的任務。

阻塞執行緒的特點:

放棄CPU,暫停執行,只有等待阻塞消除才能恢復執行。被其他執行緒中斷,該執行緒會退出阻塞狀態,並且丟擲InterruptException。

執行緒阻塞的原因:

1:執行了sleep()方法,放棄cpu,休眠完成後恢復執行。

2:執行緒執行了一段同步程式碼由於無法獲得相關的同步鎖,只好進入阻塞狀態,等到獲得後方可執行。

3:執行緒執行了wait ()方法,進入阻塞狀態,只有等待其他執行緒執行了notify ()和notifyAll ()方法後,才能將其喚醒。

執行緒執行了I/O操作進行遠端通訊時,會因為等待相關的資源而進入阻塞狀態。

在遠端通訊中造成阻塞的原因:

1:請求與伺服器連線時,或執行connect()方法時,會進入阻塞狀態。直到連線成功

2:執行緒從Socket的輸入流讀入資料的時候,如果沒有足夠的資料就會進入阻塞狀態

直到讀入足夠的資料,或者到達輸入流的末尾。

3:執行緒向socket的輸出流寫入一批資料,可能會進入阻塞狀態,等到輸出了所有資料,或者出現異常,才從輸出流的write()方法中返回異常。

伺服器程式用多執行緒處理阻塞通訊的侷限:

當主執行緒接收客戶連線以及工作執行緒執行I/O操作時,儘管能滿足多個各戶的需求但是仍有侷限:

1:java虛擬機器會為每個執行緒分配獨立的堆疊空間,工作執行緒數目越多,系統的開銷就越大,增加了JVM排程執行緒的負擔,增加了執行緒之間同步的複雜性,提高了執行緒死鎖的可能性。

2:工作線層的許多時間都浪費在I/O操作上,JVM需要頻繁的切換上下文。增加系統的開銷。

工作執行緒並不是越多越好,保持適量的工作執行緒,會提高伺服器的併發效能。當工作執行緒達到某個極限,反而會降低併發效能。

非阻塞式通訊:

非阻塞式通訊採用輪詢的工作方式,當某一操作完成時,就會執行該操作,否則就檢視是否還有其他就緒的操作可以執行,執行緒不會因為某一個操作還沒有就緒,就進入阻塞狀態。

為了使輪詢的工作方式順利進行,接收客戶的連線,讀資料,寫資料,都應該是非阻塞的,就是執行這些操作時,如果操作還沒有就緒,就立即返回,而不會一直等待操作就緒。

例如:當執行緒從輸入流中讀取資料,如果輸入流中沒有資料就立即返回或者沒有足夠的資料那麼就讀取現有的資料,然後返回。

java.nio包下提供了支援非阻塞通訊的類。

ServerSocketChannel :ServerSocket的替代類,支援阻塞,與非阻塞通訊

SocketChannel: Socket的替代類,支援阻塞,與非阻塞通訊。

Selector:為ServerSocketChannel監控接收連線就緒的事件,為SocketChannel監控連線就緒,讀,寫就緒。

SelectionKey :代表ServerSocketChannel,SocketChannel 向Selector註冊事件的控制代碼,

當一個SelectionKey物件位於Selector物件的Selected-keys集合中時,就表示這個SelectionKey物件的相關事件的發生。

 ServerSocketChannel與SocketChannel都是SelectableChannel的子類,SelectableChannel類及其子類都委託Selector來監控一些可能發生的事件,這種委託過程也稱為註冊事件過程。

ServerSocketChannel向Selector註冊接收連線就緒事件的程式碼:

SelectionKey key = ServerSocketChannel.register (selector,SelectionKey.OP_ACCEPY);

SelectionKey.OP_ACCEPY:接收連線就緒事件,表示至少有一個客戶連線,伺服器可以接收這個連線。

SocketChannel可能發生如下三種事件:

SelectionKey.OP_CONNECT:連線就緒,客戶與伺服器連線成功

SelectionKey.OP_READ :讀就緒,輸入流中已有可讀資料,可以執行讀操作了

SelectionKey.OP_WRITE : 寫就緒,表示可以向輸入流寫資料了。

SocketChannel提供的接收發送方法:

read (ByteBuffer ,buffer):接收資料將其存放到引數指定的ByteBuffer

write (ByteBuffer ,buffer):把指定的ByteBuffer中的資料傳送出去。

緩衝區Buffer:(資料輸入輸出往往比較耗時)

作用:1:減少實際的物理讀寫次數。

      2:緩衝區在建立的時候被非配記憶體,這塊記憶體一直被重用,這可以減少動態分配和回收記憶體的次數。

所有的緩衝區都有以下屬性:

容量:儲存資料的空間大小。

極限:表示緩衝區當前的終點,不能對緩衝區中超過極限的區域進行讀寫操作,極限可以修改,這有利於緩衝區的重用。

位置:表示緩衝區中下一個讀寫單元的位置,每次讀寫緩衝區的資料時都會改變,為下一條資料做準備,位置是一非負數不應該大於極限。

java.nio.Buffer 類是一個抽象類:提供了來兩個獲得ByteBuffer例項的靜態工廠方法:

allocate(int capacity):返回一個ByteBuffer物件,引數指定緩衝區的容量。

directAllocate (int capacity) ,該方法的緩衝區稱為直接緩衝區,與當前作業系統能夠更好的耦合,因此進一步提高I/o的操作速度。但是系統的開銷很大,因此只有在緩衝區較大或者經常重用時才用。

字元編碼:CharSet

java.nio.CharSet類的每一個例項代表特定的字元編碼型別

把位元組序列轉換為字串的過程稱為解碼,把字元轉換為位元組序列的過程稱為編碼

CharSet類提供的編碼與解碼方式:

ByteBuffer encodes (String str):對引數指定的字串進行編碼,並把的到的位元組序列放在一個ByteBuffer物件中。

CharBuffer decode (ByteBuffer  bb):把引數bb指定的ByteBuffer 中的位元組序列進行解碼,並把得到的字元序列存放在CharBuffer中。

Charset 的forName(String , encode) :方法返回一個CharSet物件,引數代表指定的編碼型別。

通道Channel

通道Channel用來連線緩衝區與資料來源或資料匯(資料目的地)。

通道在建立時被開啟,一旦關閉就不能重新開啟。

SelectableChannel :是一種支援阻塞與非阻塞的i/o通道。在非阻塞模式下,讀寫資料不會阻塞,SelectableChannel 可以向Selector註冊讀就緒和寫就緒事件。

Selector類:

只要ServerSocketChannel即SocketChannel向Selector()註冊了特定的事件,Selector就會監控這些資料是否發生,SelectableChannel的register方法負責註冊事件,

SelectionKey :

ServerSocketChannel即SocketChannel向Selector()註冊了特定的事件,register()方法會建立一個SelectionKey物件,這個SelectionKey物件用來跟蹤註冊事件的控制代碼,

在其有效期內會一直監控與SelectionKey物件相關的事件,如果有事件發生,就會把SelectionKey物件加入到selected-key集合中。