Netty启动过程源码剖析

一、netty自带的example代码

public final class EchoServer {

    static final boolean SSL = System.getProperty("ssl") != null;
    static final int PORT = Integer.parseInt(System.getProperty("port", "8007"));

    public static void main(String[] args) throws Exception {
        // Configure SSL.
        final SslContext sslCtx;
        if (SSL) {
            SelfSignedCertificate ssc = new SelfSignedCertificate();
            sslCtx = SslContextBuilder.forServer(ssc.certificate(), ssc.privateKey()).build();
        } else {
            sslCtx = null;
        }

        // Configure the server.
        EventLoopGroup bossGroup = new NioEventLoopGroup(1);
        EventLoopGroup workerGroup = new NioEventLoopGroup();
        try {
            ServerBootstrap b = new ServerBootstrap();
            b.group(bossGroup, workerGroup)
             .channel(NioServerSocketChannel.class)
             .option(ChannelOption.SO_BACKLOG, 100)
             .handler(new LoggingHandler(LogLevel.INFO))
             .childHandler(new ChannelInitializer<SocketChannel>() {
                 @Override
                 public void initChannel(SocketChannel ch) throws Exception {
                     ChannelPipeline p = ch.pipeline();
                     if (sslCtx != null) {
                         p.addLast(sslCtx.newHandler(ch.alloc()));
                     }
                     p.addLast(new LoggingHandler(LogLevel.INFO));
                     //p.addLast(new EchoServerHandler());
                 }
             });

            // Start the server.
            ChannelFuture f = b.bind(PORT).sync();

            // Wait until the server socket is closed.
            f.channel().closeFuture().sync();
        } finally {
            // Shut down all event loops to terminate all threads.
            bossGroup.shutdownGracefully();
            workerGroup.shutdownGracefully();
        }
    }
}

二、NioEventLoopGroup的初始化

 EventLoopGroup bossGroup = new NioEventLoopGroup(1);
 EventLoopGroup workerGroup = new NioEventLoopGroup();

bossGroup用于接受Tcp请求,并将请求交给workerGroup,workerGroup会获取到真正的连接,然后和连接通信,比如读写解码编码等操作。EventLoopGroup是事件循环组(线程组)含有多个EventLoop,每个EventLoop中都有个selector,可以注册channel。
创建bossGroup 时的参数1是指定的线程数。如果没有指定则线程数为cpu核数*2。

DEFAULT_EVENT_LOOP_THREADS = Math.max(1, SystemPropertyUtil.getInt(
        "io.netty.eventLoopThreads", NettyRuntime.availableProcessors() * 2));

追着构造函数一层层点进去,这行代码是创建EventExecutor数组,数组元素个数即为线程数。每个元素类型是NIOEventLoop,NIOEventLoop实现了EventExecutor接口

children = new EventExecutor[nThreads];

在这里插入图片描述
下面这段是循环构造children 数组中的每一个元素

for (int i = 0; i < nThreads; i ++) {
    boolean success = false;
    try {
        children[i] = newChild(executor, args);
        success = true;
    } catch (Exception e) {
        // TODO: Think about if this is a good exception type
        throw new IllegalStateException("failed to create a child event loop", e);
    } finally {
        if (!success) {
            //这里是构造失败后的处理
       }
    }
}

为数组中的每个元素(NIOEventLoop)循环注册监听器

for (EventExecutor e: children) {
    e.terminationFuture().addListener(terminationListener);
}

总结:
bossGroup和workerGroup都可以包含多个NIOEventLoop,默认个数是核数*2,也可以指定。NIOEventLoop实现了EventLoop接口和 Executor 接口。

三、NioEventLoopGroup初始化代码

参数说明:

@param nThreads使用的线程数,默认为 core *2 [可以追踪源码]
@param executor   执行器:如果传入null,则采用Netty默认的线程工厂和默认的执行器ThreadPerTaskExecutor
@param chooserFactory单例new DefaultEventExecutorChooserFactory()
@param argsargs在创建执行器的时候传入固定参数
protected MultithreadEventExecutorGroup(int nThreads, Executor executor,
                                          EventExecutorChooserFactory chooserFactory, Object... args) {
      if (nThreads <= 0) {
          throw new IllegalArgumentException(String.format("nThreads: %d (expected: > 0)", nThreads));
      }
    //如果传入的执行器是空的则采用默认的线程工厂和默认的执行器
      if (executor == null) {
          executor = new ThreadPerTaskExecutor(newDefaultThreadFactory());
      }
     //创建指定线程数的执行器数组
      children = new EventExecutor[nThreads];
      //初始化线程数组
      for (int i = 0; i < nThreads; i ++) {
          boolean success = false;
          try {
              // 创建 new NioEventLoop
              children[i] = newChild(executor, args);
              success = true;
          } catch (Exception e) {
              // TODO: Think about if this is a good exception type
              throw new IllegalStateException("failed to create a child event loop", e);
          } finally {
               // 如果创建失败,优雅关闭
              if (!success) {
                  for (int j = 0; j < i; j ++) {
                      children[j].shutdownGracefully();
                  }

                  for (int j = 0; j < i; j ++) {
                      EventExecutor e = children[j];
                      try {
                          while (!e.isTerminated()) {
                              e.awaitTermination(Integer.MAX_VALUE, TimeUnit.SECONDS);
                          }
                      } catch (InterruptedException interrupted) {
                          // Let the caller handle the interruption.
                          Thread.currentThread().interrupt();
                          break;
                      }
                  }
              }
          }
      }

      chooser = chooserFactory.newChooser(children);

      final FutureListener<Object> terminationListener = new FutureListener<Object>() {
          @Override
          public void operationComplete(Future<Object> future) throws Exception {
              if (terminatedChildren.incrementAndGet() == children.length) {
                  terminationFuture.setSuccess(null);
              }
          }
      };
	 //为每一个单例线程池添加一个关闭监听器
      for (EventExecutor e: children) {
          e.terminationFuture().addListener(terminationListener);
      }
	 //将所有的单例线程池添加到一个 HashSet 中。
      Set<EventExecutor> childrenSet = new LinkedHashSet<EventExecutor>(children.length);
      Collections.addAll(childrenSet, children);
      readonlyChildren = Collections.unmodifiableSet(childrenSet);
  }

总结:

  1. 如果 executor 是null,创建一个默认的ThreadPerTaskExecutor,使用Netty默认的线程工厂。
  2. 根据传入的线程数(CPU*2)创建一个线程池(单例线程池)数组。
  3. 循环填充数组中的元素。如果异常,则关闭所有的单例线程池。
  4. 根据线程选择工厂创建一个线程选择器。
  5. 为每一个单例线程池添加一个关闭监听器。
  6. 将所有的单例线程池添加到一个 HashSet 中。

四、ServerBootstrap 初始化

ServerBootstrap b = new ServerBootstrap();
    b.group(bossGroup, workerGroup)
   .channel(NioServerSocketChannel.class)
   .option(ChannelOption.SO_BACKLOG, 100)
   .handler(new LoggingHandler(LogLevel.INFO))
   .childHandler(new ChannelInitializer<SocketChannel>() {
		//省略
});

ServerBootstrap的初始化代码如下

ServerBootstrap b = new ServerBootstrap();

调用的构造函数

public ServerBootstrap() { }

下面这行代码是将bossGroup和workerGroup分别赋值给ServerBootstrap的两个成员变量

 b.group(bossGroup, workerGroup)

然后添加了一个 channel,其中参数一个Class对象,引导类将通过这个 Class 对象反射创建ChannelFactory。

.channel(NioServerSocketChannel.class)

这里注册的handler是给bossGroup的

.handler(new LoggingHandler(LogLevel.INFO))

这里注册的handler是给workerGroup的

.childHandler(xxx)

总结:

  1. 链式调用:group方法,将boss和worker传入,boss 赋值给parentGroup属性,worker 赋值给childGroup属性
  2. channel 方法传入NioServerSocketChannel class 对象。会根据这个 class 创建 channel 对象。
  3. option 方法传入 TCP 参数,放在一个LinkedHashMap中。
  4. handler 方法传入一个 handler 中,这个hanlder只专属于ServerSocketChannel而不是SocketChannel
  5. childHandler传入一个hanlder,这个handler 将会在每个客户端连接的时候调用。供SocketChannel使用

五、绑定端口分析

示例程序:

ChannelFuture f = b.bind(PORT).sync();

点击进去

public ChannelFuture bind(int inetPort) {
    return bind(new InetSocketAddress(inetPort));
}

再点击进去

public ChannelFuture bind(SocketAddress localAddress) {
   validate();
   if (localAddress == null) {
       throw new NullPointerException("localAddress");
   }
   return doBind(localAddress);
}

创建了一个端口对象,并做了一些空判断,核心代码doBind。

initAndRegister方法

1、NioServerSocketChannel的创建
 channel = channelFactory.newChannel();

(1) 通过 NIO 的SelectorProvider的openServerSocketChannel方法得到JDK 的 channel。目的是让Netty包装 JDK 的 channel。
(2) 创建了一个唯一的ChannelId,创建了一个NioMessageUnsafe,用于操作消息,创建了一个DefaultChannelPipeline管道,是个双向链表结构,用于过滤所有的进出的消息。
(3) 创建了一个NioServerSocketChannelConfig对象,用于对外展示一些配置。
总结:
channelFactory.newChannel() 方法的作用通过ServerBootstrap的通道工厂反射创建一个NioServerSocketChannel。

2、init方法对channel的初始化操作
init(channel)

(1) init方法,这是个抽象方法(AbstractBootstrap类的),由ServerBootstrap实现(可以追一下源码 //setChannelOptions(channel, options, logger);)。
(2) 设置NioServerSocketChannel的 TCP 属性。
(3) 由于LinkedHashMap是非线程安全的,使用同步进行处理。
(4) 对NioServerSocketChannel的ChannelPipeline添加ChannelInitializer处理器。
(5) 可以看出,init的方法的核心作用在和ChannelPipeline相关。
(6) 从NioServerSocketChannel的初始化过程中,我们知道,pipeline 是一个双向链表,并且,他本身就初始化了 head 和 tail,如果调用了他的addLast方法,也就是将整个 handler 插入到 tail 的前面,因为 tail 永远会在后面,需要做一些系统的固定工作。

handler的addLast方法

p.addLast(new LoggingHandler(LogLevel.INFO));

追入addLast源码可发现如下代码

@Override
public final ChannelPipeline addLast(EventExecutorGroup group, String name, ChannelHandler handler) {
    final AbstractChannelHandlerContext newCtx;
    synchronized (this) {
    	// 检查该 handler 是否符合标准
        checkMultiplicity(handler);
        newCtx = newContext(group, filterName(name, handler), handler);
        addLast0(newCtx);
		/*
		省略
		*/        
}

调用addLast0方法把AbstractChannelHandlerContext 对象放入双向链表,把新节点放到tail节点的前一个节点

private void addLast0(AbstractChannelHandlerContext newCtx) {
   AbstractChannelHandlerContext prev = tail.prev;
   newCtx.prev = prev;
   newCtx.next = tail;
   prev.next = newCtx;
   tail.prev = newCtx;
}

总结:
addLast方法在DefaultChannelPipeline类中, 创建一个AbstractChannelHandlerContext对象,ChannelHandlerContext对象是ChannelHandler和ChannelPipeline之间的关联,每当有ChannelHandler添加到 Pipeline 中时,都会创建 Context。Context 的主要功能是管理他所关联的 Handler 和同一个 Pipeline 中的其他 Handler 之间的交互。 将 Context 添加到链表中。也就是追加到 tail 节点的前面。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值