摘要:我想這很好的解釋了中,僅僅一個都這么復雜,在單線程或者說串行的程序中,編程往往是很簡單的,說白了就是調用,調用,調用然后返回。
Netty源碼分析(三) 前提概要
這次停更很久了,原因是中途迷茫了一段時間,不過最近調整過來了。不過有點要說下,前幾天和業內某個大佬聊天,收獲很多,所以這篇博文和之前也會不太一樣,我們會先從如果是我自己去實現這個功能需要怎么做開始,然后去看netty源碼,與自己的實現做對比。
Server端NIO復習Netty有基于很多IO的實現(BIO/OIO/NIO...),而我們最常用的也就是NIO了,我們這次分析源碼,也是基于NIO的實現,前提條件要先弄清楚NIO的流程,再分析Netty是怎么基于他開發出這個高性能網絡框架的,這里我們先來簡單的復習下,已經熟悉的同學可以跳過不看了。
四個步驟拋開數據的讀寫,我們把NIO服務端監聽分成四個步驟
channel初始化
注冊 selector到 channel上
channel綁定端口
循環select 等待事件
其中第二步又分為幾個小步驟
創建selector
調用channel的register
然后第四步也分為幾個步驟
selector.select(或者幾個帶參的重寫方法)
處理所有的selectedKey
然后再繼續分解:[處理所有的selectedKey]
獲取selectedKey的channel
根據selectedKey的狀態處理channel
其中需要注意的是:如果key的狀態(或者說是readyops)是accept的話,需要把channel轉成ServerSocktChannel然后通過accept獲取新的channel,再把這個selector注冊到這個channel中去。如果是其他的讀寫狀態的話,獲取的channel要轉為SocketChannel,然后進行操作。
Register的實現通過NIO的復習,我們可以看到一個完整的NIO的創建流程。在之前的兩篇博文中,我們實現了第一步初始化以及第三步綁定端口的流程,現在我們先來實現channel的register。
這里多提一句,網上很多人都是在bind端口之后,再channel register的,但是其實這個前后順序并沒有關系,Netty中則是在bind之前進行的。
先看下之前的代碼,我們在doBind方法中,先實例化了一個channel,然后調用channel的bind方法。
private void doBind(Integer port){ Channel channel = channelFactory.newChannel(); channel.bind(new InetSocketAddress(port)); }
這樣看,我們現在需要做的就是在這兩行代碼中插入一段channel register的代碼就可以了,那是不是可以這樣,Channel中新增一個register接口,然后在實現類中去實現他,最后在這里中間寫一句channel.register,貌似看起來沒問題啊,不管怎么樣,先動手實現下。
/* in Channel Interface */ void register(Selector selector); /* in AbstractNioChannel */ @Override public void register(Selector selector) throws ClosedChannelException { ch().register(selector,0,null); } /*in AbstractBootstrap*/ try { channel.register(Selector.open()); } catch (IOException e) { e.printStackTrace(); }
我們最后的doBind看起來就會是這樣
private void doBind(Integer port){ Channel channel = channelFactory.newChannel(); try { channel.register(Selector.open()); } catch (IOException e) { e.printStackTrace(); } channel.bind(new InetSocketAddress(port)); }
功能貌似是實現了。。。但是,看起來是不是有些問題?
沒錯,首先看起來很丑,try/catch一大塊的很難看,但其次拋去美觀問題不談,這么寫不符合設計思想,我這個類是一個抽象的啟動類,但是這個register只是NIO的寫法,那BIO,AIO...其他IO實現怎么辦?
那我們先來做下最簡單的改造,軟件不就是在一次次的重構中,慢慢成長起來的嘛。我簡單的分了下步驟。
Channel接口更改,取消入參和異常拋出
void register();
抽象實現類中增加新的帶參方法,供接口方法調用
@Override public void register(){ try { register(Selector.open()); } catch (IOException e) { e.printStackTrace(); } } private void register(Selector selector) throws ClosedChannelException { ch().register(selector,0,null); }
doBind內register調用
private void doBind(Integer port){ Channel channel = channelFactory.newChannel(); channel.register(); channel.bind(new InetSocketAddress(port)); }
這樣看,代碼是不是美觀很多?最關鍵的是,此時的我們的抽象啟動類里,真正做到了抽象,不管什么IO實現,都會去調用自身Channel實現的register方法。
目前來看還挺不錯的,接下來看下Netty是怎么實現的吧,自己寫的思路對的,但是肯定有很多地方沒考慮到的。
Netty中的Register How to doNetty中,doBind方法里是包裝了一個initAndRegister方法去完成初始化和注冊地功能,這里我們直接看下initAndRegister這個方法體,看下Netty怎么做的
是不是和我們寫的差不多呢,先實例化一個channel,然后再進行register的,不過有兩點不同的地方,其是register之前先要調用一個init方法,玩過Netty的應該都清楚,主要是處理我們自定義的一些配置的,這里我們先不提,等后面再說。先來說下第二個不同,也就是register方法。
我們的代碼里,是直接通過擴展channel的接口,直接調用channel的register的方法的,但是Netty這里則是通過group(包含了Netty很重要的一個角色EventLoop,我們后面會詳細說他),傳入channel對象,然后再去調用channel的register方法,不信,我們看下他的調用鏈。
撇去Netty復雜的繼承關系,我們最終定位到方法的最終調用的地方,SingleThreadEventLoop里
我們可以看到,register方法里,先是包裝了一個promise對象(實現promise接口的對象,這里維護了channel對象和這個eventloop對象,題外話:promise接口其實是future接口的一個超集),然后調用了promise里的channel的unsafe對象的register(ps:好繞口的感覺),這個unsafe就是channel的一個內部類,感覺越來越接近了,我們就到unsafe里面看下register方法吧。
這下應該很清晰了吧,unsafe的register里又調用了doRegister,然后就和我們的方法差不多了,這里的javaChannel其實就是JAVA NIO的channel對象,唯一不同的就是這里的selector不是open出來的,而是早在eventloop初始化的時候就存下來了,可能有人會問:這里eventloop是怎么來的呢?答案就在上面,在調用unsafe的register的時候,我們傳入了兩個對象,第一個this就是指向的eventloop(別忘了我們是在eventloop里調用的register)。然后就是把this(AbstractNioChannel)作為register的第三個參數(后面可以通過selectedKey.attachment獲取到)
How to sayNetty中怎么做的我們也看過了,相信你和我一樣,有很多疑問(大神們請自動無視)。這里我舉出我覺得比較重要的兩個。
為什么一個注冊這么復雜?明明用原生NIO只需要一行就搞定的,又是promise又是unsafe,在channel.register之間抽象出來兩層。
為什么最后的doRegister里,要用一個無限循環包起來呢?
答案還是要從源碼里獲得,先看下eventloop的register的接口注釋
簡單來說,就是把eventloop對象注冊到channel里,并且要能知道注冊的結果,并且返回。這句話,可能有些同學不太明白,這是Java中的異步調用,我們傳統開發的Java web程序,我們所處理的基本都是單線程的模式,首先這符合人的大腦的習慣嘛(人的大腦本質是串行處理事情的),其次是多線程的部分,框架和容器已經幫我做好了,所以可能會不太理解異步這個概念。
然后再看下unsafe這個register接口的注釋
看到了吧,執行promise的channel的注冊并在完成的時候通知返回,這是靠一個叫觀察者模式來完成的,具體的內容在下一章會詳細講解。
我想這很好的解釋了Netty中,僅僅一個register都這么復雜,在單線程或者說串行的程序中,編程往往是很簡單的,說白了就是調用,調用,調用然后返回。但Netty是個充滿并行和異步的程序,所以光從設計上就會比較復雜,這讓我想起了沈神說的一句話:簡單的容易理解的模型性能都差,性能好的都很復雜。雖然他指的是數據庫的設計,但是我覺得道理是相通的。
還有一點就是這些設計也是為了同時兼容服務端和客戶端,軟件開發的思想里,很重要的一點就是復用。這也是為什么會有第二個疑問,我們還是要看下注釋。
jdk的register的注釋說明,如果這個channel已經被注冊過了,并且再次注冊的過程中連接斷了,則會拋出這個CanceledKeyException異常,那么這里捕獲了異常并且調用select.selectNow,理論上會從selector中移除這個無效的channel,但是事實上并沒有,所以下面也寫到了,可能是個jdk bug,所以要顯示的往外繼續拋出異常。
仔細想想,如果僅僅是服務端的channel register,怎么可能發生上述的情況呢,所以這里的doRegister同時也是客戶端channel復用的注冊方法。
總結&&預告這一章差不多就結束了,我們做了哪些事情呢?
復習Java NIO 流程
動手實現channel register
讀Netty源碼,理解Netty是如何實現register以及為什么這樣實現的
軟件開發的思想
然后畫個簡單的圖來總結下把。
接下來在實現循環select監聽和處理事件之前,我們先實現一下Netty中一個很重要的EventLoop,并且了解下運用到的異步框架(Future and Promise),希望我的拖延癥得到解決,應該在下周會寫出來。。。
希望同學們看到后會有收獲,如果我有理解不對的地方,歡迎來指正。
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://m.specialneedsforspecialkids.com/yun/68364.html
摘要:對于,目前大家只知道是個線程組,其內部到底如何實現的,它的作用到底是什么,大家也都不太清楚,由于篇幅原因,這里不作詳細介紹,后面會有文章作專門詳解。 在上一篇《ServerBootstrap 與 Bootstrap 初探》中,我們已經初步的了解了ServerBootstrap是netty進行服務端開發的引導類。 且在上一篇的服務端示例中,我們也看到了,在使用netty進行網絡編程時,我...
摘要:下面無恥的貼點源碼。啟動類我們也學,把啟動類抽象成兩層,方便以后寫客戶端。別著急,我們慢慢來,下一篇我們會了解以及他的成員,然后,完善我們的程序,增加其接收數據的能力。文章的源碼我會同步更新到我的上,歡迎大家,哈哈。 廢話兩句 這次更新拖了很長時間,第一是自己生病了,第二是因為最開始這篇想寫的很大,然后構思了很久,發現不太合適把很多東西寫在一起,所以做了點拆分,準備國慶前完成這篇博客。...
摘要:一些想法這個系列想開很久了,自己使用也有一段時間了,利用也編寫了一個簡單的框架,并運用到工作中了,感覺還不錯,趁著這段時間工作不是很忙,來分析一波源碼,提升下技術硬實力。 一些想法 這個系列想開很久了,自己使用netty也有一段時間了,利用netty也編寫了一個簡單的框架,并運用到工作中了,感覺還不錯,趁著這段時間工作不是很忙,來分析一波源碼,提升下技術硬實力。 結構 這里先看下net...
摘要:本篇將通過實例化過程,來深入剖析。及初始化完成后,它們會相互連接。我們在回到的構造方法父類構造方法調用完成后,還要初始化一下自己的配置對象是的內部類而又是繼承自,通過代碼分析,此對象就是就會對底層一些配置設置行為的封裝。 根據上一篇《Netty4.x 源碼實戰系列(二):服務端bind流程詳解》所述,在進行服務端開發時,必須通過ServerBootstrap引導類的channel方法來...
摘要:而用于主線程池的屬性都定義在中本篇只是簡單介紹了一下引導類的配置屬性,下一篇我將詳細介紹服務端引導類的過程分析。 從Java1.4開始, Java引入了non-blocking IO,簡稱NIO。NIO與傳統socket最大的不同就是引入了Channel和多路復用selector的概念。傳統的socket是基于stream的,它是單向的,有InputStream表示read和Outpu...
閱讀 2422·2021-11-25 09:43
閱讀 1202·2021-09-07 10:16
閱讀 2616·2021-08-20 09:38
閱讀 2943·2019-08-30 15:55
閱讀 1462·2019-08-30 13:21
閱讀 894·2019-08-29 15:37
閱讀 1446·2019-08-27 10:56
閱讀 2097·2019-08-26 13:45