Netty啟動(dòng)流程注冊多路復(fù)用源碼解析
前文傳送門:Netty啟動(dòng)流程服務(wù)端channel初始化
注冊多路復(fù)用
回到上一小節(jié)的代碼:
final ChannelFuture initAndRegister() {
Channel channel = null;
try {
//創(chuàng)建channel
channel = channelFactory.newChannel();
//初始化channel
init(channel);
} catch (Throwable t) {
//忽略非關(guān)鍵代碼
}
//注冊channel
ChannelFuture regFuture = config().group().register(channel);
//忽略非關(guān)鍵代碼
return regFuture;
}注冊channel的步驟
我們講完創(chuàng)建channel和初始化channel的關(guān)鍵步驟, 我們繼續(xù)跟注冊channel的步驟:
ChannelFuture regFuture = config().group().register(channel);
其中, 重點(diǎn)關(guān)注下register(channel)這個(gè)方法, 這個(gè)方法最終會(huì)調(diào)用到AbstractChannel中內(nèi)部類AbstractUnsafe的register()方法, 具體如何調(diào)用到這個(gè)方法, 可以簡單帶大家捋一下
首先看下config()方法
由于是ServerBootstrap調(diào)用的, 所以我們跟進(jìn)去:
public final ServerBootstrapConfig config() {
return config;
}返回的config是ServerBootrap的成員變量config:
private final ServerBootstrapConfig config = new ServerBootstrapConfig(this);
跟到ServerBootstrapConfig的構(gòu)造方法:
ServerBootstrapConfig(ServerBootstrap bootstrap) {
super(bootstrap);
}繼續(xù)跟到其父類AbstractBootstrapConfig的構(gòu)造方法:
protected AbstractBootstrapConfig(B bootstrap) {
this.bootstrap = ObjectUtil.checkNotNull(bootstrap, "bootstrap");
}我們發(fā)現(xiàn)我們創(chuàng)建的ServerBootstrap作為參數(shù)初始化了其成員變量bootstrap
回到initAndRegister()方法:
config()返回的是ServerBootstrapConfig對象
再繼續(xù)跟到其group()方法:
public final EventLoopGroup group() {
return bootstrap.group();
}這里調(diào)用Bootstrap的group()方法:
public final EventLoopGroup group() {
return group;
}這里返回了AbstractBootstrap的成員變量group, 我們回顧下第一小節(jié), 還記得AbstractBootstrap的group(EventLoopGroup group)方法嗎?
public B group(EventLoopGroup group) {
this.group = group;
return (B) this;
}group(EventLoopGroup group)方法初始化了我們boss線程, 而group()返回了boss線程, 也就是說 config().group().register(channel) 中的register()方法是boss線程對象調(diào)用的, 由于我們當(dāng)初初始化的是NioEventLoopGroup, 因此走的是NioEventLoopGroup的父類的MultithreadEventLoopGroup的register()方法
跟到MultithreadEventLoopGroup的register()方法:
public ChannelFuture register(Channel channel) {
return next().register(channel);
}這里的代碼看起來有點(diǎn)暈, 沒關(guān)系, 以后會(huì)講到, 現(xiàn)在可以大概做個(gè)了解, NioEventLoopGroup是個(gè)線程組, 而next()方法就是從線程組中選出一個(gè)線程, 也就是NioEventLoop線程, 所以這里的next()方法返回的是NioEventLoop對象, 其中register(channel)最終會(huì)調(diào)用NioEventLoop的父類SingleThreadEventLoop的register(channel)方法
跟到SingleThreadEventLoop的register(channel)方法:
public ChannelFuture register(Channel channel) {
return register(new DefaultChannelPromise(channel, this));
}其中DefaultChannelPromise類我們之后也會(huì)講到
我們先跟到register(new DefaultChannelPromise(channel, this)):
public ChannelFuture register(final ChannelPromise promise) {
ObjectUtil.checkNotNull(promise, "promise");
promise.channel().unsafe().register(this, promise);
return promise;
}channel()會(huì)返回我們初始化的NioServerSocketChannel, unsafe()會(huì)返回我們創(chuàng)建channel的時(shí)候初始化的unsafe對象
跟進(jìn)去看AbstractChannel的unsafe()的實(shí)現(xiàn):
public Unsafe unsafe() {
return unsafe;
}這里返回的unsafe, 就是我們初始化channel創(chuàng)建的unsafe
回顧下第二小節(jié)channel初始化的步驟:
protected AbstractChannel(Channel parent) {
this.parent = parent;
id = newId();
unsafe = newUnsafe();
pipeline = newChannelPipeline();
}我們看unsafe的初始化:unsafe=newUnsafe()
跟到newUnsafe()中, 我們之前講過NioServerSokectChannel的父類是AbstractNioMessageChannel, 所以會(huì)調(diào)用到到AbstractNioMessageChannel類中的newUnsafe()
跟到AbstractNioMessageChannel類中的newUnsafe():
protected AbstractNioUnsafe newUnsafe() {
return new NioMessageUnsafe();
}我們看到這里創(chuàng)建了NioMessageUnsafe()對象, 所以在 promise.channel().unsafe().register(this, promise) 代碼中, unsafe()是返回的NioMessageUnsafe()對象, 最后調(diào)用其父類AbstractUnsafe(也就是AbstractChannel的內(nèi)部類)的register()方法,
簡單介紹下unsafe接口, unsafe顧名思義就是不安全的, 因?yàn)楹芏鄬hannel的io方法都定義在unsafe中, 所以netty將其作為內(nèi)部類進(jìn)行封裝, 防止被外部直接調(diào)用, unsafe接口是Channel接口的內(nèi)部接口, unsafe的子類也分別封裝在Channel的子類中, 比如我們現(xiàn)在剖析的register()方法, 就是封裝在AbstractChannel類的內(nèi)部類AbstractUnsafe中的方法, 有關(guān)Unsafe和Channel的繼承關(guān)系如下:

以上內(nèi)容如果不明白沒有關(guān)系, 有關(guān)NioEventLoop相關(guān)會(huì)在后面的章節(jié)講到, 目前我們只是了解是如何走到AbstractUnsafe類的register()即可
我們繼續(xù)看看register()方法:
public final void register(EventLoop eventLoop, final ChannelPromise promise) {
//代碼省略
//所有的復(fù)制操作, 都交給eventLoop處理(1)
AbstractChannel.this.eventLoop = eventLoop;
if (eventLoop.inEventLoop()) {
register0(promise);
} else {
try {
eventLoop.execute(new Runnable() {
@Override
public void run() {
//做實(shí)際主注冊(2)
register0(promise);
}
});
} catch (Throwable t) {
//代碼省略
}
}
}我們跟著注釋的步驟繼續(xù)走, 第一步, 綁定eventLoop線程:
AbstractChannel.this.eventLoop = eventLoop;
eventLoop是AbstractChannel的成員變量, 有關(guān)eventLoop, 我們會(huì)在緒章節(jié)講到, 這里我們只需要知道, 每個(gè)channel綁定唯一的eventLoop線程, eventLoop線程和channel的綁定關(guān)系就是在這里展現(xiàn)的
再看第二步, 做實(shí)際注冊:
我們先看if判斷, if(eventLoop.inEventLoop())
這里是判斷是不是eventLoop線程, 顯然我們現(xiàn)在是main()方法所在的線程, 所以走的else, eventLoop.execute()是開啟一個(gè)eventLoop線程, 而register0(promise)就是再開啟線程之后, 通過eventLoop線程執(zhí)行的, 這里大家暫時(shí)作為了解
我們重點(diǎn)關(guān)注register0(promise), 跟進(jìn)去:
private void register0(ChannelPromise promise) {
try {
//做實(shí)際的注冊(1)
doRegister();
neverRegistered = false;
registered = true;
//觸發(fā)事件(2)
pipeline.invokeHandlerAddedIfNeeded();
safeSetSuccess(promise);
//觸發(fā)注冊成功事件(3)
pipeline.fireChannelRegistered();
if (isActive()) {
if (firstRegistration) {
//傳播active事件(4)
pipeline.fireChannelActive();
} else if (config().isAutoRead()) {
beginRead();
}
}
} catch (Throwable t) {
//省略代碼
}
}我們重點(diǎn)關(guān)注doRegister()這個(gè)方法
doRegister()最終會(huì)調(diào)用AbstractNioChannel的doRegister()方法:
protected void doRegister() throws Exception {
boolean selected = false;
for (;;) {
try {
//jdk底層的注冊方法
//第一個(gè)參數(shù)為selector, 第二個(gè)參數(shù)表示不關(guān)心任何事件
selectionKey = javaChannel().register(eventLoop().selector, 0, this);
return;
} catch (CancelledKeyException e) {
//省略代碼
}
}
}我們終于看到和java底層相關(guān)的方法了
跟到j(luò)avaChannel()的方法中:
protected SelectableChannel javaChannel() {
return ch;
}這個(gè)ch, 就是本章第二小節(jié)創(chuàng)建NioServerSocketChannel中初始化的jdk底層ServerSocketChannel
這里register(eventLoop().selector, 0, this)方法中eventLoop().selector, 是獲得每一個(gè)eventLoop綁定的唯一的selector, 0代表這次只是注冊, 并不監(jiān)聽任何事件, this是代表將自身(NioEventLoopChannel)作為屬性綁定在返回的selectionKey當(dāng)中, 這個(gè)selectionKey就是與每個(gè)channel綁定的jdk底層的SelectionKey對象, 熟悉nio的小伙伴應(yīng)該不會(huì)陌生, 這里不再贅述
回到register0(ChannelPromise promise)方法, 我們看后續(xù)步驟:
步驟(2)是觸發(fā)handler的需要添加事件, 事件傳遞的內(nèi)容我們將在后續(xù)課程詳細(xì)介紹, 這里不必深究
步驟(3)是觸發(fā)注冊成功事件(3), 同上
步驟(4)是傳播active事件(4), 這里簡單強(qiáng)調(diào)一下, 這里的方法pipeline.fireChannelActive()第一個(gè)注冊是執(zhí)行不到的, 因?yàn)閕sActive()會(huì)返回false, 因?yàn)殒溌窙]完成
本小節(jié)梳理了有注冊多路復(fù)用的相關(guān)邏輯, 同學(xué)們可以跟著代碼自己走一遍以加深印象
以上就是Netty啟動(dòng)流程注冊多路復(fù)用源碼分析的詳細(xì)內(nèi)容,更多關(guān)于Netty啟動(dòng)流程的資料請關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
java?byte數(shù)組轉(zhuǎn)String的幾種常用方法
在Java中數(shù)組是一種非常常見的數(shù)據(jù)結(jié)構(gòu),它可以用來存儲(chǔ)多個(gè)相同類型的數(shù)據(jù),有時(shí)候,我們需要將數(shù)組轉(zhuǎn)換為字符串,以便于輸出或者傳遞給其他方法,這篇文章主要給大家介紹了關(guān)于java?byte數(shù)組轉(zhuǎn)String的幾種常用方法,需要的朋友可以參考下2024-09-09
LIS 最長遞增子序列 Java的簡單實(shí)現(xiàn)
下面小編就為大家?guī)硪黄狶IS 最長遞增子序列 Java的簡單實(shí)現(xiàn)。小編覺得挺不錯(cuò)的,現(xiàn)在就分享給大家,也給大家做個(gè)參考。一起跟隨小編過來看看吧2016-09-09
java request.getParameter中文亂碼解決方法
今天跟大家分享幾個(gè)解決java Web開發(fā)中,request.getParameter()獲取URL中文參數(shù)亂碼的解決辦法,需要的朋友可以參考下2020-02-02
Java編程實(shí)現(xiàn)服務(wù)器端支持?jǐn)帱c(diǎn)續(xù)傳的方法(可支持快車、迅雷)
這篇文章主要介紹了Java編程實(shí)現(xiàn)服務(wù)器端支持?jǐn)帱c(diǎn)續(xù)傳的方法,涉及Java文件傳輸?shù)南嚓P(guān)技巧,具有一定參考借鑒價(jià)值,需要的朋友可以參考下2015-11-11
Java之while與do-while循環(huán)的用法詳解
在上一篇文章中,給大家講解了循環(huán)的概念,并重點(diǎn)給大家講解了for循環(huán)的使用。但在Java中,除了for循環(huán)之外,還有while、do-while、foreach等循環(huán)形式。這篇文章給大家講解while循環(huán)的使用2023-05-05
Java 使用JdbcTemplate 中的queryForList發(fā)生錯(cuò)誤解決辦法
這篇文章主要介紹了Java 使用JdbcTemplate 中的queryForList發(fā)生錯(cuò)誤解決辦法的相關(guān)資料,需要的朋友可以參考下2017-07-07
Java設(shè)計(jì)模式之訪問者模式使用場景及代碼示例
這篇文章主要介紹了Java設(shè)計(jì)模式之訪問者模式使用場景及代碼示例,小編覺得還是挺不錯(cuò)的,這里分享給大家,供需要的朋友參考。2017-11-11
java實(shí)現(xiàn)飛機(jī)大戰(zhàn)案例詳解
這篇文章主要為大家詳細(xì)介紹了java實(shí)現(xiàn)飛機(jī)大戰(zhàn)案例,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2021-03-03

