Netty的入門基礎知識

目錄

1 同步異步 阻塞非阻塞

2 Linux的網絡模型

同步阻塞I/O

同步非阻塞 I/O

I/O複用模型

信號驅動式I/O模型

異步非阻塞 I/O

各種I/O模型的比較

 3 NIO編程

緩衝器Buffer

通道Channel

多路複用器Selector

4 NIO客戶端和服務端序列圖


 

1 同步異步 阻塞非阻塞

故事:老王燒開水。

出場人物:老張,水壺兩把(普通水壺,簡稱水壺;會響的水壺,簡稱響水壺)。

老王想了想,有好幾種等待方式

1.老王用水壺煮水,並且站在那裏不管水開沒開,每隔一定時間看看水開了沒。-同步阻塞

老王想了想,這種方法不夠聰明。

2.老王還是用水壺煮水,不再傻傻的站在那裏看水開,跑去寢室上網但是還是會每隔一段時間過來看看水開了沒有,水沒有開就走人。-同步非阻塞

老王想了想,現在的方法聰明瞭些,但是還是不夠好。

3.老王這次使用高大上的響水壺來煮水,站在那裏但是不會再每隔一段時間去看水開,而是等水開了,水壺會自動的通知他。-異步阻塞

老王想了想,不會呀,既然水壺可以通知我,那我爲什麼還要傻傻的站在那裏等呢,嗯,得換個方法。

4.老王還是使用響水壺煮水,跑到客廳上網去,等着響水壺自己把水煮熟了以後通知他,老王豁然,這下感覺輕鬆了很多-異步非阻塞

  • 同步和異步

    同步就是燒開水,需要自己去輪詢(每隔一段時間去看看水開了沒),異步就是水開了,然後水壺會通知你水已經開了,你可以回來處理這些開水了。
    同步和異步是相對於操作結果來說,會不會等待結果返回。

  • 阻塞和非阻塞

    阻塞就是說在煮水的過程中,你不可以去幹其他的事情,非阻塞就是在同樣的情況下,可以同時去幹其他的事情。阻塞和非阻塞是相對於線程是否被阻塞。

其實,這兩者存在本質的區別,它們的修飾對象是不同的。阻塞和非阻塞是指進程訪問的數據如果尚未就緒,進程是否需要等待,簡單說這相當於函數內部的實現區別,也就是未就緒時是直接返回還是等待就緒。
而同步和異步是指訪問數據的機制,同步一般指主動請求並等待I/O操作完畢的方式,當數據就緒後在讀寫的時候必須阻塞,異步則指主動請求數據後便可以繼續處理其它任務,隨後等待I/O,操作完畢的通知,這可以使進程在數據讀寫時也不阻塞。

2 Linux的網絡模型

   

同步阻塞I/O

      最常用的一個模型是同步阻塞 I/O 模型。其行爲非常容易理解,其用法對於典型的應用程序來說都非常有效。在調用 read 系統調用時,應用程序會阻塞並對內核進行上下文切換。然後會觸發讀操作,當響應返回時(從我們正在從中讀取的設備中返回),數據就被移動到用戶空間的緩衝區中。然後應用程序就會解除阻塞(read 調用返回)。

       圖 2. 同步阻塞 I/O 模型

 

同步非阻塞 I/O

     同步阻塞 I/O 的一種效率稍低的變種是同步非阻塞 I/O。在這種模型中,設備是以非阻塞的形式打開的。這意味着 I/O 操作不會立即完成,read操作可能會返回一個錯誤代碼,說明這個命令不能立即滿足(EAGAIN 或 EWOULDBLOCK

      圖 3. 同步非阻塞 I/O 模型

      當一個應用進程像這樣對一個非阻塞描述符循環調用recvfrom時,我們稱之爲輪詢(polling)。應用進程只需輪詢內核,以查看某個操作是否就緒。這麼做往往耗費大量CPU時間。

I/O複用模型

      I/O 複用有時又被稱爲 事件驅動 I/O, 它的最大優勢在於,我們可以將感興趣的多個I/O事件(更精確的說,應該是 I/O 所對應的文件描述符)註冊到 select/poll/epoll/kqueue 之中某一個系統調用上(很多時候,這些系統調用又被稱爲多路複用器。假設此時我們選擇了 select() )。此後,調用進程會阻塞在 select() 系統調用之上(而不是阻塞在真正的 I/O 系統調用(如 read(), write() 等)上)。select() 會負責監視所有已註冊的 I/O 事件,一旦有任意一個事件的數據準備好,那麼 select() 會立即返回,此時我們的用戶進程便能夠進行數據的複製操作。

        圖 4.  I/O複用模型

     總而言之,I/O 複用的優點就在於可以同時等待多個I/O事件;而缺點是會進行兩次系統調用(一次 select(), 一次 read() )。

信號驅動式I/O模型

     在這種模型下,我們首先開啓套接字的信號驅動式I/O功能,並通過sigaction系統調用安裝一個信號處理函數。改系統調用將立即返回,我們的進程繼續工作,也就是說他沒有被阻塞。當數據報準備好讀取時,內核就爲該進程產生一個SIGIO信號。我們隨後就可以在信號處理函數中調用read讀取數據報,並通知主循環數據已經準備好待處理,也可以立即通知主循環,讓它讀取數據報。

       圖 5. 信號驅動I/O模型

     無論如何處理SIGIO信號,這種模型的優勢在於等待數據報到達期間進程不被阻塞。主循環可以繼續執行,只要等到來自信號處理函數的通知:既可以是數據已準備好被處理,也可以是數據報已準備好被讀取。

異步非阻塞 I/O

     異步非阻塞 I/O 模型是一種處理與 I/O 重疊進行的模型。讀請求會立即返回,說明 read 請求已經成功發起了。在後臺完成讀操作時,應用程序然後會執行其他處理操作。當 read 的響應到達時,就會產生一個信號或執行一個基於線程的回調函數來完成這次 I/O 處理過程。

      圖 6. 異步非阻塞I/O模型

      在一個進程中爲了執行多個 I/O 請求而對計算操作和 I/O 處理進行重疊處理的能力利用了處理速度與 I/O 速度之間的差異。當一個或多個 I/O 請求掛起時,CPU 可以執行其他任務;或者更爲常見的是,在發起其他 I/O 的同時對已經完成的 I/O 進行操作。

各種I/O模型的比較

      通過上面的討論可以清楚的看到,同步 I/O 總會有阻塞的過程,這就是“同步”最本質的特徵。而如前文所說,異步 I/O 的最大特點在於用戶進程均不阻塞。 用戶進程告知內核啓動某一 I/O 操作, 並讓內核全權代爲執行(包括等待數據及拷貝數據至用戶空間),此後用戶進程可以立即執行其它的任何操作。等到所有 I/O 過程執行完成後, 內核會通知用戶程。由此可見,在整個過程中,用戶進程均不阻塞。

       圖 7. 各種I/O模型的比較1

      圖 8 . I/O 模型比較2

          I/O模型                                                               讀寫操作和阻塞階段
          阻塞I/O                                                                應用阻塞於讀寫函數
          I/O複用                     應用阻塞於I/O複用系統調用,但可同時監聽多個I/O事件。對I/O本身的讀寫操作是非阻塞的
       信號驅動I/O                                     信號觸發讀寫就緒事件,用戶程序執行讀寫操作。應用沒有阻塞階段
          異步I/O                                     內核執行讀寫操作並觸發讀寫完成事件。應用沒有阻塞階段

 

 

 

 

       

 

 3 NIO編程

 在傳統的I/O中查看JDK源碼的時候,關於read()方法可以在其註釋上看見

從輸入流中讀取下一個數據字節。 值字節作爲int返回,範圍爲0到255.如果沒有字節可用,因爲已到達流的末尾,則返回值-1。 此方法將阻塞,直到輸入數據可用,檢測到流的末尾或拋出異常

對於NIO有人會認爲N代表的是new一個新的IO,而更加貼切的說法是Non-block非阻塞。所以對你NIO來說是一種同步非阻塞式的IO模型。

基本概念

緩衝器Buffer

     Buffer是一個對象,它包含要寫入和讀取的數據,在讀取數據的時候直接從Buffer中讀取,在寫入數據的時候,直接寫入到Buffer中區。Buffer實質是一個數組,通常是一個ByteBuffer字節數組,當然也可以是其他六種基本類型的數組,只不過其他數組到最後都要轉成ByteBuffer數組,所以最常用的就是ByteBuffer。

通道Channel

    Channel是一個通道,像一個水管一樣,網絡數據通過Channel進行讀取和寫入的操作,不同於傳統的IO流,Channel是雙向適用於讀,寫或者兩者一起操作。

   Channel主要有兩大類 適用於網絡通信的SelectableChannel和適用於文件的FileChannel,而Netty主要的是使用SelectableCahnnel,而在網絡通信中就有服務器和客戶端一說,服務器對應的是ServerSocketChannel,而客戶端對應的就是SocketChannel。

多路複用器Selector

    多路複用器提供了選擇已經就緒的任務的能力,簡單來說就是Selector會不斷的輪詢註冊在其上的Channel,只要某個Channel發生讀或者寫,那麼這Channel就處於就緒狀態。一個多路複用器Selector可以同時輪詢多個Channel,這也就表明,只需要一個線程負責Selector的輪詢,就可以接入成千上萬的客戶端。

4 NIO客戶端和服務端序列圖

服務端

步驟一:打開ServerSocketChannel,用於監聽客戶端的連接,它是所有客戶端連接的父管道,代碼示例如下:

ServerSocketChannel acceptorSvr = ServerSocketChannel.open();

步驟二:綁定監聽端口,設置連接爲非阻塞模式,示例代碼如下:

acceptorSvr.socket().bind(new InetSocketAddress(InetAddress.getByName(“IP”), port)); 
acceptorSvr.configureBlocking(false);

步驟三:創建Reactor線程,創建多路複用器並啓動線程,代碼如下:

Selector selector = Selector.open(); 
New Thread(new ReactorTask()).start();

步驟四:將ServerSocketChannel註冊到Reactor線程的多路複用器Selector上,監聽ACCEPT事件,代碼如下:

SelectionKey key = acceptorSvr.register( selector, SelectionKey.OP_ACCEPT, ioHandler);

步驟五:多路複用器在線程run方法的無限循環體內輪詢準備就緒的Key,代碼如下:

int num = selector.select(); 
Set selectedKeys = selector.selectedKeys(); 
Iterator it = selectedKeys.iterator(); 
while (it.hasNext()) { 
     SelectionKey key = (SelectionKey)it.next(); 
     // ... deal with I/O event ... 
}

步驟六:多路複用器監聽到有新的客戶端接入,處理新的接入請求,完成TCP三次握手,建立物理鏈路,代碼示例如下:

SocketChannel channel = svrChannel.accept();

步驟七:設置客戶端鏈路爲非阻塞模式,示例代碼如下:

channel.configureBlocking(false); 
channel.socket().setReuseAddress(true);

步驟八:將新接入的客戶端連接註冊到Reactor線程的多路複用器上,監聽讀操作,用來讀取客戶端發送的網絡消息,代碼如下:

SelectionKey key = socketChannel.register( selector, SelectionKey.OP_READ, ioHandler);

步驟九:異步讀取客戶端請求消息到緩衝區,示例代碼如下:

int  readNumber =  channel.read(receivedBuffer);

步驟十:對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 messageE : messageList)  
  handlerTask(messageE); 
}

步驟十一:將POJO對象encode成ByteBuffer,調用SocketChannel的異步write接口,將消息異步發送給客戶端,示例代碼如下:

socketChannel.write(buffer);

 

客戶端

1

步驟一:打開SocketChannel,綁定客戶端本地地址(可選,默認系統會隨機分配一個可用的本地地址),示例代碼如下:

 

SocketChannel clientChannel = SocketChannel.open();

步驟二:設置SocketChannel爲非阻塞模式,同時設置客戶端連接的TCP參數,示例代碼如下:

clientChannel.configureBlocking(false);
socket.setReuseAddress(true);
socket.setReceiveBufferSize(BUFFER_SIZE);
socket.setSendBufferSize(BUFFER_SIZE);

步驟三:異步連接服務端,示例代碼如下:

boolean connected = clientChannel.connect(new InetSocketAddress(“ip”,port));

步驟四:判斷是否連接成功,如果連接成功,則直接註冊讀狀態位到多路複用器中,如果當前沒有連接成功(異步連接,返回false,說明客戶端已經發送sync包,服務端沒有返回ack包,物理鏈路還沒有建立),示例代碼如下:

if (connected) 
{
	clientChannel.register( selector, SelectionKey.OP_READ, ioHandler);
}
else
{
    clientChannel.register( selector, SelectionKey.OP_CONNECT, ioHandler);
}


步驟五:向Reactor線程的多路複用器註冊OP_CONNECT狀態位,監聽服務端的TCP ACK應答,示例代碼如下:

clientChannel.register( selector, SelectionKey.OP_CONNECT, ioHandler);

步驟六:創建Reactor線程,創建多路複用器並啓動線程,代碼如下:

Selector selector = Selector.open();
New Thread(new ReactorTask()).start();

步驟七:多路複用器在線程run方法的無限循環體內輪詢準備就緒的Key,代碼如下:

int num = selector.select();
Set selectedKeys = selector.selectedKeys();
Iterator it = selectedKeys.iterator();
while (it.hasNext()) {
if (key.isConnectable())
  //handlerConnect(); 
}

步驟九:判斷連接結果,如果連接成功,註冊讀事件到多路複用器,示例代碼如下:

if (channel.finishConnect())
  registerRead();

步驟十:註冊讀事件到多路複用器:

clientChannel.register( selector, SelectionKey.OP_READ, ioHandler);   

步驟十一:異步讀客戶端請求消息到緩衝區,示例代碼如下:

int readNumber = channel.read(receivedBuffer);

步驟十二:對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 messageE : messageList)
   handlerTask(messageE);
}

步驟十三:將POJO對象encode成ByteBuffer,調用SocketChannel的異步write接口,將消息異步發送給客戶端,示例代碼如下:

socketChannel.write(buffer);

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章