摘要:原理剖析第篇之服務(wù)端啟動工作原理分析下一大致介紹由于篇幅過長難以發(fā)布,所以本章節(jié)接著上一節(jié)來的,上一章節(jié)為原理剖析第篇之服務(wù)端啟動工作原理分析上那么本章節(jié)就繼續(xù)分析的服務(wù)端啟動,分析的源碼版本為二三四章節(jié)請看上一章節(jié)詳見原理剖析第篇之
原理剖析(第 011 篇)Netty之服務(wù)端啟動工作原理分析(下)
-
一、大致介紹1、由于篇幅過長難以發(fā)布,所以本章節(jié)接著上一節(jié)來的,上一章節(jié)為【原理剖析(第 010 篇)Netty之服務(wù)端啟動工作原理分析(上)】; 2、那么本章節(jié)就繼續(xù)分析Netty的服務(wù)端啟動,分析Netty的源碼版本為:netty-netty-4.1.22.Final;二、三、四章節(jié)請看上一章節(jié)
詳見 原理剖析(第 010 篇)Netty之服務(wù)端啟動工作原理分析(上)
四、源碼分析Netty服務(wù)端啟動上一章節(jié),我們主要分析了一下線程管理組對象是如何被實例化的,并且還了解到了每個線程管理組都有一個子線程數(shù)組來處理任務(wù);
那么接下來我們就直接從4.6開始分析了:
1、源碼:
// NettyServer.java
// 將 Boss、Worker 設(shè)置到 ServerBootstrap 服務(wù)端引導(dǎo)類中
serverBootstrap.group(bossGroup, workerGroup)
.channel(NioServerSocketChannel.class)
// 指定通道類型為NioServerSocketChannel,一種異步模式,OIO阻塞模式為OioServerSocketChannel
.localAddress("localhost", port)//設(shè)置InetSocketAddress讓服務(wù)器監(jiān)聽某個端口已等待客戶端連接。
.childHandler(new ChannelInitializer() {//設(shè)置childHandler執(zhí)行所有的連接請求
@Override
protected void initChannel(Channel ch) throws Exception {
ch.pipeline().addLast(new PacketHeadDecoder());
ch.pipeline().addLast(new PacketBodyDecoder());
ch.pipeline().addLast(new PacketHeadEncoder());
ch.pipeline().addLast(new PacketBodyEncoder());
ch.pipeline().addLast(new PacketHandler());
}
});
2、主要為后序的通信設(shè)置了一些配置參數(shù)而已,指定構(gòu)建的Channel為NioServerSocketChannel,說明需要啟動的是服務(wù)端Netty;
而后面的服務(wù)端Channel實例化,就是需要通過這個參數(shù)反射實例化得到;
3、同時還設(shè)置childHandler,這個childHandler也是有順序的,服務(wù)端讀數(shù)據(jù)時執(zhí)行的順序是PacketHeadDecoder、PacketBodyDecoder、PacketHandler;
而服務(wù)端寫數(shù)據(jù)時執(zhí)行的順序是PacketHandler、PacketBodyEncoder、PacketHeadEncoder;
所以在書寫方式大家千萬別寫錯了,按照本示例代碼的方式書寫即可;
4.7、serverBootstrap調(diào)用bind綁定注冊
1、源碼:
// NettyServer.java
// 最后綁定服務(wù)器等待直到綁定完成,調(diào)用sync()方法會阻塞直到服務(wù)器完成綁定,然后服務(wù)器等待通道關(guān)閉,因為使用sync(),所以關(guān)閉操作也會被阻塞。
ChannelFuture channelFuture = serverBootstrap.bind().sync();
2、這里其實沒什么好看的,接下來我們就主要看看這個bind()方法主要干了些啥,就這么簡簡單單一句代碼就把服務(wù)端給啟動起來了,有點神氣了;
4.8、bind()操作
1、源碼:
// AbstractBootstrap.java
/**
* Create a new {@link Channel} and bind it.
*/
public ChannelFuture bind() {
validate();
SocketAddress localAddress = this.localAddress;
if (localAddress == null) {
throw new IllegalStateException("localAddress not set");
}
return doBind(localAddress); // 創(chuàng)建一個Channel,并且綁定它
}
// AbstractBootstrap.java
private ChannelFuture doBind(final SocketAddress localAddress) {
final ChannelFuture regFuture = initAndRegister(); // 初始化和注冊
// 執(zhí)行到此,服務(wù)端大概完成了以下幾件事情:
// 1、實例化NioServerSocketChannel,并為Channel配備了pipeline、config、unsafe對象;
// 2、將多個handler添加至pipeline雙向鏈表中,并且等待Channel注冊成功后需要給每個handler觸發(fā)添加或者移除事件;
// 3、將NioServerSocketChannel注冊到NioEventLoop的多路復(fù)用器上;
final Channel channel = regFuture.channel();
if (regFuture.cause() != null) {
return regFuture;
}
// 既然NioServerSocketChannel的Channel綁定到了多路復(fù)用器上,那么接下來就是綁定地址,綁完地址就可以正式進行通信了
if (regFuture.isDone()) {
// At this point we know that the registration was complete and successful.
ChannelPromise promise = channel.newPromise();
doBind0(regFuture, channel, localAddress, promise);
return promise;
} else {
// Registration future is almost always fulfilled already, but just in case it"s not.
final PendingRegistrationPromise promise = new PendingRegistrationPromise(channel);
regFuture.addListener(new ChannelFutureListener() {
@Override
public void operationComplete(ChannelFuture future) throws Exception {
Throwable cause = future.cause();
if (cause != null) {
// Registration on the EventLoop failed so fail the ChannelPromise directly to not cause an
// IllegalStateException once we try to access the EventLoop of the Channel.
promise.setFailure(cause);
} else {
// Registration was successful, so set the correct executor to use.
// See https://github.com/netty/netty/issues/2586
promise.registered();
doBind0(regFuture, channel, localAddress, promise);
}
}
});
return promise;
}
}
2、大致一看,原來doBind方法主要干了兩件事情,initAndRegister與doBind0;
3、initAndRegister主要做的事情就是初始化服務(wù)端Channel,并且將服務(wù)端Channel注冊到bossGroup子線程的多路復(fù)用器上;
4、doBind0則主要完成服務(wù)端啟動的最后一步,綁定地址,綁定完后就可以正式進行通信了;
4.9、initAndRegister()初始化和注冊
1、源碼:
// AbstractBootstrap.java
final ChannelFuture initAndRegister() {
Channel channel = null;
try {
// 反射調(diào)用clazz.getConstructor().newInstance()實例化類
// 同時也實例化了Channel,如果是服務(wù)端的話則為NioServerSocketChannel實例化對象
// 在實例化NioServerSocketChannel的構(gòu)造方法中,也為每個Channel創(chuàng)建了一個管道屬性對象DefaultChannelPipeline=pipeline對象
// 在實例化NioServerSocketChannel的構(gòu)造方法中,也為每個Channel創(chuàng)建了一個配置屬性對象NioServerSocketChannelConfig=config對象
// 在實例化NioServerSocketChannel的構(gòu)造方法中,也為每個Channel創(chuàng)建了一個unsafe屬性對象NioMessageUnsafe=unsafe對象
channel = channelFactory.newChannel(); // 調(diào)用ReflectiveChannelFactory的newChannel方法
// 初始化剛剛被實例化的channel
init(channel);
} catch (Throwable t) {
if (channel != null) {
// channel can be null if newChannel crashed (eg SocketException("too many open files"))
channel.unsafe().closeForcibly();
// as the Channel is not registered yet we need to force the usage of the GlobalEventExecutor
return new DefaultChannelPromise(channel, GlobalEventExecutor.INSTANCE).setFailure(t);
}
// as the Channel is not registered yet we need to force the usage of the GlobalEventExecutor
return new DefaultChannelPromise(new FailedChannel(), GlobalEventExecutor.INSTANCE).setFailure(t);
}
// config().group()=bossGroup或parentGroup,然后利用parentGroup去注冊NioServerSocketChannel=channel
ChannelFuture regFuture = config().group().register(channel);
if (regFuture.cause() != null) {
if (channel.isRegistered()) {
channel.close();
} else {
channel.unsafe().closeForcibly();
}
}
// If we are here and the promise is not failed, it"s one of the following cases:
// 1) If we attempted registration from the event loop, the registration has been completed at this point.
// i.e. It"s safe to attempt bind() or connect() now because the channel has been registered.
// 2) If we attempted registration from the other thread, the registration request has been successfully
// added to the event loop"s task queue for later execution.
// i.e. It"s safe to attempt bind() or connect() now:
// because bind() or connect() will be executed *after* the scheduled registration task is executed
// because register(), bind(), and connect() are all bound to the same thread.
return regFuture;
}
2、逐行分析后會發(fā)現(xiàn),首先通過反射實例化服務(wù)端channel對象,然后將服務(wù)端channel初始化一下;
3、然后調(diào)用bossGroup的注冊方法,將服務(wù)端channel作為參數(shù)傳入;
4、至此,方法名也表明該段代碼的意圖,實例化并初始化服務(wù)端Channel,然后注冊到bossGroup子線程的多路復(fù)用器上;
4.10、init服務(wù)端Channel
1、源碼:
// ServerBootstrap.java
@Override
void init(Channel channel) throws Exception {
final Map, Object> options = options0();
synchronized (options) {
setChannelOptions(channel, options, logger);
}
final Map, Object> attrs = attrs0();
synchronized (attrs) {
for (Entry, Object> e: attrs.entrySet()) {
@SuppressWarnings("unchecked")
AttributeKey
4.11、config().group().register(channel)
1、源碼:
// MultithreadEventLoopGroup.java
@Override
public ChannelFuture register(Channel channel) {
// next()對象其實是NioEventLoopGroup內(nèi)部中的children[]屬性中的其中一個,通過一定規(guī)則挑選一個NioEventLoop
// 那么也就是說我們最終調(diào)用的是NioEventLoop來實現(xiàn)注冊channel
return next().register(channel);
// 從另外一個層面來講,我們要想注冊一個Channel,那么就可以直接調(diào)用NioEventLoopGroup父類中的register(Channel)即可注冊Channel,
// 并且會按照一定的規(guī)則順序通過next()挑選一個NioEventLoop并將Channel綁定到它上面
// 如果NioEventLoopGroup為bossGroup的話,那么該方法注冊的肯定是NioServerSocketChannel對象
// 如果NioEventLoopGroup為workerGroup的話,那么該方法注冊的肯定是ServerSocketChannel對象
}
// SingleThreadEventLoop.java
@Override
public ChannelFuture register(Channel channel) {
// 當(dāng)前this對象是屬于children[]屬性中的其中一個
// 將傳入的Channel與當(dāng)前對象this一起封裝成DefaultChannelPromise對象
// 然后再調(diào)用當(dāng)前對象的register(ChannelPromise)注冊方法
return register(new DefaultChannelPromise(channel, this));
}
// SingleThreadEventLoop.java
@Override
public ChannelFuture register(final ChannelPromise promise) {
// 校驗當(dāng)前傳參是否為空,原則上既然是不可能為空的,因為上一個步驟是通過new出來的一個對象
ObjectUtil.checkNotNull(promise, "promise");
// promise.channel()其實就是上面new DefaultChannelPromise(channel, this)通過封裝后又取出這個channel對象
// promise.channel().unsafe()而每個Channel都有一個unsafe對象,對于NioServerSocketChannel來說NioMessageUnsafe=unsafe
// 當(dāng)前this對象是屬于children[]屬性中的其中一個
promise.channel().unsafe().register(this, promise);
return promise;
}
// AbstractUnsafe.java
@Override
public final void register(EventLoop eventLoop, final ChannelPromise promise) {
// eventLoop對象是屬于children[]屬性中的其中一個
// 而當(dāng)前類又是Channel的一個抽象類AbstractChannel,也是NioServerSocketChannel的父類
if (eventLoop == null) {
throw new NullPointerException("eventLoop");
}
if (isRegistered()) {
promise.setFailure(new IllegalStateException("registered to an event loop already"));
return;
}
if (!isCompatible(eventLoop)) {
promise.setFailure(
new IllegalStateException("incompatible event loop type: " + eventLoop.getClass().getName()));
return;
}
// 這里的 this.eventLoop 就是Children[i]中的一個,也就是具體執(zhí)行任務(wù)的線程封裝對象
AbstractChannel.this.eventLoop = eventLoop;
if (eventLoop.inEventLoop()) { // 如果對象eventLoop中的線程對象和當(dāng)前線程比對是一樣的話
register0(promise); // 那么則直接調(diào)用注冊方法register0
} else {
try {
// 比對的結(jié)果如果不一樣,十有八九都是該eventLoop的線程還未啟動,
// 因此利用eventLoop的execute將register0(promise)方法作為任務(wù)添加到任務(wù)隊列中,并啟動線程來執(zhí)行任務(wù)
eventLoop.execute(new Runnable() {
@Override
public void run() {
register0(promise);
}
});
// 而服務(wù)端Channel的注冊,走的是該else分支,因為線程都還沒創(chuàng)建,eventLoop.inEventLoop()肯定就是false結(jié)果
} catch (Throwable t) {
logger.warn(
"Force-closing a channel whose registration task was not accepted by an event loop: {}",
AbstractChannel.this, t);
closeForcibly();
closeFuture.setClosed();
safeSetFailure(promise, t);
}
}
}
// SingleThreadEventExecutor.java
/**
* 向任務(wù)隊列中添加任務(wù)task。
*
* @param task
*/
@Override
public void execute(Runnable task) {
if (task == null) { // 如果傳入的task任務(wù)為空,則直接拋空指針異常,此方法嚴格控制傳入?yún)?shù)必須非空
throw new NullPointerException("task");
}
boolean inEventLoop = inEventLoop(); // 判斷要添加的任務(wù)的這個線程,是不是和正在運行的nioEventLoop的處于同一個線程?
if (inEventLoop) { // 如果是,則說明就是當(dāng)前線程正在添加task任務(wù),那么則直接調(diào)用addTask方法添加到隊列中
addTask(task); // 添加task任務(wù)
} else {
startThread(); // 如果不是當(dāng)前線程,則看看實例化的對象nioEventLoop父類中state字段是否標(biāo)識有新建線程,沒有的話則利用線程池新創(chuàng)建一個線程,有的話則不用理會了
addTask(task); // 添加task任務(wù)
// 防止意外情況,還需要判斷下是否被關(guān)閉掉,如果被關(guān)閉掉的話,則將剛剛添加的任務(wù)刪除掉并采取拒絕策略直接拋出RejectedExecutionException異常
if (isShutdown() && removeTask(task)) {
reject(); // 拒絕策略直接拋出RejectedExecutionException異常
}
}
// addTaskWakesUp:添加任務(wù)時需要喚醒標(biāo)志,默認值為false,通過構(gòu)造方法傳進來的也是false
// wakesUpForTask(task):不是NonWakeupRunnable類型的task則返回true,意思就是只要不是NonWakeupRunnable類型的task,都需要喚醒阻塞操作
if (!addTaskWakesUp && wakesUpForTask(task)) {
wakeup(inEventLoop);
}
}
2、通過一路跟蹤config().group().register(channel)該方法進去,最后會發(fā)現(xiàn),源碼會調(diào)用一個register0(promise)的代碼來進行注冊;
3、但是跳出來一看,細細回味config().group().register(channel)這段代碼,可以得出這樣的一個結(jié)論:
若以后大家想注冊channel的話,直接通過線程管理組調(diào)用register方法,傳入想要注冊的channel對象即可;
4、當(dāng)然還有一點請大家留意,execute(Runnable task)可以隨意調(diào)用添加任務(wù),如果線程已啟動則直接添加,未啟動的話則先啟動線程再添加任務(wù);
5、那么我們還是先盡快進入register0(promise)看看究竟是如何注冊channel的;
4.12、register0(promise)
1、源碼:
// AbstractUnsafe.java
private void register0(ChannelPromise promise) {
try {
// check if the channel is still open as it could be closed in the mean time when the register
// call was outside of the eventLoop
if (!promise.setUncancellable() || !ensureOpen(promise)) {
return;
}
boolean firstRegistration = neverRegistered;
doRegister(); // 調(diào)用Channel的注冊方法,讓Channel的子類AbstractNioChannel來實現(xiàn)注冊
// 執(zhí)行到此,說明Channel已經(jīng)注冊到了多路復(fù)用器上,并且也沒有拋出什么異常,那么接下來就賦值變量表明已經(jīng)注冊成功
neverRegistered = false;
registered = true;
// Ensure we call handlerAdded(...) before we actually notify the promise. This is needed as the
// user may already fire events through the pipeline in the ChannelFutureListener.
pipeline.invokeHandlerAddedIfNeeded(); // 會回調(diào)initAndRegister中init方法的p.addLast的initChannel回調(diào)
safeSetSuccess(promise);
pipeline.fireChannelRegistered();
// Only fire a channelActive if the channel has never been registered. This prevents firing
// multiple channel actives if the channel is deregistered and re-registered.
if (isActive()) { // 檢測Channel是否處于活躍狀態(tài),這里調(diào)用的是底層的socket的活躍狀態(tài)
if (firstRegistration) {
pipeline.fireChannelActive(); // 這里也是注冊成功后會僅僅只會被調(diào)用一次
} else if (config().isAutoRead()) {
// This channel was registered before and autoRead() is set. This means we need to begin read
// again so that we process inbound data.
//
// See https://github.com/netty/netty/issues/4805
beginRead(); // 設(shè)置Channel的讀事件
}
}
} catch (Throwable t) {
// Close the channel directly to avoid FD leak.
closeForcibly();
closeFuture.setClosed();
safeSetFailure(promise, t);
}
}
// AbstractNioChannel.java
@Override
protected void doRegister() throws Exception {
boolean selected = false;
for (;;) { // 自旋式的死循環(huán),如果正常操作不出現(xiàn)異常的話,那么則會一直嘗試將Channel注冊到多路復(fù)用器selector上面
try {
// eventLoop()對象是屬于children[]屬性中的其中一個,children是NioEventLoop類型的對象
// 而前面也了解到過,在實例化每個children的時候,會為每個children創(chuàng)建一個多路復(fù)用器selector與unwrappedSelector
selectionKey = javaChannel().register(eventLoop().unwrappedSelector(), 0, this);
// 如果將Channel注冊到了多路復(fù)用器上的成功且沒有拋什么異常的話,則返回跳出循環(huán)
return;
} catch (CancelledKeyException e) {
if (!selected) {
// Force the Selector to select now as the "canceled" SelectionKey may still be
// cached and not removed because no Select.select(..) operation was called yet.
eventLoop().selectNow();
selected = true;
} else {
// We forced a select operation on the selector before but the SelectionKey is still cached
// for whatever reason. JDK bug ?
throw e;
}
}
}
}
// DefaultChannelPipeline.java
final void invokeHandlerAddedIfNeeded() {
assert channel.eventLoop().inEventLoop();
if (firstRegistration) { // pipeline標(biāo)識是否已注冊,默認值為true
firstRegistration = false; // 馬上置位false,告訴大家該方法只會被調(diào)用一次
// We are now registered to the EventLoop. It"s time to call the callbacks for the ChannelHandlers,
// that were added before the registration was done.
// 到此為止,我們已經(jīng)將Channel注冊到了NioEventLoop的多路復(fù)用器上,那么接下來是時候回調(diào)Handler被添加進來
callHandlerAddedForAllHandlers();
}
}
// DefaultChannelPipeline.java
private void callHandlerAddedForAllHandlers() {
final PendingHandlerCallback pendingHandlerCallbackHead;
synchronized (this) {
assert !registered; // 測試registered是否為false,因為該方法已經(jīng)表明只會被調(diào)用一次,所以這里就嚴格判斷
// This Channel itself was registered.
registered = true; // 而且當(dāng)registered設(shè)置為true后,就不會再改變該值的狀態(tài)
pendingHandlerCallbackHead = this.pendingHandlerCallbackHead;
// Null out so it can be GC"ed.
this.pendingHandlerCallbackHead = null;
}
// This must happen outside of the synchronized(...) block as otherwise handlerAdded(...) may be called while
// holding the lock and so produce a deadlock if handlerAdded(...) will try to add another handler from outside
// the EventLoop.
PendingHandlerCallback task = pendingHandlerCallbackHead;
// 通過while循環(huán),單向鏈表一個個回調(diào)task的execute,該回調(diào)添加的就回調(diào)添加,該回調(diào)移除的則回調(diào)移除
while (task != null) {
task.execute();
task = task.next;
}
}
2、看完register0(promise)是不是覺得,原來服務(wù)端channel的注冊是這么簡單,最后就是調(diào)用javaChannel().register(...)這個方法一下,然后就這么稀里糊涂的注冊到多路復(fù)用器上了;
3、在注冊完之際,還會找到之前的單向鏈表對象pendingHandlerCallbackHead,并且依依回調(diào)task.execute方法;
4、然后觸發(fā)fireChannelRegistered注冊成功事件,告知上層說我們的服務(wù)端channel已經(jīng)注冊成功了,大家請知悉一下;
5、最后通過beginRead設(shè)置服務(wù)端的讀事件標(biāo)志,就是說服務(wù)端的channel僅對讀事件感興趣;
6、至此initAndRegister這塊算是講完了,那么接下來就看看最后一個步驟綁定ip地址,完成通信前的最后一步;
4.13、doBind0(regFuture, channel, localAddress, promise)
1、源碼:
// AbstractBootstrap.java
private static void doBind0(
final ChannelFuture regFuture, final Channel channel,
final SocketAddress localAddress, final ChannelPromise promise) {
// This method is invoked before channelRegistered() is triggered. Give user handlers a chance to set up
// the pipeline in its channelRegistered() implementation.
// 服務(wù)端啟動最后一個步驟,綁完地址就可以正式進行通信了
channel.eventLoop().execute(new Runnable() {
@Override
public void run() {
if (regFuture.isSuccess()) {
// 服務(wù)端channel直接調(diào)用bind方法進行綁定地址
channel.bind(localAddress, promise).addListener(ChannelFutureListener.CLOSE_ON_FAILURE);
} else {
promise.setFailure(regFuture.cause());
}
}
});
}
// AbstractChannel.java
@Override
public ChannelFuture bind(SocketAddress localAddress, ChannelPromise promise) {
return pipeline.bind(localAddress, promise);
}
// DefaultChannelPipeline.java
@Override
public final ChannelFuture bind(SocketAddress localAddress, ChannelPromise promise) {
return tail.bind(localAddress, promise);
}
// AbstractChannelHandlerContext.java
@Override
public ChannelFuture bind(final SocketAddress localAddress, final ChannelPromise promise) {
if (localAddress == null) {
throw new NullPointerException("localAddress");
}
if (isNotValidPromise(promise, false)) {
// cancelled
return promise;
}
final AbstractChannelHandlerContext next = findContextOutbound();
EventExecutor executor = next.executor();
if (executor.inEventLoop()) {
next.invokeBind(localAddress, promise);
} else {
safeExecute(executor, new Runnable() {
@Override
public void run() {
next.invokeBind(localAddress, promise);
}
}, promise, null);
}
return promise;
}
// AbstractChannelHandlerContext.java
private void invokeBind(SocketAddress localAddress, ChannelPromise promise) {
if (invokeHandler()) {
try {
((ChannelOutboundHandler) handler()).bind(this, localAddress, promise);
} catch (Throwable t) {
notifyOutboundHandlerException(t, promise);
}
} else {
bind(localAddress, promise);
}
}
// HeadContext.java
@Override
public void bind(
ChannelHandlerContext ctx, SocketAddress localAddress, ChannelPromise promise)
throws Exception {
unsafe.bind(localAddress, promise);
}
// AbstractUnsafe.java
@Override
public final void bind(final SocketAddress localAddress, final ChannelPromise promise) {
assertEventLoop();
if (!promise.setUncancellable() || !ensureOpen(promise)) {
return;
}
// See: https://github.com/netty/netty/issues/576
if (Boolean.TRUE.equals(config().getOption(ChannelOption.SO_BROADCAST)) &&
localAddress instanceof InetSocketAddress &&
!((InetSocketAddress) localAddress).getAddress().isAnyLocalAddress() &&
!PlatformDependent.isWindows() && !PlatformDependent.maybeSuperUser()) {
// Warn a user about the fact that a non-root user can"t receive a
// broadcast packet on *nix if the socket is bound on non-wildcard address.
logger.warn(
"A non-root user can"t receive a broadcast packet if the socket " +
"is not bound to a wildcard address; binding to a non-wildcard " +
"address (" + localAddress + ") anyway as requested.");
}
boolean wasActive = isActive();
try {
doBind(localAddress);
} catch (Throwable t) {
safeSetFailure(promise, t);
closeIfClosed();
return;
}
if (!wasActive && isActive()) {
invokeLater(new Runnable() {
@Override
public void run() {
pipeline.fireChannelActive();
}
});
}
safeSetSuccess(promise);
}
// NioServerSocketChannel.java
@Override
protected void doBind(SocketAddress localAddress) throws Exception {
if (PlatformDependent.javaVersion() >= 7) {
javaChannel().bind(localAddress, config.getBacklog());
} else {
javaChannel().socket().bind(localAddress, config.getBacklog());
}
}
2、經(jīng)過這么一路調(diào)用,其實最終會發(fā)現(xiàn),綁定地址也是通過javaChannel().bind(...)這么簡短的一句話就搞定了;
而前面的注冊到多路復(fù)用器上調(diào)用的是javaChannel().register(...)一句簡短代碼;
從而可得出這么一個結(jié)論:只要關(guān)系到channel的注冊綁定,最終核心底層都是調(diào)用這個channel的bind和register方法;
3、至此,服務(wù)端的啟動流程算是完結(jié)了。。
五、總結(jié)
最后我們來總結(jié)下,通過分析Netty的服務(wù)端啟動,經(jīng)過的流程如下:
? 創(chuàng)建兩個線程管理組,以及實例化每個線程管理組的子線程數(shù)組children[];
? 設(shè)置啟動類參數(shù),比如channel、localAddress、childHandler等參數(shù);
? 反射實例化NioServerSocketChannel,創(chuàng)建ChannelId、unsafe、pipeline等對象;
? 初始化NioServerSocketChannel,設(shè)置attr、option,添加新的handler到服務(wù)端pipeline管道中;
? 調(diào)用JDK底層做ServerSocketChannel注冊到多路復(fù)用器上,并且注冊成功后回調(diào)pipeline管道中的單向鏈表依次執(zhí)行task任務(wù);
? 調(diào)用JDK底層做NioServerSocketChannel綁定端口,并觸發(fā)active事件;
六、下載地址
https://gitee.com/ylimhhmily/SpringCloudTutorial.git
SpringCloudTutorial交流QQ群: 235322432
SpringCloudTutorial交流微信群: 微信溝通群二維碼圖片鏈接
歡迎關(guān)注,您的肯定是對我最大的支持!!!
文章版權(quán)歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請注明本文地址:http://m.hztianpu.com/yun/71082.html
摘要:端引導(dǎo)類線程管理組線程管理組將設(shè)置到服務(wù)端引導(dǎo)類中指定通道類型為,一種異步模式,阻塞模式為設(shè)置讓服務(wù)器監(jiān)聽某個端口已等待客戶端連接。 原理剖析(第 010 篇)Netty之服務(wù)端啟動工作原理分析(上) - 一、大致介紹 1、Netty這個詞,對于熟悉并發(fā)的童鞋一點都不陌生,它是一個異步事件驅(qū)動型的網(wǎng)絡(luò)通信框架; 2、使用Netty不需要我們關(guān)注過多NIO的API操作,簡簡單單的使用即可...
摘要:簡單來說就是把注冊的動作異步化,當(dāng)異步執(zhí)行結(jié)束后會把執(zhí)行結(jié)果回填到中抽象類一般就是公共邏輯的處理,而這里的處理主要就是針對一些參數(shù)的判斷,判斷完了之后再調(diào)用方法。 閱讀這篇文章之前,建議先閱讀和這篇文章關(guān)聯(lián)的內(nèi)容。 1. 詳細剖析分布式微服務(wù)架構(gòu)下網(wǎng)絡(luò)通信的底層實現(xiàn)原理(圖解) 2. (年薪60W的技巧)工作了5年,你真的理解Netty以及為什么要用嗎?(深度干貨)...
摘要:在結(jié)構(gòu)上引入了頭結(jié)點和尾節(jié)點,他們分別指向隊列的頭和尾,嘗試獲取鎖入隊服務(wù)教程在它提出十多年后的今天,已經(jīng)成為最重要的應(yīng)用技術(shù)之一。隨著編程經(jīng)驗的日積月累,越來越感覺到了解虛擬機相關(guān)要領(lǐng)的重要性。 JVM 源碼分析之 Jstat 工具原理完全解讀 http://click.aliyun.com/m/8315/ JVM 源碼分析之 Jstat 工具原理完全解讀 http:...
摘要:本文會以引出問題為主,后面有時間的話,筆者陸續(xù)會抽些重要的知識點進行詳細的剖析與解答。敬請關(guān)注服務(wù)端思維微信公眾號,獲取最新文章。 原文地址:梁桂釗的博客博客地址:http://blog.720ui.com 這里,筆者結(jié)合自己過往的面試經(jīng)驗,整理了一些核心的知識清單,幫助讀者更好地回顧與復(fù)習(xí) Java 服務(wù)端核心技術(shù)。本文會以引出問題為主,后面有時間的話,筆者陸續(xù)會抽些重要的知識點進...
摘要:大家好,我是冰河有句話叫做投資啥都不如投資自己的回報率高。馬上就十一國慶假期了,給小伙伴們分享下,從小白程序員到大廠高級技術(shù)專家我看過哪些技術(shù)類書籍。 大家好,我是...
閱讀 3300·2021-10-13 09:40
閱讀 3866·2019-08-30 15:54
閱讀 1390·2019-08-30 13:20
閱讀 3088·2019-08-30 11:26
閱讀 540·2019-08-29 11:33
閱讀 1153·2019-08-26 14:00
閱讀 2440·2019-08-26 13:58
閱讀 3438·2019-08-26 10:39