关于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/

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

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

评论

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

这个站点使用 Akismet 来减少垃圾评论。了解你的评论数据如何被处理