本文是Netty文集中“Netty 源碼解析”系列的文章。主要對Netty的重要流程以及類進行源碼解析,以使得我們更好的去使用Netty。Netty是一個非常優秀的網絡框架,對其源碼解讀的過程也是不斷學習的過程。
嗯,本文與其說是ChannelConfig、Attribute源碼解析,不如說是對ChannelConfig以及Attribute結構層次的分析。因為這才是它們在Netty中使用到的重要之處。
ChannelConfig
在Netty 源碼解析 ——— 服務端啟動流程 (下)中說過,當我們在構建NioServerSocketChannel的時候同時會構建一個NioServerSocketChannelConfig對象賦值給NioServerSocketChannel的成員變量config。
而這一個NioServerSocketChannelConfig是當前NioServerSocketChannel配置屬性的集合。NioServerSocketChannelConfig主要用于對NioServerSocketChannel相關配置的設置(如,網絡的相關參數配置),比如,配置Channel是否為非阻塞、配置連接超時時間等等。
下面我們來對NioServerSocketChannelConfig的結構做個詳細介紹NioServerSocketChannelConfig其實是一個ChannelConfig實例。ChannelConfig表示為一個Channel相關的配置屬性的集合。所以NioServerSocketChannelConfig就是針對于NioServerSocketChannel的配置屬性的集合。
ChannelConfig是Channel所需的公共配置屬性的集合,如,setAllocator(設置用于channel分配buffer的分配器)。而不同類型的網絡傳輸對應的Channel有它們自己特有的配置,因此可以通過擴展ChannelConfig來補充特有的配置,如,ServerSocketChannelConfig是針對基于TCP連接的服務端ServerSocketChannel相關配置屬性的集合,它補充了針對TCP服務端所需的特有配置的設置setBacklog、setReuseAddress、setReceiveBufferSize。
DefaultChannelConfig作為ChannelConfig的默認實現,對ChannelConfig中的配置提供了默認值。
接下來,我們來看一個設置ChannelConfig的流程:
serverBootstrap.option(ChannelOption.SO_REUSEADDR, true);
我們可以在啟動服務端前通過ServerBootstrap來進行相關配置的設置,該選項配置會在Channel初始化時被獲取并設置到Channel中,最終會調用底層ServerSocket.setReuseAddress方法來完成配置的設置。
ServerBootstrap的init()方法:
首先對option和value進行校驗,其實就是進行非空校驗。
然后判斷對應的是哪個常量屬性,并進行相應屬性的設置。如果傳進來的ChannelOption不是已經設定好的常量屬性,則會打印一條警告級別的日志,告知這是未知的channel option。
Netty提供ChannelOption的一個主要的功能就是讓特定的變量的值給類型化。因為從’ChannelOption<T> option’和’T value’可以看出,我們屬性的值類型T,是取決于ChannelOption的泛型的,也就屬性值類型是由屬性來決定的。
ChannelOption
這里,我們可以看到有個ChannelOption類,它允許以類型安全的方式去配置一個ChannelConfig。支持哪一種ChannelOption取決于ChannelConfig的實際的實現并且也可能取決于它所屬的傳輸層的本質。
可見ChannelOption是一個Consant擴展類,Consant是Netty提供的一個單例類,它能安全去通過’==’來進行比較操作。通過ConstantPool進行管理和創建。
常量由一個id和name組成。id:表示分配給常量的唯一數字;name:表示常量的名字。
ConstantPool
如上所說,Constant是由ConstantPool來進行管理和創建的,那么ConstantPool又是個什么樣的類了?
首先從constants中get這個name對應的常量,如果不存在則調用newConstant()來構建這個常量tempConstant,然后在調用constants.putIfAbsent方法來實現“如果該name沒有存在對應的常量,則插入,否則返回該name所對應的常量。(這整個的過程都是原子性的)”,因此我們是根據putIfAbsent方法的返回來判斷該name對應的常量是否已經存在于constants中的。如果返回為null,則說明當前創建的tempConstant就為name所對應的常量;否則,將putIfAbsent返回的name已經對應的常量值返回。(注意,因為ConcurrentHashMap不會允許value為null的情況,所以我們可以根據putIfAbsent返回為null則代表該name在此之前并未有對應的常量值)
ChannelOption類中屬性
好了,到目前為止,我們已經知道ChannelOption是一個Constant的擴展,因此它可以由ConstantPool來管理和創建。接下來,我們繼續來看看ChannelOption類中的一些重要屬性:正如我們前面所說的,這個ConstantPool<ChannelOption<Object>> pool(即,ChannelOption常量池)是ChannelOption的一個私有靜態成員屬性,用于管理和創建ChannelOption。
同時,ChannelOption中將所有的與相關的配置項名稱都已常量形式定義好了。如:這些定義好的ChannelOption常量都已經存儲數到ChannelOption的常量池(ConstantPool)中了。
注意,ChannelOption本身并不維護選項值的信息,它只是維護選項名字本身。比如,“public static final ChannelOption<Integer> SO_RCVBUF = valueOf("SO_RCVBUF");”??這只是維護了“SO_RCVBUF”這個選項名字的信息,同時泛型表示選擇值類型,即“SO_RCVBUF”選項值為Integer。
好了,到目前為止,我們對Netty的ChannelOption的設置以及底層的實現已經分析完了,簡單的來說:Netty在初始化Channel時會構建一個ChannelConfig對象,而ChannelConfig是Channel配置屬性的集合。比如,Netty在初始化NioServerSocketChannel的時候同時會構建一個NioServerSocketChannelConfig對象,并將其賦值給NioServerSocketChannel的成員變量config,而這個config(NioServerSocketChannelConfig)維護了NioServerSocketChannel的所有配置屬性。比如,NioServerSocketChannelConfig提供了setConnectTimeoutMillis方法來設置NioServerSocketChannel連接超時的時間。
同時,程序可以通過ServerBootstrap或Boostrap的option(ChannelOption<T> option, T value)方法來實現配置的設置。這里,我們通過ChannelOption來實現配置的設置,ChannelOption中已經將常用的配置項預定義為了常量供我們直接使用,同時ChannelOption的一個主要的功能就是讓特定的變量的值給類型化。因為從’ChannelOption<T> option’和’T value’可以看出,我們屬性的值類型T,是取決于ChannelOption的泛型的,也就屬性值類型是由屬性來決定的。
Attribute
一個attribute允許存儲一個值的引用。它可以被自動的更新并且是線程安全的。
其實Attribute就是一個屬性對象,這個屬性的名稱為AttributeKey<T> key,而屬性的值為T value。
我們可以通過程序ServerBootstrap或Boostrap的attr方法來設置一個Channel的屬性,如:
serverBootstrap.attr(AttributeKey.valueOf("userID"), UUID.randomUUID().toString());
當Netty底層初始化Channel的時候,就會將我們設置的attribute給設置到Channel中:
如上面所說,Attribute就是一個屬性對象,這個屬性的名稱為AttributeKey<T> key,而屬性的值為T value。
而AttributeKey也是Constant的一個擴展,因此也有一個ConstantPool來管理和創建,這和ChannelOption是類似的。
Channel類本身繼承了AttributeMap類,而AttributeMap它持有多個Attribute,這些Attribute可以通過AttributeKey來訪問的。所以,才可以通過channel.attr(key).set(value)的方式將屬性設置到channel中了(即,這里的attr方法實際上是AttributeMap接口中的方法)。
AttributeKey、Attribute、AttributeMap間的關系:
AttributeMap相對于一個map,AttributeKey相當于map的key,Attribute是一個持有key(AttributeKey)和value的對象。因此在map中我們可以通過AttributeKey key獲取Attribute,從而獲取Attribute中的value(即,屬性值)。
關于ChannelHandlerContext.attr(..) 和 Channel.attr(..)
Q:ChannelHandlerContext和Channel都提供了attr方法,那么它們設置的屬性作用域有什么不同了?
A:在Netty 4.1版本之前,它們兩設置的屬性作用域確實存在著不同,但從Netty 4.1版本開始,它們兩設置的屬性的作用域已經完全相同了。
而在Netty4.1之前,Channel內部保留有一個AttributeMap,而每個ChannelHandlerContext內部又保留有它們自己的AttributeMap,這樣通過Channel.attr()放入的屬性,是無法通過ChannelHandlerContext.attr()得到的,反之亦然。這種行為不僅令人困惑還會浪費內存。因此有了Netty 4.1將attr作用域統一的做法。
后記
若文章有任何錯誤,望大家不吝指教:)
參考
圣思園《精通并發與Netty》