使用NIO實現非阻塞Socket通信
從JDK 開始Java提供的NIO API來開發高性能網絡服務器前面介紹的網絡通信程序是基於阻塞式API的即當程序執行輸入輸出操作後在這些操作返回之前會一直阻塞該線程所以服務器必須為每個客戶端都提供一條獨立線程進行處理當服務器需要同時處理大量客戶端時這種做法會導致性能下降使用NIO API則可以讓服務器使用一個或有限幾個線程來同時處理連接到服務器上的所有客戶端
如果讀者忘記了NIO裡ChannelBufferCharset等API的概念和用法讀者可以再次閱讀本書第章關於新IO的內容
Java的NIO為非阻塞式的Socket通信提供了如下幾個特殊類
Selector:它是SelectableChannel對象的多路復用器所有希望采用非阻塞方式進行通信的Channel都應該注冊到Selector對象可通過調用此類的靜態open()方法來創建Selector實例該方法將使用系統默認的Selector來返回新的Selector
Selector可以同時監控多個SelectableChannel的IO狀況是非阻塞IO的核心一個Selector實例有個SelectionKey的集合
所有SelectionKey集合代表了注冊在該Selector上的Channel這個集合可以通過keys()方法返回
被選擇的SelectionKey集合代表了所有可通過select()方法監測到需要進行IO處理的Channel這個集合可以通過selectedKeys()返回
被取消的SelectionKey集合代表了所有被取消注冊關系的Channel在下一次執行select()方法時這些Channel對應的SelectionKey會被徹底刪除程序通常無須直接訪問該集合
除此之外Selector還提供了系列和select()相關的方法如下所示
int select()監控所有注冊的Channel當它們中間有需要處理的IO操作時該方法返回並將對應的SelectionKey加入被選擇的SelectionKey集合中該方法返回這些Channel的數量
int select(long timeout)可以設置超時時長的select()操作
int selectNow()執行一個立即返回的select()操作相對於無參數的select()方法而言該方法不會阻塞線程
Selector wakeup()使一個還未返回的select()方法立刻返回
SelectableChannel:它代表可以支持非阻塞IO操作的Channel對象可以將其注冊到Selector上這種注冊的關系由SelectionKey實例表示
Selector對象提供了一個select()方法該方法允許應用程序同時監控多個IO Channel
應用程序可調用SelectableChannel 的register()方法將其注冊到指定Selector上當該Selector上某些SelectableChannel上有需要處理的IO操作時程序可以調用Selector實例的select()方法獲取它們的數量並可以通過selectedKeys()方法返回它們對應的SelectKey集合通過該集合就可以獲取所有需要處理IO操作的SelectableChannel集
SelectableChannel對象支持阻塞和非阻塞兩種模式(所有channel默認都是阻塞模式)必須使用非阻塞式模式才可以利用非阻塞IO操作
SelectableChannel提供了如下兩個方法來設置和返回該Channel的模式狀態
SelectableChannel configureBlocking(boolean block)設置是否采用阻塞模式
boolean isBlocking()返回該Channel是否是阻塞模式
不同的SelectableChannel所支持的操作不一樣例如ServerSocketChannel代表一個ServerSocket它就只支持OP_ACCEPT操作
SelectableChannel提供如下方法來返回它支持的所有操作
int validOps() :返回一個bit mask表示這個channel上支持的IO操作
在SelectionKey中用靜態常量定義了種IO操作OP_READ()OP_WRITE()OP_CONNECT()OP_ACCEP()這四值任意個個個進行按位或的結果和相加的結果相等而且它們任意個個個相加的結果總是互不相同所以系統可以根據validOps()方法的返回值確定該SelectableChannel支持的操作例如返回我們知道它支持讀()和寫()
除此之外SelectableChannel還提供了如下幾個方法來獲取它的注冊狀態
boolean isRegistered()返回該Channel是否已注冊在一個或多個Selector上
SelectionKey keyFor(Selector sel)返回該Channel和sel Selector之間的注冊關系如果不存在注冊關系則返回null
SelectionKey:該對象代表SelectableChannel和Selector之間的注冊關系
ServerSocketChannel:支持非阻塞操作對應於javanetServerSocket這個類提供了TCP協議IO接口只支持OP_ACCEPT操作該類也提供了accept()方法功能相當於ServerSocket提供的accept()方法
SocketChannel:支持非阻塞操作對應於javanetSocket這個類提供了TCP協議IO接口支持OP_CONNECTOP_READ和OP_WRITE操作這個類還實現了ByteChannel接口ScatteringByteChannel接口和GatheringByteChannel接口所以可以直接通過SocketChannel來讀寫ByteBuffer對象
圖顯示了使用NIO實現非阻塞式服務器的示意圖
圖 NIO的非阻塞式服務器示意
從圖中可以看出服務器上所有Channel(包括ServerSocketChannel和SocketChannel)都需要向Selector注冊而該Selector則負責監視這些Socket的IO狀態當其中任意一個或多個Channel具有可用的IO操作時該Selector的select()方法將會返回大於的整數該整數值就表示該Selector上有多少個Channel具有可用的IO操作並提供了selectedKeys()方法來返回這些Channel對應的SelectionKey集合正是通過Selector使得服務器端只需要不斷地調用Selector實例的select()方法即可知道當前所有Channel是否有需要處理的IO操作
當Selector上注冊的所有Channel都沒有需要處理的IO操作時select()方法將被阻塞調用該方法的線程被阻塞
本示例程序使用NIO實現了多人聊天室的功能服務器使用循環不斷獲取Selector的select()方法返回值當該返回值大於時就處理該Selector上被選擇SelectionKey所對應的Channel
服務器端需要使用ServerSocketChannel來監聽客戶端的連接請求Java中該類的設計比較糟糕它不是ServerSocket的完整抽象所以不能直接讓該Channel監聽某個端口而且不允許使用ServerSoceket的getChannel()方法來獲取ServerSocketChannel實例程序必須先調用它的socket()方法獲得關聯ServerSocket對象再用該ServerSocket對象綁定到來指定監聽IP和端口創建一個可用的ServerSocketChannel需采用如下代碼片段
//通過open方法來打開一個未綁定的ServerSocketChannel實例
ServerSocketChannel server = ServerSocketChannelopen()
InetSocketAddress isa = new InetSocketAddress( )
//將該ServerSocketChannel綁定到指定IP地址
serversocket()bind(isa)
如果需要使用非阻塞方式來處理該ServerSocketChannel還應該設置它的非阻塞模式並將其注冊到指定的Selector如下代碼片段
//設置ServerSocket以非阻塞方式工作
serverconfigureBlocking(false)
//將server注冊到指定Selector對象
serverregister(selector SelectionKeyOP_ACCEPT)
返回目錄瘋狂Java講義
編輯推薦
Java程序性能優化讓你的Java程序更快更穩定
新手學Java 編程
Java程序設計培訓視頻教程
From:http://tw.wingwit.com/Article/program/Java/hx/201311/27263.html