Netty之引題,netty
本文介紹Java BIO(同步阻塞IO),偽非同步IO,NIO(非阻塞IO),AIO(非同步IO)這四種IO的情況,並對不同IO模型作比較。
目錄
1.BIO
2.偽非同步IO
3.NIO
4.AIO
5.四種IO比較
6.BIO\偽非同步IO\NIO\AIO源碼下載
1.BIO
採用BIO通訊模型的伺服器,通常由一個獨立的Acceptor線程負責監聽用戶端的串連,它接收到用戶端串連請求後為每個用戶端建立一個新的線程進程鏈路串連處理,處理完後,通過輸出資料流返回應答給用戶端,線程銷毀。
2.偽非同步IO
為瞭解決同步阻塞IO面臨的一個連結需要一個線程處理情況,現在引入了“池”的概念,加入了線程池。
當有新的用戶端串連的時候,將用戶端的Socket封裝為Task(java的Runnable介面實現了)投遞到後端線程池中進行處理。由於線程池可以設定訊息佇列的大小和最大線程數,因此它的資源是可控的,無論多少個用戶端並發訪問,都不會導致資源的耗盡和宕機。
3.NIO
NIO庫,是在JDK1.4中引入的,NIO彌補了同步阻塞IO的不足。在所有的資料,NIO都是用緩衝區處理掉的(Buffer),任何時候訪問NIO中的資料,都是通過緩衝區進行操作。緩衝區實際就是一個數組。Java NIO的基礎是多工器Selector,簡單來說,selector會不斷的輪詢註冊在其上的Channel(通道,全雙工系統的),如果某個Channel上有新的TCP串連接入、讀寫事件,這個Channel會處於就緒狀態,會被Selector輪詢出來,然後通過SelectionKey可以擷取就緒的select集合,進行後續的IO操作。
一個多工器可以同時輪詢多個Channel,而且由於jdk使用了epoll替代了select實現,所以沒有最大串連控制代碼的限制。(題外話,這裡說的eopll、select是說的linux下的IO複用,和select、epoll一樣,清楚流程概念請直接看源碼)。
NIO服務端順序圖表
ServerSocketChannel accptorSvr = ServerSocketChannel.open();
2.綁定監聽連接埠,設定串連為非阻塞模式。
acceptorSvr.socket().bind( new InetSocketAddress(InetAddress.getByName("IP"),port));acceptorSvr.configureBlocking(false);
3.建立Reactor線程,建立多工器並啟動線程。
Selector selectot = Selector.open();new Thread(new RectorTask()).start();
4.將SelectSocketChannel註冊到Reactor線程的多工器selector上,監聽accept事件。
SelectionKey key = acceptorSvr.register(selector,SelectionKey.OP_ACCEPT,ioHandler);
5.多工器線上程run方法中無線迴圈裡輪詢準備就緒的key。
int num = selector.select();Set selectkeys = selector.selectedKeys();Iterator it = selectkeys.iterator();while(it.hasNext){ SelectionKey key = (SelectionKey)it.next; /* deal with IO event */ }
6.多工監聽到有新的使用者接入,處理新的接入請求,完成TCP三向交握,建立物理串連。
SocketChannel sc = ssc.accept();
7.設定用戶端鏈路為非阻塞模式
sc.configureBlocking(false);sc.socket().setReuseAddress(true);...
8.將新接入的用戶端串連註冊到Reactor線程的多工器上,監聽讀操作,用來讀取用戶端發送的網路訊息。
SelectionKey key = sc.register(selector,SelectionKey.OP_READ,ioHangler);
9.非同步讀取用戶端請求訊息到緩衝區
int readNumber = channel.read(receivedBuffer);
10.對bytebuffer進行編解碼,如果有半包訊息指標reset,繼續讀取後續的報文,將解碼成功的訊息封裝成task,投遞到業務線程池中,進行商務邏輯處理。
Object message = null; while (buffer.hasRemain()){ byteBuffer.mark(); Object message = decode(byteBuffer); if(message==null){ byteBuffer.reset(); break; } messageList.add(message); } if(!byteBuffer.hasRemain()){ byteBuffer.clear(); } else byteBuffer.compact(); if(messageList!=null & !messageList.isEmpty()) { for(Object messageF:messageList) handleTask(messageE); }
11.將pojo對象encode成bytebuffer,調用SocketChannel的非同步write介面,將訊息非同步發送到用戶端。
socketChannel.wite(buffer);
注意:如果發送區TCP緩衝區滿了,會導致寫半包,此時,需要註冊寫操作位,迴圈寫,直到整個包訊息寫入TCP緩衝區。
NIO用戶端順序圖表(大多數和服務端類似)
SocketChannel clientChannel = SocketChannel.open();
2.設定SocketChannel為非阻塞模式,同時設定串連的TCP參數。
SocketChannel.configureBlocking(false);
socket.setReuseAddress(true);
socket.setReceiveBufferSize(BUFFER_SIZE);
socket.setSendBufferSize(BUFFER_SIZE);
3.非同步串連伺服器。
boolean connected = clientChannel.connect(new InetSocketAdress("ip",port));
4.判斷是否串連成功,如果成功,則直接註冊讀狀態位到多工器中,如果沒成功(非同步串連,返回false,說明用戶端已經已經發送sync包,服務端沒有返回ack包,物理串連還沒建立——關於ack、sync包,請讀者自行查閱TCP/IP中的TCP的三向交握,四次分手的過程)
if(connect)
clientChannel.register(selector,SelectionKey.OP_READ,ioHandler);
else
clientChannel.register(selector,SelectionKey.OP_CONNECT,ioHandler);
5.向Reactor線程的多工器註冊OP_CONNECT狀態位,監聽伺服器的TCP ACK應答。
clientChannel.register(selector,SelectionKey.OP_CONNECT,ioHandler);
6.建立Reactor線程,建立多工器並啟動線程。
Selector selectot = Selector.open();new Thread(new RectorTask()).start();
7.多工器線上程run方法中無線迴圈裡輪詢準備就緒的key。
int num = selector.select();Set selectkeys = selector.selectedKeys();Iterator it = selectkeys.iterator();while(it.hasNext){ SelectionKey key = (SelectionKey)it.next; /* deal with IO event */ }
8.接收connect事件進行處理
if(key.isConnectable())
//handlerConnect();
9.判斷串連結果,如果串連成功,註冊讀事件到多工器
if(channel.finishConnect())
registerRead();
10.註冊讀事件到多工器
clientChannel.register(selector,SelectionKey.OP_READ,ioHandler);
11.非同步讀取用戶端請求訊息到緩衝區
int readNumber = channel.read(receivedBuffer);
12.對bytebuffer進行編解碼,如果有半包訊息指標reset,繼續讀取後續的報文,將解碼成功的訊息封裝成task,投遞到業務線程池中,進行商務邏輯處理。
Object message = null; while (buffer.hasRemain()){ byteBuffer.mark(); Object message = decode(byteBuffer); if(message==null){ byteBuffer.reset(); break; } messageList.add(message); } if(!byteBuffer.hasRemain()){ byteBuffer.clear(); } else byteBuffer.compact(); if(messageList!=null & !messageList.isEmpty()) { for(Object messageF:messageList) handleTask(messageE); }
13.將pojo對象encode成bytebuffer,調用SocketChannel的非同步write介面,將訊息非同步發送到用戶端。
socketChannel.wite(buffer);
註:以上的用戶端和服務端過程,瞭解就行,上層的代碼不一定這樣寫的,具體參考能啟動並執行代碼。
源碼在src/main/java/NIO下,分為用戶端和服務端。
4.AIO
NIO2.0中引入了新的非同步通道的概念,並提供了非同步檔案通道h額非同步通訊端通道的實現。
非同步通道提供2種方式擷取操作結果:
- 通過java.util.concurrent.Futurn類來表示非同步作業的結果;
- 在執行非同步作業的時候傳入一個java.nio.channels.
CompletionHandler介面的實作類別作為操作完成的回溯。
NIO2.0的非同步通訊端通道,對應UNIX網路編程中的事件驅動IO(AIO),它不需要通過多工器(Selector)對註冊的通道進行輪詢操作。
源碼在src/main/java/AIO下,分為用戶端和服務端。
5.四種IO比較
6.BIO\偽非同步IO\NIO\AIO源碼下載
GitHub地址:https://github.com/orange1438/Netty_Course