美文网首页我爱编程
Netty4源码分析-Bootstrap

Netty4源码分析-Bootstrap

作者: 闪电是只猫 | 来源:发表于2017-09-18 01:10 被阅读184次

    Bootstrap是Netty提供的一个便利的工具类, 通过Bootstrap类能够容易地启动一个客户端使之与服务器端进行通信。

    下面的代码用于从服务器中获取时间:

    public class TimeClient {
    
        public static void main(String[] args) throws Exception {
            int port = 8080;
            if (args != null && args.length > 0) {
                try {
                    port = Integer.valueOf(args[0]);
                } catch (NumberFormatException e) {
                    
                }
            }
            new TimeClient().connect(port, "127.0.0.1");
        }
    
        public void connect(int port, String host) throws Exception {
            // 配置客户端NIO线程组
            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 {
                                ch.pipeline().addLast(new TimeClientHandler());
                            }
                        });
    
                // 发起异步连接操作
                ChannelFuture f = b.connect(host, port).sync();
    
                // 当代客户端链路关闭
                f.channel().closeFuture().sync();
            } finally {
                // 优雅退出,释放NIO线程组
                group.shutdownGracefully();
            }
        }
    }
    

    TimeClientHandler代码如下:

    public class TimeClientHandler extends ChannelInboundHandlerAdapter {
    
        private static final Logger logger = Logger
                .getLogger(TimeClientHandler.class.getName());
    
        private final ByteBuf firstMessage;
    
        /**
         * Creates a client-side handler.
         */
        public TimeClientHandler() {
            byte[] req = "QUERY TIME ORDER".getBytes();
            firstMessage = Unpooled.buffer(req.length);
            firstMessage.writeBytes(req);
    
        }
    
        @Override
        public void channelActive(ChannelHandlerContext ctx) {
            ctx.writeAndFlush(firstMessage);
        }
    
        @Override
        public void channelRead(ChannelHandlerContext ctx, Object msg)
                throws Exception {
            ByteBuf buf = (ByteBuf) msg;
            byte[] req = new byte[buf.readableBytes()];
            buf.readBytes(req);
            String body = new String(req, "UTF-8");
            System.out.println("Now is : " + body);
        }
    
        @Override
        public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
            // 释放资源
            logger.warning("Unexpected exception from downstream : "
                    + cause.getMessage());
            ctx.close();
        }
    }
    

    在TimeClient中创建了一个NioEventLoopGroup对象,它用来处理IO事件。在group中添加了一个TimeClientHandler,在通道准备就绪时会调用channelActive方法向服务器发送QUERY TIME ORDER;在收到服务器的响应后,会调用channelRead方法,输出服务器返回的时间。

    ChannelFuture f = b.connect(host, port).sync();用于异步连接服务端,我们进入到connect方法,该方法会调用doResolveAndConnect方法:

    private ChannelFuture doResolveAndConnect(final SocketAddress remoteAddress, final SocketAddress localAddress) {
        
        // 初始化并注册
        final ChannelFuture regFuture = initAndRegister();
        final Channel channel = regFuture.channel();
    
        // 判断初始化和注册是否已经完成
        if (regFuture.isDone()) {
            // 如果注册失败,则返回ChannelFuture对象
            if (!regFuture.isSuccess()) {
                return regFuture;
            }
            // 如果注册成功,则创建一个该channel对应的Promise对象作为参数
            return doResolveAndConnect0(channel, remoteAddress, localAddress, channel.newPromise());
        } 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 {
                    // Directly obtain the cause and do a null check so we only need one volatile read in case of a
                    // failure.
                    Throwable cause = future.cause();
                    // 如果有异常,则将promise设置为失败
                    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标记为已注册
                        promise.registered();
                        doResolveAndConnect0(channel, remoteAddress, localAddress, promise);
                    }
                }
            });
            return promise;
        }
    }
    

    如果注册未完成,则创建一个PendingRegistrationPromise类型的对象,并向regFuture对象中添加一个listener,在注册完成后会调用该listener的operationComplete方法。

    下面看下doResolveAndConnect0方法:

    private ChannelFuture doResolveAndConnect0(final Channel channel, SocketAddress remoteAddress,
                                               final SocketAddress localAddress, final ChannelPromise promise) {
        try {
            final EventLoop eventLoop = channel.eventLoop();
            final AddressResolver<SocketAddress> resolver = this.resolver.getResolver(eventLoop);
    
            if (!resolver.isSupported(remoteAddress) || resolver.isResolved(remoteAddress)) {
                // Resolver has no idea about what to do with the specified remote address or it's resolved already.
                doConnect(remoteAddress, localAddress, promise);
                return promise;
            }
    
            final Future<SocketAddress> resolveFuture = resolver.resolve(remoteAddress);
    
            if (resolveFuture.isDone()) {
                final Throwable resolveFailureCause = resolveFuture.cause();
    
                if (resolveFailureCause != null) {
                    // Failed to resolve immediately
                    channel.close();
                    promise.setFailure(resolveFailureCause);
                } else {
                    // Succeeded to resolve immediately; cached? (or did a blocking lookup)
                    doConnect(resolveFuture.getNow(), localAddress, promise);
                }
                return promise;
            }
    
            // Wait until the name resolution is finished.
            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;
    }
    

    该方法主要对地址进行一些检查和解析,然后会调用doConnect方法:

    private static void doConnect(
                final SocketAddress remoteAddress, final SocketAddress localAddress, final ChannelPromise connectPromise) {
    
        // This method is invoked before channelRegistered() is triggered.  Give user handlers a chance to set up
        // the pipeline in its channelRegistered() implementation.
        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);
            }
        });
    }
    

    这里把具体的连接工作放到了channel对应的eventLoop来执行,这方面的内容以后会讲到,现在可以理解为放入一个线程池中执行。

    具体的连接操作其实是通过pipeline调用了AbstractNioUnsafe类中的connect方法来执行,这些内容将在以后分析。

    我们回过头来看下doResolveAndConnect方法中调用的initAndRegister方法,该方法在AbstractBootstrap中实现:

    final ChannelFuture initAndRegister() {
        Channel channel = null;
        try {
            // 创建一个Channel对象
            channel = channelFactory.newChannel();
            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);
        }
    
        // 通过group执行注册
        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;
    }
    

    该方法很简单,创建了一个Channel对象,然后调用init方法对该对象进行初始化:

    @Override
    @SuppressWarnings("unchecked")
    void init(Channel channel) throws Exception {
        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());
            }
        }
    }
    

    回顾一下在TimeClient中的如下代码:

    b.group(group).channel(NioSocketChannel.class)
                    .option(ChannelOption.TCP_NODELAY, true)
                    .handler(new ChannelInitializer<SocketChannel>() {
                        @Override
                        public void initChannel(SocketChannel ch)
                                throws Exception {
                            ch.pipeline().addLast(new TimeClientHandler());
                        }
                    });
    

    init方法就是把上面的option和handler添加到channel中。

    至此,整个连接操作算是完成了,以后的通信工作就会交给EventLoop来执行,Bootstrap只是提供了一个启动的工作,主要是进行初始化和连接的操作。

    相关文章

      网友评论

        本文标题:Netty4源码分析-Bootstrap

        本文链接:https://www.haomeiwen.com/subject/hilvsxtx.html