netty源码浅析-客户端启动流程

客户端启动流程

我们以下面的demo作为本次netty源码分析的入口

public static void main(String[] args) throws Exception {
        // 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 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();
        }
    }
}

客户端启动和服务端启动有很多类似的地方,首先我们看到相比于服务端,客户端只创建了一个NioEventLoopGroup,不太清楚创建过程的同学可以翻看一下前面的分析。与服务端引导类不同,客户端使用Bootstrap进行引导,同样通过group方法绑定eventLoop,通过channel方法设置为nio模式,同样也通过handler方法绑定了channel初始化器,以此向channelPipeline中添加channelHandler。完成上面的配置后,调用入口方法connect方法,我们跟踪方法执行会走到Bootstrap的doResolveAndConnect方法

public ChannelFuture connect(String inetHost, int inetPort) {
        return connect(InetSocketAddress.createUnresolved(inetHost, inetPort));
    }
private ChannelFuture doResolveAndConnect(final SocketAddress remoteAddress, final SocketAddress localAddress) {
        //1.创建channel,完成初始化和注册
        final ChannelFuture regFuture = initAndRegister();
        final Channel channel = regFuture.channel();

        if (regFuture.isDone()) {
            if (!regFuture.isSuccess()) {
                return regFuture;
            }
            return doResolveAndConnect0(channel, remoteAddress, localAddress, channel.newPromise());
        } else {
            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) {
                        promise.setFailure(cause);
                    } else {
                        promise.registered();
                        doResolveAndConnect0(channel, remoteAddress, localAddress, promise);
                    }
                }
            });
            return promise;
        }
    }

通过上面的代码我们可以看到,首先和服务端执行bind方法相同,先执行initAndRegister方法,先通过反射创建 channel然后完成初始化,之后完成注册。与服务端不同这里创建的是NioSocketChannel对象,我们先跟踪到这个类的无参构造方法中。

public NioSocketChannel() {
        this(DEFAULT_SELECTOR_PROVIDER);
    }
public NioSocketChannel(SelectorProvider provider) {
        //通过SelectorProvider创建SocketChannel
        this(newSocket(provider));
    }
public NioSocketChannel(Channel parent, SocketChannel socket) {
        //1.调用父类构造函数
        //socket jdk底层Nio的channel
        super(parent, socket);
        //2.创建config对象
        config = new NioSocketChannelConfig(this, socket.socket());
    }

这里首先调用父类的构造函数,我们先跟踪到父类中

 protected AbstractNioByteChannel(Channel parent, SelectableChannel ch) {
        //parent=null  socketChannel 关注的IO事件
        super(parent, ch, SelectionKey.OP_READ);
    }  

可以看到继续调用父类构造函数,设置了关注IO事件

protected AbstractNioChannel(Channel parent, SelectableChannel ch, int readInterestOp) {
        super(parent);
        this.ch = ch;//nio的ServerSocketChannel
        this.readInterestOp = readInterestOp;//连接事件
        try {
            ch.configureBlocking(false);//设置非阻塞模式
        } catch (IOException e) {
            try {
                ch.close();//发生异常就关闭channel
            } catch (IOException e2) {
                if (logger.isWarnEnabled()) {
                    logger.warn(
                            "Failed to close a partially initialized socket.", e2);
                }
            }

            throw new ChannelException("Failed to enter non-blocking mode.", e);
        }
    }
 protected AbstractChannel(Channel parent) {
        this.parent = parent;//null
        //1.channel唯一标识id
        id = newId();
        //2.unsafe是channel的内部类,封装了很多底层nio的操作
        unsafe = newUnsafe();
        //3.每创建一个channel就会创建一个channelPipe用于组织handler
        pipeline = newChannelPipeline();
    }

这段代码在服务端启动流程中我们也看到过,继续调用父类的构造函数,给每一个NioSocketChannel创建一个唯一的id,channel内部创建了一个unsafe对象,里面封装了一些IO的底层操作,还创建了一个pipeline对象,用来组织handler。然后将channel设置为非阻塞。
完成NioSocketChannel的初始化后,我们继续跟踪到init初始化方法中。这里客户端的引导类是BootStrap,所以会走到这个类的init方法中

void init(Channel channel) throws Exception {
        //向pipeline中加入ChannelInitializer
        ChannelPipeline p = channel.pipeline();
        p.addLast(config.handler());
        //配置
        final Map<ChannelOption<?>, Object> options = options0();
        synchronized (options) {
            setChannelOptions(channel, options, logger);
        }
        //属性
        final Map<AttributeKey<?>, Object> attrs = attrs0();
        synchronized (attrs) {
            for (Entry<AttributeKey<?>, Object> e: attrs.entrySet()) {
                channel.attr((AttributeKey<Object>) e.getKey()).set(e.getValue());
            }
        }
    }

这里方法很加单,首先向pipeline中添加了一个Channellnitializer,这个我们在上面也已经分析过 就是新加了
个PendingHandlerAddedTask任务,当channel完成注册的时候回调handlerAdd方法,调用Channellnitializer的 initChannel方法,向pipeline中添加handler,然后再把Channellnitializer初始化器从链表中删除。然后对channel的属性和配置进行解析。
channel初始化完成后开始执行register方法

public ChannelFuture register(Channel channel) {
       //1.创建一个DefaultChannelPromise用于返回执行结果
       return register(new DefaultChannelPromise(channel, this));
   }
public ChannelFuture register(final ChannelPromise promise) {
       ObjectUtil.checkNotNull(promise, "promise");//校验
       //调用unsafe的register方法
       promise.channel().unsafe().register(this, promise);
       return promise;
   }

最终会走到AbstractChannel的内部类AbstractUnsafe的register方法

public final void register(EventLoop eventLoop, final ChannelPromise promise) {
            if (eventLoop == null) {
                throw new NullPointerException("eventLoop");
            }
            if (isRegistered()) {
                promise.setFailure(new IllegalStateException("registered to an event loop already"));
                return;
            }
            if (!isCompatible(eventLoop)) {
                promise.setFailure(
                        new IllegalStateException("incompatible event loop type: " + eventLoop.getClass().getName()));
                return;
            }
            //关联channel和eventLoop
            AbstractChannel.this.eventLoop = eventLoop;
            //1.当前运行的线程是eventLoopGroup线程,及内部线程
            if (eventLoop.inEventLoop()) {
                register0(promise);
            } else {
                try {
                    //2.调用execute方法将register0方法包装成一个任务放入NioEventLoop的
                    //任务队列中,由NioEventLoop线程执行
                    eventLoop.execute(new Runnable() {
                        @Override
                        public void run() {
                            register0(promise);
                        }
                    });
                } catch (Throwable t) {
                    logger.warn(
                            "Force-closing a channel whose registration task was not accepted by an event loop: {}",
                            AbstractChannel.this, t);
                    closeForcibly();
                    closeFuture.setClosed();
                    safeSetFailure(promise, t);
                }
            }
        }
private void register0(ChannelPromise promise) {
            try {
                // check if the channel is still open as it could be closed in the mean time when the register
                // call was outside of the eventLoop
                if (!promise.setUncancellable() || !ensureOpen(promise)) {
                    return;
                }
                //1.设置第一此执行注册标志
                boolean firstRegistration = neverRegistered;
                //2.完成channel注册,返回连接selectionKey
                doRegister();
                neverRegistered = false;
                registered = true;

                // Ensure we call handlerAdded(...) before we actually notify the promise. This is needed as the
                // user may already fire events through the pipeline in the ChannelFutureListener.
                //3.检查是否触发HandlerAdded事件
                pipeline.invokeHandlerAddedIfNeeded();
                //4.设置promise注册成功
                safeSetSuccess(promise);
                //5.触发ChannelRegistered事件
                pipeline.fireChannelRegistered();
                // Only fire a channelActive if the channel has never been registered. This prevents firing
                // multiple channel actives if the channel is deregistered and re-registered.
                //6.检查channel是不是已经属于连接成功状态,这里channel还没有bind,所以返回false
                if (isActive()) {
                    if (firstRegistration) {
                        pipeline.fireChannelActive();
                    } else if (config().isAutoRead()) {
                        // This channel was registered before and autoRead() is set. This means we need to begin read
                        // again so that we process inbound data.
                        //
                        // See https://github.com/netty/netty/issues/4805
                        //7.给channel注册读事件
                        beginRead();
                    }
                }
            } catch (Throwable t) {
                // Close the channel directly to avoid FD leak.
                closeForcibly();
                closeFuture.setClosed();
                safeSetFailure(promise, t);
            }
        }

下面的注册流程和我们对服务端的分析相同,由于这里调用的线程是main线程,所以会执行else,将Runnable任务添加到NioEventLoop的task任务队列中,并且检查NioEventLoop线程是否启动,如果没有启动就将这个线程启动,并设置thread属性就是当前运行的NioEventLoop,然后执行其内部的run方法,循环执行,上面说的三个事
到这里完成了NioSocketChannel的初始化和注册了,下面开始正在执行connect方法

private ChannelFuture doResolveAndConnect0(final Channel channel, SocketAddress remoteAddress,
                                               final SocketAddress localAddress, final ChannelPromise promise) {
        try {
            //获取channel关联的eventLoop
            final EventLoop eventLoop = channel.eventLoop();
            //获取连接地址
            final AddressResolver<SocketAddress> resolver = this.resolver.getResolver(eventLoop);

            if (!resolver.isSupported(remoteAddress) || resolver.isResolved(remoteAddress)) {
                doConnect(remoteAddress, localAddress, promise);
                return promise;
            }

            final Future<SocketAddress> resolveFuture = resolver.resolve(remoteAddress);
            //解析是否完成
            if (resolveFuture.isDone()) {
                final Throwable resolveFailureCause = resolveFuture.cause();
                //解析失败设置失败原因
                if (resolveFailureCause != null) {
                    channel.close();
                    promise.setFailure(resolveFailureCause);
                } else {
                    //如果成功开始执行doConnect方法
                    doConnect(resolveFuture.getNow(), localAddress, promise);
                }
                return promise;
            }
            //如果上面的解析还没有完成就添加一个回调事件,如果解析完成后还是会调用doConnect方法
            resolveFuture.addListener(new FutureListener<SocketAddress>() {
                @Override
                public void operationComplete(Future<SocketAddress> future) throws Exception {
                    if (future.cause() != null) {
                        channel.close();
                        promise.setFailure(future.cause());
                    } else {
                        doConnect(future.getNow(), localAddress, promise);
                    }
                }
            });
        } catch (Throwable cause) {
            promise.tryFailure(cause);
        }
        return promise;
    }
private static void doConnect(
            final SocketAddress remoteAddress, final SocketAddress localAddress, final ChannelPromise connectPromise) {
        //获取channel
        final Channel channel = connectPromise.channel();
        //调用connect方法
        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);
            }
        });
    }

然后会走到AbstractChannelHandlerContext的connect方法

public ChannelFuture connect(
            final SocketAddress remoteAddress, final SocketAddress localAddress, final ChannelPromise promise) {

        if (remoteAddress == null) {
            throw new NullPointerException("remoteAddress");
        }
        if (isNotValidPromise(promise, false)) {
            // cancelled
            return promise;
        }
        //查询outbound的ChannelHandlerContext,这里就是headContext
        final AbstractChannelHandlerContext next = findContextOutbound();
        EventExecutor executor = next.executor();
        if (executor.inEventLoop()) {
            next.invokeConnect(remoteAddress, localAddress, promise);
        } else {
            safeExecute(executor, new Runnable() {
                @Override
                public void run() {
                    next.invokeConnect(remoteAddress, localAddress, promise);
                }
            }, promise, null);
        }
        return promise;
    }

经过逐层跟踪调用跟踪会走到AbstractNioUnsafe#connect方法

public final void connect(
                final SocketAddress remoteAddress, final SocketAddress localAddress, final ChannelPromise promise) {
            if (!promise.setUncancellable() || !ensureOpen(promise)) {
                return;
            }

            try {
                if (connectPromise != null) {
                    // Already a connect in process.
                    throw new ConnectionPendingException();
                }
                //检查是不是channel是不是已经是活跃状态
                boolean wasActive = isActive();
                //连接远程服务端
                if (doConnect(remoteAddress, localAddress)) {
                    //如果连接成功设置promise状态并通知,如果没有则关闭
                    fulfillConnectPromise(promise, wasActive);
                } else {
                    //如果连接失败
                    connectPromise = promise;
                    requestedRemoteAddress = remoteAddress;

                    // Schedule connect timeout.
                    //默认30秒
                    int connectTimeoutMillis = config().getConnectTimeoutMillis();
                    if (connectTimeoutMillis > 0) {
                        connectTimeoutFuture = eventLoop().schedule(new Runnable() {
                            @Override
                            public void run() {
                                ChannelPromise connectPromise = AbstractNioChannel.this.connectPromise;
                                ConnectTimeoutException cause =
                                        new ConnectTimeoutException("connection timed out: " + remoteAddress);
                                //定时30秒后,如果连接失败就报错并关闭
                                if (connectPromise != null && connectPromise.tryFailure(cause)) {
                                    close(voidPromise());
                                }
                            }
                        }, connectTimeoutMillis, TimeUnit.MILLISECONDS);
                    }
                    //给promise添加一个Listener回调,如果取消了连接任务则将连接任务取消并关闭
                    promise.addListener(new ChannelFutureListener() {
                        @Override
                        public void operationComplete(ChannelFuture future) throws Exception {
                            if (future.isCancelled()) {
                                if (connectTimeoutFuture != null) {
                                    connectTimeoutFuture.cancel(false);
                                }
                                connectPromise = null;
                                close(voidPromise());
                            }
                        }
                    });
                }
            } catch (Throwable t) {
                promise.tryFailure(annotateConnectException(t, remoteAddress));
                closeIfClosed();
            }
        }

这里我们关注的还是doConnect方法方法我们继续跟进

protected boolean doConnect(SocketAddress remoteAddress, SocketAddress localAddress) throws Exception {
        //这里传入的localAddress是null则不会执行
        if (localAddress != null) {
            doBind0(localAddress);
        }

        boolean success = false;
        try {
            //调用connect方法
            boolean connected = SocketUtils.connect(javaChannel(), remoteAddress);
            //如果连接还没有建立,或者在非阻塞模式下正在连接,则设置关心connect事件
            if (!connected) {
                selectionKey().interestOps(SelectionKey.OP_CONNECT);
            }
            success = true;
            return connected;
        } finally {
            if (!success) {
                doClose();
            }
        }
    }

这里我们可以看到,完成了nio底层的SocketChannel的connect方法调用,并设置了关注事件OP_CONNECT,到这里客户端的启动连接就完成了。如果有分析错误的地方还请不吝指正。感谢!!!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值