前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >NioServerSocketChannel的初始化源码

NioServerSocketChannel的初始化源码

作者头像
止术
发布2021-07-16 14:52:24
2150
发布2021-07-16 14:52:24
举报
文章被收录于专栏:求道求道

有道无术,术尚可求也!有术无道,止于术!

源码分析

上一节课我们就NioEventLoop的初始化进行了一个初步的讲解,他是Netty很重要的一个类,后面还有针对它的分析,大家先对我前面介绍的组件有一个初步的认识!仔细的看,看到后面会有一种豁然开朗的感觉!

我们这一节课学习服务端的ServerSocketChannel的初始化源码,首先,我们还是老规矩,我告诉你你从哪里找,他是如何一步一步调用到ServerSocketChannel的,然后在进行分析!

一、入口寻找

首先,我们大家再开发Netty服务端的时候,都会有这样几行代码:

代码语言:javascript
复制
ServerBootstrap serverBootstrap = new ServerBootstrap();
serverBootstrap.group(boss,work)
    .channel(NioServerSocketChannel.class)
    .childOption(ChannelOption.TCP_NODELAY,true)
    .childAttr(AttributeKey.newInstance("childAttr"),"childAttrValue")
    .handler(...)
    .childHandler(...);
serverBootstrap.bind(8888).sync();

1. channel()

我们先具体分析下:ServerBootstrap再初始化过程中做了什么,我们具体看两个地方,channelchildHandler, 其余的大家可以自己试着看,都是一样的,我们进入到.channel内部查看源码:

代码语言:javascript
复制
public B channel(Class<? extends C> channelClass) {
    return channelFactory(new ReflectiveChannelFactory<C>(
        ObjectUtil.checkNotNull(channelClass, "channelClass")
    ));
}

为了分析过程中尽量做到简洁,我们只分析主线代码,支线代码,我会在用到的时候做具体的讲解:

我们看到上述的代码,将我们传入的通道类型NioServerSocketChannel包装为了一个ReflectiveChannelFactory对象,从名字我们基本可以知道,他是和反射相关的工厂,然后把ReflectiveChannelFactory对象传入到channelFactory方法里面,我们跟进去看下源码:

代码语言:javascript
复制
public B channelFactory(ChannelFactory<? extends C> channelFactory) {
    .....忽略不必要代码......
	//保存SocketChannel的包装对象
    this.channelFactory = channelFactory;
    return self();
}

我们可以看到,他只是将我们的NioServerSocketChannel的包装对象给保存了起来!

我们再回过头来看一下ReflectiveChannelFactory做了什么:

代码语言:javascript
复制
public ReflectiveChannelFactory(Class<? extends T> clazz) {
    try {
        //.channel 传入的  NioServerSocketChannel
        this.constructor = clazz.getConstructor();
    } catch (NoSuchMethodException e) {
       ........................................
    }
}

我们可以看到,ReflectiveChannelFactory的逻辑也很简单,就只是将我们传入的NioServerSocketChannel,获取他的空构造方法,然后保存起来!

2. childHandler()

我们再回头看childHandler方法,基本的原理是一样的:

代码语言:javascript
复制
public ServerBootstrap childHandler(ChannelHandler childHandler) {
    this.childHandler = ObjectUtil.checkNotNull(childHandler, "childHandler");
    return this;
}

也是一样的逻辑,只是将 我们设置到出站入栈处理器保存起来,并没有做其他特别多的操作,其余的方法大家可以试着分析一下,全部都是将我们要设置的一些属性保存起来,供后续调用!

3. bind方法

我们讲一些属性保存了起来,那么在哪里调用呢? 最主要的方法就是这个bind()方法了,他是启动服务端的主要入口!

代码语言:javascript
复制
public ChannelFuture bind(int inetPort) {
    return bind(new InetSocketAddress(inetPort));
}

首先,他将port端口包装为一个InetSocketAddress对象,和我们NIO开发中基本一致,我们继续跟下去:

代码语言:javascript
复制
public ChannelFuture bind(SocketAddress localAddress) {
    validate();
    return doBind(ObjectUtil.checkNotNull(localAddress, "localAddress"));
}

//没什么好说的  再往下跟
private ChannelFuture doBind(final SocketAddress localAddress) {
        //创建服务端的channel
        //初始化并注册 Channel,同时返回一个 ChannelFuture 实例 regFuture  异步
        final ChannelFuture regFuture = initAndRegister();
    ..........其余代码后续分析..............
}

我们向下跟了两层,终于看到了大段的代码,我们只分析第一行代码,后面的代码再后面全部分析了,这一节课我们只关注和NioServerSocketChannel相关的代码,我们进入到initAndRegister方法里面

I. initAndRegister
代码语言:javascript
复制
final ChannelFuture initAndRegister() {
        Channel channel = null;
        try {
            //创建服务端的channel  反射创建
            //io.netty.channel.ReflectiveChannelFactory.newChannel
            channel = channelFactory.newChannel();
            //初始化channel
            init(channel);
        }case{
            ..............忽略..............
        }
    ..............忽略..............
}

这里我们调用channelFactory.newChannel()创建了一个Channel对象,channelFactory是什么?我们再设置ServerSocketChannel的时候,内部将channelFactory包装为了ReflectiveChannelFactory对象,忘了的话看下前面!我们跟进io.netty.channel.ReflectiveChannelFactory#newChannel源码里面:

代码语言:javascript
复制
@Override
public T newChannel() {
    try {
        //反射创建  NioServerSocketChannel
        return constructor.newInstance();
    } catch (Throwable t) {
        ........................................
    }
}

这段代码相信大家就及其熟悉了,利用我们再构建ReflectiveChannelFactory的时候保存的构造方法对象,创建出来一个NioServerSocketChannel对象! 因为之前获取的是无参构造,所以,我们需要进入到NioServerSocketChannel的无参构造里面寻找他的逻辑!

二、源码分析

前面基本描述了我们要分析NioServerSocketChannel的源码入口,下面开始正式的分析它,我们进入到NioServerSocketChannel的无参构造方法:

代码语言:javascript
复制
/**
     * 创建一个新实例
     */
public NioServerSocketChannel() {
    //DEFAULT_SELECTOR_PROVIDER:SelectorProvider.provider()
    //newSocket 创建一个channel
    this(newSocket(DEFAULT_SELECTOR_PROVIDER));
}

首先,我们先关注一下newSocket方法:

代码语言:javascript
复制
private static ServerSocketChannel newSocket(SelectorProvider provider) {
    try {
        return provider.openServerSocketChannel();
    } catch (IOException e) {
        .........................
    }
}

newSocket方法使用provider创建了一个JDK底层的ServerSocketChannel,注意该对象是JDK原始的通道对象,至此,我们基本可以推断出,Netty的Channel是基于JDK的Channel进行封装的!我们继续回到无参构造方法:

代码语言:javascript
复制
public NioServerSocketChannel(ServerSocketChannel channel) {
    //保存对应的配置项  同时保存关注连接事件 OP_ACCEPT
    super(null, channel, SelectionKey.OP_ACCEPT);
    //创建一个配置类 你保存的是当前对象以及jdk底层的socket
    config = new NioServerSocketChannelConfig(this, javaChannel().socket());
}

我们关注super方法,这里将上一步创建的JDK NIO底层的SocketChannel,和一个客户端接入事件传入进去,我们跟进看一下:

代码语言:javascript
复制
protected AbstractNioMessageChannel(Channel parent, SelectableChannel ch, int readInterestOp) {
    super(parent, ch, readInterestOp);
}



//没什么好说的  继续往下跟
protected AbstractNioChannel(Channel parent, SelectableChannel ch, int readInterestOp) {
    //创建关键数据
    super(parent);
    //保存jdk底层channel
    this.ch = ch;
    //保存关注的事件
    this.readInterestOp = readInterestOp;
    try {
        //设置为非阻塞
        ch.configureBlocking(false);
    } catch (IOException e) {
       。。。。。。。。。。。。。。。。。。。。
    }
}

我们还是暂时先略过super方法,先分析下面的,下面的分析完,再反过来分析super方法:

  1. 首先将我们前面获取的JDK NIO Channel对象保存起来!
  2. 将前面传入的SelectionKey.OP_ACCEPT事件保存起来!
  3. 调用JDK NIO的方法,将原生的Channel设置为非阻塞!

这里会保存这几个对象,注意后面使用这些属性的时候,千万别想不起来这些属性哪里来的!

我们开始分析super方法

代码语言:javascript
复制
protected AbstractChannel(Channel parent) {
    //保存channel
    this.parent = parent;
    //channel的唯一标识
    id = newId();
    //jdk底层操作读写的类
    //unsafe 操作底层读写
    //NioServerSocketChannel创建的是  NioMessageUnsafe  这个是处理连接的
    //NioSocketChannel创建的是 NioByteUnsafe 这个是处理字节读取的
    unsafe = newUnsafe();
    //管道 pipeline 负责业务处理器编排
    pipeline = newChannelPipeline();
}
  1. 首先,我们会创建一个id,你可以把它认为是一个唯一标识,分为长标识和短标识,他们可以唯一标识一段管道,通过这行代码我们可以了解到,每一个Channel对象,都会由一个唯一的id与之对应!
  2. 创建一个newUnsafe, 想要进入到这行代码,就要知道NioServerSocketChannel的继承关系,不然一点出来一大片就比如这样:

image-20210428095058571

  1. ,你也不知道该看那个源码,想要了解这个,我就必须要了解他的类的层次结构,NioServerSocketChannel的继承关系入下:

image-20210428095001582

如图可以看到,NioServerSokcetChannel继承于AbstractNioMessageChannel,那么,我们自然而然就要进入到AbstractNioMessageChannel的实现:

代码语言:javascript
复制
@Override
protected AbstractNioUnsafe newUnsafe() {
    return new NioMessageUnsafe();
}

可以看到,这里返回的是一个NioMessageUnsafe,我希望大家着重记一个东西,就是NioServerSocketChannel对象里面的unsafe属性,是NioMessageUnsafe类型的!

我们知道了unsafe属性的类型之后,我们回到主线继续向下分析,该看pipeline的初始化了,我们进入到newChannelPipeline方法查看源码,这种通过查看上述的继承关系图,很轻易的就能够知道走到这个对象里面:

image-20210428095509567

代码语言:javascript
复制
protected DefaultChannelPipeline newChannelPipeline() {
    return new DefaultChannelPipeline(this);
}

这里创建了一个DefaultChannelPipeline对象,我们继续往下跟:

代码语言:javascript
复制
protected DefaultChannelPipeline(Channel channel) {
    this.channel = ObjectUtil.checkNotNull(channel, "channel");
    succeededFuture = new SucceededChannelFuture(channel, null);
    voidPromise =  new VoidChannelPromise(channel, true);

    tail = new TailContext(this);
    head = new HeadContext(this);

    head.next = tail;
    tail.prev = head;
}

这里的逻辑还是比较清晰的,我们重点关注后四行代码,注意这里创建了一个双向链表,默认存在tail和head节点,结构如下图:

image-20210428103747413

我们通过上述分析可以知道,再初始化NioServerSocketChannel的时候 pipeline属性会默认创建一个双向链表,并默认存在两个节点,头节点和尾节点,并组成双向链表!

至此,NioServerSocketChannel的创建就完成了,

我们直接回到最开始反射创建Channel的地方initAndRegister方法:

代码语言:javascript
复制
channel = channelFactory.newChannel();
init(channel);

这里通过反射创建一个channel对象,经过上述的过程已经变成了一个初具雏形的Channel,我们需要再对他进行一次初始化的调用,以便后续使用,我们跟进到init方法,至于为什么选下图指示的,就不用我多说了:

image-20210428101000185

代码语言:javascript
复制
//.option方法传入的
setChannelOptions(channel, options0().entrySet().toArray(EMPTY_OPTION_ARRAY), logger);
//.attr方法传入的
setAttributes(channel, attrs0().entrySet().toArray(EMPTY_ATTRIBUTE_ARRAY));

这里只是将我们构建的 .option和.attr传入的参数,设置进通道里面!

代码语言:javascript
复制
//拿到管道
ChannelPipeline p = channel.pipeline();
//获取worker Group
final EventLoopGroup currentChildGroup = childGroup;
//获取先前设置的 .childHandler
final ChannelHandler currentChildHandler = childHandler;
//获取先前设置的 .childOption方法
final Entry<ChannelOption<?>, Object>[] currentChildOptions =
    childOptions.entrySet().toArray(EMPTY_OPTION_ARRAY);
//获取先前设置的 .attr属性
final Entry<AttributeKey<?>, Object>[] currentChildAttrs = childAttrs.entrySet().toArray(EMPTY_ATTRIBUTE_ARRAY);
  1. 先获取我们在初始化ServerSocketChannel的时候创建的管道
  2. 获取在创建ServerBootstrap的时候设置的childxxxx()相关的属性
代码语言:javascript
复制
p.addLast(new ChannelInitializer<Channel>() {
    @Override
    public void initChannel(final Channel ch) {
        final ChannelPipeline pipeline = ch.pipeline();
        //将用户自定义的handler添加进管道  handler 是在构建ServerBootStr的时候传入的  handler
        ChannelHandler handler = config.handler();
        if (handler != null) {
            pipeline.addLast(handler);
        }
        ch.eventLoop().execute(() -> {
            pipeline.addLast(new ServerBootstrapAcceptor(
                ch, currentChildGroup, currentChildHandler, currentChildOptions, currentChildAttrs));
        });
    }
});

p是我们在创建Channel对象的时候创建的管道,默认存在两个节点,我们在上面讲解过,那么addLast方法是干什么呢? 我们看一下:

代码语言:javascript
复制
private void addLast0(AbstractChannelHandlerContext newCtx) {
    AbstractChannelHandlerContext prev = tail.prev;
    newCtx.prev = prev;
    newCtx.next = tail;
    prev.next = newCtx;
    tail.prev = newCtx;
}

这里我截取一段比较重要的代码,有关这一块详细的我会在后面的章节做具体讲解,从上面这段代码可以基本看明白,他是想双向链表追加一个handler,此时我们的管道就变成了如下图这种格式:

image-20210428104053217

三、总结

  1. 通过ServerBootstrap设置一些属性,譬如:NioServerSocketChannel、handler等等
  2. bind方法,创建NioServerSocketChannel
    1. 保存JDK原生的SocketChannel,并设置为非阻塞
    2. 创建并保存通道对应的唯一ID
    3. 创建一个unsafe对象,他是NioMessageUnsafe类型的
    4. 创建一个双向链表,存在Head和Tail节点
  3. 初始化创建完成的channel,设置自定义的配置,添加一个ChannelInitializer到双向链表!

至此NioServerSocketChannel初始化完成!

才疏学浅,如果文章中理解有误,欢迎大佬们私聊指正!欢迎关注作者的公众号,一起进步,一起学习!

代码语言:javascript
复制
本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2021-07-07,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 源码学徒 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 源码分析
    • 一、入口寻找
      • 1. channel()
      • 2. childHandler()
      • 3. bind方法
    • 二、源码分析
      • 三、总结
      领券
      问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档