关于程序员:NioServerSocketChannel的初始化源码

39次阅读

共计 8214 个字符,预计需要花费 21 分钟才能阅读完成。

源码剖析

上一节课咱们就 NioEventLoop 的初始化进行了一个初步的解说,他是 Netty 很重要的一个类,前面还有针对它的剖析,大家先对我后面介绍的组件有一个初步的意识!认真的看,看到前面会有一种恍然大悟的感觉!

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

一、入口寻找

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

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 外部查看源码:

public B channel(Class<? extends C> channelClass) {
    return channelFactory(new ReflectiveChannelFactory<C>(ObjectUtil.checkNotNull(channelClass, "channelClass")
    ));
}

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

咱们看到上述的代码,将咱们传入的通道类型 NioServerSocketChannel 包装为了一个 ReflectiveChannelFactory 对象,从名字咱们根本能够晓得,他是和反射相干的工厂,而后把 ReflectiveChannelFactory 对象传入到 channelFactory 办法外面,咱们跟进去看下源码:

public B channelFactory(ChannelFactory<? extends C> channelFactory) {
    ..... 疏忽不必要代码......
    // 保留 SocketChannel 的包装对象
    this.channelFactory = channelFactory;
    return self();}

咱们能够看到,他只是将咱们的 NioServerSocketChannel 的包装对象给保留了起来!

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

public ReflectiveChannelFactory(Class<? extends T> clazz) {
    try {
        //.channel 传入的  NioServerSocketChannel
        this.constructor = clazz.getConstructor();} catch (NoSuchMethodException e) {........................................}
}

咱们能够看到,ReflectiveChannelFactory 的逻辑也很简略,就只是将咱们传入的 NioServerSocketChannel, 获取他的空构造方法,而后保存起来!

2. childHandler()

咱们再回头看 childHandler 办法,根本的原理是一样的:

public ServerBootstrap childHandler(ChannelHandler childHandler) {this.childHandler = ObjectUtil.checkNotNull(childHandler, "childHandler");
    return this;
}

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

3. bind 办法

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

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

首先,他将 port 端口包装为一个 InetSocketAddress 对象,和咱们 NIO 开发中基本一致,咱们持续跟上来:

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

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 源码外面:

@Override
public T newChannel() {
    try {
        // 反射创立  NioServerSocketChannel
        return constructor.newInstance();} catch (Throwable t) {........................................}
}

这段代码置信大家就及其相熟了,利用咱们再构建 ReflectiveChannelFactory 的时候保留的构造方法对象,创立进去一个 NioServerSocketChannel 对象!因为之前获取的是无参结构,所以,咱们须要进入到 NioServerSocketChannel 的无参结构外面寻找他的逻辑!

二、源码剖析

后面根本形容了咱们要剖析 NioServerSocketChannel 的源码入口,上面开始正式的剖析它,咱们进入到 NioServerSocketChannel 的无参构造方法:

/**
     * 创立一个新实例
     */
public NioServerSocketChannel() {//DEFAULT_SELECTOR_PROVIDER:SelectorProvider.provider()
    //newSocket 创立一个 channel
    this(newSocket(DEFAULT_SELECTOR_PROVIDER));
}

首先,咱们先关注一下 newSocket 办法:

private static ServerSocketChannel newSocket(SelectorProvider provider) {
    try {return provider.openServerSocketChannel();
    } catch (IOException e) {.........................}
}

newSocket 办法应用 provider 创立了一个 JDK 底层的 ServerSocketChannel,留神该对象是 JDK 原始的通道对象,至此,咱们根本能够推断出,Netty 的 Channel 是基于 JDK 的 Channel 进行封装的!咱们持续回到无参构造方法:

public NioServerSocketChannel(ServerSocketChannel channel) {
    // 保留对应的配置项  同时保留关注连贯事件 OP_ACCEPT
    super(null, channel, SelectionKey.OP_ACCEPT);
    // 创立一个配置类 你保留的是以后对象以及 jdk 底层的 socket
    config = new NioServerSocketChannelConfig(this, javaChannel().socket());
}

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

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 办法

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 的继承关系,不然一点进去一大片就比方这样:

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

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

@Override
protected AbstractNioUnsafe newUnsafe() {return new NioMessageUnsafe();
}

能够看到,这里返回的是一个 NioMessageUnsafe,我心愿大家着重记一个货色,就是 NioServerSocketChannel 对象外面的 unsafe 属性,是 NioMessageUnsafe 类型的!

咱们晓得了 unsafe 属性的类型之后,咱们回到主线持续向下剖析,该看 pipeline 的初始化了,咱们进入到 newChannelPipeline 办法查看源码, 这种通过查看上述的继承关系图,很轻易的就可能晓得走到这个对象外面:

protected DefaultChannelPipeline newChannelPipeline() {return new DefaultChannelPipeline(this);
}

这里创立了一个 DefaultChannelPipeline 对象,咱们持续往下跟:

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 节点,构造如下图:

咱们通过上述剖析能够晓得,再初始化 NioServerSocketChannel 的时候 pipeline 属性会默认创立一个双向链表,并默认存在两个节点,头节点和尾节点,并组成双向链表!

至此,NioServerSocketChannel 的创立就实现了,

咱们间接回到最开始反射创立 Channel 的中央 initAndRegister 办法:

channel = channelFactory.newChannel();
init(channel);

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

//.option 办法传入的
setChannelOptions(channel, options0().entrySet().toArray(EMPTY_OPTION_ARRAY), logger);
//.attr 办法传入的
setAttributes(channel, attrs0().entrySet().toArray(EMPTY_ATTRIBUTE_ARRAY));

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

// 拿到管道
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()相干的属性
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 办法是干什么呢? 咱们看一下:

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

这里我截取一段比拟重要的代码,无关这一块具体的我会在前面的章节做具体解说,从下面这段代码能够根本看明确,他是想双向链表追加一个 handler,此时咱们的管道就变成了如下图这种格局:

三、总结

  1. 通过 ServerBootstrap 设置一些属性,譬如:NioServerSocketChannel、handler 等等
  2. bind 办法,创立 NioServerSocketChannel

    1. 保留 JDK 原生的 SocketChannel,并设置为非阻塞
    2. 创立并保留通道对应的惟一 ID
    3. 创立一个 unsafe 对象,他是 NioMessageUnsafe 类型的
    4. 创立一个双向链表,存在 Head 和 Tail 节点
  3. 初始化创立实现的 channel,设置自定义的配置,增加一个 ChannelInitializer 到双向链表!

至此 NioServerSocketChannel 初始化实现!

本文由 mdnice 多平台公布

正文完
 0