Netty(一)引題

本文介紹Java BIO(同步阻塞IO),僞異步IO,NIO(非阻塞IO),AIO(異步IO)這四種IO的狀況,並對不一樣IO模型做比較。html

目錄java

1.BIOlinux

2.僞異步IOgit

3.NIOgithub

4.AIO編程

5.四種IO比較後端

6.BIO\僞異步IO\NIO\AIO源碼下載數組

 

1.BIO服務器

採用BIO通訊模型的服務器,一般由一個獨立的Acceptor線程負責監聽客戶端的鏈接,它接收到客戶端鏈接請求後爲每一個客戶端建立一個新的線程進程鏈路鏈接處理,處理完後,經過輸出流返回應答給客戶端,線程銷燬。網絡

該模型最大的問題性能問題,當客戶端併發訪問增長後,服務端線程增長,當線程數膨脹後,系統的性能降低,隨着併發量增大,系統會發生線程堆棧溢出、建立新線程失敗等問題,最終致使線程宕機或者僵死,不能對外提供服務。並且開線程有很大的開銷,影響服務器性能。

源碼在src/main/java/NIOInduction/BIO下,分爲客戶端和服務端,簡單的網絡、線程的處理。

 

2.僞異步IO

爲了解決同步阻塞IO面臨的一個連接須要一個線程處理狀況,如今引入了「池」的概念,加入了線程池。

當有新的客戶端鏈接的時候,將客戶端的Socket封裝爲Task(java的Runnable接口實現了)投遞到後端線程池中進行處理。因爲線程池能夠設置消息隊列的大小和最大線程數,所以它的資源是可控的,不管多少個客戶端併發訪問,都不會致使資源的耗盡和宕機。

僞異步IO通信框架採用了線程池的實現,所以避免了爲每一個請求都建立一個獨立的線程形成的線程資源耗盡問題。可是因爲它的底層的通訊依然採用的同步阻塞模型,所以沒法從根本上解決問題。

java輸出流InputStream:當對socket的輸入流進行讀操做時,它會一直阻塞下去,直到發生如下三種事件。

  • 有數據可讀;
  • 可用數據已經讀取完畢;
  • 發生空指針或者IO異常。

這意味着當對方發數據請求或者應答消息緩慢(網絡傳輸慢)時,讀取寫入流一方的通信線程將長時間阻塞,若是對方要100s纔有消息發生完成,讀取的一方的IO線程也會將同步阻塞100s,在此時間裏,其餘接入消息只能在消息隊列中排隊。

java輸入流OutputStream:當調用OutputStream的write方法寫輸出流的時候,它將會唄阻塞,直到全部要發送的字節所有寫入完畢,或者發生異常。搞過TCP/IP的都曉得,當消息的接收方處理緩慢的時候,將不能及時從TCP緩衝區讀取數據,這將致使發送方的TCP window size不斷減少,直到爲0,雙方處於keep-alive狀態,消息發送方就不能再將TCP緩衝區寫入數據,這時採用同步阻塞的IO,write操做將會無限期阻塞,直到tcp window size大於0或者發生IO異常。

源碼在src/main/java/NIOInduction/PseudoAsynchronousIO下,分爲客戶端和服務端。客戶端和BIO的客戶端同樣,服務端加入了線程池ExecutorService,相關構造函數請讀者自行查閱。

 

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實現,因此沒有最大鏈接句柄的限制。(題外話,這裏說的eopllselect是說的linux下的IO複用,和selectepoll同樣,清楚流程概念請直接看源碼)。

NIO服務端序列圖

1.打開ServerSocketChannel,用於監聽客戶端的鏈接,它是全部客戶端鏈接的父管道。

 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客戶端序列圖(大多數和服務端相似)

1.打開SocketChannel,綁定客戶端本地地址(可選,默認系統會隨機分配一個可用的本地地址)

 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/NIOInduction/NIO下,分爲客戶端和服務端。

4.AIO

 NIO2.0中引入了新的異步通道的概念,並提供了異步文件通道h額異步套接字通道的實現。

異步通道提供2種方式獲取操做結果:

  • 經過java.util.concurrent.Futurn類來表示異步操做的結果;
  • 在執行異步操做的時候傳入一個java.nio.channels.

CompletionHandler接口的實現類做爲操做完成的回溯。

NIO2.0的異步套接字通道,對應UNIX網絡編程中的事件驅動IO(AIO),它不須要經過多路複用器(Selector)對註冊的通道進行輪詢操做。

源碼在src/main/java/NIOInduction/AIO下,分爲客戶端和服務端。

 

5.四種IO比較

6.BIO\僞異步IO\NIO\AIO源碼下載

GitHub地址:https://github.com/orange1438/Netty_Course

 

 
做者:orange1438
出處:http://www.cnblogs.com/orange1438/
本文版權歸做者和博客園共有,歡迎轉載,但未經做者贊成必須保留此段聲明,且在文章頁面明顯位置給出原文鏈接,不然保留追究法律責任的權利。
相關文章
相關標籤/搜索