netty4.0 启动流程简介

又见netty更新,顺便看了一下example,发觉改动非常大,于是感觉应该看看源码,记录之

首先从example中的telnet例子开始:  

 public void run() throws Exception {
        EventLoopGroup bossGroup = new NioEventLoopGroup();
        EventLoopGroup workerGroup = new NioEventLoopGroup();
        try {
            ServerBootstrap b = new ServerBootstrap();
            b.group(bossGroup, workerGroup)
             .channel(NioServerSocketChannel.class)
             .childHandler(new TelnetServerInitializer());

            b.bind(port).sync().channel().closeFuture().sync();
        } finally {
            bossGroup.shutdownGracefully();
            workerGroup.shutdownGracefully();
        }
    }

以上是example中的telnet的TelnetServer启动端的代码实现。

bossGroup与workerGroup与前代netty一样,netty都是基于Reactor模型的一个socket io框架实现,Reactor模型可以专门开一个专题论述,这里就不详细说明了。而bossGroup,workerGroup正是Reactor中的部分体现。

*注意:每一代netty框架发布,一般都应该关注其线程池的实现,历代都有比较明显的改进。这里的EventLoopGroup类就是新一代netty框架的线程池实现了。相对于netty3.0的线程池实现,netty4.0的线程的变化相当大。关于netty4.0的线程池,会在后面开辟一个专门的栏目对其进行讨论。这边只要注意,EventLoopGroup是一个netty4.0的线程池实现就足够了。

ServerBootstrap顾名思意,就知道是一个服务端启动项类。由它出发,我们来看一下netty4.0是一个怎么样的启动流程,以此引出netty4.0的内部实现:

首先由上面的代码片段我们大致可以看到,ServerBootstrap的对象实例产生后,首先设置group,这是netty的Reactor模型中的线程池实现。设置完了线程池实例,我们就给服务器设置一个服务器通道(severChannel)实例(NioServerSocketChannel看名字就知道是什么),这个实例对象是netty服务端套接字实现的关键,将在后续开辟一个专门的栏目论述它的具体实现。最后是我们熟悉的设置handler,只不过,这里为什么是childHandler,工作原理又是怎么样的,都会在后续文章出得到答案,这里暂时不详述。

好,所有主要的设置项全部就绪后,就是我们的bind操作,一系列的bind操作完成后,我们的服务器随即启动,一切顺利的话,就可以进行访问了。

接下来,我们对bind的流程进行一下论述,以深入netty代码,在论述之前,先要交代一下ServerBootstrap类的细节:



                                                                                           图 1-1

图1-1是ServerBootStrap的一个简略的类继承关系图。中间列出的相关方法与Telnet服务器的启动流程相关,下面详述:

首先调用AbstractBootstrap类中的bind方法,最后调用进AbstractBootstrap类中的doBind方法:

 private ChannelFuture doBind(final SocketAddress localAddress) {
        final ChannelFuture regPromise = initAndRegister();
        final Channel channel = regPromise.channel();
        final ChannelPromise promise = channel.newPromise();
        if (regPromise.isDone()) {
            doBind0(regPromise, channel, localAddress, promise);
        } else {
            regPromise.addListener(new ChannelFutureListener() {
                @Override
                public void operationComplete(ChannelFuture future) throws Exception {
                    doBind0(future, channel, localAddress, promise);
                }
            });
        }

        return promise;
    }

以上是doBind方法的实现代码:

首先调用initAndRegister方法,这是一个对channel的初始化与启动类,调用这个方法这后,以本例来讲,将会使用ServerBootstrap类中的channelFactory属性,来生成一个ServerChannel实例。channelFactory属性是一个ServerBootstrapChannelFactory对象,这个属性在ServerBootstrap调用b.channel(NioServerSocketChannel.class)(详细可以参考上面telnetServer的run方法)这一方法时,默认设置为ServerBootstrap的内部类ServerBootstrapChannelFactory(图1-1中有体现)。调用这一内部类的newChannel方法,就会实例化一个NioServerSocketChannel对象实例。然后,再对这个实例对象进行注册,产生selectkeys。这一过程相信熟悉java的nio socket api的童鞋们,都非常熟悉。这里就不详细叙述了,那么,我们来看一下具体代码:

final ChannelFuture initAndRegister() {
        Channel channel = createChannel();
        try {
            init(channel);
        } catch (Throwable t) {
            channel.unsafe().closeForcibly();
            return channel.newFailedFuture(t);
        }

        ChannelPromise regPromise = channel.newPromise();
        channel.unsafe().register(regPromise);
        if (regPromise.cause() != null) {
            if (channel.isRegistered()) {
                channel.close();
            } else {
                channel.unsafe().closeForcibly();
            }
        }

        // If we are here and the promise is not failed, it's one of the following cases:
        // 1) If we attempted registration from the event loop, the registration has been completed at this point.
        //    i.e. It's safe to attempt bind() or connect() now beause the channel has been registered.
        // 2) If we attempted registration from the other thread, the registration request has been successfully
        //    added to the event loop's task queue for later execution.
        //    i.e. It's safe to attempt bind() or connect() now:
        //         because bind() or connect() will be executed *after* the scheduled registration task is executed
        //         because register(), bind(), and connect() are all bound to the same thread.

        return regPromise;
    }

 首先createChannel方法产生一个服务器通道(serverChannel):

  Channel createChannel() {
        EventLoop eventLoop = group().next();//这里得到线程池中的一根子线程
        return channelFactory().newChannel(eventLoop, childGroup);//调用ServerBootstrapChannelFactory对象实例产生设置的NioServerSocketChannel对象实例
    }

这里的channelFactory方法得到b.channel(NioServerSocketChannel.class)调用时设置的默认的内部类ServerBootstrapChannelFactory的实例,再根据设置的class类别实例化之,得到一个NioServerSocketChannel对象,童鞋们可以比照netty4.0项目的源代码,看清楚这一过程。

接下去调用ServerBootstrap对象实例的init方法,对管道进行初始化:

 @Override
    void init(Channel channel) throws Exception {
        final Map<ChannelOption<?>, Object> options = options();//这里的选项是对于NioServerSocketChannel而言的
        synchronized (options) {
            channel.config().setOptions(options);
        }

        final Map<AttributeKey<?>, Object> attrs = attrs();
        synchronized (attrs) {
            for (Entry<AttributeKey<?>, Object> e: attrs.entrySet()) {
                @SuppressWarnings("unchecked")
                AttributeKey<Object> key = (AttributeKey<Object>) e.getKey();
                channel.attr(key).set(e.getValue());
            }
        }

        ChannelPipeline p = channel.pipeline();
        if (handler() != null) {
            p.addLast(handler());
        }

        final ChannelHandler currentChildHandler = childHandler;
        final Entry<ChannelOption<?>, Object>[] currentChildOptions;
        final Entry<AttributeKey<?>, Object>[] currentChildAttrs;
        synchronized (childOptions) {
            currentChildOptions = childOptions.entrySet().toArray(newOptionArray(childOptions.size()));//childOptions是对于NioSocketChannel而言的
        }
        synchronized (childAttrs) {
            currentChildAttrs = childAttrs.entrySet().toArray(newAttrArray(childAttrs.size()));
        }

        p.addLast(new ChannelInitializer<Channel>() {
            @Override
            public void initChannel(Channel ch) throws Exception {
                ch.pipeline().addLast(new ServerBootstrapAcceptor(currentChildHandler, currentChildOptions,
                        currentChildAttrs));
            }
        });
    }

以上是ServerBootstrap类中的具体的init流程,从得到options(这里是serverChannel的各种设置参数),设置serverChannel中的各参数 -> 得到serverChannel中的att以及设置 -> 设置serverChannel的handler -> 设置childOptions(这是应答通道,相当于普通的socketChannel) -> 设置child通道的att -> 最后为NioServerSocketChannel对象实例的pipeline注册一个ServerBootstrapAcceptor对象的handler,以处理客户端向服务端发起的链接请求(acceptor在netty3中是一个内部类,这里使用handler的方式解决)。

对serverChannel进行初始化后,就是对其进行register操作(channel.unsafe().register(regPromise))。从上面列出的代码片段中可以看到,这里执行register操作的是channel的unsafe属性。这是一个实现Unsafe interface的对象实例。这里的Unsafe类的接口定义如下:



 

以上是Unsafe interface

看了定义的方法就可以感觉到,unsafe接口定义了对于channel对象的一些封装,他是一个channel操作的代理类。netty4.0中Unsafe接口定义于Channel接口中,是一个内部类接口,这也就印证了,Unsafe是一个对Channel的代理类接口,它是专门为对Channel进行操作的代理类定义的接口。

通过unsafe对象实例,我们可以实现对于channel的register操作。从而将selector对象与channel关联,建立套接字链接。

AbstractNioChannel的doRegister方法实现了register操作,如下所示:

  protected void doRegister() throws Exception {
        boolean selected = false;
        for (;;) {
            try {
                selectionKey = javaChannel().register(eventLoop().selector, 0, this);
                return;
            } catch (CancelledKeyException e) {
                if (!selected) {
                    // Force the Selector to select now as the "canceled" SelectionKey may still be
                    // cached and not removed because no Select.select(..) operation was called yet.
                    eventLoop().selectNow();
                    selected = true;
                } else {
                    // We forced a select operation on the selector before but the SelectionKey is still cached
                    // for whatever reason. JDK bug ?
                    throw e;
                }
            }
        }
    }

无论是NioServerSocketChannel对象实例还是NioSocketChannel对象实例,皆使用此方法,实现Selector对象实例与channel的关联。只是最终调用的流程有些区别,这会在后面的channel篇中提到。当register结束以后,bind流程也进入到了尾声。我们使用Promise对象实现的sync方法来等待serverChannel被关闭。当serverChannel接收到关闭指令的时候,就会调用closeFuture的sync来等待服务器彻底关闭。从而整个服务流程结束。

*注:以上流程涉及二方面的概念:

首先是Future类的概念,以及它的子类Promise类的概念,在前几代的netty框架中,Future类一直是一个重要的概念。现在,在netty4.0中又出现了一个前代中从来没有出现的Promise类。熟悉netty的用户(包括mina用户)都应该对Future非常熟悉,它是一种多线程情况下,线程间通信的一种手段。是对wait、 nofity的一次封装。以达到多线程代码中的事件一致性等目的。而在netty4.0是,Promise类作为Future的一个实现类,又加入了sync等方法,对Future进行增强。加入了线程唤醒前的listener机制。使子线程控制更加强大。这些都会在另外开辟的专题中详细论述。

其次就是pipeline(管道)。netty框架是以事件驱动为基础的套接字框架。它的实现在历代netty都非常重要。pipeline的基本实现,相信netty的老用户都很熟悉,这里就不详述了。总得来说,它是一个监听链,以事件驱动的方式,来完成监听事项,处理netty内部事务,以及用户定义的外部事务。是netty框架实现业务的主要场所。具体内部细节请关注后面的netty pipeline专题。

这里粗略介绍了整个服务器的大致启动流程,还存有不少描述得不太清楚的点。比如,NioServerSocketChannel与NioSocketChannel调用doRegister的过程是不一样的,中间涉及到流程的不同,没有详细描述。netty4.0中的线程池中的改进,也没有论述。重要的inbound outbound机制。以及netty框架中向来非常重要的buffer这一块,encode,decode机制、channel的继承体系,Promise类等等。

这些都将在将来的专题介绍中一一介绍。

本人文笔粗陋,不到之外还请名位观者包涵。欢迎转载,转载请注明出处,谢谢

下面可能会完成对于netty4.0的线程池的介绍。

猜你喜欢

转载自sunwuwuwu.iteye.com/blog/1962090