前言html
今天研究ServerBootstrap的bind方法,該方法能夠說是netty的重中之重、核心中的核心。前兩節的NioEventLoopGroup和ServerBootstrap的初始化就是爲bind作準備。照例粘貼一下這個三朝元老的demo,開始本文內容。java
1 public class NettyDemo1 { 2 // netty服務端的通常性寫法 3 public static void main(String[] args) { 4 EventLoopGroup boss = new NioEventLoopGroup(1); 5 EventLoopGroup worker = new NioEventLoopGroup(); 6 try { 7 ServerBootstrap bootstrap = new ServerBootstrap(); 8 bootstrap.group(boss, worker).channel(NioServerSocketChannel.class) 9 .option(ChannelOption.SO_BACKLOG, 100) 10 .handler(new NettyServerHandler()) 11 .childHandler(new ChannelInitializer<SocketChannel>() { 12 @Override 13 protected void initChannel(SocketChannel socketChannel) throws Exception { 14 ChannelPipeline pipeline = socketChannel.pipeline(); 15 pipeline.addLast(new StringDecoder()); 16 pipeline.addLast(new StringEncoder()); 17 pipeline.addLast(new NettyServerHandler()); 18 } 19 }); 20 ChannelFuture channelFuture = bootstrap.bind(90); 21 channelFuture.channel().closeFuture().sync(); 22 } catch (Exception e) { 23 e.printStackTrace(); 24 } finally { 25 boss.shutdownGracefully(); 26 worker.shutdownGracefully(); 27 } 28 } 29 }
1、bind及doBind方法編程
1.ServerBootstrap.bind方法bootstrap
該方法有多個重載方法,但核心做用只有一個,就是將參數轉爲InetSocketAddress對象傳給 --->promise
1 public ChannelFuture bind(int inetPort) { 2 return bind(new InetSocketAddress(inetPort)); 3 }
1 public ChannelFuture bind(String inetHost, int inetPort) { 2 return bind(SocketUtils.socketAddress(inetHost, inetPort)); 3 }
1 public ChannelFuture bind(InetAddress inetHost, int inetPort) { 2 return bind(new InetSocketAddress(inetHost, inetPort)); 3 }
下面這個bind方法,在該方法中調用了doBind方法。安全
1 public ChannelFuture bind(SocketAddress localAddress) { 2 validate(); 3 return doBind(ObjectUtil.checkNotNull(localAddress, "localAddress")); 4 }
二、ServerBootstrap的doBind方法多線程
doBind方法位於父類AbstractBootstrap中,它有兩大功能,均在下面代碼中標識了出來,它們分別對應經過原生nio進行server端初始化時的兩個功能,第1步對應將channel註冊到selector上;第2步對應將server地址綁定到channel上。app
1 private ChannelFuture doBind(final SocketAddress localAddress) { 2 final ChannelFuture regFuture = initAndRegister(); // 1)、初始化和註冊,重要*** 3 final Channel channel = regFuture.channel(); 4 if (regFuture.cause() != null) { 5 return regFuture; 6 } 7 8 if (regFuture.isDone()) { 9 // At this point we know that the registration was complete and successful. 10 ChannelPromise promise = channel.newPromise(); 11 doBind0(regFuture, channel, localAddress, promise); // 2)、將SocketAddress和channel綁定起來,最終執行的是nio中的功能,重要** 12 return promise; 13 } else { 14 // 省略異常判斷、添加監聽器和異步調用doBind0方法 15 } 16 }
爲方便關聯對照,下面再粘貼一個簡單的原生NIO編程的服務端初始化方法,其實doBind方法的邏輯基本就是對下面這個方法的封裝,只是增長了不少附加功能。異步
由於上述兩步都有些複雜,因此此處分兩部分進行追蹤。socket
2、AbstractBootstrap的initAndRegister方法
該方法代碼以下所示,一共有三個核心方法,邏輯比較清晰,將channel new出來,初始化它,而後註冊到selector上。下面咱們各個擊破。
1 final ChannelFuture initAndRegister() { 2 Channel channel = null; 3 try { // 1)、實例化channel,做爲服務端初始化的是NioServerSocketChannel 4 channel = channelFactory.newChannel(); 5 init(channel); // 2)、初始化channel,即給channel中的屬性賦值 6 } catch (Throwable t) { 7 if (channel != null) { 8 channel.unsafe().closeForcibly(); 9 return new DefaultChannelPromise(channel, GlobalEventExecutor.INSTANCE).setFailure(t); 10 } 11 return new DefaultChannelPromise(new FailedChannel(), GlobalEventExecutor.INSTANCE).setFailure(t); 12 } 13 // 3)、註冊,即最終是將channel 註冊到selector上 14 ChannelFuture regFuture = config().group().register(channel); 15 if (regFuture.cause() != null) { 16 if (channel.isRegistered()) { 17 channel.close(); 18 } else { 19 channel.unsafe().closeForcibly(); 20 } 21 } 22 return regFuture; 23 }
一、channelFactory.newChannel()方法
1 @Override 2 public T newChannel() { 3 try { 4 return constructor.newInstance(); 5 } catch (Throwable t) { 6 throw new ChannelException("Unable to create Channel from class " + constructor.getDeclaringClass(), t); 7 } 8 }
該方法完成了channel的實例化,channelFactory的賦值可參見上一篇博文【Netty源碼學習系列之3-ServerBootstrap的初始化】(地址 http://www.javashuo.com/article/p-eebupwce-bd.html),對服務端來講,這裏channelFactory值爲ReflectiveChannelFactory,且其內部的constructor是NioServerSocketChannel的無參構造器,下面追蹤NioServerSocketChannel的無參構造方法。
1.1)、new NioServerSocketChannel()
1 public NioServerSocketChannel() { 2 this(newSocket(DEFAULT_SELECTOR_PROVIDER)); 3 }
1 private static final SelectorProvider DEFAULT_SELECTOR_PROVIDER = SelectorProvider.provider(); 2 3 private static ServerSocketChannel newSocket(SelectorProvider provider) { 4 try { 5 return provider.openServerSocketChannel(); 6 } catch (IOException e) { 7 throw new ChannelException( 8 "Failed to open a server socket.", e); 9 } 10 }
可見,它先經過newSocket方法獲取nio原生的ServerSocketChannel,而後傳給了重載構造器,以下,其中第三行是對NioServerSocketChannelConfig config進行了賦值,邏輯比較簡單,下面主要看對父類構造方法的調用。
1 public NioServerSocketChannel(ServerSocketChannel channel) { 2 super(null, channel, SelectionKey.OP_ACCEPT); 3 config = new NioServerSocketChannelConfig(this, javaChannel().socket()); 4 }
1.2)、對NioServerSocketChannel父類構造方法的調用
1 protected AbstractNioChannel(Channel parent, SelectableChannel ch, int readInterestOp) { 2 super(parent); 3 this.ch = ch; 4 this.readInterestOp = readInterestOp; 5 try { 6 ch.configureBlocking(false); 7 } catch (IOException e) { 8 try { 9 ch.close(); 10 } catch (IOException e2) { 11 if (logger.isWarnEnabled()) { 12 logger.warn( 13 "Failed to close a partially initialized socket.", e2); 14 } 15 } 16 17 throw new ChannelException("Failed to enter non-blocking mode.", e); 18 } 19 }
中間通過了AbstractNioMessageChannel,而後調到下面AbstractNioChannel的構造方法。此時parent爲null,ch爲上面獲取到的nio原生ServerSocketChannel,readInterestOp爲SelectionKey的Accept事件(值爲16)。能夠看到,將原生渠道ch賦值、感興趣的事件readInterestOp賦值、設置非阻塞。而後重點看對父類構造器的調用。
1.3)、AbstractChannel構造器
1 protected AbstractChannel(Channel parent) { 2 this.parent = parent; 3 id = newId(); 4 unsafe = newUnsafe(); 5 pipeline = newChannelPipeline(); 6 }
能夠看到,此構造方法只是給四個屬性進行了賦值,咱們挨個看下這四個屬性。
第一個屬性是this.parent,類型爲io.netty.channel.Channel,但此時值爲null;
第二個屬性id類型爲io.netty.channel.ChannelId,就是一個id生成器,值爲new DefaultChannelId();
第三個屬性unsafe類型爲io.netty.channel.Channel.Unsafe,該屬性很重要,封裝了對事件的處理邏輯,最終調用的是AbstractNioMessageChannel中的newUnsafe方法,賦的值爲new NioMessageUnsafe();
第四個屬性pipeline類型爲io.netty.channel.DefaultChannelPipeline,該屬性很重要,封裝了handler處理器的邏輯,賦的值爲 new DefaultChannelPipeline(this) this即當前的NioServerSocketChannel對象。
其中DefaultChannelPipeline的構造器須要額外看一下,以下,將NioServerSocketChannel對象存入channel屬性,而後初始化了tail、head兩個成員變量,且對應的先後指針指向對方。TailContext和HeadContext都繼承了AbstractChannelHandlerContext,在這個父類裏面維護了next和prev兩個雙向指針,看到這裏有經驗的園友應該一會兒就能看出來,DefaultChannelPipeline內部維護了一個雙向鏈表。
1 protected DefaultChannelPipeline(Channel channel) { 2 this.channel = ObjectUtil.checkNotNull(channel, "channel"); 3 succeededFuture = new SucceededChannelFuture(channel, null); 4 voidPromise = new VoidChannelPromise(channel, true); 5 6 tail = new TailContext(this); 7 head = new HeadContext(this); 8 9 head.next = tail; 10 tail.prev = head; 11 }
至此,完成了上面initAndRegister方法中的第一個功能:channel的實例化。此時NioServerSocketChannel的幾個父類屬性快照圖以下所示:
二、init(channel)方法
init(channel)方法位於ServerBootstrap中(由於這裏是經過ServerBootstrap過來的,若是是經過Bootstrap進入的這裏則調用的就是Bootstrap中的init方法),主要功能以下注釋所示。本質都是針對channel進行初始化,初始化channel中的option、attr和pipeline。
1 void init(Channel channel) throws Exception { 2 // 一、獲取AbstractBootstrap中的options屬性,與channel進行關聯 3 final Map<ChannelOption<?>, Object> options = options0(); 4 synchronized (options) { 5 setChannelOptions(channel, options, logger); 6 } 7 // 二、獲取AbstractBootstrap中的attr屬性,與channel關聯起來 8 final Map<AttributeKey<?>, Object> attrs = attrs0(); 9 synchronized (attrs) { 10 for (Entry<AttributeKey<?>, Object> e: attrs.entrySet()) { 11 @SuppressWarnings("unchecked") 12 AttributeKey<Object> key = (AttributeKey<Object>) e.getKey(); 13 channel.attr(key).set(e.getValue()); 14 } 15 } 16 // 三、獲取pipeline,並將一個匿名handler對象添加進去,重要*** 17 ChannelPipeline p = channel.pipeline(); 18 final EventLoopGroup currentChildGroup = childGroup; 19 final ChannelHandler currentChildHandler = childHandler; 20 final Entry<ChannelOption<?>, Object>[] currentChildOptions; 21 final Entry<AttributeKey<?>, Object>[] currentChildAttrs; 22 synchronized (childOptions) { 23 currentChildOptions = childOptions.entrySet().toArray(newOptionArray(0)); 24 } 25 synchronized (childAttrs) { 26 currentChildAttrs = childAttrs.entrySet().toArray(newAttrArray(0)); 27 } 28 p.addLast(new ChannelInitializer<Channel>() { 29 @Override 30 public void initChannel(final Channel ch) throws Exception { 31 final ChannelPipeline pipeline = ch.pipeline(); 32 ChannelHandler handler = config.handler(); 33 if (handler != null) { 34 pipeline.addLast(handler); 35 } 36 37 ch.eventLoop().execute(new Runnable() { 38 @Override 39 public void run() { 40 pipeline.addLast(new ServerBootstrapAcceptor( 41 ch, currentChildGroup, currentChildHandler, currentChildOptions, currentChildAttrs)); 42 } 43 }); 44 } 45 }); 46 }
1跟2的功能都比較容易理解,功能3是init的核心,雖然代碼很多但很容易理解,它就是往channel中的pipeline裏添加了一個匿名handler對象,其initChannel方法只有在有客戶端鏈接接入時纔會調用,initChannel方法的功能是什麼呢?能夠看到,它就是往入參channel中的eventLoop裏添加了一個任務,這個任務的功能就是往pipeline中再添加一個handler,最後添加的這個handler就不是匿名的了,它是ServerBootstrapAcceptor對象。由於這裏的initChannel方法和後面的run方法都是有客戶端接入時纔會調用的,因此這裏只是提一下,後面會詳述。至此完成init方法,下面進入register。
三、config().group().register(channel)方法
3.1)、config().group()方法
由前面能夠知道,config().group().register(channel)這行代碼位於AbstractBootstrap類中的initAndRegister方法中,但因爲當前對象是ServerBootstrap,故此處config()方法實際調用的都是ServerBootstrap中重寫的方法,獲得了ServerBootstrapConfig。
ServerBootstrapConfig的group方法以下,調用的是它的父類AbstractBootstrapConfig中的方法。經過類名就能知道,ServerBootstrapConfig中的方法是獲取ServerBootstrap中的屬性,而AbstractBootstrapConfig中的方法是獲取AbstractBootstrap中的屬性,兩兩對應。故此處獲取的EventLoopGroup就是AbstractBootstrap中存放的group,即文章開頭demo中的boss對象。
1 public final EventLoopGroup group() { 2 return bootstrap.group(); 3 }
獲取到了名叫boss的這個NioEventLoopGroup對象,下面追蹤NioEventLoopGroup.register(channel)方法
3.2)、 NioEventLoopGroup.register(channel)方法
該方法是對以前初始化屬性的應用,需結合NioEventLoopGroup的初始化流程看,詳見【Netty源碼學習系列之2-NioEventLoopGroup的初始化】(連接【http://www.javashuo.com/article/p-hoszwlau-dp.html】)一文,此處就不贅述了,下面把該類的繼承類圖粘貼出來,以便有個總體認識。
3.2.1)、next()方法
下面的register方法位於MultithreadEventLoopGroup類中,是NioEventLoopGroup的直接父類,以下:
1 public ChannelFuture register(Channel channel) { 2 return next().register(channel); 3 }
next方法以下,調用了父類的next方法,下面的就是父類MultithreadEventExecutorGroup中的next實現,能夠看到調用的是chooser的next方法。經過初始化流程可知,此處boss的線程數是1,是2的n次方,因此chooser就是PowerOfTwoEventExecutorChooser,經過next方法從EventExecutor[]中選擇一個對象。須要注意的是chooser.next()經過輪詢的方式選擇的對象。
1 public EventLoop next() { 2 return (EventLoop) super.next(); 3 }
1 public EventExecutor next() { 2 return chooser.next(); 3 }
3.2.2)、NioEventLoop.register方法
next以後是register方法,中間將NioServerSocketChannel和當前的NioEventLoop封裝成一個DefaultChannelPromise對象往下傳遞,在下面第二個register方法中能夠看到,實際上調用的是NioServerSocketChannel中的unsafe屬性的register方法。
1 public ChannelFuture register(Channel channel) { 2 return register(new DefaultChannelPromise(channel, this)); 3 }
1 public ChannelFuture register(final ChannelPromise promise) { 2 ObjectUtil.checkNotNull(promise, "promise"); 3 promise.channel().unsafe().register(this, promise); 4 return promise; 5 }
3.2.3)、NioMessageUnsafe的register方法
經過本文第一部分中第1步中的1.3)能夠知道,NioServerSocketChannel中的unsafe是NioMessageUnsafe對象,下面繼續追蹤其register方法:
1 public final void register(EventLoop eventLoop, final ChannelPromise promise) { 2 if (eventLoop == null) {// 判斷非空 3 throw new NullPointerException("eventLoop"); 4 } 5 if (isRegistered()) {// 判斷是否註冊 6 promise.setFailure(new IllegalStateException("registered to an event loop already")); 7 return; 8 } 9 if (!isCompatible(eventLoop)) {// 判斷eventLoop類型是否匹配 10 promise.setFailure( 11 new IllegalStateException("incompatible event loop type: " + eventLoop.getClass().getName())); 12 return; 13 } 14 // 完成eventLoop屬性的賦值 15 AbstractChannel.this.eventLoop = eventLoop; 16 // 判斷eventLoop中的Reactor線程是否是當前線程 ***重要1 17 if (eventLoop.inEventLoop()) { 18 register0(promise); // 進行註冊 19 } else { 20 try {// 不是當前線程則將register0任務放入eventLoop隊列中讓Reactor線程執行(若是Reactor線程未初始化還要將其初始化) ***重要2 21 eventLoop.execute(new Runnable() { 22 @Override 23 public void run() { 24 register0(promise);// 註冊邏輯 ***重要3 25 } 26 }); 27 } catch (Throwable t) { 28 // 省略異常處理 29 } 30 } 31 }
該方法位於io.netty.channel.AbstractChannel.AbstractUnsafe中(它是NioMessageUnsafe的父類),根據註釋能瞭解每一步作了什麼,但若是要理解代碼邏輯意圖則須要結合netty的串行無鎖化(串行無鎖化參見博主的netty系列第一篇文章http://www.javashuo.com/article/p-dlefkipf-bx.html)。它實際就是讓每個NioEventLoop對象的thread屬性記錄一條線程,用來循環執行NioEventLoop的run方法,後續這個channel上的全部事件都由這一條線程來執行,若是當前線程不是Reactor線程,則會將任務放入隊列中,Reactor線程會不斷從隊列中獲取任務執行。這樣以來,全部事件都由一條線程順序處理,線程安全,也就不須要加鎖了。
說完總體思路,再來結合代碼看看。上述代碼中標識【***重要1】的地方就是經過inEventLoop方法判斷eventLoop中的thread屬性記錄的線程是否是當前線程:
先調到父類AbstractEventExecutor中,獲取了當前線程:
1 public boolean inEventLoop() { 2 return inEventLoop(Thread.currentThread()); 3 }
而後調到SingleThreadEventExecutor類中的方法,以下,比對thread與當前線程是不是同一個:
1 public boolean inEventLoop(Thread thread) { 2 return thread == this.thread; 3 }
此時thread未初始化,因此確定返回false,則進入【***重點2】的邏輯,將register放入run方法中封裝成一個Runnable任務,而後執行execute方法,以下,該方法位於SingleThreadEventExecutor中:
1 public void execute(Runnable task) { 2 if (task == null) { 3 throw new NullPointerException("task"); 4 } 5 6 boolean inEventLoop = inEventLoop(); 7 addTask(task); //將任務放入隊列中 ***重要a 8 if (!inEventLoop) { 9 startThread(); //判斷當前線程不是thread線程,則調用該方法 ***重要b 10 if (isShutdown()) { 11 boolean reject = false; 12 try { 13 if (removeTask(task)) { 14 reject = true; 15 } 16 } catch (UnsupportedOperationException e) { 17 // 省略註釋 18 } 19 if (reject) { 20 reject(); 21 } 22 } 23 } 24 25 if (!addTaskWakesUp && wakesUpForTask(task)) { 26 wakeup(inEventLoop); 27 } 28 }
有兩個重要的邏輯,已經在上面代碼中標出,先看看【***重要a】,以下,可見最終就是往SingleThreadEventExecutor的taskQueue隊列中添加了一個任務,若是添加失敗則調reject方法執行拒絕策略,經過前文分析能夠知道,此處的拒絕策略就是直接拋錯。
1 protected void addTask(Runnable task) { 2 if (task == null) { 3 throw new NullPointerException("task"); 4 } 5 if (!offerTask(task)) { 6 reject(task); 7 } 8 }
1 final boolean offerTask(Runnable task) { 2 if (isShutdown()) { 3 reject(); 4 } 5 return taskQueue.offer(task); 6 }
而後在看【***重要b】,以下,該方法雖然叫startThread,但內部有控制,不能無腦開啓線程,由於調這個方法的時候會有兩種狀況:1).thread變量爲空;2).thread不爲空且不是當前線程。第一種狀況須要開啓新的線程,但第二種狀況就不能直接建立線程了。因此看下面代碼能夠發現,它內部經過CAS+volatile(state屬性加了volatile修飾)實現的開啓線程的原子控制,保證多線程狀況下也只會有一個線程進入doStartThread()方法。
1 private void startThread() { 2 if (state == ST_NOT_STARTED) { 3 if (STATE_UPDATER.compareAndSet(this, ST_NOT_STARTED, ST_STARTED)) { 4 boolean success = false; 5 try { 6 doStartThread(); 7 success = true; 8 } finally { 9 if (!success) { 10 STATE_UPDATER.compareAndSet(this, ST_STARTED, ST_NOT_STARTED); 11 } 12 } 13 } 14 } 15 }
繼續往下看一下doStartThread()的方法邏輯:
1 private void doStartThread() { 2 assert thread == null; 3 executor.execute(new Runnable() { //此處的executor內部執行的就是ThreadPerTaskExecutor的execute邏輯,建立一個新線程運行下面的run方法 4 @Override 5 public void run() { 6 thread = Thread.currentThread(); //將Reactor線程記錄到thread變量中,保證一個NioEventLoop只有一個主線程在運行 7 if (interrupted) { 8 thread.interrupt(); 9 } 10 11 boolean success = false; 12 updateLastExecutionTime(); 13 try { 14 SingleThreadEventExecutor.this.run(); //調用當前對象的run方法,該run方法就是Reactor線程的核心邏輯方法,後面會重點研究 15 success = true; 16 } catch (Throwable t) { 17 logger.warn("Unexpected exception from an event executor: ", t); 18 } finally { 19 // 省略無關邏輯 20 } 21 } 22 }); 23 }
能夠看到,在上面的方法中完成了Reactor線程thread的賦值和核心邏輯NioEventLoop中run方法的啓動。這個run方法啓動後,第一步作的事情是什麼?讓咱們往前回溯,回到3.2.3),固然是執行當初封裝了 register0方法的那個run方法的任務,即執行register0方法,下面填以前埋得坑,對【***重要3】進行追蹤:
1 private void register0(ChannelPromise promise) { 2 try { 3 // 省略判斷邏輯 4 boolean firstRegistration = neverRegistered; 5 doRegister();// 執行註冊邏輯 6 neverRegistered = false; 7 registered = true; 8 pipeline.invokeHandlerAddedIfNeeded();// 調用pipeline的邏輯 9 10 safeSetSuccess(promise); 11 pipeline.fireChannelRegistered(); 12 // 省略無關邏輯 13 } catch (Throwable t) { 14 // 省略異常處理 15 } 16 }
doRegister()方法的實如今AbstractNioChannel中,以下,就是完成了nio中的註冊,將nio的ServerSocketChannel註冊到selector上:
1 protected void doRegister() throws Exception { 2 boolean selected = false; 3 for (;;) { 4 try { 5 selectionKey = javaChannel().register(eventLoop().unwrappedSelector(), 0, this); 6 return; 7 } catch (CancelledKeyException e) { 8 // 省略異常處理 9 } 10 } 11 }
再看pipeline.invokeHandlerAddedIfNeeded()方法,該方法調用鏈路比較長,此處就不詳細粘貼了,只是說一下流程。回顧下上面第二部分的第2步,在裏面最後addLast了一個匿名的內部對象,重寫了initChannel方法,此處經過pipeline.invokeHandlerAddedIfNeeded()方法就會調用到這個匿名對象的initChannel方法(只有第一次註冊時纔會調),該方法往pipeline中又添加了一個ServerBootstrapAcceptor對象。執行完方法後,netty會在finally中將以前那個匿名內部對象給remove掉,這時pipeline中的handler以下所示:
至此,算是基本完成了initAndRegister方法的邏輯,固然限於篇幅(本篇已經夠長了),其中還有不少細節性的處理未說起。
3、AbstractBootstrap的doBind0方法
doBind0方法邏輯以下所示,new了一個Runnable任務交給Reactor線程執行,execute執行過程已經分析過了,此處再也不贅述,集中下所剩無幾的精力看下run方法中的bind邏輯。
1 private static void doBind0( 2 final ChannelFuture regFuture, final Channel channel, 3 final SocketAddress localAddress, final ChannelPromise promise) { 4 5 channel.eventLoop().execute(new Runnable() { 6 @Override 7 public void run() { 8 if (regFuture.isSuccess()) { 9 channel.bind(localAddress, promise).addListener(ChannelFutureListener.CLOSE_ON_FAILURE); 10 } else { 11 promise.setFailure(regFuture.cause()); 12 } 13 } 14 }); 15 }
channel.bind方法,以下:
1 public ChannelFuture bind(SocketAddress localAddress, ChannelPromise promise) { 2 return pipeline.bind(localAddress, promise); 3 }
調用了pipeline的bind方法:
1 public final ChannelFuture bind(SocketAddress localAddress, ChannelPromise promise) { 2 return tail.bind(localAddress, promise); 3 }
tail.bind方法:
1 public ChannelFuture bind(final SocketAddress localAddress, final ChannelPromise promise) { 2 if (localAddress == null) { 3 throw new NullPointerException("localAddress"); 4 } 5 if (isNotValidPromise(promise, false)) { 6 // cancelled 7 return promise; 8 } 9 // 從tail開始往前,找到第一個outbond的handler,這時只有head知足要求,故此處next是head 10 final AbstractChannelHandlerContext next = findContextOutbound(MASK_BIND); 11 EventExecutor executor = next.executor(); 12 if (executor.inEventLoop()) {// 由於當前線程就是executor中的Reactor線程,因此直接進入invokeBind方法 13 next.invokeBind(localAddress, promise); 14 } else { 15 safeExecute(executor, new Runnable() { 16 @Override 17 public void run() { 18 next.invokeBind(localAddress, promise); 19 } 20 }, promise, null); 21 } 22 return promise; 23 }
下面進入head.invokeBind方法:
1 private void invokeBind(SocketAddress localAddress, ChannelPromise promise) { 2 if (invokeHandler()) { 3 try { 4 ((ChannelOutboundHandler) handler()).bind(this, localAddress, promise); 5 } catch (Throwable t) { 6 notifyOutboundHandlerException(t, promise); 7 } 8 } else { 9 bind(localAddress, promise); 10 } 11 }
核心邏輯就是handler.bind方法,繼續追蹤:
1 public void bind( 2 ChannelHandlerContext ctx, SocketAddress localAddress, ChannelPromise promise) { 3 unsafe.bind(localAddress, promise); 4 }
此處的unsafe是NioMessageUnsafe,繼續追蹤會看到在bind方法中又調用了NioServerSocketChannel中的doBind方法,最終在這裏完成了nio原生ServerSocketChannel和address的綁定:
1 protected void doBind(SocketAddress localAddress) throws Exception { 2 if (PlatformDependent.javaVersion() >= 7) { 3 javaChannel().bind(localAddress, config.getBacklog()); 4 } else { 5 javaChannel().socket().bind(localAddress, config.getBacklog()); 6 } 7 }
至此,ServerBootstrap的bind方法完成。
小結
本文從頭至尾追溯了ServerBootstrap中bind方法的邏輯,將前面netty系列中的2、三兩篇初始化給串聯了起來,是承上啓下的一個位置。後面的netty系列將圍繞本文中啓動的NioEventLoop.run方法展開,能夠這麼說,本文跟前面的三篇只是爲run方法的出現作的一個鋪墊,run方法纔是核心功能的邏輯所在。
本文斷斷續續更新了一週,今天才完成,也沒想到會這麼長,就這樣吧,後面繼續netty run方法的學習。