Netty解析-服务端启动

一、服务端启动

我们首先来看测试类,调试入口:io.netty.example.echo.EchoServer

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();
        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();
        }
    }
}

解释:

1、初始化了两个NioEventLoopGroup(可以理解为两个线程池),

          1、其中一个是bossGroup:用于accept客户端的请求,并创建SocketChannel转发到workGroup

          2、workGroup:用于处理真正的I/O操作等业务逻辑

接下来我们来看ServerBootstrap(服务端启动指引类),客户端的是Bootstrap后面再说

我们先看图大概了解一下工作原理

解释:

  1. 每个NioEventLoopGroup里边有多个NioEventLoop,一个NioEventLoop包含一个Selector和一个taskQueue
    1. NioEventLoop:就是一个线程,主要功能是将Client端的SocketChannel里的I/O操作绑定到Selector,Channel和Selector关系:1:N
    2. Selector:选择器,将I/O操作绑定到Selector后请求返回,Selector不断扫描准备好后进行通知
    3. taskQueue:将非I/O操作放入队列
  2. 服务启动时:
    1. bossGroup里的NioEventLoop让Selector监听Accept事件,并创建taskQueue
    2. workGroup里的NioEventLoop让Selector监听Read、Write等事件,并创建taskQueue
  3. Client发起请求后发生的3件事
    1. Selector不断循环扫描Accept事件
    2. 处理Accept事件,与客户端建立连接,并将I/O操作封装成SocketChannel发送到workGroup的NioEventLoop与Selector绑定;将非I/O操作放入taskQueue
    3. 处理taskQueue里的任务
  4. 请求发送到workGroup里时发生的3件事
    1. Selector不断扫描Read、Write等事件
    2. 处理事件并在NioSocketChannel可操作时发送ChannelPipeline(ChannelHandler链)进行处理
    3. 处理taskQueue任务
  5. Handler处理完毕后结果封装到future

我们来看启动时的代码分析

从上面b.bind(PORT)进入到AbstractBootstrap#bind(int inetPort);根据端口号生成SocketAddress(这里是本地启动所以没有host)

/**
     * Create a new {@link Channel} and bind it.
     */
    public ChannelFuture bind(int inetPort) {
        return bind(new InetSocketAddress(inetPort));
    }

然后进入doBind(finall SocketAddress localAddress)

 private ChannelFuture doBind(final SocketAddress localAddress) {
    	//初始化并注册返回ChannelFuture
        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(这也是一个Future)
            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;
        }
    }

解释:

     1、初始化并注册返回ChannelFuture(因为注册时异步的所以返回Future)

     2、绑定地址和ip

我们来看initAndRegister()

 final ChannelFuture initAndRegister() {
        Channel channel = null;
        try {
        	//Channel实例化
            channel = channelFactory.newChannel();
            //初始化Channel
            init(channel);
        } catch (Throwable t) {
            if (channel != null) {
                // channel can be null if newChannel crashed (eg SocketException("too many open files"))
                channel.unsafe().closeForcibly();
                // as the Channel is not registered yet we need to force the usage of the GlobalEventExecutor
                return new DefaultChannelPromise(channel, GlobalEventExecutor.INSTANCE).setFailure(t);
            }
            // as the Channel is not registered yet we need to force the usage of the GlobalEventExecutor
            return new DefaultChannelPromise(new FailedChannel(), GlobalEventExecutor.INSTANCE).setFailure(t);
        }
		//注册
        ChannelFuture regFuture = config().group().register(channel);
        if (regFuture.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 because 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 regFuture;
    }

解释:

     1、实例化Channel

     2、初始化

     3、注册

我们冲newChannel()断电进入

/**
     * Create a new instance using the given {@link ServerSocketChannel}.
	 * 使用给定的ServerSocketChannel穿件一个实例
	 * OP_ACCEPT:16:接收事件
     */
    public NioServerSocketChannel(ServerSocketChannel channel) {
        super(null, channel, SelectionKey.OP_ACCEPT);
        config = new NioServerSocketChannelConfig(this, javaChannel().socket());
    }

我们进入super()

/**
     * Create a new instance
     *
     * @param parent            the parent {@link Channel} by which this instance was created. May be {@code null}
     * @param ch                the underlying {@link SelectableChannel} on which it operates
     * @param readInterestOp    the ops to set to receive data from the {@link SelectableChannel}
     */
    protected AbstractNioChannel(Channel parent, SelectableChannel ch, int readInterestOp) {
        super(parent);
        this.ch = ch;
        this.readInterestOp = readInterestOp;
        try {
        	//设置channel为非阻塞模式
            ch.configureBlocking(false);
        } catch (IOException e) {
            try {
                ch.close();
            } catch (IOException e2) {
                logger.warn(
                            "Failed to close a partially initialized socket.", e2);
            }

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

我们再进入super()

/**
     * Creates a new instance.
     *
     * @param parent
     *        the parent of this channel. {@code null} if there's no parent.
     */
    protected AbstractChannel(Channel parent) {
        this.parent = parent;
        id = newId();
        unsafe = newUnsafe();
        pipeline = newChannelPipeline();
    }

我们看newChannelPipelin(),这个是不是就是我们上面说的ChannelHandler链

我们反回去看initAndRegister#init(channel)初始化操作

 @Override
    void init(Channel channel) {
    	//设置Channel可选操作集合
        setChannelOptions(channel, options0().entrySet().toArray(newOptionArray(0)), logger);
        //设置属性
        setAttributes(channel, attrs0().entrySet().toArray(newAttrArray(0)));
		//得到上步的pipeline
        ChannelPipeline p = channel.pipeline();
		
        final EventLoopGroup currentChildGroup = childGroup;
        final ChannelHandler currentChildHandler = childHandler;
        final Entry<ChannelOption<?>, Object>[] currentChildOptions =
                childOptions.entrySet().toArray(newOptionArray(0));
        final Entry<AttributeKey<?>, Object>[] currentChildAttrs = childAttrs.entrySet().toArray(newAttrArray(0));
		
        p.addLast(new ChannelInitializer<Channel>() {
            @Override
            public void initChannel(final Channel ch) {
                final ChannelPipeline pipeline = ch.pipeline();
                ChannelHandler handler = config.handler();
                //添加handler到pipeline
                if (handler != null) {
                    pipeline.addLast(handler);
                }

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

解释:初始化各种属性,将handler加入到pipeline中

我们再来看config().group().register()跟到里边

 @Override
    protected void doRegister() throws Exception {
        boolean selected = false;
        for (;;) {
            try {
            	//原生的将Nio的channel对象注册到Selector上
                selectionKey = javaChannel().register(eventLoop().unwrappedSelector(), 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;
                }
            }
        }
    }

解释:到这一步Channel就已经初始化完毕,并且将自己注册到Selector

进入doBind()绑定端口,我们看AbstractChannel#bind(final SocketAddress localAddress,final ChannelPromise promise)

   @Override
   public final void bind(final SocketAddress localAddress, final ChannelPromise promise) {
            assertEventLoop();

            if (!promise.setUncancellable() || !ensureOpen(promise)) {
                return;
            }

            // See: https://github.com/netty/netty/issues/576
            if (Boolean.TRUE.equals(config().getOption(ChannelOption.SO_BROADCAST)) &&
                localAddress instanceof InetSocketAddress &&
                !((InetSocketAddress) localAddress).getAddress().isAnyLocalAddress() &&
                !PlatformDependent.isWindows() && !PlatformDependent.maybeSuperUser()) {
                // Warn a user about the fact that a non-root user can't receive a
                // broadcast packet on *nix if the socket is bound on non-wildcard address.
                logger.warn(
                        "A non-root user can't receive a broadcast packet if the socket " +
                        "is not bound to a wildcard address; binding to a non-wildcard " +
                        "address (" + localAddress + ") anyway as requested.");
            }

            boolean wasActive = isActive();
            try {
            	//调用底层绑定ip端口
                doBind(localAddress);
            } catch (Throwable t) {
                safeSetFailure(promise, t);
                closeIfClosed();
                return;
            }

            if (!wasActive && isActive()) {
				//获取注册时生成的SelectionKey绑定真正感兴趣的accept事件
                invokeLater(new Runnable() {
                    @Override
                    public void run() {
                        pipeline.fireChannelActive();
                    }
                });
            }
			//设置future成功
            safeSetSuccess(promise);
        }

这里主要做了:

       1、调用底层绑定ip端口;

       2、获取注册时生产的SelectionKey绑定正在感兴趣的accept事件,这样就可以接受客户端请求了

       3、设置返回结果

绑定直接看NioServerSocketChannel#doBind(SocketAddress localAddress)

@Override
    protected void doBind(SocketAddress localAddress) throws Exception {
        if (PlatformDependent.javaVersion() >= 7) {
            javaChannel().bind(localAddress, config.getBacklog());
        } else {
            javaChannel().socket().bind(localAddress, config.getBacklog());
        }
    }

调用原始bind方法将ServerSocketChannel绑定ip端口,监听访问

接下来看绑定accept事件:pipeline.fireChannelActive()----->AbstractNioChannel#doBeginRead()

protected void doBeginRead() throws Exception {
        // Channel.read() or ChannelHandlerContext.read() was called
        final SelectionKey selectionKey = this.selectionKey;
        if (!selectionKey.isValid()) {
            return;
        }

        readPending = true;
		//accept事件:16
        final int interestOps = selectionKey.interestOps();
        if ((interestOps & readInterestOp) == 0) {
        	//真正标注感兴趣操作accept事件
            selectionKey.interestOps(interestOps | readInterestOp);
        }
    }

到此为止,

1、创建ServerSocketChannel并将其注册到Selector

2、绑定ip端口

3、将真正感兴趣的accept事件注册到Selector

4、设置future服务启动完毕

自我感觉理解太表面,异步代码太多,不好调试,大概应该是这个思路

总结:

  1. 创建boss、work线程池
    1. boss线程池:负责处理客户端的Accept请求,并转发到work线程池
    2. work线程池:负责处理IO操作
  2. 创建服务端启动类ServerBootstrap,设置属性(异步、线程池等),设置childhandler(用于处理接收新建的请求)
  3. 初始化SorcketChannel,获取channelPipeline,将默认handler放入Pipeline,并注册到Selector
  4. 将感兴趣的事件(Accept)绑定到selector
  5. 初始化NioServerSocketChannel,获取channelPipeline,将默认handler放入Pipeline,并注册到Selector
  6. 将NioServerSocketChannel绑定ip端口,监听服务
  7. 将感兴趣的事件(read,write)绑定到selector

 

公众号主要记录各种源码、面试题、微服务技术栈,帮忙关注一波,非常感谢

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值