netty 客户端连接过程分析

来源:互联网 发布:简单数据库设计实例 编辑:程序博客网 时间:2024/06/07 11:16

经过上面的各种分析后, 我们大致了解了 Netty 初始化时, 所做的工作, 那么接下来我们就直奔主题, 分析一下客户端是如何发起 TCP 连接的.

客户端通过调用 Bootstrap 的 connect 方法进行连接.

    public ChannelFuture connect(String inetHost, int inetPort) {        return connect(InetSocketAddress.createUnresolved(inetHost, inetPort));    }    public ChannelFuture connect(InetAddress inetHost, int inetPort) {        return connect(new InetSocketAddress(inetHost, inetPort));    }    public ChannelFuture connect(SocketAddress remoteAddress) {        if (remoteAddress == null) {            throw new NullPointerException("remoteAddress");        }        validate();        return doResolveAndConnect(remoteAddress, config.localAddress());    }
    private ChannelFuture doResolveAndConnect(final SocketAddress remoteAddress, final SocketAddress localAddress) {        final ChannelFuture regFuture = initAndRegister();        final Channel channel = regFuture.channel();        .............................        netty 初始化完成后,开始做解析并连接的相关工作        .............................        return doResolveAndConnect0(channel, remoteAddress, localAddress, channel.newPromise());        }    }

在 connect 中, 会进行一些参数检查后, 最终调用的是 doConnect 方法, 其实现如下:

    private static void doConnect(            final SocketAddress remoteAddress, final SocketAddress localAddress, final ChannelPromise connectPromise) {        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 中, 会在 event loop 线程中调用 Channel 的 connect 方法, 而这个 Channel 的具体类型是什么呢? 我们在 Channel 初始化这一小节中已经分析过了, 这里 channel 的类型就是 NioSocketChannel.
进行跟踪到 channel.connect 中, 我们发现它调用的是 AbstractChannel#connect, 而 AbstractChannel 的 connect 代码如下:

    @Override    public ChannelFuture connect(SocketAddress remoteAddress, ChannelPromise promise) {        .......................................        这里的 pipeline 为 DefaultChannelPipeline        .......................................        return pipeline.connect(remoteAddress, promise);    }

我们继续跟踪,发现调用的是DefaultChannelPipeline#connect,其代码如下:

    @Override    public final ChannelFuture connect(SocketAddress remoteAddress, ChannelPromise promise) {        return tail.connect(remoteAddress, promise);    }

而 tail 字段, 我们已经分析过了, 是一个 TailContext 的实例, 而 TailContext 又是 AbstractChannelHandlerContext 的子类, 并且没有实现 connect 方法, 因此这里调用的其实是 AbstractChannelHandlerContext.connect, 我们看一下这个方法的实现:

    @Override    public ChannelFuture connect(SocketAddress remoteAddress, ChannelPromise promise) {        return connect(remoteAddress, null, promise);    }
 public ChannelFuture connect(            final SocketAddress remoteAddress, final SocketAddress localAddress, final ChannelPromise promise) {        ...........................................        找到第一个outbound 为 true 的 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;    }

上面的代码中有一个关键的地方, 即 final AbstractChannelHandlerContext next = findContextOutbound(), 这里调用 findContextOutbound 方法, 从 DefaultChannelPipeline 内的双向链表的 tail 开始, 不断向前寻找第一个 outbound 为 true 的 AbstractChannelHandlerContext,

    private AbstractChannelHandlerContext findContextOutbound() {        AbstractChannelHandlerContext ctx = this;        do {            ctx = ctx.prev;        } while (!ctx.outbound);        return ctx;    }

然后调用它的 invokeConnect 方法, 其代码如下:

    private void invokeConnect(SocketAddress remoteAddress, SocketAddress localAddress, ChannelPromise promise) {        if (invokeHandler()) {            try {                .....................................................                handler()  实际上就是 HeadContext                ....................................                ((ChannelOutboundHandler) handler()).connect(this, remoteAddress, localAddress, promise);            } catch (Throwable t) {                notifyOutboundHandlerException(t, promise);            }        } else {            connect(remoteAddress, localAddress, promise);        }    }

HeadContext.connect, 其代码如下

        @Override        public void connect(                ChannelHandlerContext ctx,                SocketAddress remoteAddress, SocketAddress localAddress,                ChannelPromise promise) throws Exception {            unsafe.connect(remoteAddress, localAddress, promise);        }

这个unsafe 其实就是 NioSocketChannelUnsafe,而NioSocketChannelUnsafe.connnect方法的实现是 AbstractNioUnsafe.connect,其代码实现:

@Overridepublic final void connect(        final SocketAddress remoteAddress, final SocketAddress localAddress, final ChannelPromise promise) {    boolean wasActive = isActive();    if (doConnect(remoteAddress, localAddress)) {        fulfillConnectPromise(promise, wasActive);    } else {        ...    }}

AbstractNioUnsafe.connect 的实现如上代码所示, 在这个 connect 方法中, 调用了 doConnect 方法, 注意, 这个方法并不是 AbstractNioUnsafe 的方法, 而是 AbstractNioChannel 的抽象方法. doConnect 方法是在 NioSocketChannel 中实现的, 因此进入到 NioSocketChannel.doConnect 中:

@Overrideprotected boolean doConnect(SocketAddress remoteAddress, SocketAddress localAddress) throws Exception {    if (localAddress != null) {        javaChannel().socket().bind(localAddress);    }    boolean success = false;    try {        boolean connected = javaChannel().connect(remoteAddress);        if (!connected) {            selectionKey().interestOps(SelectionKey.OP_CONNECT);        }        success = true;        return connected;    } finally {        if (!success) {            doClose();        }    }}

至此连接结束