本文是Netty文集中“Netty 源碼解析”系列的文章藕筋。主要對Netty的重要流程以及類進行源碼解析葫督,以使得我們更好的去使用Netty叽赊。Netty是一個非常優(yōu)秀的網(wǎng)絡框架,對其源碼解讀的過程也是不斷學習的過程顺呕。
嗯枫攀,本文與其說是ChannelConfig、Attribute源碼解析株茶,不如說是對ChannelConfig以及Attribute結(jié)構(gòu)層次的分析来涨。因為這才是它們在Netty中使用到的重要之處。
ChannelConfig
在Netty 源碼解析 ——— 服務端啟動流程 (下)中說過启盛,當我們在構(gòu)建NioServerSocketChannel的時候同時會構(gòu)建一個NioServerSocketChannelConfig對象賦值給NioServerSocketChannel的成員變量config蹦掐。
而這一個NioServerSocketChannelConfig是當前NioServerSocketChannel配置屬性的集合。NioServerSocketChannelConfig主要用于對NioServerSocketChannel相關(guān)配置的設(shè)置(如僵闯,網(wǎng)絡的相關(guān)參數(shù)配置)卧抗,比如,配置Channel是否為非阻塞鳖粟、配置連接超時時間等等社裆。
下面我們來對NioServerSocketChannelConfig的結(jié)構(gòu)做個詳細介紹NioServerSocketChannelConfig其實是一個ChannelConfig實例。ChannelConfig表示為一個Channel相關(guān)的配置屬性的集合向图。所以NioServerSocketChannelConfig就是針對于NioServerSocketChannel的配置屬性的集合泳秀。
ChannelConfig是Channel所需的公共配置屬性的集合,如榄攀,setAllocator(設(shè)置用于channel分配buffer的分配器)嗜傅。而不同類型的網(wǎng)絡傳輸對應的Channel有它們自己特有的配置,因此可以通過擴展ChannelConfig來補充特有的配置檩赢,如吕嘀,ServerSocketChannelConfig是針對基于TCP連接的服務端ServerSocketChannel相關(guān)配置屬性的集合,它補充了針對TCP服務端所需的特有配置的設(shè)置setBacklog、setReuseAddress币他、setReceiveBufferSize。
DefaultChannelConfig作為ChannelConfig的默認實現(xiàn)憔狞,對ChannelConfig中的配置提供了默認值蝴悉。
接下來,我們來看一個設(shè)置ChannelConfig的流程:
serverBootstrap.option(ChannelOption.SO_REUSEADDR, true);
我們可以在啟動服務端前通過ServerBootstrap來進行相關(guān)配置的設(shè)置瘾敢,該選項配置會在Channel初始化時被獲取并設(shè)置到Channel中拍冠,最終會調(diào)用底層ServerSocket.setReuseAddress方法來完成配置的設(shè)置。
ServerBootstrap的init()方法:
首先對option和value進行校驗晃财,其實就是進行非空校驗。
然后判斷對應的是哪個常量屬性典蜕,并進行相應屬性的設(shè)置断盛。如果傳進來的ChannelOption不是已經(jīng)設(shè)定好的常量屬性,則會打印一條警告級別的日志愉舔,告知這是未知的channel option钢猛。
Netty提供ChannelOption的一個主要的功能就是讓特定的變量的值給類型化。因為從’ChannelOption<T> option’和’T value’可以看出轩缤,我們屬性的值類型T命迈,是取決于ChannelOption的泛型的,也就屬性值類型是由屬性來決定的火的。
ChannelOption
這里壶愤,我們可以看到有個ChannelOption類,它允許以類型安全的方式去配置一個ChannelConfig馏鹤。支持哪一種ChannelOption取決于ChannelConfig的實際的實現(xiàn)并且也可能取決于它所屬的傳輸層的本質(zhì)公你。
可見ChannelOption是一個Consant擴展類,Consant是Netty提供的一個單例類假瞬,它能安全去通過’==’來進行比較操作陕靠。通過ConstantPool進行管理和創(chuàng)建。
常量由一個id和name組成脱茉。id:表示分配給常量的唯一數(shù)字剪芥;name:表示常量的名字。
ConstantPool
如上所說琴许,Constant是由ConstantPool來進行管理和創(chuàng)建的税肪,那么ConstantPool又是個什么樣的類了?
首先從constants中g(shù)et這個name對應的常量净捅,如果不存在則調(diào)用newConstant()來構(gòu)建這個常量tempConstant疑枯,然后在調(diào)用constants.putIfAbsent方法來實現(xiàn)“如果該name沒有存在對應的常量,則插入蛔六,否則返回該name所對應的常量荆永。(這整個的過程都是原子性的)”,因此我們是根據(jù)putIfAbsent方法的返回來判斷該name對應的常量是否已經(jīng)存在于constants中的国章。如果返回為null具钥,則說明當前創(chuàng)建的tempConstant就為name所對應的常量;否則液兽,將putIfAbsent返回的name已經(jīng)對應的常量值返回骂删。(注意,因為ConcurrentHashMap不會允許value為null的情況四啰,所以我們可以根據(jù)putIfAbsent返回為null則代表該name在此之前并未有對應的常量值)
ChannelOption類中屬性
好了桃漾,到目前為止,我們已經(jīng)知道ChannelOption是一個Constant的擴展拟逮,因此它可以由ConstantPool來管理和創(chuàng)建撬统。接下來,我們繼續(xù)來看看ChannelOption類中的一些重要屬性:正如我們前面所說的敦迄,這個ConstantPool<ChannelOption<Object>> pool(即恋追,ChannelOption常量池)是ChannelOption的一個私有靜態(tài)成員屬性,用于管理和創(chuàng)建ChannelOption罚屋。
同時苦囱,ChannelOption中將所有的與相關(guān)的配置項名稱都已常量形式定義好了。如:這些定義好的ChannelOption常量都已經(jīng)存儲數(shù)到ChannelOption的常量池(ConstantPool)中了脾猛。
注意撕彤,ChannelOption本身并不維護選項值的信息,它只是維護選項名字本身猛拴。比如羹铅,“public static final ChannelOption<Integer> SO_RCVBUF = valueOf("SO_RCVBUF");”??這只是維護了“SO_RCVBUF”這個選項名字的信息,同時泛型表示選擇值類型愉昆,即“SO_RCVBUF”選項值為Integer职员。
好了,到目前為止跛溉,我們對Netty的ChannelOption的設(shè)置以及底層的實現(xiàn)已經(jīng)分析完了焊切,簡單的來說:Netty在初始化Channel時會構(gòu)建一個ChannelConfig對象扮授,而ChannelConfig是Channel配置屬性的集合。比如专肪,Netty在初始化NioServerSocketChannel的時候同時會構(gòu)建一個NioServerSocketChannelConfig對象刹勃,并將其賦值給NioServerSocketChannel的成員變量config,而這個config(NioServerSocketChannelConfig)維護了NioServerSocketChannel的所有配置屬性嚎尤。比如荔仁,NioServerSocketChannelConfig提供了setConnectTimeoutMillis方法來設(shè)置NioServerSocketChannel連接超時的時間贡这。
同時,程序可以通過ServerBootstrap或Boostrap的option(ChannelOption<T> option, T value)方法來實現(xiàn)配置的設(shè)置须眷。這里爷抓,我們通過ChannelOption來實現(xiàn)配置的設(shè)置,ChannelOption中已經(jīng)將常用的配置項預定義為了常量供我們直接使用定硝,同時ChannelOption的一個主要的功能就是讓特定的變量的值給類型化。因為從’ChannelOption<T> option’和’T value’可以看出,我們屬性的值類型T坪哄,是取決于ChannelOption的泛型的,也就屬性值類型是由屬性來決定的势篡。
Attribute
一個attribute允許存儲一個值的引用翩肌。它可以被自動的更新并且是線程安全的。
其實Attribute就是一個屬性對象禁悠,這個屬性的名稱為AttributeKey<T> key念祭,而屬性的值為T value。
我們可以通過程序ServerBootstrap或Boostrap的attr方法來設(shè)置一個Channel的屬性碍侦,如:
serverBootstrap.attr(AttributeKey.valueOf("userID"), UUID.randomUUID().toString());
當Netty底層初始化Channel的時候粱坤,就會將我們設(shè)置的attribute給設(shè)置到Channel中:
如上面所說,Attribute就是一個屬性對象濒旦,這個屬性的名稱為AttributeKey<T> key株旷,而屬性的值為T value。
而AttributeKey也是Constant的一個擴展尔邓,因此也有一個ConstantPool來管理和創(chuàng)建晾剖,這和ChannelOption是類似的。
Channel類本身繼承了AttributeMap類梯嗽,而AttributeMap它持有多個Attribute钞瀑,這些Attribute可以通過AttributeKey來訪問的。所以慷荔,才可以通過channel.attr(key).set(value)的方式將屬性設(shè)置到channel中了(即雕什,這里的attr方法實際上是AttributeMap接口中的方法)缠俺。
AttributeKey、Attribute贷岸、AttributeMap間的關(guān)系:
AttributeMap相對于一個map壹士,AttributeKey相當于map的key,Attribute是一個持有key(AttributeKey)和value的對象偿警。因此在map中我們可以通過AttributeKey key獲取Attribute躏救,從而獲取Attribute中的value(即,屬性值)。
關(guān)于ChannelHandlerContext.attr(..) 和 Channel.attr(..)
Q:ChannelHandlerContext和Channel都提供了attr方法螟蒸,那么它們設(shè)置的屬性作用域有什么不同了盒使?
A:在Netty 4.1版本之前,它們兩設(shè)置的屬性作用域確實存在著不同七嫌,但從Netty 4.1版本開始少办,它們兩設(shè)置的屬性的作用域已經(jīng)完全相同了。
而在Netty4.1之前蔓纠,Channel內(nèi)部保留有一個AttributeMap,而每個ChannelHandlerContext內(nèi)部又保留有它們自己的AttributeMap吗蚌,這樣通過Channel.attr()放入的屬性腿倚,是無法通過ChannelHandlerContext.attr()得到的,反之亦然蚯妇。這種行為不僅令人困惑還會浪費內(nèi)存敷燎。因此有了Netty 4.1將attr作用域統(tǒng)一的做法。
后記
若文章有任何錯誤侮措,望大家不吝指教:)
參考
圣思園《精通并發(fā)與Netty》