1. Netty原始碼研究筆記(3)——Channel系列
依舊是通過先縱向再橫向的研究方法,在開篇中,我們發現不管是Sever還是Client,最終的啟動是通過呼叫channel的對應方法來完成的,而這個動作實際在channel系結的eventLoop中執行,
接下來,我們繼續EchoSever、EchoClient的呼叫鏈來揭開channel、eventloop的面紗,
1.1. 呼叫鏈分析
1.1.1. initAndRegister呼叫鏈
在AbstractBootstrap::initAndRegister的呼叫event loop group的register方法處打個斷點,并進入,
我們忽略在EventLoopGroup、EventLoop部分的方法呼叫,發現最終進入了AbstractUnsafe::register方法,
前面是些校驗邏輯:判斷是否已經注冊過了、判斷channel是否和eventloop兼不兼容,
主要的邏輯是說,在eventloop中去執行真正的注冊操作:register0,注意,目前是在主執行緒中,
在doRegister0打個執行緒斷點,進入到EventLoop執行緒中:
這個ChannelPromise指示register的成功與否,
首先確保這個ChannelPromise不能被取消,和Channel是open的(沒有close),
doRegister()方法是AbstractChannel的protect空方法,留給子類實作(AbstractNioChannel、AbstractEpollChannel、AbstractKqueueChannel、LocalChannel、LocalServerChannel、EmbeddedChannel),
在AbstractNioChannel::deRegister的實作僅僅是將netty channel中的java channel注冊到NioEventLoop中的java selector上,沒有指定任何interest ops,
deRegister完成后,就處理pipeline中的pending task(回呼ChannelInitializer,它負責添加需要的channel handler,在兩個Bootstrap的init方法中被添加到channel pipeline中),然后就向pipeline發射channel register事件,
如果channel已經激活(對于NioSocketChannel來說,其持有的java channel已經open并且connected;對于NioServerSocketChannel來說,其持有的java channel已經open,并且已經bound),這時:
-
如果channel是第一次注冊,那么向channel pipeline發送channelActive事件
-
如果channel之前已經注冊過、取消注冊過并且,channel被設定成autoRead,那么進行beginRead,這個方法由AbstractChannel的子類實作,對于NioChannel來說,它把自己注冊到EventLoop上時得到的java selection key給關注上讀事件,
1.1.2. Server的doBind0呼叫鏈
在AbstractBootstrap::doBind0處打個斷點,注意,此時的執行緒是EventLoop對應的執行緒,非主執行緒,
結果進入到AbstractChannel::bind方法中,
忽略channel pipeline中的執行流程,我們發現最終進入到AbstractChannel的內部類AbstractUnsafe::bind中:
bind的時候對SO_BROADCAST這個ChannelOption(表示廣播)進行檢查:如設定了這個option為true,并且不是windows平臺(當前是unix、linix等平臺),并且系結的本地地址不為0.0.0.0(對于服務器來說這個地址表示自己所有的ip地址,當服務系結這個地址后,那么使用服務器的所有ip地址都可以訪問到這個服務),并且當前程式不是以super user的身份運行,那么就warning:非root用戶如果沒有系結到0.0.0.0的話不能收到broadcast packet,但程式還是按用戶請求系結到指定的非0.0.0.0的localaddress,
獲取channel當前的是否active(對于NioServerSocketChannel來說如果它的ServerSocketChannel沒有close并且系結到local address,那么就算active),
然后呼叫channel的doBind方法,它一個抽象方法,對于NioServerSocketChannel來說具體的操作是將自己的java channel系結到local address,
然后再檢查channel的active狀態,
如果此前沒有active而doBind后active了那么就讓channel pipeline來fireChannelActive(這個動作被invokeLater了),對于NioServerSocketChannel來說,如果是第一次bind那么會fireChannelActive,
最后將用戶持有的指示bind程序的ChannelPromise設定為success,
1.1.3. Client的doConnect呼叫鏈
在Bootstrap::doConnect處打上斷點,然后進入,發現進入了AbstractChannel::connect方法中,
AbstractChannel::connect方法委托給其持有的channel pipeline執行,我們忽略channel pipeline中的執行邏輯,發現,最終呼叫到AbstractNioChannel的內部類中的方法AbstractUioUnsafe::connect,
AbstractUioUnsafe::connect看起來很長,但實際做的事情不復雜:
首先設定指示該操作的promise為不能取消,并且檢查channel是否open,這兩個操作都必須成功,
然后進行實際的doConnect,這是一個抽象方法,對于NioSocketChannel來說,它呼叫自己持有的java channel的connect方法,如果沒有馬上就連上,那么就設定selection key關注OP_CONNECT事件,
如果doConnect馬上就連上了,那么就進行收尾作業:fulfillConnectPromise,告知其他執行緒持有的指示connect操作的promise句柄:操作已成功,并且,如果之前channel沒有激活,而當前channel激活的話,還要向channel pipeline發送channelActive事件,
如果doConnect沒有馬上成功,即當前還在連接中,那么起一個超時檢測的延時任務,當這個任務給觸發的時候來檢查connect promise來檢查操作是否完成,如果沒有完成,那么就給promise設定成操作失敗,這個程序同時也是雙向的:又給connect promise添加了一個監聽器,使得,如果在連接中,超時前,如果其他執行緒將該connect promise取消的話,即取消連接操作,那么就取消這個超時檢查的延時任務,
備注:doConnect沒有馬上成功的話,最終,如果eventloop中輪詢到selector的OP_CONNECT事件后,會呼叫AbstractNioUnsafe::finishConnect方法來完成連接操作,
1.2. Channel系列
Channel是Netty中的核心類,它位于EventLoop、ChannelPipeline之間,
Channel的實作種類有很多,而常用的一般為兩種:
- 端到端的TCP:NioServerSocketChannel、NioSocketChannel
- 無連接的UDP:NioiDatagramChannel,
1.2.1. 繼承關系
Channel 介面繼承自 ChannelOutboundInvoker,AttributeMap,Comparable,介面內部包含 一個UnSafe 介面,
-
ChannelOutboundInvoker:喚起Channel向外的操作,(注:從eventloop到ChannelPipeline是Inbound,從ChannelPipeline到eventloop是outbound)這些操作都是異步操作,回傳的是ChannelFuture,這些操作包括:
-
bind,connect,close,disconnect,deregister(register是eventloop group提供的),read(將資料從channel中讀取到inbound buffer),write,flush,writeAndFlush
-
此外還有newPromise(ChannelPromise),newProgressivePromise(ChannelProgressivePromise),voidPromise(ChannelPromise)
-
以及newSucceededFuture,newFailedFuture(ChannelFuture)
-
-
Channel介面的功能:
-
獲取關聯資訊:id(ChannelId),eventloop,parent channel,channel config(ChannelConfig),meta data (ChannelMetadata),local address,remote address(SocketAddress),close future,ByteBufAllocator,channel pipeline,Unsafe
-
獲取狀態資訊:isOpen,isRegistered,isActive,isWritable,bytesBeforewritable,bytesBeforeUnWritable
-
動作:read,flush方法,二者重定義自ChannelOutboundInvoker,將回傳值設定為自身,以支持鏈式呼叫操作
-
-
ServerChannel:是一個標記性質的空介面,
-
ServerSocketChannel:重定義了remote address,local address 的回傳型別(InetSocketAddress),channel config的型別(ServerSocketChannelConfig)
-
DuplexChannel:表示雙向信道,output 、input兩個方向可以獨立關閉、并且提供了對這兩個方法的傳輸是否關閉進行判斷的方法,
-
SocketChannel:重寫了Channel回傳的parent channel、channel config、local address、remote address,這四者的回傳型別(是原來回傳型別的子型別),
-
DatagramChannel:重寫了Channel回傳的channel config、local addresss、remote address的回傳型別;新增了isConnected、joinGroup、leaveGroup、block這幾種方法,
1.2.2. AbstractChannel
AbstractChannel實作了Channel介面中的絕大多數功能:
-
對于AttributeMap介面中的功能,它是通過繼承自DefaultAttributeMap來實作的
-
對于ChannelOutboundInvoker介面中的功能,是通過委托給自己持有的channel pipeline來實作的(channel pipeline也實作了該介面)
-
對于Channel介面中新增的功能:
-
一部分是委托給自己持有的channel outbound buffer來實作的:isWritable、bytesBeforeWritable、bytesBeforeUnwritable
-
剩下的一些狀態獲取、組件獲取的操作,直接回傳自己持有的狀態欄位、組件欄位,比如:
-
isRegistered()對應registered欄位
-
closeFuture()對應closeFuture欄位
-
eventLoop()對應eventLoop欄位
-
localAddress() remoteAddress() 對應 localAddress、remoteAddress欄位,等等
-
-
注意,Channel介面中的一些方法AbstractChannel沒有實作,而是留給子類取實作比如:config()、metaData()、isOpen()、isActive(),
-
-
然后還定義了一些protected的抽象方法,留給子類實作,這些方法由Unsafe使用,
- doRegister、doBind、doDisconnect、doClose、doShutdownOutput、doDeregister、doBeginRead、doWrite
注意,AbstractChannel是其他所有channel的公共祖先,這些其他的channel可以是nio channel、oio channel、local (server) channel、embedd channel、failed channel、epoll channel、kqueue channel,
1.2.3. AbstractNioChannel
AbstractNioChannel是所有nio channel的祖先,它在AbstractChannel的基礎上加上了這個channel是NIO的語意,
因此,它持有了:java的selectable channel、selection key、interest op,
AbstractNioChannel下面又分為byte channel、message channel兩種(一個基于位元組、一個基于訊息),因此AbstractNioChannel類中還實作了,具有NIO語意的并且跟byte、message沒有區別的一些操作:
-
doRegister:不管是byte channel還是message channel,就register操作而言,都是將自己持有的java selectable channel給注冊到eventloop中的java selector中,并拿到selection key
-
doBeginRead:不管都是byte channel還是message channel,就begin read來講,都是將selection key給關聯上interest op,
-
isCompatible:不管都是byte channel還是message channel,它們都必須和NioEventLoop來配合,
-
由于NIO是非阻塞的(NioEventLoop非阻塞)所以connect流程必須弄成異步,這個異步流程由自己持有的NioUnsafe完成,
1.2.4. AbstractNioByteChannel、AbstractNioMessageChannel
byte channel、message channel的區別主要是read、write的策略,
而實際的read、write策略非常復雜,目前的縱向流程只到了bind、connect,因此暫不分析,
1.3. Unsafe系列
由于Netty網路通信程序例外復雜,大致可分為兩部分的操作:
- 第一部分是對java channel的操作的封裝,比如connect、close等,
- 另一部分是和netty體系相關,比如異步流程、讀策略、寫策略等,這些需要和netty的其他組件打交道,比如event loop、channel pipeline、channel outbound buffer等,這些操作例外復雜,精細,正如Unsafe字面的語意,
因此,為了將這兩部分的操作進行隔離,netty引入了Unsafe這個內部介面,它從屬于Channel,并且它的不同實作類散落在不同的Channel實作類中,將后者操作放入unsafe中實作,將前者操作放在netty的channel中實作,這樣可以保證channel的純凈,
1.3.1. 繼承關系
由于我們目前只關心三種常用的channel:NioSocketChannel、NioServerSocketChannel、NioDatagramChannel,因此它們涉及Unsafe的繼承關系如下:
1.3.2. Unsafe
Unsafe operations that should never be called from user-code. These methods are only provided to implement the actual transport, and must be invoked from an I/O thread except for the following methods:
- localAddress()
- remoteAddress()
- closeForcibly()
- register(EventLoop, ChannelPromise)
- deregister(ChannelPromise)
- voidPromise()
由于Unsafe是對Channel的復雜流程操作的封裝,從屬于Channel,所以其方法和Channel很相近:
- 一類是資訊的獲取操作:RecvByteBufAllocator.Handle、local address、remote address、voidPromise、channelOutBoundbuffer
- 另一類是動作型別的操作:bind、register、deregister、connect、disconnect、close、closeFocibly、beginRead、write、flush
作者: 邁吉
出處: https://www.cnblogs.com/stepfortune/
關于作者:邁吉
本文著作權歸作者和博客園共有,歡迎轉載,但未經作者同意必須保留此段宣告,且在文章頁面明顯位置給出, 原文鏈接 如有問題, 可郵件([email protected])咨詢.
轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/479207.html
標籤:Java
