Netty學習(二)----JAVA NIO 以及NIO簡易廣播羣聊

JAVA NIO - Buffer

在之前提到JAVA NIO中,引入了Buffer、Channel 、Selectors.

  • Buffer:
    • 定義:
      Java NIO Buffers用於和NIO Channel交互。 我們從Channel中讀取數據到buffers裏,從Buffer把數據寫入到Channels. Buffer本質上就是一塊內存區,可以用來寫入數據,並在稍後讀取出來。 這塊內存被NIO Buffer包裹起來,對外提供一系列的讀寫方便開發的接口。
    • 屬性:
 // Invariants: mark <= position <= limit <= capacity
    private int mark = -1;//標誌位
    private int position = 0;//當前遊標(指針)位置
    private int limit;//最大容量
    private int capacity;//當前容量
  • 交互步驟:
    1.申明緩存區大小(直接緩衝區(allocateDirect)或非直接緩衝區(allocate))1
    2.把數據寫入buffer;
    3.調用flip;
    4.從Buffer中讀取數據;
    5.調用buffer.clear()或者buffer.compact()。

理解:

Buffer緩衝區實質上就是一塊內存,用於寫入數據,也供後續再次讀取數據。
這塊內存被NIO Buffer管理,並提供一系列的方法用於更簡單的操作這塊內存。
position和limit的具體含義取決於當前buffer的模式。capacity在兩種模式下都表示容量。
(其實這裏有點像切片的概念,指針、界限、最大容量)

  • 容量(Capacity)
    作爲一塊內存,buffer有一個固定的大小,叫做capacit(容量)。也就是最多隻能寫入容量值得字節,整形等數據。一旦buffer寫滿了就需要清空已讀數據以便下次繼續寫入新的數據

  • 上限(Limit)
    在寫模式,limit的含義是我們所能寫入的最大數據量,它等同於buffer的容量。 一旦切換到讀模式,limit則代表我們所能讀取的最大數據量,他的值等同於寫模式下position的位置。換句話說,您可以讀取與寫入數量相同的字節數(限制設置爲寫入的字節數,由位置標記)。(0位至 之前寫模式將指針挪到的 索引位)

  • 位置(Position)
    當寫入數據到Buffer的時候需要從一個確定的位置開始,默認初始化時這個位置position爲0,一旦寫入了數據比如一個字節,整形數據,那麼position的值就會指向數據之後的一個單元,position最大可以到capacity-1. 當從Buffer讀取數據時,也需要從一個確定的位置開始。buffer從寫入模式變爲讀取模式時,position會歸零,每次讀取後,position向後移動。

buffer 中的絕大多數操作都是圍繞以上三個屬性進行操作的,

舉例 :
  1. 標記當前指針位置/回退到指針位
	/**
     * Sets this buffer's mark at its position.
     *
     * @return  This buffer
     */
public final Buffer mark() {
    mark = position;
    return this;
}
/**
 回退都指針位置
 */
public final Buffer reset() {

      int m = mark;
      if (m < 0)
          throw new InvalidMarkException();
      position = m;
      return this;
}
  1. clear
public final Buffer clear() {
    position = 0;
    limit = capacity;
    mark = -1;
    return this;
}

應該明確認知clear只是改變有了遊標位置等,並不會真實的清空了數據。
好比之前dubbo泛化調用中的destroy不會清除zk上的節點,而只是不watch,T_T。

  1. flip
public final Buffer flip() {
    limit = position;
    position = 0;
    mark = -1;
    return this;
}

數據存入:

當聲明一組緩存區時,pos爲0位。
每次讀、寫時,pos會+1滑動至下一個索引位;
在這裏插入圖片描述
在這裏插入圖片描述


JAVA NIO - Channel

在之前提到JAVA NIO中,引入了Buffer、Channel 、Selectors.

  • Channel:

    • 定義:
      Java NIO Buffers用於和NIO Channel交互,而Channel等價於之前的**DMA**,但不存在總線問題。 Channel表示IO源於目標打開的連接。Channel類似傳統的‘流’ ,但Channel本身不能直接訪問數據,

    • 交互步驟:
      1.Channel只能與Buffer進行交互,且本身不存儲數據。
      2.按照通信目標分爲FileChannel(本地,File理解爲FD)、SocketChannel(tcp client)、ServerSocketChannel(tcp server)、DatagramChannel(udp)每種類型交互存在差異;

  • 通道獲取方式:

    1. 可使用API直接對已有的流、socket進行get(),如本地I/O中的FileInputStream、FileOutputStream、RandomAccessFile合網絡IO中的Socket、ServerSocket、DatagramSocket。
    2. 對各個通道進行open()調用。
    3. Files中的newByteChannel()
    4. 通道與緩衝區的交互支持 Scatter(分散讀取) 與 Gather (Gather)1

Pipe 不做介紹,簡單理解爲Go裏面的 <-Channel 以及 ->Channel 單向


JAVA NIO - Select(ableChannel)

在之前提到JAVA NIO中,引入了Buffer、Channel 、Selectors.

  • Channel:

    • 定義:
      Java NIO Select(ableChannel)是多路複用器。用於監控相應I/O的狀態

    • 分類:

      • java.nio.channels.Channel 接口:
        • SelectableChannel
          • SocketChannel
          • ServerSocketChannel
          • DatagramChannel
          • Pipe.SinkChannel
          • Pipe.SourceChannel
  • 交互方式:

    1. 當調用register(Selector sel , int pos) 將通道組成選擇器時,選擇器對通道的監聽事件,通過params2進行指定。
    2. 可監聽到的事件類型有。
      – 1.讀 SelectionKey.OP_READ(1)
      – 2.寫 SelectionKey.OP_WRITE(4)
      – 3.連接 SelectionKey.OP_CONNECT(8)
      – 4.接收 SelectionKey.OP_ACCEPT(16)
    3. 通道與緩衝區的交互支持 Scatter(分散讀取) 與 Gather (Gather)2
    4. 若註冊 不止一個監聽事件,在可以通過使用“|”操作符拼接
SelectionKey keyWithRW =serverSocketChannel.register(selector, SelectionKey.OP_READ | SelectionKey.OP_WRITE);

零拷貝(無CPU拷貝)

  • 零拷貝是網絡編程的關鍵,很多性能都是圍繞該點。
  • JAVA中,常用的零拷貝有mmap(內存映射)和sendFile。於操作系統而言,在內核緩衝區之間,沒有數據是重複的(只有Kernel buffer 一份數據)。
	@Test
    public void testCopy() throws IOException {
        RandomAccessFile file = new RandomAccessFile(new File("test.txt"), "rw");
        byte[] arr = new byte[(int) file.length()];
        file.read(arr);
        Socket socket = new ServerSocket(8080).accept();
        socket.getOutputStream().write(arr);
    }

在這裏插入圖片描述
數據一共經過4次拷貝,線程經過3次切換。


mmap優化

  • mmap通過內存映射,將文件映射都內存緩衝區,同時,用戶空間可以共享內核空間的數據。這樣,在進行網絡傳輸時,就可以減少內核空間到用戶控件的零拷貝次。
  • 在這裏插入圖片描述

sendFile 優化

  • Linux2.1 版本提供了sendFile 函數,其基本原理如下:數據根本不通過用戶態,直接從內核緩衝區進入到SocketBuffer,同時,由於和用戶態無關,就減少了一次上下文切換,2.4中會從kernal buffer直接拷貝到協議棧。
    在這裏插入圖片描述
    mmap與sendFile區別
    1)mmap適合小數據量讀寫,sendFile適合大文件傳輸。
    2)mmap需要四次上下文切換,3次數據拷貝;sendFile需要3次上下文切換,最少2次數據拷貝。
  1. sendFile可以利用DMA方式,減少CPU拷貝,mmap則不能(必須由內核拷貝到socket緩衝區)

理論概念整體較爲雜亂,構建了相應demo,對ApI進行了嘗試調用,並做了一發簡易的羣聊整理NIO知識,完整代碼 https://github.com/LikeElephantintheforest/netty NIO-Group-chat 分支,客戶端telnet實現。

簡介:

  • 客戶端可進行註冊至服務端
  • 服務端可打印客戶端上行數據
  • 服務端可將該消息準發至其他客戶端

在這裏插入圖片描述
較爲重要的API使用:

//1--服務端註冊Selector , 監聽客戶端連接事件。
serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);
//2---通過該selector監聽OP事件,可得知客戶端建連。
boolean haveAccept = selector.select(10000) > 0;
//3----當客戶端連接建立成功服務端知曉後,可對當前socket進行可讀事件監聽
if (e.isAcceptable()) {
	SocketChannel socketChannel = serverSocketChannel.accept();
    socketChannel.configureBlocking(Boolean.FALSE);
    //be notified by OP_ACCEPT event,then register OP_READ on socket
    socketChannel.register(selector, SelectionKey.OP_READ);
    System.out.println(String.format("IP: %s sign up ", socketChannel.getRemoteAddress()));
}
//4-----當客戶端有數據寫入,服務端判定有可讀時,將該消息廣播至所有其他已註冊的客戶端。
 if (acceptedMsg) {
	for (SelectionKey select : selector.keys()) {
	                                        //except self
	   if (e != select) {
	
	       if (select.channel() instanceof SocketChannel) {
	
	           SocketChannel clientSocketChannel = (SocketChannel) select.channel();
	           clientSocketChannel.configureBlocking(Boolean.FALSE);
	
	            try {
	
	                clientSocketChannel.write(ByteBuffer.wrap(receiveMsg.getBytes()));
	
	            } catch (IOException ex) {
	//
	                ex.printStackTrace();
	
	            }
	        }
	    }
	}
}

  1. ·
    1:字節緩衝區要麼是直接的,要麼是非直接的。
    2:如果爲直接字節緩衝區,則java虛擬機會盡最大努力直接在此緩衝區上執行本機I/O操作。再每次調用os的一個本機I/O操作時,虛擬機都會盡量避免將緩衝區的內容複製到中間的緩衝區中。
    3:直接緩衝區的內容可以駐留在常規的垃圾回收堆之外,因爲對應用程序造成的內存需求影響並不明顯。
    4:非直緩衝區必然存在讀寫時用戶態與內核態的拷貝,程序無法直接通過內核態交交互OS。
    5:直接緩衝區通過形成物理內存映射文件,交互操作系統物理內存,不做拷貝。 ↩︎ ↩︎

  2. ·
    分散讀取(Scatter Reads):是指從Channel中讀取的數據“分散”到多個buffer中。(有序)
    聚集寫入(Gathering Writes):是指將多個Buffer中的數據“聚集”到Channel中。 ↩︎

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