前言

今天研究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方法的学习。

Netty源码学习系列之4-ServerBootstrap的bind方法的更多相关文章

  1. Netty源码学习系列之2-ServerBootstrap的初始化

    前言 根据前文我们知道,NioEventLoopGroup和NioEventLoop是netty对Reactor线程模型的实现,而本文要说的ServerBootstrap是对上面二者的整合与调用,是一 ...

  2. Netty源码学习系列之1-netty的串行无锁化

    前言 最近趁着跟老东家提离职之后.到新公司报道之前的这段空闲时期,着力研究了一番netty框架,对其有了一些浅薄的认识,后续的几篇文章会以netty为主,将近期所学记录一二,也争取能帮未对netty有 ...

  3. Netty源码学习系列之5-NioEventLoop的run方法

    前言     NioEventLoop的run方法,是netty中最核心的方法,没有之一.在该方法中,完成了对已注册的channel上来自底层操作系统的socket事件的处理(在服务端时事件包括客户端 ...

  4. Netty源码学习系列之1-NioEventLoopGroup的初始化

    前言 NioEventLoopGroup是netty对Reactor线程组这个抽象概念的具体实现,其内部维护了一个EventExecutor数组,而NioEventLoop就是EventExecuto ...

  5. 【Netty源码学习】ServerBootStrap

    上一篇博客[Netty源码学习]BootStrap中我们介绍了客户端使用的启动服务,接下来我们介绍一下服务端使用的启动服务. 总体来说ServerBootStrap有两个主要功能: (1)调用父类Ab ...

  6. Netty 源码学习——EventLoop

    Netty 源码学习--EventLoop 在前面 Netty 源码学习--客户端流程分析中我们已经知道了一个 EventLoop 大概的流程,这一章我们来详细的看一看. NioEventLoopGr ...

  7. Netty 源码分析系列(二)Netty 架构设计

    前言 上一篇文章,我们对 Netty做了一个基本的概述,知道什么是Netty以及Netty的简单应用. Netty 源码分析系列(一)Netty 概述 本篇文章我们就来说说Netty的架构设计,解密高 ...

  8. Netty源码阅读(一) ServerBootstrap启动

    Netty源码阅读(一) ServerBootstrap启动 转自我的Github Netty是由JBOSS提供的一个java开源框架.Netty提供异步的.事件驱动的网络应用程序框架和工具,用以快速 ...

  9. JDK源码学习系列05----LinkedList

                                             JDK源码学习系列05----LinkedList 1.LinkedList简介 LinkedList是基于双向链表实 ...

随机推荐

  1. 2020年腾讯实习生C++面试题&持续更新中(1)

    2020年腾讯实习生C++面试题&持续更新中(1) 腾讯面试整理(1) 最近大三的学生找实习生的同学非常多,给大家分享一篇腾讯实习生的面试题,关于面试题,会持续更新~~~ 也算是今天开通博客的 ...

  2. 让.NetCore程序跑在任何有docker的地方

    一.分别在Windows/Mac/Centos上安装Docker Windows上下载地址:https://docs.docker.com/docker-for-windows/install/(wi ...

  3. nginx均衡负载

    一直在担心session 问题,结果试了2个web 论坛,discuz 和phpbb ,前面用nginx 均衡负载,后端是apache httpd +php ,mysql 用同一个,修改一下confi ...

  4. 【雕爷学编程】Arduino动手做(64)---RGB全彩LED模块

    37款传感器与执行器的提法,在网络上广泛流传,其实Arduino能够兼容的传感器模块肯定是不止这37种的.鉴于本人手头积累了一些传感器和执行器模块,依照实践出真知(一定要动手做)的理念,以学习和交流为 ...

  5. 【雕爷学编程】Arduino动手做(52)---MicroSD卡读写模块

    37款传感器与模块的提法,在网络上广泛流传,其实Arduino能够兼容的传感器模块肯定是不止37种 的.鉴于本人手头积累了一些传感器和模块,依照实践(动手试试)出真知的理念,以学习和交流为目的,这里准 ...

  6. zabbix配置主动式监控的步骤(原创)

    步骤如下: 1.克隆模板.命名新的模板名,并点击"监控项",全选,批量更新时第一个“类型”打勾,客户端改为主动式: 2.添加客户端或更改原有的模板为新模板(服务器端添加客户端时的配 ...

  7. iOS私有api检测工具使用

    背景:这两天提审了一款新的APP,由于项目中使用了老版本的TZImagePicker中访问了私有API,导致提审失败. 预审经验分享: https://baijiahao.baidu.com/s?id ...

  8. 基于postman测试接口(整套接口测试)

    基于postman测试接口(整套接口测试) 可以解决的问题 几百个接口人工测试接口过于繁杂 大多测试无法使用请求结果当参数 可以使用随机参数 支持swagger信息导入 随账号持久化保存数据 对集合一 ...

  9. JS中Class的两种写法

    写法一:使用函数: var ClassName = function() { this.message = 'dat.gui'; this.speed = 0.8; this.displayOutli ...

  10. 写给程序员的机器学习入门 (五) - 递归模型 RNN,LSTM 与 GRU

    递归模型的应用场景 在前面的文章中我们看到的多层线性模型能处理的输入数量是固定的,如果一个模型能接收两个输入那么你就不能给它传一个或者三个.而有时候我们需要根据数量不一定的输入来预测输出,例如文本就是 ...