Netty如何封裝Socket客戶端Channel,Netty的Channel都有哪些類型?


 

更多技術分享可關注我

前言

原文:Netty如何封裝Socket客戶端Channel,Netty的Channel都有哪些類型?

前面分析過Netty封裝的服務端Channel——NioServerSocketChannel,對應的客戶端也會封裝一個Channel——NioSocketChannel,可以對比OIO網絡編程模型中的ServerSocket和Socket,對應NIO中的ServerSocketChannel和SocketChannel,看看這兩個組件是如何被Netty封裝使用的。

Netty封裝客戶端SocketChannel的源碼分析

如下是Netty服務端讀取新連接的核心代碼,在文章:Netty是如何處理新連接接入事件的?中已經總結分析過:

在黃色1處獲取了JDK的SocketChannel后,在黃色2處Netty調用了NioSocketChannel的構造器,目的是將JDK的SocketChannel封裝為Netty自己的客戶端Channel——NioSocketChannel。下面跟進源碼,看看Netty都封裝了什么東西,以及為何要這么做。

總的來說,NioSocketChannel的構造器主要做了兩件事:

1、調用一系列父類構造器,對SocketChannel做初始化工作

  • 設置SocketChannel為非阻塞模式

  • 保存初始化SocketChannel需要注冊的I/O事件——OP_READ=16

  • 創建SocketChannel的唯一id

  • 創建SocketChannel的unsafe實現類——NioByteUnsafe,后續專門總結Netty的unsafe,這里先知道。

  • 創建SocketChannel的默認pipeline組件,這個組件是Netty額外封裝JDK的Channel的核心原因,目的就是更好的設計自己的架構,這里和服務端封裝ServerSocketChannel是一樣的邏輯,復用了代碼

2、調用配置類:主要是禁止TCP Nagle算法

下面看下細節,下面是NioSocketChannel的構造器源碼:

首先,層層調用父類構造器對其初始化,復習Netty Channel的繼承關系:

比如先調用直接父類——AbstractNioByteChannel構造器,該構造器會將SocketChannel的I/O讀事件保存:

然后繼續調用父類構造器——AbstractNioChannel,這里和Netty封裝服務端Channel——NioServerSocketChannel,共用了一段邏輯:

主要做了三件事:

1、保存JDK的SocketChannel,也就是AbstractNioChannel中的ch屬性

2、為Netty封裝的Channel保存interest的I/O事件

3、設置JDK的SocketChannel為非阻塞模式

當然在做這三件事前,會繼續先調用上層父類AbstractChannel的構造器:

該構造器主要作用是初始化Netty客戶端Channel的一些共有配置,比如唯一id,大動脈pipeline組件,以及為其創建unsafe,和封裝服務端Channel一樣的流程

全部搞完,返回到NioSocketChannel構造器,繼續執行如下初始化config屬性的邏輯:

這里初始化config,最重要的一件事是禁止了TCP的Nagle算法,如下代碼:

至於何時才會自動關閉該算法,需要看if判斷邏輯里的判斷方法——canEnableTcpNoDelayByDefault是什么東西,如下:

發現一個默認的變量CAN_ENABLE_TCP_NODELAY_BY_DEFAULT,它在非安卓環境下設置為關閉,即在非安卓端上部署Netty,它會自動關閉TCP的nagle算法。

TCP協議默認開啟了Nagle算法(即默認關閉TCP_NODELAY選項,注意意思是相反的)

Netty客戶端NioSocketChannel的創建很簡單,至此分析完畢。

小結

Netty封裝客戶端NioSocketChannel主要就是三件事:

1、配置SocketChannel為非阻塞——configureBlocking(false)

2、保存OP_READ事件,但是是延遲注冊的,且會為Channel創建唯一id,unsafe組件(負責底層數據讀寫)和pipeline組件(業務數據流動的載體)

3、在NioSocketChannelConfig()中判斷設置是否打開TCP的Nagle算法,即在非安卓環境下,執行setTcpNoDelay(true),即禁止Nagle算法,希望把小數據包盡量發送出去,降低延遲,而Nagle算法會通過減少需要傳輸的數據包來優化網絡。在Linux內核中,數據包的發送和接受會先做緩存,分別對應於寫緩存和讀緩存。目的是為了盡可能發送大塊數據,避免網絡中充斥着許多小數據塊。

Netty的Channel類型總結

下面全面總結一下Netty的Channel類型,當然主要是NIO模型下的Channel。Netty的NIO模型的Channel主要分為兩類:NioSocketChannel和NioServerSocketChannel,分別封裝了JDK的SocketChannel和ServerSocketChannel,對應了客戶端Socket和服務端Socket。

一個精簡版的類圖如下,藍色部分是服務端Channel,白色是客戶端的Channel。

我們只關心對JDK NIO的封裝設計,從頂到下,基本脈絡是:Channel接口>AbstractChannel(所有Channel的骨架實現)>AbstractNioChannel(NIO模型下Channel的骨架),而從AbstractNioChannel又開始分支,如下:

Channel是Netty的所有Channel的共同接口,定義了一系列的Socket或者Netty自身的I/O操作的接口,而Channel的底層I/O功能的實現都是由Unsafe接口負責,該接口聚合在了Channel接口,作為其內部接口。

AbstractChannel是Channel的骨架實現,負責實現不同類型Channel的共同組件或者基礎屬性,比如保存Channel的標識id,unsafe骨架實現,pipeline,EventLoop屬性等。

AbstractNioChannel是NIO模型下Channel的骨架實現,相對的,阻塞模型下Channel的骨架實現就是AbstractOioChannel,這很少有人用,不討論它。AbstractNioChannel最大的特性是聚合了JDK的I/O多路復用器——Selector,主要負責NIO相關的Channel的抽象實現,且內置了JDK底層的Channel接口,可以給客戶端/服務端Channel設置非阻塞模式,保存interest的I/O事件,SelectionKey等,前面分析過這個構造器。

從AbstractNioChannel后,開始細分服務端和客戶端的Channel:

主要就是這兩類Channel,它們都直接繼承AbstractNioChannel,兩者最明顯的區別是默認設置的I/O事件不一樣,NioSocketChannel是設置OP_READ事件,而NioServerSocketChannel是設置OP_ACCEPT事件,即前者是讀取已建立連接上的數據,后者是讀取新連接。

還有一個Unsafe,前面說過每個Netty的Channel都有一個Unsafe接口與之綁定,Unsafe接口的相關實現類負責實現Netty的Channel所有I/O操作,一共有兩類unsafe的實現類,即服務端Channel的NioMessageUnsafe,它的主要作用就是讀新連接,還有客戶端Channel的NioByteUnsafe,它的主要作用是讀、寫已有連接上的數據,復習下兩者的繼承關系:

最后,還能知道每個Netty的Channel都有一個config——配置工具類,存儲了每類Channel的底層網絡配置,其繼承關系如下:

整個結果一氣呵成,充分利用了模板方法等設計模式,不論是命名上,還是具體實現上,都非常美觀和流暢,可以學習這種組件分類設計的方式。

小結

NIO模型下,Netty中的Channel分類:

1、NioServerSocketChannel是服務端Channel,繼承AbstractNioMessageChannel,啟動時注冊的I/O事件為OP_ACCEPT,並創建NioServerSocketChannelConfig和NioMessageUnsafe,核心作用就是讀新客戶端連接

2、NioSocketChannel是客戶端Channel,繼承AbstractNioByteChannel,初始化時注冊的I/O事件為OP_READ,創建NioSocketChannelConfig和NioByteUnsafe,核心作用是讀、寫已有連接上的數據

歡迎關注

dashuai的博客是終身學習踐行者,大廠程序員,且專注於工作經驗、學習筆記的分享和日常吐槽,包括但不限於互聯網行業,附帶分享一些PDF電子書,資料,幫忙內推,歡迎拍磚!


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM