前幾天初步接觸瞭解了NIO,發現性能方面完爆BIO。所以決定將以前一個項目的服務端改形成NIO。可是NIO學習難度比BIO大,所以在網上查找相關資料,而後將我本身理解並簡化的NIO服務器搭建思路在這裏作個記錄分享。所以會有一些錯誤和忽略的地方,若要更加詳細請點擊下方的原文。html
思路來源:《Java NIO文檔》非阻塞式服務器,原文:Java NIO Tutorial.java
NIO相關的這裏就不作相關介紹,畢竟不能算真正的教程。服務器
大體思路以下:socket
整個系統主要爲兩個線程:AccepterThread,ProcessorThread。分別用於接收成功鏈接的Socket對象以及對消息的接收處理。函數
兩個線程之間使用隊列通訊,用於傳遞Socket對象性能
在消息的處理中又對Channel中的操做分爲讀操做和寫操做,這兩個操做分別創建兩個不一樣的類用來執行,原做者對此的解釋是爲了防止讀取數據的時候數據缺失以及寫數據的時候未徹底寫入。學習
讀寫操做分別又兩個對應的Selector對其進行監聽。this
對請求的接收就和通常的ServerSocket同樣,while循環直到有請求進來,這裏原文將準備好的SocketChannel包裝進另外編寫的Socket類中,這個Socket類相關定義以下:spa
public class Socket {
public long socketId;
public SocketChannel socketChannel;
public MessageReader reader;
public MessageWriter writer;
public Socket(SocketChannel channel) {
this.socketChannel = channel;
}
public int read(Bytebuffer buffer) {
//讀方法實現
}
public int write(Bytebuffer buffer) {
//寫方法實現
}
}
複製代碼
這裏將最重要的讀寫操做進行包裝,而且使用本身的消息讀/寫器來對Channel中的字節進行讀寫操做。線程
Accepter在包裝好Socket對象後將該對象add進隊列中。
在Processor中,存在一個while(true)循環,該循環遍歷鏈接AccepterThread的隊列,一旦隊列中有對象,則進行數據的處理操做。
Processor中存在兩個Selector,一個負責讀,另外一個負責寫。
在Accepter中包裝的Socket對象只有SocketChannel,所以在進行信息的讀寫以前要將對應的讀寫器以及SocketId裝入Socket對象中。這裏原做者對此的操做要規範複雜的多,翻譯的原文是:
一個Message Reader必定知足特定的協議。Message Reader須要知道它嘗試讀取的消息的消息格式。若是咱們的服務器能夠經過協議來複用,那它須要有可以插入Message Reader實現的功能 – 可能經過接收一個Message Reader工廠做爲配置參數。
在這裏進行包裝的好處是能給每一個SocketChannel都有讀/寫器,甚至可讓每一個SocketChannel的讀寫器不一樣。
再將信息包裝好後將Socket中的SocketChannel註冊進讀Selector中,同時將Socket對象做爲附件裝入。
等待消息。
當消息到達後觸發Selector,將讀就緒的SocketChannel獲取出來。其中的附件是包裝了的Socket對象,裏面有這個SocketChannel對應的讀寫器。將信息完整讀取後就是對信息的處理了,在處理這裏採用的是函數處理。即在程序的入口處就將處理程序設定完畢,而後將對應的函數傳遞給Processor,這樣當咱們須要對處理處理進行修改的時候不須要進入程序的主體,而只要在入口處修改便可。同時讀寫之間也是採用隊列進行通訊。信息處理完成後若是須要返回相關細心或者要將信息發送給其餘SocketChannel,就能夠將該操做offer進寫隊列當中。
對信息處理完成若是要進行寫操做,就將SocketChannel註冊進寫Selector中。這裏一開始以爲爲何不直接從隊列中獲取到對象就就進行寫操做,仔細考慮後發現沒法保證這個SocketChannel在執行寫的時候Client是沒有數據發送過來的,所以要將該SocketChannel註冊進寫Selector中保證在寫就緒狀態進行數據寫入。 同時在寫Selector中也不能保證全部都是對於流程來講能夠寫就緒的,畢竟空閒的時候就處於寫就緒狀態,可是此時服務器根本不會主動發送信息給客戶端,所以要檢測全部在寫Selector中註冊的SocketChannel中的Message是否寫入完畢,若是寫入完畢則將其移除Selector若是沒有則寫入。
只要在獲取到包裝好的Socket對象後將其保存在Hash表中則能夠隨時根據Socket的Id獲取到對應的SocketChannel而後根據將要進行的操做將該Socket添加到對應的隊列中等待註冊。