Netty启动流程总结

1、什么是netty

   Netty,一个异步事件驱动的网络应用框架,用于快速开发可维护的高性能服务器和客户端。(对java的NIO进行封装,简化开发)

1.1 java Nio

BIO模型,一个client线程请求服务端之后,服务端accept请求,并新建线程来处理请求。当并发量变大,服务端创建线程数量过多,线程上下午切换等问题造成性能下降甚至宕机,同时,如果服务端发出读事件的动作,若数据还未准备好,就会阻塞当前线程。  
NIO模型,un-blocking-io,采用非阻塞方式来读取客户端传给服务端的数据。进行了IO复用与非阻塞模式读取数据。
IO多路复用(又称事件驱动):一个线程,监控多个socket的读写就绪状况,这样,多个描述符的IO操作可以在一个线程内并发交替的顺序的完成,这就叫IO多路复用。映射在NIO中即Selector的监听。
非阻塞模式读取数据:线程进行读取数据,若数据为准备好,线程立马返回,不会阻塞当前线程。

1.2 NIO中的组件

1、channel(管道)
主要做数据的写入与读出操作。
2、Buffer(缓冲)
从channel中读出的数据与写入到channel中的数据,都是存放在Buffer中。Buffer有三个属性,容量(capacity),位置(position),长度限制(limit)。写模式下,limit-position,表示还有多少数据可以写入。
3、Selector,即IO多路复用的线程。

1.3 NIO初始化流程

1、初始化channel。
2、初始化Selector并把channel注册到Selector中。
3、轮训Selector返回的SelectKeys,找出就绪事件做对应的处理。

2、netty启动流程分析

客户端代码:

public final class EchoClient {

    static final boolean SSL = System.getProperty("ssl") != null;
    static final String HOST = System.getProperty("host", "127.0.0.1");
    static final int PORT = Integer.parseInt(System.getProperty("port", "8007"));
    static final int SIZE = Integer.parseInt(System.getProperty("size", "256"));

    public static void main(String[] args) throws Exception {
        // Configure SSL.git
        final SslContext sslCtx;
        if (SSL) {
            sslCtx = SslContextBuilder.forClient()
                .trustManager(InsecureTrustManagerFactory.INSTANCE).build();
        } else {
            sslCtx = null;
        }

        // Configure the client.
        EventLoopGroup group = new NioEventLoopGroup();
        try {
            Bootstrap b = new Bootstrap();
            b.group(group)
             .channel(NioSocketChannel.class)
             .option(ChannelOption.TCP_NODELAY, true)
             .handler(new ChannelInitializer<SocketChannel>() {
                 @Override
                 public void initChannel(SocketChannel ch) throws Exception {
                     ChannelPipeline p = ch.pipeline();
                     if (sslCtx != null) {
                         p.addLast(sslCtx.newHandler(ch.alloc(), HOST, PORT));
                     }
                     //p.addLast(new LoggingHandler(LogLevel.INFO));
                     p.addLast(new EchoClientHandler());
                 }
             });

            // Start the client.
            ChannelFuture f = b.connect(HOST, PORT).sync();

            // Wait until the connection is closed.
            f.channel().closeFuture().sync();
        } finally {
            // Shut down the event loop to terminate all threads.
            group.shutdownGracefully();
        }
    }
}

服务端代码:

public final class EchoServer {

    static final boolean SSL = System.getProperty("ssl") != null;
    static final int PORT = Integer.parseInt(System.getProperty("port", "8007"));

    public static void main(String[] args) throws Exception {
        // Configure SSL.
        final SslContext sslCtx;
        if (SSL) {
            SelfSignedCertificate ssc = new SelfSignedCertificate();
            sslCtx = SslContextBuilder.forServer(ssc.certificate(), ssc.privateKey()).build();
        } else {
            sslCtx = null;
        }

        // Configure the server.
        EventLoopGroup bossGroup = new NioEventLoopGroup(1);
        EventLoopGroup workerGroup = new NioEventLoopGroup();
        SingleThreadEventLoop singleThreadEventLoop;
        final EchoServerHandler serverHandler = new EchoServerHandler();
        try {
            ServerBootstrap b = new ServerBootstrap();
            b.group(bossGroup, workerGroup)
             .channel(NioServerSocketChannel.class)
             .option(ChannelOption.SO_BACKLOG, 100)
             .handler(new LoggingHandler(LogLevel.INFO))
             .childHandler(new ChannelInitializer<SocketChannel>() {
                 @Override
                 public void initChannel(SocketChannel ch) throws Exception {
                     ChannelPipeline p = ch.pipeline();
                     if (sslCtx != null) {
                         p.addLast(sslCtx.newHandler(ch.alloc()));
                     }
                     //p.addLast(new LoggingHandler(LogLevel.INFO));
                     p.addLast(serverHandler);
                 }
             });

            // Start the server.
            ChannelFuture f = b.bind(PORT).sync();

            // Wait until the server socket is closed.
            f.channel().closeFuture().sync();
        } finally {
            // Shut down all event loops to terminate all threads.
            bossGroup.shutdownGracefully();
            workerGroup.shutdownGracefully();
        }
    }
}

启动流程简述:

  • 服务端先创建两个线程池组,分别为BossGroup与WorkerGroup,BossGroup用来监听客户端的连接,并把已连接的客户端分配给WorkerGroup线程池组做处理。初始化好两个线程池组之后,将两个线程池组存放在ServerBootStrap中,并设置服务端监听类NioServerSocketChannel。为该类添加Handler,本例中即为LoggingHandler。给WorkerGroup线程组中管理的SocketChannel线程添加自定义的handler类,通过childHandler()方法加入。所有准备工作做好之后,通过bind()方法启动服务器监听工作。
  • 客户端创建一个线程组,并设置到一个新建的BootStrap中,设置客户端连接类NioSocketChannel,并为该类设置自定义的handler,通过Connect来启动连接。

2.1 客户端启动流程详解

1、创建NioEventLoopGroup。

public NioEventLoopGroup(int nThreads) {
        this(nThreads, (Executor) null);
    }
protected MultithreadEventLoopGroup(int nThreads, Executor executor, Object... args) {
       super(nThreads == 0 ? DEFAULT_EVENT_LOOP_THREADS : nThreads, executor, args);
 }

判断传入的线程数是否为0,如果不为0,则使用我们传入的值,如果未0,则设置为操作系统内核数*2。

static {
        DEFAULT_EVENT_LOOP_THREADS = Math.max(1, SystemPropertyUtil.getInt(
                "io.netty.eventLoopThreads", NettyRuntime.availableProcessors() * 2));
    }

最终的构造方法为MultithreadEventExecutorGroup()

protected MultithreadEventExecutorGroup(int nThreads, Executor executor,
                                            EventExecutorChooserFactory chooserFactory, Object... args) {
        //初始化Executor
        if (executor == null) {
            executor = new ThreadPerTaskExecutor(newDefaultThreadFactory());
        }
        //创建nThreads个线程池(单例线程池)数组
        children = new EventExecutor[nThreads];

        for (int i = 0; i < nThreads; i ++) {
            boolean success = false;
            try {
                children[i] = newChild(executor, args);
                success = true;
            } catch (Exception e) {
     			。。。。
            } finally {
         
        }
        chooser = chooserFactory.newChooser(children);
        final FutureListener<Object> terminationListener = new FutureListener<Object>() {
            @Override
            public void operationComplete(Future<Object> future) throws Exception {
                if (terminatedChildren.incrementAndGet() == children.length) {
                    terminationFuture.setSuccess(null);
                }
            }
        };
        for (EventExecutor e: children) {
            e.terminationFuture().addListener(terminationListener);
        }
        Set<EventExecutor> childrenSet = new LinkedHashSet<EventExecutor>(children.length);
        Collections.addAll(childrenSet, children);
        readonlyChildren = Collections.unmodifiableSet(childrenSet);
    }

在这个构造方法中,主要做了一下四步操作。
(1)、初始化executor
(2)、初始化children属性指向的数组
(3)、通过children数组创建chooser,判断数组长度是不是2的指数倍数,创建不同的实例。
(4)、为每一个children数组中的实例设置监听,并把children数组中的元素加入到set中。

2、接着创建Bootstrap对象,并设置group与channel等参数。

 Bootstrap b = new Bootstrap();
            b.group(group)
             .channel(NioSocketChannel.class)
             .option(ChannelOption.TCP_NODELAY, true)
             .handler(new ChannelInitializer<SocketChannel>() {
                 @Override
                 public void initChannel(SocketChannel ch) throws Exception {
                     ChannelPipeline p = ch.pipeline();
                     if (sslCtx != null) {
                         p.addLast(sslCtx.newHandler(ch.alloc(), HOST, PORT));
                     }
                     //p.addLast(new LoggingHandler(LogLevel.INFO));
                     p.addLast(new EchoClientHandler());
                 }
             });

            // Start the client.
            ChannelFuture f = b.connect(HOST, PORT).sync();

            // Wait until the connection is closed.
            f.channel().closeFuture().sync();

3、之后通过connect方法让客户端启动。也是整个netty客户端的入口。
connect->doResolveAndConnect方法,在这个方法下,主要做两个操作。
1、initAndRegister()。初始化NIOSocketChannel对象,并初始化,在注册
2、doResolveAndConnect0()。做实际的连接操作。
进入到initAndRegister()方法中。
通过以下代码,创建NIOSocketChannel对象。

channel = channelFactory.newChannel();

而这个Factory在bootstrap.channel(NIOSocketChannel)这个方法被调用的时候进行创建。
当创建NIOSocketChannel对象时,进入该对象的构造方法。

public NioSocketChannel(SelectorProvider provider) {
        this(newSocket(provider));
    }

通过provider.openSocketChannel()方法,创建了一个SocketChannel对象。

public NioSocketChannel(Channel parent, SocketChannel socket) {
        super(parent, socket);
        config = new NioSocketChannelConfig(this, socket.socket());
    }
.......
protected AbstractNioByteChannel(Channel parent, SelectableChannel ch) {
        super(parent, ch, SelectionKey.OP_READ);
    }

进入到实际的构造方法AbstractNioChannel()中。
在这,设置this.ch = ch,即保存我们创建的SocketChannel。
设置此Channel的模式为非阻塞,并且设置读事件。

   protected AbstractNioChannel(Channel parent, SelectableChannel ch, int readInterestOp) {
        //parent:null ch:serverSocketChannel,readInterestOp:accept
        super(parent);
        this.ch = ch;
        //accept事件
        this.readInterestOp = readInterestOp;
        try {
            ch.configureBlocking(false);
        } catch (IOException e) {
    }

调用super方法。

protected AbstractChannel(Channel parent) {
        //parent:null
        this.parent = parent;
        // id 是通过算法生成唯一ID,ChannelId
        id = newId();
        //NIO 的 message 的操作类,是 Netty 的核心组件,unsafe
        unsafe = newUnsafe();
        pipeline = newChannelPipeline();
    }

为每个channel创建为一Id,unsafe类与pipeline。
unsafe是netty与java NIO进行通信的类。
pipeline是netty核心类,保存我们自定义的handler。内部维持一个双向链表结构。
返回initAndRegister()方法中。调用Init(channel)方法,主要是加入我们自定义的handler到我们创建的pipeline中。

void init(Channel channel) throws Exception {
        ChannelPipeline p = channel.pipeline();
        p.addLast(config.handler());
    }

进入到addLast方法中,最终调用DefaultChannelPipeline中的addLast方法。在这个addLast()方法中,将我们的handler包装成一个DefaultChannelHandlerContext对象。调用addLast0方法做实际的插入。插入之后调用以下方法,进行注册。

 ChannelFuture regFuture = config().group().register(channel);

最终调用unsafe的register方法。register方法设置好AbstractChannel.this.eventLoop之后,调用register0方法。reigister方法中又主要做doRegister()方法。doRegister()方法调用AbstractNioChannel的doRegister()方法。在内部通过for循环注册,把当前的SocketChannel注册到线程组eventLoop的其中一个线程上,每个线程组相当于一个Selector。完成实际的注册工作。完成注册工作之后,调用pipeline.fireChannelRegistered()方法。
此方法重head结点开始,通过next方法向下遍历,找到下一个inbound结点,执行该节点的channelRegistered方法。在调用这个方法时,会调用到ChannelInitializer的之channelRegistered()方法,这个方法会把我们自定义的handler加入到pipeline中,加入自定义的handler之后,又会删除ChannelInitializer对象。代码如下:

public final void channelRegistered(ChannelHandlerContext ctx) throws Exception {
        if (initChannel(ctx)) {
            ctx.pipeline().fireChannelRegistered();
        } else {
            ctx.fireChannelRegistered();
        }
    }
 private boolean initChannel(ChannelHandlerContext ctx) throws Exception {
        if (initMap.putIfAbsent(ctx, Boolean.TRUE) == null) { // Guard against re-entrance.
            try {
                initChannel((C) ctx.channel());
            } catch (Throwable cause) {
                exceptionCaught(ctx, cause);
            } finally {
                remove(ctx);
            }
            return true;
        }
        return false;
    }

之后,initAndRegister()方法执行完成。调用connect方法做实际的连接服务端工作。而最终又会调用head的connect方法,head的connect方法调用unsafe的connect方法完成连接。

2.2 服务端启动流程详解

  • 看完客户端的启动过程,再来看服务端的启动流程。
    服务端新建两个线程组,分为BoosGroup和WorkerGroup,创建引导类ServerBootstap,并将两个线程组放入到引导类中,同时,创建boosGroup的handler以及workerGroup的childHanler,设置好后,通过bind方法启动流程。
    同样的,EventLoopGroup的初始化操作也NioServerSocketChannel的初始化同客户端的类似,就不做分析,我们依然从bind方法开始,bind()方法最终调用的事doBind()方法,doBind()方法中,主要有两步。
    1、initAndRegister()。
    2、doBind0()。
    进入initAndRegister的init方法中,该方法被ServerBootstrap重写。在这个方法中,添加了ChannelInitializer代表的handler。进入addLast方法,包装handler成context对象,加入到pipeline中。返回,进入到register方法中。调用register0()方法,register0调用doRegister()方法。doRegister方法从bossGroup线程组中选中一个线程池作为Selector,将当前线程注册到Selector中,注册之后,调用pipeline.fireChannelRegistered();该方法从head结点开始,找到下一个inBound结点,执行结点的channelRegistered()方法,调用过程中,进入ChannelInitializer的initChannel()方法中,在该方法中,调用initChannel方法,该方法会加入一个ServerBootstrapAcceptor对象,加入完成之后,会删除ChannelInitializer这个handler结点。之后,继续向下调用,直到调用到tail结点的channelRegistered方法。而在ServerBootstrapAcceptor对象中,又一个channelRead方法,该方法中加入了我们自定义的Handler加入到pipeline中,同时,将此SocketChannel与workerGroup绑定在一起。那么这个channelRead()方法何时被调用的呢?当一个client连接到服务端的时候,NioServerSocketChannel会有一个SelectionKey.OP_ACCEPT 就绪事件,接着会调用NioServerSocketChannel.doReadMessages方法,而这个方法中会创建多个NIOSocketChannel对象,同时传入对象(this),由此我们知道创建这个NioSocketChannel的父channel就是NioServerSocketChannel实例。接下来,Netty的pipeline机制会把读事件发送到各个handler中,于是就触发我们前面提到的ServerBootstrapAcceptor.channelRead方法了,所以我们的处理请求的操作也已经分配给worker线程来处理了。由此,init方法也执行完成,执行完成之后,调用doBind0()方法,而最终会又tail结点触发,调用到head结点,处理bind,head调用unsafe做实际的bind操作。
    服务端就不做代码分析,纯粹总结。
    服务端关键的地方在于,init方法,加入了ServerBootStrapAcceptor对象,该对象的channelRead为每一个新建的SocketChannel对象分配pipeline。

3 梳理请求流程

EventLoopGroup的每个实例为SingleThreadEventExecutor,而这个类中有一个Thread属性,而这个属性的作用就是让调用NioEventLoop.run(),这个Thread属性代表的线程在register0方法被执行的时候启动,即eventLoop.execute()提交一个任务的时候,就会让这个线程启动。继续跟踪NioEventLoop.run() 方法

protected void run() {
        for (;;) {
            try {
                switch (selectStrategy.calculateStrategy(selectNowSupplier, hasTasks())) {
                    case SelectStrategy.CONTINUE:
                        continue;
                    case SelectStrategy.SELECT:
                        select(wakenUp.getAndSet(false));
                        if (wakenUp.get()) {
                            selector.wakeup();
                        }
                        // fall through
                    default:
                }

                cancelledKeys = 0;
                needsToSelectAgain = false;
                final int ioRatio = this.ioRatio;
                if (ioRatio == 100) {
                    try {
                        processSelectedKeys();
                    } finally {
                        // Ensure we always run tasks.
                        runAllTasks();
                    }
                } else {
                    final long ioStartTime = System.nanoTime();
                    try {
                        processSelectedKeys();
                    } finally {
                        // Ensure we always run tasks.
                        final long ioTime = System.nanoTime() - ioStartTime;
                        runAllTasks(ioTime * (100 - ioRatio) / ioRatio);
                    }
                }
            } catch (Throwable t) {
                handleLoopException(t);
            }
            // Always handle shutdown even if the loop processing threw an exception.
            try {
                if (isShuttingDown()) {
                    closeAll();
                    if (confirmShutdown()) {
                        return;
                    }
                }
            } catch (Throwable t) {
                handleLoopException(t);
            }
        }
    }

通过for循环判断当前是否还有task任务,若有任务调用selectNow()方法,否则,调用select(oldWakenUp) 方法。即若有任务,则立马返回,执行task,否则阻塞等待。当IO事件返回时,就处理IO事件,分别是processSelectedKeys()和runAllTasks();方法,第一个方法找到IO事件并处理,第二个方法是运行taskQueue任务。而processSelectedKey 方法底层就是调用unsafe类的read,write和connect事件,而unsafe处理这些事件即调用pipeline中的handler做对应处理。处理完后,如果任务队列中还有任务,则取出任务做执行。
到此,netty的原码暂时告一段落。

4 总结

1、初始化线程池组。
2、初始化引导类。
3、调用bind或connect方法做服务触发。
4、initAndRegister方法中,通过工程产生对应的NioServerSocketChannel或NioSocketChannel,并初始化,构建unsafe和pipeline。
5、调用init方法初始化,添加默认的handler,ChannelInitializer,然后ChannelInitializer的方法来添加自定义的handler。服务端通过ServerBootstrapAcceptor添加自定义的handler,并把SocketChannel与客户端绑定。
6、调用Register方法,做注册,最终调用register0。
7、通过doRegister()方法,将当前SocketChannel与线程组中的Selector绑定。
8、调用pipeline.fireChannelRegistered()方法,将自定义handler注册到pipeline中,workergroup与SocketChannel做绑定。
9、调用doBind方法,做实际绑定。
10、调用结束。
当一个client连接到server时,server产生一个accept事件,调用doReadMessages创建NioSocketChannel对象,当client传来数据是,服务端的NioEventLoop会针对client不同的事件,调用unsafe中处理对应事件的方法。而unsafe通过调用pipeline来做对应方法的执行。而对于pipeline中的handler来说,outbound事件都是请求事件。即某件事情发生,outbound做事件通知,从tail到head.

ChannelHandlerContext.bind(SocketAddress, ChannelPromise)
ChannelHandlerContext.connect(SocketAddress, SocketAddress, ChannelPromise)
ChannelHandlerContext.write(Object, ChannelPromise)
ChannelHandlerContext.flush()
ChannelHandlerContext.read()
ChannelHandlerContext.disconnect(ChannelPromise)
ChannelHandlerContext.close(ChannelPromise)
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章