netty(十七)源码分析之客户端创建

来源:互联网 发布:芭碧琪面膜怎么样 知乎 编辑:程序博客网 时间:2024/06/06 11:44

相对于服务端,Netty客户端的创建更加复杂,除了要考虑线程模型、异步连接、客户端连接超时等因素外,还需要对连接过程中的各种异常进行考虑。

下面我们直接分析客户端连接操作:

首先要创建和初始化NioSocketChannel,代码如下:
    private ChannelFuture doConnect(final SocketAddress remoteAddress, final SocketAddress localAddress) {        final ChannelFuture regFuture = initAndRegister();        final Channel channel = regFuture.channel();        if (regFuture.cause() != null) {            return regFuture;        }

从NioEventLoopGroup总获取NioEventLoop,然后使用其作为参数创建NioSocketChannel,代码如下:
    Channel createChannel() {        EventLoop eventLoop = group().next();        return channelFactory().newChannel(eventLoop);    }

初始化Channel之后,将其注册到Selector上,代码如下:
        ChannelPromise regFuture = channel.newPromise();        channel.unsafe().register(regFuture);

链路创建成功之后,发起异步TCP连接,代码如下:
    private static void doConnect0(            final ChannelFuture regFuture, final Channel channel,            final SocketAddress remoteAddress, final SocketAddress localAddress, final ChannelPromise promise) {        // This method is invoked before channelRegistered() is triggered.  Give user handlers a chance to set up        // the pipeline in its channelRegistered() implementation.        channel.eventLoop().execute(new Runnable() {            @Override            public void run() {                if (regFuture.isSuccess()) {                    if (localAddress == null) {                        channel.connect(remoteAddress, promise);                    } else {                        channel.connect(remoteAddress, localAddress, promise);                    }

由上述代码可以看出,从doConnnect0操作开始,连接操作切换到了Netty的NIO线程NioEventLoop中进行,此时客户端返回,连接操作异步执行。

doConnect0最终调用Headhandler的connect方法,代码如下:
        public void connect(                ChannelHandlerContext ctx,                SocketAddress remoteAddress, SocketAddress localAddress,                ChannelPromise promise) throws Exception {            unsafe.connect(remoteAddress, localAddress, promise);        }

AbstractNioUnsafe的connect操作如下。
                if (doConnect(remoteAddress, localAddress)) {                    fulfillConnectPromise(promise, wasActive);                } else {

需要注意的是,SocketChannel执行connect()操作后有以下三种结果。
(1)连接成功,返回true;
(2)暂时没有连接上,服务端没有返回ACK应答,连接结果不确定,返回false;
(3)连接失败,直接抛出I/O异常。
如果是第二种结果,需要将NioSocketChannel中的selectionKey设置为OP_CONNECT,监听连接结果。
异步连接返回之后,需要判断连接结果,如果连接成功,则触发ChannelActive事件,代码如下(在fulfillConnectPromise方法中):
            if (!wasActive && isActive()) {                pipeline().fireChannelActive();            }

ChannelActive事件处理最终会将NioSocketChannel中的selectionKey设置为SelectionKey.OP_READ,用于监听网络读操作。


客户端连接超时机制

对于SocketChannel接口,JDK并没有提供连接超时机制,需要NIO框架或者用户自己扩展实现。Netty利用定时器提供了客户端连接超时控制功能,下面我们详细看下该功能:
首先,用户在创建Netty客户端的时候,可以通过ChannelOption.CONNECT_TIMEOUT_MILLIS配置项设置连接超时时间,代码如下:

b.group().opration(ChannelOption.CONNECT_TIMEOUT_MILLIS,3000);

发起连接的同时,启动连接超时检测定时器,代码如下(在AbstractNioChannel中):
                        connectTimeoutFuture = eventLoop().schedule(new Runnable() {                            @Override                            public void run() {                                ChannelPromise connectPromise = AbstractNioChannel.this.connectPromise;                                ConnectTimeoutException cause =                                        new ConnectTimeoutException("connection timed out: " + remoteAddress);                                if (connectPromise != null && connectPromise.tryFailure(cause)) {                                    close(voidPromise());                                }                            }                        }, connectTimeoutMillis, TimeUnit.MILLISECONDS);

一旦超时定时器执行,说明客户端连接超时,构造连接超时异常,将异常结果设置到connectPromise中,同时关闭客户端连接,释放句柄。

如果在连接超时前获取到连接结果,则删除连接超时定时器,防止其被触发,代码如下:
                    promise.addListener(new ChannelFutureListener() {                        @Override                        public void operationComplete(ChannelFuture future) throws Exception {                            if (future.isCancelled()) {                                if (connectTimeoutFuture != null) {                                    connectTimeoutFuture.cancel(false);                                }

无论连接是否成功,只要获取到连接结果,之后就删除连接超时定时器。





原创粉丝点击