【Netty】服务端启动Netty是都做了哪些事情?(一):创建服务端Channel

【Netty】服务端启动Netty是都做了哪些事情?(一):创建服务端Channel

Scroll Down

前言

本文就来来记录一下我们在启动Netty时,底层大概都做了哪些事情。不过因为我对Netty还只是刚刚入门,可能会跳过某些细节,也有可能会有写错的地方,如果漏了或错了,日后发现会进行补充或改正。

我将Netty启动简单分为了四个阶段,每一篇记录一个。

Netty version:4.1.6


先贴下代码

Server


import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.*;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import io.netty.util.AttributeKey;

public final class Server {

    public static void main(String[] args) throws Exception {
        // 两大线程
        // 对应Socket中Server的启动监听线程
        EventLoopGroup bossGroup = new NioEventLoopGroup(1);
        // 对应Socket中Client中主函数的线程
        EventLoopGroup workerGroup = new NioEventLoopGroup();

        try {
            // 辅助类
            ServerBootstrap server = new ServerBootstrap();
            // 装配线程
            server.group(bossGroup, workerGroup)
                    // 设置Channel的类型
                    .channel(NioServerSocketChannel.class)
                    // 给每个客户端的连接设置一些tcp的基本属性
                    .childOption(ChannelOption.TCP_NODELAY, true)
                    // 每次创建客户端时,绑定一些基本的属性
                    .childAttr(AttributeKey.newInstance("childAttr"), "childAttrValue")
                    // 服务端启动时做的逻辑
                    .handler(new ServerHandler())
                    // 给pipeline配置handler,Channel发生某种变化时对应的处理逻辑,todo 幕信里就写了一条链
                    .childHandler(new ChannelInitializer<SocketChannel>() {
                        @Override
                        public void initChannel(SocketChannel ch) {
                            // 往pipeline中添加Handler
                            //ch.pipeline().addLast(new AuthHandler());
                        }
                    });

            // 辅助类,绑定端口,这里使用了同步
            ChannelFuture future = server.bind(8888).sync();
            // 关闭
            future.channel().closeFuture().sync();
        } finally {
            bossGroup.shutdownGracefully();
            workerGroup.shutdownGracefully();
        }
    }
}

代码中的ServerHandler可以无视,因为追踪时暂时不会用到,所以暂时不贴。


下面的代码分析记录都是根据上面的代码。

启动的四个阶段


创建服务端Channel

大致流程:
创建服务端Channel大概流程.png

从上面Server代码中的bind()这一段开始追踪:

ChannelFuture future = server.bind(8888).sync();

进入bind()函数后,连跳,看到doBind():
bind追踪1.png

进入doBind(),看到第一行如下,找到了流程第二步:

final ChannelFuture regFuture = initAndRegister();

再进入initAndRegister(),找到如下一段,这段就是创建Channel的核心:

channel = channelFactory.newChannel();

再进入newChannel(ReflectiveChannelFactory),发现底层是通过反射调用无参构造函数的:

return clazz.newInstance();

虽然知道是通过反射构造的,但具体是哪个类呢?其实这个在我们写代码的时候自定义了,视角回到上面的Server的一段代码:

.channel(NioServerSocketChannel.class)

我们进入channel(),可以看到:

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

可以看到:

  • 其实是将我们设置的类,经几度包装最终返回一个channelFactory,也就是上面使用反射时构造的类。
  • channelFactory()函数没有什么特别操作,就是转下类型,所以我们应该将重点放到ReflectiveChannelFactory的构造函数上

服务端创建Channel其实到反射调用构造那里就已经结束了,下面只是继续探究ReflectiveChannelFactory的构造函数到底做了什么。



ReflectiveChannelFactory构造函数

先展示一下大致流程:

NioServerSocketChannel构造函数简图.png

图中的方法暂时先忽略参数。


现在,我们先进入NioServerSocketChannel的构造函数:

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

再进入上面的newSocket()函数:

    private static ServerSocketChannel newSocket(SelectorProvider provider) {
        try {
            return provider.openServerSocketChannel();
        } catch (IOException e) {
            throw new ChannelException(
                    "Failed to open a server socket.", e);
        }
    }

可以看到方法参数是SelectorProvider,返回值是ServerSocketChannel,这两是jdk的类,说明此处Netty是调用jdk的接口去创建类的,只是后来再进一步封装。(两个class都是jdk 1.4后引入的nio)


现在将视角转回最初的NioServerSocketChannel无参构造函数,进入this(),我们可以看见config类NioServerSocketChannelConfig,由于我们上面的Server代码设置了tcp,所以这里的配置类会帮我们初始化tcp的一些参数:

    /**
     * 位置:io.netty.channel.socket.nio.NioServerSocketChannel#NioServerSocketChannel(java.nio.channels.ServerSocketChannel)
     **/
    public NioServerSocketChannel(ServerSocketChannel channel) {
        super(null, channel, SelectionKey.OP_ACCEPT);
        config = new NioServerSocketChannelConfig(this, javaChannel().socket());
    }

然后,我们进入super函数:

    protected AbstractNioMessageChannel(Channel parent, SelectableChannel ch, int readInterestOp) {
        super(parent, ch, readInterestOp);
    }

继续super:

    protected AbstractNioChannel(Channel parent, SelectableChannel ch, int readInterestOp) {
        // parent为null,parent是给客户端channel存储服务端channel的
        super(parent);
        this.ch = ch;
        this.readInterestOp = readInterestOp;
        try {
            // nio关键,就是这里设置了非阻塞I/O
            ch.configureBlocking(false);
        } catch (IOException e) {
            try {
                ch.close();
            } catch (IOException e2) {
                if (logger.isWarnEnabled()) {
                    logger.warn(
                            "Failed to close a partially initialized socket.", e2);
                }
            }

            throw new ChannelException("Failed to enter non-blocking mode.", e);
        }
    }

其中ch.configureBlocking(false);就是负责配置非阻塞I/O。

再super,可以看到此处最终完成了AbstractChannel的构造:

    protected AbstractChannel(Channel parent) {
        // parent为null,如果是客户端channel则不同
        this.parent = parent;
        id = newId();
        // 返回NioMessageUnsafe(服务端),如果是构造客户端channel,则返回NioSocketChannelUnsafe
        unsafe = newUnsafe();
        pipeline = newChannelPipeline();
    }

以上就是Netty服务端创建Channel的过程了,虽然暂时忽略了一些细节,不过算是对流程有一个大概的认识,如果之后有更多参考资料,会继续补全。

关于提到的客户端channel,其实就是是NioSocketChannel,有兴趣的可以自己追下它的构造方法,我会在后面遇到的时候再写博客记录。