乐趣区

关于java:netty系列之BootstrapServerBootstrap和netty中的实现

简介

尽管 netty 很弱小,然而应用 netty 来构建程序却是很简略,只须要把握特定的 netty 套路就能够写出弱小的 netty 程序。每个 netty 程序都须要一个 Bootstrap,什么是 Bootstrap 呢?Bootstrap 翻译成中文来说就是鞋拔子,在计算机世界中,Bootstrap 指的是疏导程序,通过 Bootstrap 能够轻松构建和启动程序。

在 netty 中有两种 Bootstrap:客户端的 Bootstrap 和服务器端的 ServerBootstrap。两者有什么不同呢?netty 中这两种 Bootstrap 到底是怎么工作的呢?一起来看看吧。

Bootstrap 和 ServerBootstrap 的分割

首先看一下 Bootstrap 和 ServerBootstrap 这两个类的继承关系,如下图所示:

<img src=”https://img-blog.csdnimg.cn/4eda7812e7eb4825aa471bbc679e7cad.png” style=”zoom:67%;” />

能够看到 Bootstrap 和 ServerBootstrap 都是继承自 AbstractBootstrap, 而 AbstractBootstrap 则是实现了 Cloneable 接口。

AbstractBootstrap

有仔细的同学可能会问了,下面图中还有一个 Channel,channel 跟 AbstractBootstrap 有什么关系呢?

咱们来看下 AbstractBootstrap 的定义:

public abstract class AbstractBootstrap<B extends AbstractBootstrap<B, C>, C extends Channel> implements Cloneable

AbstractBootstrap 承受两个泛型参数,一个是 B 继承自 AbstractBootstrap, 一个是 C 继承自 Channel。

咱们先来察看一下一个简略的 Bootstrap 启动须要哪些元素:

EventLoopGroup bossGroup = new NioEventLoopGroup();
        EventLoopGroup workerGroup = new NioEventLoopGroup();
        try {ServerBootstrap b = new ServerBootstrap();
            b.group(bossGroup, workerGroup)
                    .channel(NioServerSocketChannel.class)
                    .childHandler(new ChannelInitializer<SocketChannel>() {
                        @Override
                        public void initChannel(SocketChannel ch) throws Exception {ch.pipeline().addLast(new FirstServerHandler());
                        }
                    })
                    .option(ChannelOption.SO_BACKLOG, 128)
                    .childOption(ChannelOption.SO_KEEPALIVE, true);

            // 绑定端口并开始接管连贯
            ChannelFuture f = b.bind(port).sync();
            // 期待 server socket 敞开
            f.channel().closeFuture().sync();

下面的代码是一个最根本也是最规范的 netty 服务器端的启动代码。能够看到和 Bootstrap 相干的元素有这样几个:

  1. EventLoopGroup,次要用来进行 channel 的注册和遍历。
  2. channel 或者 ChannelFactory,用来指定 Bootstrap 中应用的 channel 的类型。
  3. ChannelHandler, 用来指定具体 channel 中音讯的解决逻辑。
  4. ChannelOptions, 示意应用的 channel 对应的属性信息。
  5. SocketAddress,bootstrap 启动是绑定的 ip 和端口信息。

目前看来和 Bootstrap 相干的就是这 5 个值, 而 AbstractBootstrap 的构造函数中也就定义了这些属性的赋值:

    AbstractBootstrap(AbstractBootstrap<B, C> bootstrap) {
        group = bootstrap.group;
        channelFactory = bootstrap.channelFactory;
        handler = bootstrap.handler;
        localAddress = bootstrap.localAddress;
        synchronized (bootstrap.options) {options.putAll(bootstrap.options);
        }
        attrs.putAll(bootstrap.attrs);
    }

示例代码中的 group,channel,option 等办法实际上都是向这些属性中赋值,并没有做太多的业务操作。

留神,AbstractBootstrap 中只存在一个 group 属性,所以两个 group 属性是在 ServerBootstrap 中增加的扩大属性。

在 Bootstrap 中,channel 其实是有两种赋值办法,一种是间接传入 channel,另外一种办法是传入 ChannelFactory。两者的实质都是一样的,咱们看下 channel 是怎么转换成为 ChannelFactory 的:

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

channelClass 被封装在一个 ReflectiveChannelFactory 中,最终还是设置的 channelFactory 属性。

AbstractBootstrap 中真正启动服务的办法就是 bind,bind 办法传入的是一个 SocketAddress, 返回的是 ChannelFuture, 很显著,bind 办法中会创立一个 channel。咱们来看一下 bind 办法的具体实现:

   private ChannelFuture doBind(final SocketAddress localAddress) {final ChannelFuture regFuture = initAndRegister();
        final Channel channel = regFuture.channel();
        if (regFuture.cause() != null) {return regFuture;}

        if (regFuture.isDone()) {
            // At this point we know that the registration was complete and successful.
            ChannelPromise promise = channel.newPromise();
            doBind0(regFuture, channel, localAddress, promise);
            return promise;
        } else {
            // Registration future is almost always fulfilled already, but just in case it's not.
            final PendingRegistrationPromise promise = new PendingRegistrationPromise(channel);
            regFuture.addListener(new ChannelFutureListener() {
                @Override
                public void operationComplete(ChannelFuture future) throws Exception {Throwable cause = future.cause();
                    if (cause != null) {
                        // Registration on the EventLoop failed so fail the ChannelPromise directly to not cause an
                        // IllegalStateException once we try to access the EventLoop of the Channel.
                        promise.setFailure(cause);
                    } else {
                        // Registration was successful, so set the correct executor to use.
                        // See https://github.com/netty/netty/issues/2586
                        promise.registered();

                        doBind0(regFuture, channel, localAddress, promise);
                    }
                }
            });
            return promise;
        }
    }

在 doBind 办法中,首先调用 initAndRegister 办法去初始化和注册一个 channel。

channel 是通过 channelFactory 的 newChannel 办法来创立的:

channel = channelFactory.newChannel();

接着调用初始化 channel 的 init 办法。这个 init 办法在 AbstractBootstrap 中并没有实现,须要在具体的实现类中实现。

有了 channel 之后,通过调用 EventLoopGroup 的 register 办法将 channel 注册到 EventLoop 中,并将注册生成的 ChannelFuture 返回。

而后通过判断返回的 regFuture 的状态,来判断 channel 是否注册胜利,如果注册胜利, 最初调用 doBind0 办法, 实现最初的绑定工作:

    private static void doBind0(
            final ChannelFuture regFuture, final Channel channel,
            final SocketAddress localAddress, final ChannelPromise promise) {// This method is invoked before channelRegistered() is triggered.  Give user handlers a chance to set up
        // the pipeline in its channelRegistered() implementation.
        channel.eventLoop().execute(new Runnable() {
            @Override
            public void run() {if (regFuture.isSuccess()) {channel.bind(localAddress, promise).addListener(ChannelFutureListener.CLOSE_ON_FAILURE);
                } else {promise.setFailure(regFuture.cause());
                }
            }
        });
    }

因为 eventLoop 自身是一个 Executor,所以能够执行一个具体的命令的,在它的 execute 办法中,传入了一个新的 Runnable 对象,在其中的 run 办法中执行了 channel.bind 办法,将 channel 跟 SocketAddress 进行绑定。

到此,Bootstrap 的 bind 办法执行结束。

咱们再来回顾一下 bind 办法的根本流程:

  1. 通过 ChannelFactory 创立一个 channel。
  2. 将 channel 注册到 Bootstrap 中的 EventLoopGroup 中。
  3. 如果 channel 注册胜利,则调用 EventLoopGroup 的 execute 办法,将 channel 和 SocketAddress 进行绑定。

是不是很清晰?

讲完 AbstractBootstrap,接下来,咱们再持续探讨一下 Bootstrap 和 ServerBootstrap。

Bootstrap 和 ServerBootstrap

首先来看下 Bootstrap,Bootstrap 次要应用在客户端应用, 或者 UDP 协定中。

先来看下 Bootstrap 的定义:

public class Bootstrap extends AbstractBootstrap<Bootstrap, Channel> 

Bootstrap 和 AbstractBootstrap 相比,次要多了一个属性和一个办法。

多的一个属性是 resolver:

private static final AddressResolverGroup<?> DEFAULT_RESOLVER = DefaultAddressResolverGroup.INSTANCE;

private volatile AddressResolverGroup<SocketAddress> resolver =
            (AddressResolverGroup<SocketAddress>) DEFAULT_RESOLVER;

AddressResolverGroup 外面有一个 IdentityHashMap, 它的 key 是 EventExecutor,value 是 AddressResolver:

    private final Map<EventExecutor, AddressResolver<T>> resolvers =
            new IdentityHashMap<EventExecutor, AddressResolver<T>>();

实际上 AddressResolverGroup 保护了一个 EventExecutor 和 AddressResolver 的映射关系。

AddressResolver 次要用来解析近程的 SocketAddress 的地址。因为近程的 SocketAddress 可能并不是一个 IP 地址,所以须要应用 AddressResolver 解析一下。

这里的 EventExecutor 实际上就是 channel 注册的 EventLoop。

另外 Bootstrap 作为一个客户端的利用,它须要连贯到服务器端,所以 Bootstrap 类中多了一个 connect 到近程 SocketAddress 的办法:

    public ChannelFuture connect(SocketAddress remoteAddress) {ObjectUtil.checkNotNull(remoteAddress, "remoteAddress");
        validate();
        return doResolveAndConnect(remoteAddress, config.localAddress());
    }

connect 办法和 bind 办法的逻辑相似,只是多了一个 resolver 的 resolve 过程。

解析结束之后,会调用 doConnect 办法,进行真正的连贯:

    private static void doConnect(final SocketAddress remoteAddress, final SocketAddress localAddress, final ChannelPromise connectPromise) {// This method is invoked before channelRegistered() is triggered.  Give user handlers a chance to set up
        // the pipeline in its channelRegistered() implementation.
        final Channel channel = connectPromise.channel();
        channel.eventLoop().execute(new Runnable() {
            @Override
            public void run() {if (localAddress == null) {channel.connect(remoteAddress, connectPromise);
                } else {channel.connect(remoteAddress, localAddress, connectPromise);
                }
                connectPromise.addListener(ChannelFutureListener.CLOSE_ON_FAILURE);
            }
        });
    }

能够看到 doConnect 办法和 doBind 办法很相似,都是通过以后 channel 注册的 eventLoop 来执行 channel 的 connect 或者 bind 办法。

再看一下 ServerBootstrap 的定义:

public class ServerBootstrap extends AbstractBootstrap<ServerBootstrap, ServerChannel>

因为是 ServerBootstrap 用在服务器端,所以不选 Bootstrap 那样去解析 SocketAddress,所以没有 resolver 属性。

然而对应服务器端来说,能够应用 parent EventLoopGroup 来承受连贯,而后应用 child EventLoopGroup 来执行具体的命令。所以在 ServerBootstrap 中多了一个 childGroup 和对应的 childHandler:

    private volatile EventLoopGroup childGroup;
    private volatile ChannelHandler childHandler;

因为 ServerBootstrap 有两个 group,所以 ServerBootstrap 蕴含一个含有两个 EventLoopGroup 的 group 办法:

    public ServerBootstrap group(EventLoopGroup parentGroup, EventLoopGroup childGroup) 

还记得 bind 办法须要实现的 init 办法吗?咱们看下 ServerBootstrap 中 init 的具体逻辑:

   void init(Channel channel) {setChannelOptions(channel, newOptionsArray(), logger);
        setAttributes(channel, newAttributesArray());

        ChannelPipeline p = channel.pipeline();

        final EventLoopGroup currentChildGroup = childGroup;
        final ChannelHandler currentChildHandler = childHandler;
        final Entry<ChannelOption<?>, Object>[] currentChildOptions = newOptionsArray(childOptions);
        final Entry<AttributeKey<?>, Object>[] currentChildAttrs = newAttributesArray(childAttrs);

        p.addLast(new ChannelInitializer<Channel>() {
            @Override
            public void initChannel(final Channel ch) {final ChannelPipeline pipeline = ch.pipeline();
                ChannelHandler handler = config.handler();
                if (handler != null) {pipeline.addLast(handler);
                }

                ch.eventLoop().execute(new Runnable() {
                    @Override
                    public void run() {
                        pipeline.addLast(new ServerBootstrapAcceptor(ch, currentChildGroup, currentChildHandler, currentChildOptions, currentChildAttrs));
                    }
                });
            }
        });
    }

首先是设置 channel 的一些属性,而后通过 channel.pipeline 办法取得 channel 对应的 pipeline,而后向 pipeline 中增加 channelHandler。

这些都是惯例操作,咱们要留神的是最初通过 channel 注册到的 eventLoop,将 ServerBootstrapAcceptor 退出到了 pipeline 中。

很显著 ServerBootstrapAcceptor 自身应该是一个 ChannelHandler, 它的次要作用就是用来承受连贯:

    private static class ServerBootstrapAcceptor extends ChannelInboundHandlerAdapter

咱们来看一下它的 channelRead 办法:

        public void channelRead(ChannelHandlerContext ctx, Object msg) {final Channel child = (Channel) msg;

            child.pipeline().addLast(childHandler);

            setChannelOptions(child, childOptions, logger);
            setAttributes(child, childAttrs);

            try {childGroup.register(child).addListener(new ChannelFutureListener() {
                    @Override
                    public void operationComplete(ChannelFuture future) throws Exception {if (!future.isSuccess()) {forceClose(child, future.cause());
                        }
                    }
                });
            } catch (Throwable t) {forceClose(child, t);
            }
        }

因为 server 端承受的是客户端 channel 的 connect 操作,所以对应的 channelRead 中的对象实际上是一个 channel。这里把这个承受到的 channel 称作 child。通过给这个 child channel 增加 childHandler,childOptions 和 childAttrs,一个可能解决 child channel 申请的逻辑就造成了。

最初将 child channel 注册到 childGroup 中,至此整个 ServerBootstrapAcceptor 承受 channel 的工作就实现了。

这里最妙的局部就是将客户端的 channel 通过 server 端的 channel 传到 server 端,而后在 server 端为 child channel 装备 handler 进行具体的业务解决,十分奇妙。

总结

通过具体分析 AbstractBootstrap,Bootstrap 和 ServerBootstrap 的构造和实现逻辑,置信大家对 netty 服务的启动流程有了大略的意识,前面咱们会具体解说 netty 中的 channel 和十分重要的 eventLoop。

本文已收录于 http://www.flydean.com/03-1-netty-boots…-serverbootstrap/

最艰深的解读,最粗浅的干货,最简洁的教程,泛滥你不晓得的小技巧等你来发现!

欢送关注我的公众号:「程序那些事」, 懂技术,更懂你!

退出移动版