Netty源碼學習系列之4-ServerBootstrap的bind方法

前言

    今天研究ServerBootstrap的bind方法,該方法可以說是netty的重中之重、核心中的核心。前兩節的NioEventLoopGroup和ServerBootstrap的初始化就是為bind做準備。照例粘貼一下這個三朝元老的demo,開始本文內容。

 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 }

 

一、bind及doBind方法

1.ServerBootstrap.bind方法

    該方法有多個重載方法,但核心作用只有一個,就是將參數轉為InetSocketAddress對象傳給 —>

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     }

2、ServerBootstrap的doBind方法

    doBind方法位於父類AbstractBootstrap中,它有兩大功能,均在下面代碼中標識了出來,它們分別對應通過原生nio進行server端初始化時的兩個功能,第1步對應將channel註冊到selector上;第2步對應將server地址綁定到channel上。

 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方法的邏輯基本就是對下面這個方法的封裝,只是增加了很多附加功能。

    因為上述兩步都有些複雜,所以此處分兩部分進行追蹤。

二、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     }

1、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的初始化】(地址 https://www.cnblogs.com/zzq6032010/p/13027161.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的幾個父類屬性快照圖如下所示:

 

2、init(channel)方法

    init(channel)方法位於ServerBootstrap中(因為這裡是通過ServerBootstrap過來的,如果是通過Bootstrap進入的這裏則調用的就是Bootstrap中的init方法),主要功能如下註釋所示。本質都是針對channel進行初始化,初始化channel中的option、attr和pipeline。

 1 void init(Channel channel) throws Exception {
 2         // 1、獲取AbstractBootstrap中的options屬性,與channel進行關聯
 3         final Map<ChannelOption<?>, Object> options = options0();
 4         synchronized (options) {
 5             setChannelOptions(channel, options, logger);
 6         }
 7         // 2、獲取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         // 3、獲取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。

3、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的初始化】(鏈接【https://www.cnblogs.com/zzq6032010/p/12872989.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系列第一篇文章https://www.cnblogs.com/zzq6032010/p/12872993.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方法的邏輯,當然限於篇幅(本篇已經夠長了),其中還有很多細節性的處理未提及。

 

三、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系列中的二、三兩篇初始化給串聯了起來,是承上啟下的一個位置。後面的netty系列將圍繞本文中啟動的NioEventLoop.run方法展開,可以這麼說,本文跟前面的三篇只是為run方法的出現做的一個鋪墊,run方法才是核心功能的邏輯所在。

    本文斷斷續續更新了一周,今天才完成,也沒想到會這麼長,就這樣吧,後面繼續netty run方法的學習。

本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

※回頭車貨運收費標準

※產品缺大量曝光嗎?你需要的是一流包裝設計!

※自行創業缺乏曝光? 網頁設計幫您第一時間規劃公司的形象門面

※推薦評價好的iphone維修中心

※教你寫出一流的銷售文案?

台中搬家公司教你幾個打包小技巧,輕鬆整理裝箱!

台中搬家遵守搬運三大原則,讓您的家具不再被破壞!

您可能也會喜歡…