Netty源码解析之服务端启动

前言

从本篇文章开始,将开启一系列的Netty源码分析。Netty的版本均基于4.1.37.Final作分析。Netty的各个版本的一些实现可能不同,但大致的思想是相同的,希望读者观其大意,理解其思想为主

至于BIO与NIO的优势劣势、Netty是什么、why Netty这些内容都在—> Netty前言 中,这里就不多赘述,直入正题。本篇文章主要分析Netty服务端在启动的时候都做了什么,由于接下来的几篇文章都会围绕服务端启动后的详细细节,所以这里首先介绍一下服务端的启动源码

Sample

先来看一段服务端启动的代码

public static void start(int port) {
   

  // 创建两个线程池,在此可看成一个黑盒,在下篇文章中会介绍到
  EventLoopGroup bossEventLoop = new NioEventLoopGroup(1);
  EventLoopGroup workerEventLoop = new NioEventLoopGroup();

  try {
   
    // 服务端启动引导类
    ServerBootstrap bootstrap = new ServerBootstrap();

    // 设置线程池
    bootstrap.group(bossEventLoop, workerEventLoop)
      // 设置服务端channel
      .channel(NioServerSocketChannel.class)
      // 设置一个处理客户端channel的handler
      .childHandler(new ChannelInitializer() {
   
        @Override
        protected void initChannel(Channel ch) throws Exception {
   
          ChannelPipeline pipeline = ch.pipeline();
          // 添加某个Handler
          pipeline.addLast(null);
        }
      });

    // 绑定端口
    ChannelFuture channelFuture = bootstrap.bind(port);
    // 阻塞在此,直到被关闭
    channelFuture.channel().closeFuture().sync();
  } catch (Exception e) {
   
    e.printStackTrace();
  } finally {
   
    // 优雅关闭资源
    bossEventLoop.shutdownGracefully();
    workerEventLoop.shutdownGracefully();
  }
}

ServerBootstrap为本章分析的重点,这个类引导了各个Netty组件的初始化工作,它存在的意义就是为了简化Netty的启动,内部封装了各种流程。

以此为切入点,接下来我们来一步步的剖析这个引导类的工作

引导类的启动

线程池的设置

这里先剧透一下,EventLoopGroup这个类可以说是Netty的发动机,整个Netty服务端的运转都靠这个类来驱动着,在前言中我们提到,Netty是异步-事件-驱动的,这里的驱动,就是指EventLoopGroup这个线程组类。

在这里只介绍它于引导类中的设置,对于EventLoopGroup的详细介绍将放到下面几篇文章中去。

group(bossEventLoop, workerEventLoop)

在上面的例子代码中,我们看到首先调用了这个方法,设置了两个线程池给引导类,跟进去看看:

public ServerBootstrap group(EventLoopGroup parentGroup, EventLoopGroup childGroup) {
   
  // 将此变量设置为自己的成员变量
  // this.group = parentGroup
  super.group(parentGroup);
  if (childGroup == null) {
   
    throw new NullPointerException("childGroup");
  }
  if (this.childGroup != null) {
   
    throw new IllegalStateException("childGroup set already");
  }
  // 将第二个gourp设置为childGroup成员变量
  this.childGroup = childGroup;
  return this;
}

这里只需要记住,引导类将其设置为两个成员变量即可

Channel的设置

在例子中,我们调用这段代码将channel设置进去

.channel(NioServerSocketChannel.class)

其实就是将Class保存在一个工厂里,保存在成员变量中

public B channel(Class<? extends C> channelClass) {
   
  if (channelClass == null) {
   
    throw new NullPointerException("channelClass");
  }
  return channelFactory(new ReflectiveChannelFactory<C>(channelClass));
}
public B channelFactory(ChannelFactory<? extends C> channelFactory) {
   
  ...
  this.channelFactory = channelFactory;
  return (B) this;
}

这个工厂是干啥用的呢?我们这里进入上面具体实例化的工厂类ReflectiveChannelFactory中:

public T newChannel() {
   
  try {
   
    return clazz.newInstance();
  } catch (Throwable t) {
   
    throw new ChannelException("Unable to create Channel from class " + clazz, t);
  }
}

其实就是利用反射去实例化一个对象,这个对象就是我们最上面调用channel()方法中的参数,也就是NioServerSocketChannel这个类。在什么时候会实例化呢?下面就会看到,这里只是简单的设置一下

Hanlder的设置

public ServerBootstrap childHandler(ChannelHandler childHandler) {
   
  ...
  this.childHandler = childHandler;
  return this;
}

这里简要带过一下,其实以上设置都只是将实例保存到引导类的成员变量中而已,这里记住,设置了childHandler这个成员变量的值

引导类正式启动

到这里,引导类才真正开始做事情,由以下代码开始驱动:

bootstrap.bind(port);

所以,以此为入口来看看都做了什么

public ChannelFuture bind(InetAddress inetHost, int inetPort) {
   
  // 简单将端口和host包装成一个对象
  return bind(new InetSocketAddress(inetHost, inetPort));
}

public ChannelFuture bind(SocketAddress localAddress) {
   
  ...
  return doBind(localAddress);
}
private ChannelFuture doBind(final SocketAddress localAddress) {
   
  // 初始化并注册
  final ChannelFuture regFuture = initAndRegister();
  ...
	// 真正将channel进行绑定
  doBind0(regFuture, channel, localAddress, promise);   
}

这里省略了大量判断的代码,只是确保第一步初始化并注册完成之后,才进行真正的channel绑定到端口的工作。

这里可以将channel看成是socket的一个抽象,可以往channel里写数据读数据,是双向的,一个客户端连接就是一个客户端channel,服务端也有服务端自己的channel。

就像在项目描述那篇文章中的那样,在聊天室的实现中,或是仿支付的实现中,我们都会把客户端channel保存下来,因为这对应了一个连接,到我们需要向客户端写数据的时候就会把该channel拿出来写,就会通过TCP连接写数据到对端。当然除了TCP协议,像UDP之类的协议Netty也有封装,不过Channel是别的对象,但概念都是一样的。

channel的初始化与注册工作

可以说,到现在才来到了最为关键的地方,回忆一下,我们在开头设置了NioServerSocketChannel这个channel类,这个channel属于服务端的channel,主要负责接受客户端的连接,它的初始化工作就在上面initAndRegister这个方法中做的:

final ChannelFuture initAndRegister() {
   
  Channel channel = null;
  try {
   
    // 这里调用了我们上面说的工厂,实例化了一个channel
    channel = channelFactory.newChannel();
    // 初始化channel
    init(channel);
  } 

  // 注册channel
  ChannelFuture regFuture = config().group().register(channel);
}

这里分了3个步骤:

  1. 实例化一个channel
  2. 对channel实例进行初始化工作
  3. 注册channel

以上3步都可以是一段长逻辑,所以这里分开解析

实例化Channel

我们可以看看,NioServerSocketChannel这个类的构造函数都做了什么

// JDK的nio类,负责创建一个channel
SelectorProvider DEFAULT_SELECTOR_PROVIDER = SelectorProvider.provider();

public
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值