Netty学习笔记(4) Netty源码 - 启动流程


前言

笔记基于黑马的Netty教学讲义加上自己的一些理解,感觉这是看过的视频中挺不错的,基本没有什么废话,视频地址:黑马Netty。下面从这里开始就是尝试去看源码来理解 Netty 底层的运行机制。



1. nio 启动流程回顾

我们回顾下 nio 要启动服务端要做些什么

//1 netty 中使用 NioEventLoopGroup (简称 nio boss 线程)来封装线程和 selector
Selector selector = Selector.open(); 

//2 创建 NioServerSocketChannel,同时会初始化它关联的 handler,以及为原生 ssc 存储 config
NioServerSocketChannel attachment = new NioServerSocketChannel();

//3 创建 NioServerSocketChannel 时,创建了 java 原生的 ServerSocketChannel,serverSocketChannel的作用可以理解为一个注册器,netty中的ssc作为附件注册到原生的ssc上
ServerSocketChannel serverSocketChannel = ServerSocketChannel.open(); 
serverSocketChannel.configureBlocking(false);

//4 启动 nio boss 线程执行接下来的操作

//5 注册(仅关联 selector 和 NioServerSocketChannel),未关注事件
SelectionKey selectionKey = serverSocketChannel.register(selector, 0, attachment);

//6 head -> 初始化器 -> ServerBootstrapAcceptor -> tail,初始化器是一次性的,只为添加 acceptor

//7 绑定端口
serverSocketChannel.bind(new InetSocketAddress(8080));

//8 触发 channel active 事件,在 head 中关注 op_accept 事件
//表示当客户端接入的时候触发这个事件
selectionKey.interestOps(SelectionKey.OP_ACCEPT);

主要步骤其实是下面几步:

1. Selector selector = Selector.open();

2. ServerSocketChannel ssc = ServerSocketChannel.open();
3. SelectionKey key = ssc.register(selector, 0, nettySsc);
4. ssc.bind(new InetSocketAddress("localhost", 8080));
5. key.interestOps(SelectionKey.OP_ACCRPT);



2. 源码方法说明

以下面这段代码进行说明:

public class TestSourceServer {
    public static void main(String[] args) {
        new ServerBootstrap()
                //EventLoop有一个线程和执行器selector,用于关注事件,解决一些任务
                .group(new NioEventLoopGroup())
                .channel(NioServerSocketChannel.class)
                .childHandler(new ChannelInitializer<NioSocketChannel>(){

                    @Override
                    protected void initChannel(NioSocketChannel ch) throws Exception {
                        ch.pipeline().addLast(new LoggingHandler());
                    }
                }).bind(8080);
    }
}



1. Selector selector = Selector.open();

这部分是在new NioEventLoopGroup() 创建好的,因为 EventLoop 里面是维护了一个选择器和一个线程,所以选择器是在这里面就创建好的



2. 下面四个方法
bind方法的一些流程,主要是掠过不重要的步骤:
	bind() -> return bind(new InetSocketAddress(inetPort)) ->
	return doBind(ObjectUtil.checkNotNull(localAddress, "localAddress"));

重要方法:doBind(ObjectUtil.checkNotNull(localAddress, "localAddress"))

private ChannelFuture doBind(final SocketAddress localAddress) {
		//1, 初始化 NioServerSocketChannel 并注册到ssc上
		// regFuture 其实就是promise 对象,在结果产生的时候就会调用下面的doBind0
        final ChannelFuture regFuture = initAndRegister();
        final Channel channel = regFuture.channel();
        if (regFuture.cause() != null) {
            return regFuture;
        }

        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 {
            final PendingRegistrationPromise promise = new PendingRegistrationPromise(channel);
            regFuture.addListener(new ChannelFutureListener() {
            //Nio线程调用
                @Override
                public void operationComplete(ChannelFuture future) throws Exception {
                    Throwable cause = future.cause();
                    if (cause != null) {
                                     
                        promise.setFailure(cause);
                    } else {
                   
                        promise.registered();
						//2. 绑定端口
                        doBind0(regFuture, channel, localAddress, promise);
                    }
                }
            });
            return promise;
        }
    }

下面看代码的时候注意 dobind 方法是在主线程中执行的,但是在执行 initAndRegister 方法的时候会发生线程切换。

下面对这两个方法(initAndRegister 和 doBind0)做一个流程的简单介绍:

  1. init 和 register regFuture 处理

    1. init (main线程执行)
      a. 创建 NioServerSocketChannel (main线程执行)
      b. 添加 NioServerSocketChannel 初始化 handler (main线程执行)
          初始化 handler等待调用 (main未调用)
      向 nio ssc 中加入了 accept handler(在 accept 事件发生后建立连接)
    2. register(切换线程)
      a. 启动 nio boss 线程(main线程执行)
      b. 原生 ssc(ServerSocketChannel) 注册到 selector 未关注事件(nio-thread执行)
      c. 执行 NioServerSocketChannel 初始化 handler(nio-thread执行)
  2. regFuture 等待回调 doBind0

    1. 原生 ServerSocketChannel绑定(nio-thread执行)
    2. 触发 NioServerSocketChannel active 事件(nio-thread执行)



3. initAndRegister

方法注释的形式是以一个总的方法为主,内部方法为辅助

final ChannelFuture initAndRegister() {
		//初始化channel
        Channel channel = null;
        try {
        	//通过channelFactory工厂来创建channel
        	//创建出来的ReflectiveChannelFactory(NioServerSocketChannel.class)
        	//其实内部是使用反射的方法拿到构造器来创建的
            channel = channelFactory.newChannel();	// 1
            init(channel);							// 2
        } catch (Throwable t) {
            if (channel != null) {
        
                channel.unsafe().closeForcibly();
                return new DefaultChannelPromise(channel, GlobalEventExecutor.INSTANCE).setFailure(t);
            } 
            return new DefaultChannelPromise(new FailedChannel(), GlobalEventExecutor.INSTANCE).setFailure(t);
        }

        ChannelFuture regFuture = config().group().register(channel);//3	
        if (regFuture.cause() != null) {
            if (channel.isRegistered()) {
                channel.close();
            } else {
                channel.unsafe().closeForcibly();
            }
        }
        return regFuture;
    }



1. channel = channelFactory.newChannel()

1. channel = channelFactory.newChannel();

这个方法内部使用 channel 工厂进行创建,newChannel 内部使用反射机制调用了 NioServerSocketChannel 这个类的构造器, 我们直接去到 NioServerSocketChannel 这个类中去看源码

public NioServerSocketChannel() {
    this(newSocket(DEFAULT_SELECTOR_PROVIDER));
}

进入 newSocket(DEFAULT_SELECTOR_PROVIDER) :其实是调用了 provider.openServerSocketChannel() 这个方法
在这里插入图片描述
其实我们仔细观察下,这行代码就是 ServerSocketChannel的open方法的执行流程,下面是 ServerSocketChannel 的 open 方法:
在这里插入图片描述


总结:这行代码其实作用就是调用了 ServerSocketChannel .open 创建出来一个 NioServerSocketChannel,并且内部也把 JDK 的 ServerSockerChannel 创建好了



2. init(channel)

2. init(channel)
@Override
    void init(Channel channel) {
        setChannelOptions(channel, newOptionsArray(), logger);
        setAttributes(channel, attrs0().entrySet().toArray(EMPTY_ATTRIBUTE_ARRAY));
		//这里的p是 NioServerSocketChannel的流水线
        ChannelPipeline p = channel.pipeline();

        final EventLoopGroup currentChildGroup = childGroup;
        final ChannelHandler currentChildHandler = childHandler;
        final Entry<ChannelOption<?>, Object>[] currentChildOptions;
        synchronized (childOptions) {
            currentChildOptions = childOptions.entrySet().toArray(EMPTY_OPTION_ARRAY);
        }
        final Entry<AttributeKey<?>, Object>[] currentChildAttrs = childAttrs.entrySet().toArray(EMPTY_ATTRIBUTE_ARRAY);
		
		//从这里开始是重点
		//往 NioServerSocketChannel里面添加了一个初始化的 handler
		//里面的方法只会执行一次
        p.addLast(new ChannelInitializer<Channel>( ) {
            @Override
            public void initChannel(final Channel ch) {
                final ChannelPipeline pipeline = ch.pipeline();
                ChannelHandler handler = config.handler();
                if (handler != null) {
                    pipeline.addLast(handler);
                }

                ch.eventLoop().execute(new Runnable() {
                    @Override
                    public void run() {
                        pipeline.addLast(new ServerBootstrapAcceptor(
                                ch, currentChildGroup, currentChildHandler, currentChildOptions, currentChildAttrs));
                    }
                });
            }
        });
    }

总结:这行代码中给 NioServerSocketChannel 创建了一个初始化处理器,等待调用,这个处理器只调用一次



3. ChannelFuture regFuture = config().group().register(channel)

3. ChannelFuture regFuture = config().group().register(channel)
下面是调用链,最终去到真正的 register 方法中, 这个方法中进行了线程的切换:

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

@Override
  public final void register(EventLoop eventLoop, final ChannelPromise promise) {
       ObjectUtil.checkNotNull(eventLoop, "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;
       }

       AbstractChannel.this.eventLoop = eventLoop;
	   //这个方法是检查当前线程是不是等于 Nio 线程,当前线程肯定不等于,因为是主线程在走
       if (eventLoop.inEventLoop()) {
           register0(promise);
       } else {
           try {
           	   //创建了一个任务对象交给 eventLoop 里面的 Nio线程去执行
           	   //那么此时就进行了线程的切换,由主线程切换到 Nio线程
           	   //register0是懒加载,在调用到这里的时候才把线程创建出来
               eventLoop.execute(new Runnable() {
                   @Override
                   public void run() {
                   	
                       register0(promise);
                   }
               });
           } 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);
           }
       }
   }

进入register0(promise)方法中:

 private void register0(ChannelPromise promise) {
  try {
         if (!promise.setUncancellable() || !ensureOpen(promise)) {
             return;
         }
         boolean firstRegistration = neverRegistered;
         //核心的代码在这里
         doRegister();
         neverRegistered = false;
         registered = true;

         pipeline.invokeHandlerAddedIfNeeded();

         safeSetSuccess(promise);
         pipeline.fireChannelRegistered();
         if (isActive()) {
             if (firstRegistration) {
                 pipeline.fireChannelActive();
             } else if (config().isAutoRead()) {
                 beginRead();
             }
         }
     } catch (Throwable t) {
         // 关闭 channel
         closeForcibly();
         closeFuture.setClosed();
         safeSetFailure(promise, t);
     }
 }



进入 register0 的 doRegister() 方法中, 这个方法完成了注册,也就是 SelectionKey key = ssc.register(selector, 0, nettySsc) 这行代码

@Override
    protected void doRegister() throws Exception {
        boolean selected = false;
        for (;;) {
            try {
            //拿到java原生的ServerSocketChannel,并注册到 selector上
            //0:表示没有关注事件
            //this:就是当前的 NioServerSocketChannel对象
                selectionKey = javaChannel().register(eventLoop().unwrappedSelector(), 0, this);
                return;
            } catch (CancelledKeyException e) {
                if (!selected) {
                    eventLoop().selectNow();
                    selected = true;
                } else { 
                    throw e;
                }
            }
        }
    }



进入 register0 的 pipeline.invokeHandlerAddedIfNeeded() 方法中,这个方法的内部其实是调用了我们在 2. init(channel) 中设置好的 ChannelInitializer 初始化处理器的initChannel 方法,所以这个方法的作用就是调用第二步中没有调用的那个初始化方法,
所以这个方法其实是初始化中调用的

p.addLast(new ChannelInitializer<Channel>() {
            @Override
            public void initChannel(final Channel ch) {
                final ChannelPipeline pipeline = ch.pipeline();
                ChannelHandler handler = config.handler();
                if (handler != null) {
                    pipeline.addLast(handler);
                }
				
				//向NioServerSocketChanel 中加入了一个 ServerBootstrapAcceptor处理器,作用是在accept事件发生后建立连接
				//添加处理器的任务提交给了 channel,保证是在 channel 里面的线程中去运行
                ch.eventLoop().execute(new Runnable() {
                    @Override
                    public void run() {
                        pipeline.addLast(new ServerBootstrapAcceptor(
                                ch, currentChildGroup, currentChildHandler, currentChildOptions, currentChildAttrs));
                    }
                });
            }
        });



进入 register0 的 safeSetSuccess(promise) 方法中,其实这个方法就是用来给 promise 设置结果的,注意:源码方法的 doBind 中的promise 和这个promise是一个来的,所以在这一步完成之后就会进入 doBind0 方法中。


总结:这行代码中 config().group().register(channel) 完成了对 ssc 的注册,也就是 register 方法并在里面设置了一个 handler,这个handler 是在 accept 事件发生后执行的,并且在里面给源码doBind中的promise设置了结果,导致进doBind 方法的 doBind0 中



4. doBind0

doBind0(regFuture, channel, localAddress, promise), 内部还是熟悉的套路,把任务交给 nio ssc 的线程去执行

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.
     channel.eventLoop().execute(new Runnable() {
         @Override
         public void run() {
             if (regFuture.isSuccess()) {
                 channel.bind(localAddress, promise).addListener(ChannelFutureListener.CLOSE_ON_FAILURE);
             } else {
                 promise.setFailure(regFuture.cause());
             }
         }
     });
 }

下面是方法的执行链:一层套一层的
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

终于来到干活的方法了,这调用链太深了:同样我们只关注两个地方

@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 {
    	//1. 核心的调用方法
        doBind(localAddress);		// 1
    } catch (Throwable t) {
        safeSetFailure(promise, t);
        closeIfClosed();
        return;
    }
	//判断是不是 isActive
    if (!wasActive && isActive()) {		// 2
        invokeLater(new Runnable() {
            @Override
            public void run() {
                pipeline.fireChannelActive();
            }
        });
    }

    safeSetSuccess(promise);
}



1. doBind(localAddress)

@SuppressJava6Requirement(reason = "Usage guarded by java version check")
@Override
 protected void doBind(SocketAddress localAddress) throws Exception {
 	//判断java 版本是不是 > 7的
     if (PlatformDependent.javaVersion() >= 7) {
     	//javaChannel其实就是 ServerSocketChannel	
     	//注册
         javaChannel().bind(localAddress, config.getBacklog());
     } else {
         javaChannel().socket().bind(localAddress, config.getBacklog());
     }
 }

总结:这个方法作用是根据不同的java版本来绑定

2. pipeline.fireChannelActive()

要进入这个方法中,首先要判断是不是 isActive 的,也就是说现在的 ServerSocketChannel 经过前面一系列的操作现在是不是可用了。

到现在的操作,pipeline 上面已经有3个处理器了:
head --> acceptor --> tail

调用 pipeline.fireChannelActive() 之后会按顺序执行所有处理器的方法,当然此时 acceptor 和 tail 其实没多大影响,因为 acceptor 在前面 initAndRegister 那里已经处理了。所以现在直接定位到 HeadContext 上面,在 DefaultChannelPipeline.java 这个类里面。定位到 ChannelActice方法上面:

 @Override
 public void channelActive(ChannelHandlerContext ctx) {
     ctx.fireChannelActive();
	 //关注事件
     readIfIsAutoRead();
 }

我们追踪 readIfIsAutoRead()的调用过程:
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

找到最终的调用方法:

@Override
protected void doBeginRead() throws Exception {
     // 获取 selectionKey
     final SelectionKey selectionKey = this.selectionKey;
     //判断是否非法
     if (!selectionKey.isValid()) {
         return;
     }
		
     readPending = true;
	//获取关注的事件
     final int interestOps = selectionKey.interestOps();
     //如果关注的事件没有
     if ((interestOps & readInterestOp) == 0) {
     	// 关注读事件, | 其实就相当于加了, 比如 0000 | 0010 = 0010
     	//当然,只有在0 的时候才可以这么加 readInterestOp = 16
         selectionKey.interestOps(interestOps | readInterestOp);
     }
 }

在这里插入图片描述


总结 : 这个方法其实就是绑定感兴趣的事件





如有错误,欢迎指出!!!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值