Netty學習之知識儲備(1)

一:學習前知識儲備

1.1 Socket

  • 套接字(socket)是一個抽象層,應用程序可以通過它發送或接收數據,可對其進行像對文件一樣的打開、讀寫和關閉等操作。套接字允許應用程序將I/O插入到網絡中,並與網絡中的其他應用程序進行通信。網絡套接字是IP地址與端口的組合。
  • 一種獨立於協議的網絡編程接口

1.2 IO操作

IO操作包括:對硬盤的讀寫、對socket的讀寫以及對外設的讀寫。
基本的IO操作
在這裏插入圖片描述
分爲兩個過程:

  • DMA(直接內存存取)把數據讀取到內核空間的緩衝區(讀就緒)
  • 內核將數據拷貝到用戶空間。

同步IO、異步IO、阻塞IO、非阻塞IO解釋

  • 同步IO:當用戶發出IO請求操作之後,內核會去查看要讀取的數據是否就緒,如果數據沒有就緒,就一直等待。需要通過用戶線程或者內核不斷地去輪詢數據是否就緒,當數據就緒時,再將數據從內核拷貝到用戶空間。
  • 異步IO:只有IO請求操作的發出是由用戶線程來進行的,IO操作的兩個階段都是由內核自動完成,然後發送通知告知用戶線程IO操作已經完成。也就是說在異步IO中,不會對用戶線程產生任何阻塞。
  • 阻塞IO:當用戶線程發起一個IO請求操作(以讀請求操作爲例),內核查看要讀取的數據還沒就緒,當前線程被掛起,阻塞等待結果返回。
  • 非阻塞IO:如果數據沒有就緒,則會返回一個標誌信息告知用戶線程當前要讀的數據沒有就緒。當前線程在拿到此次請求結果的過程中,可以做其它事情。

1.3 Java中的NIO

JAVA NIO有兩種解釋:一種叫非阻塞IO(Non-blocking I/O),另一種也叫新的IO(New I/O),其實是同一個概念。它是一種同步非阻塞的I/O模型,也是I/O多路複用的基礎,已經被越來越多地應用到大型應用服務器,成爲解決高併發與大量連接、I/O處理問題的有效方式。
NIO主要有三大核心部分: Channel、Buffer、Selector
傳統IO是基於字節流和字符流進行操作(基於流),而NIO基於Channel和Buffer(緩衝區)進行操作,數據總是從通道讀取到緩衝區中,或者從緩衝區寫入到通道中。Selector(選擇區)用於監聽多個通道的事件(比如:連接打開,數據到達)。因此,單個線程可以監聽多個數據通道。

  • Channel(通道):表示到實體
    • 如硬件設備、文件、網絡套接字或可以執行一個或多個不同 I/O 操作(如讀取或寫入)的程序組件的開放的連接。
    • Channel接口的常用實現類有FileChannel(對應文件IO)、DatagramChannel(對應UDP)、SocketChannel和ServerSocketChannel(對應TCP的客戶端和服務器端)。
    • Channel和IO中的Stream(流)是差不多一個等級的。只不過Stream是單向的,譬如:InputStream, OutputStream.而Channel是雙向的,既可以用來進行讀操作,又可以用來進行寫操作。
  • Buffer(緩衝區):是一個用於存儲特定基本類型數據的容器。
    • 除了boolean外,其餘每種基本類型都有一個對應的buffer類。
    • Buffer類的子類有ByteBuffer, CharBuffer, DoubleBuffer, FloatBuffer, IntBuffer, LongBuffer, ShortBuffer 。
  • Selector(選擇器):用於監聽多個通道的事件(比如:連接打開,數據到達)。
    • 單個的線程可以監聽多個數據通道。即用選擇器,藉助單一線程,就可對數量龐大的活動I/O通道實施監控和維護。

傳統的IO處理模式
好處:由於每個Client連接有一個單獨的處理線程爲其服務,因此可保證良好的響應時間。
缺陷:每一個連接都使用一個線程。當系統負載增大(併發請求增多)時,Server端需要的線程數會增加,對於操作系統來說,線程之間上下文切換的開銷很大。佔用過多資源
在這裏插入圖片描述

非阻塞的IO處理模式,一個線程管理多個網絡連接
在這裏插入圖片描述
NIO服務器端實現非阻塞過程

  • 服務器上所有Channel需要向Selector註冊,而Selector則負責監視這些Socket的IO狀態(觀察者)。
  • 當其中任意一個或者多個Channel具有可用的IO操作時,該Selector的select()方法將會返回大於0的整數,該整數值就表示該Selector上有多少個Channel具有可用的IO操作,並提供了selectedKeys()方法來返回這些Channel對應的SelectionKey集合(一個SelectionKey對應一個就緒的通道)。
  • 正是通過Selector,使得服務器端只需要不斷地調用Selector實例的select()方法即可知道當前所有Channel是否有需要處理的IO操作。注
  • :java NIO就是多路複用IO,jdk7之後底層是epoll模型。參考下圖進行理解:
    在這裏插入圖片描述

1.4 NIO、BIO、AIO對比

  • BIO(IO)
    • 同步阻塞,傳統io方式。
    • 適用於連接數目比較小且固定的架構,這種方式對服務器資源要求比較高,併發侷限於應用中。
  • NIO
    • 同步非阻塞,jdk4開始支持。
    • 適用於連接數目多且連接比較短(輕操作)的架構,比如聊天服務器。
  • AIO
    • 異步非阻塞,jdk7開始支持。
    • 適用於連接數目多且連接比較長(重操作)的架構。

形象的理解NIO和AIO:如果把內核比作快遞,NIO就是你要自己時不時到官網查下快遞是否已經到了你所在城市,然後自己去取快遞;AIO就是快遞員送貨上門了。

1.5 服務端案例


package cn.xd.nio;
 
import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;
import java.util.Iterator;
import java.util.Set;
 
import com.sun.xml.internal.ws.util.StringUtils;
 
public class NioServer {
 
  public static void main(String[] args) throws Exception {
    // 1、初始化一個ServerSocketChannel
    ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
    InetSocketAddress inetSocketAddress = new InetSocketAddress("127.0.0.1", 9999);
    serverSocketChannel.configureBlocking(false);// 設置爲非阻塞模式,後續的accept()方法會立刻返回
    serverSocketChannel.socket().bind(inetSocketAddress, 1024);// 監聽本地9999端口的請求,第二個參數限制可以建立的最大連接數
    Selector selector = Selector.open();
    /**
     * 將通道註冊到一個選擇器上(非阻塞模式與選擇器搭配會工作的更好)
     * 注意register()方法的第二個參數。這是一個“interest集合”,意思是在通過Selector監聽Channel時對什麼事件感興趣。
     * 可以監聽四種不同類型的事件:OP_CONNECT,OP_ACCEPT,OP_READ,OP_WRITE
     * 如果你對不止一種事件感興趣,那麼可以用“位或”操作符將常量連接起來:SelectionKey.OP_READ | SelectionKey.OP_WRITE
     */
    serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);
 
    // 2、監聽連接請求並處理
    while (true) {
      int connects = selector.select(2000);// 每次最多阻塞2秒
 
      if (connects == 0) {
        System.out.println("沒有請求...");
        continue;
      } else {
        System.out.println("請求來了...");
      }
 
      // 獲取監聽到有連接請求的channel對應的selectionKey
      Set selectedKeys = selector.selectedKeys();
      // 遍歷selectionKey來訪問就緒的通道
      Iterator selectedKeyIterator = selectedKeys.iterator();
      while (selectedKeyIterator.hasNext()) {
        SelectionKey selectionKey = selectedKeyIterator.next();
        if (selectionKey.isValid()) {
 
          if (selectionKey.isAcceptable()) {// 接收就緒
            ServerSocketChannel channel = (ServerSocketChannel) selectionKey.channel();
            // 返回一個包含新進來的連接SocketChannel,因爲前面設置的非阻塞模式,這裏會立即返回。
            SocketChannel socketChannel = channel.accept();
            if (socketChannel == null) {
              return;
            }
            socketChannel.configureBlocking(false);
            socketChannel.register(selector, SelectionKey.OP_READ);
            System.out.println("連接建立完成");
            doWrite(socketChannel, "connection is established");// 連接建立完成,給客戶端發消息
 
          } else if (selectionKey.isReadable()) {// 讀就緒
 
            SocketChannel socketChannel = (SocketChannel) selectionKey.channel();
            ByteBuffer readBuffer = ByteBuffer.allocate(10);
            while ((socketChannel.read(readBuffer)) > 0) {// // 讀取客戶端發送來的消息
              readBuffer.flip();
              byte[] bytes = new byte[readBuffer.remaining()];
              readBuffer.get(bytes);
              String body = new String(bytes, "utf-8");
              doWrite(socketChannel, body);// 將客戶端發送的內容原封不動的發回去
              readBuffer.clear();
            }
            socketChannel.close();//讀取數據完畢後關閉連接,如果不關閉一直處於連接狀態。
 
          }
        }
 
        selectedKeyIterator.remove(); // 注意每次必須手動remove(),下次該通道變成就緒時,Selector會再次將其放入已選擇鍵集中
      }
    }
  }
 
  private static void doWrite(SocketChannel socketChannel, String response) throws IOException {
    if (StringUtils.isNotBlank(response)) {
      byte[] bytes = response.getBytes();
      ByteBuffer writeBuffer = ByteBuffer.allocate(1024);
      writeBuffer.put(bytes);
      writeBuffer.flip();
      // 發送消息到客戶端
      socketChannel.write(writeBuffer);
      writeBuffer.clear();
    }
 
  }
 
}

發佈了21 篇原創文章 · 獲贊 1 · 訪問量 341
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章