通过案例学Netty5-ChannelFuture

通过案例学Netty-Promise and ChannelFuture

1.写一个简单的Redis客户端,通过命令输入操作Redis

public class RedisClient {
    private static final String HOST = "你自己的Redis地址";
    private static final int PORT = Integer.parseInt(System.getProperty("port", "6379"));

    public static void main(String[] args) throws Exception {
        EventLoopGroup group = new NioEventLoopGroup();
        try {
            Bootstrap b = new Bootstrap();
            b.group(group)
             .channel(NioSocketChannel.class)
             .handler(new ChannelInitializer<SocketChannel>() {
                 @Override
                 protected void initChannel(SocketChannel ch) throws Exception {
                     ChannelPipeline p = ch.pipeline();
                     p.addLast(new RedisDecoder());
                     p.addLast(new RedisBulkStringAggregator());
                     p.addLast(new RedisArrayAggregator());
                     p.addLast(new RedisEncoder());
                     p.addLast(new RedisClientHandler());
                 }
             });
            // Start the connection attempt.
            Channel ch = b.connect(HOST, PORT).sync().channel();
            // Read commands from the stdin.
            System.out.println("Enter Redis commands (quit to end)");
            ChannelFuture lastWriteFuture = null;
            BufferedReader in = new BufferedReader(new InputStreamReader(System.in));
            for (;;) {
                final String input = in.readLine();
                final String line = input != null ? input.trim() : null;
                if (line == null || "quit".equalsIgnoreCase(line)) { // EOF or "quit"
                    ch.close().sync();
                    break;
                } else if (line.isEmpty()) { // skip `enter` or `enter` with spaces.
                    continue;
                }
                // Sends the received line to the server.
                lastWriteFuture = ch.writeAndFlush(line);
                lastWriteFuture.addListener(new GenericFutureListener<ChannelFuture>() {
                    @Override
                    public void operationComplete(ChannelFuture future) throws Exception {
                        if (!future.isSuccess()) {
                            System.err.print("write failed: ");
                            future.cause().printStackTrace(System.err);
                        }
                    }
                });
            }
            // Wait until all messages are flushed before closing the channel.
            if (lastWriteFuture != null) {
                lastWriteFuture.sync();
            }
        } finally {
            group.shutdownGracefully();
        }
    }
}

这个类非常简单,我们很熟悉的流程,New一个EventLoopGroup,再来个Bootstrap启动类,绑定上各式各样的Handler,最后一个无限for循环读取你的指令并发送给Redis

2.Promise and ChannelFuture

ChannelFuture类图

通过类图可以清晰的看到,Netty的ChannelFuture就是Java中的Future的子类。本身java的future就是用来处理异步操作的,Netty在它的基础上添加了listener机制来处理当任务完成后的操作。

也许你和有同样的疑惑,乍一看PromiseChannelFuture没有什么区别。但实际上是一种职责分离的设计思想,ChannelFuture是对外的客户端使用,它提供了一种只读的方式来观察操作的结果。用户通过ChannelFuture检查操作的状态,添加监听器,并根据操作结果采取后续行动。他是线程安全的

Promise则是操作的生产者代码使用的接口,提供了设置操作结果setSuccess,setFailure,是内部Netty自己使用的。结合Netty的基于事件驱动的网络架构,使得代码更加模块化和清晰。

3.DefaultPromise.class

RedisClient中的lastWriteFuture获取通过writeAndFulsh()返回的ChannelFuture实际上是一个ChannelPromise。我们addlistener()的操作是以数组的形式保存起来的,下面详细介绍了这个过程

private GenericFutureListener<? extends Future<?>> listener;
private DefaultFutureListeners listeners;//里面有一个GenericFutureListener数组
public Promise<V> addListener(GenericFutureListener<? extends Future<? super V>> listener) {
        checkNotNull(listener, "listener");
        synchronized (this) {
            addListener0(listener);
        }
        if (isDone()) {
            notifyListeners();
        }
        return this;
    }

private void addListener0(GenericFutureListener<? extends Future<? super V>> listener) {
        if (this.listener == null) {
            if (listeners == null) {
                this.listener = listener;
            } else {
                listeners.add(listener);
            }
        } else {
            assert listeners == null;
            listeners = new DefaultFutureListeners(this.listener, listener);
            this.listener = null;
        }
    }

4.DefalutFutureListeners

DefaultFutureListeners(
            GenericFutureListener<? extends Future<?>> first, GenericFutureListener<? extends Future<?>> second) {
        listeners = new GenericFutureListener[2];
        listeners[0] = first;
        listeners[1] = second;
        size = 2;
        if (first instanceof GenericProgressiveFutureListener) {
            progressiveSize ++;
        }
        if (second instanceof GenericProgressiveFutureListener) {
            progressiveSize ++;
        }
    }

public void add(GenericFutureListener<? extends Future<?>> l) {
        GenericFutureListener<? extends Future<?>>[] listeners = this.listeners;
        final int size = this.size;
        if (size == listeners.length) {
            this.listeners = listeners = Arrays.copyOf(listeners, size << 1);
        }
        listeners[size] = l;
        this.size = size + 1;

        if (l instanceof GenericProgressiveFutureListener) {
            progressiveSize ++;
        }
    }
第一步检查listener是否为空
第二步加锁
第三步addListeners()方法很有意思,首先去检查listener和listeners是否为空,都为空则直接复制listeners,若listeners不为空,则调用DefaultFutureListeners的add()方法。在这里会进行一次大小判断,是否进行数组扩容。如果已经存在了一个listener,则调用构造方法,可以看到默认数组大小为2,然后将listener设置为null。这样在第二次及以后的判断都会直接转到add()方法上。
疑问Netty为什么要这样设计,一个简单给数组添加元素,为什么要多此一举的弄一个listener变量出来,直接使用listeners不行吗,还判断个啥劲,直接往数组里加就好了…我在第一次看到代码的时候就十分否困惑没有看懂,看了好几遍才明白它的逻辑。
待续我在Netty的discord上发了帖子,希望能得到回到。也欢迎各位大神解答我的疑惑
后续一位Netty的开发者说在大部分情况下,我们只需要一个listener就足够了,所以特意列出了这种情况去处理,为了节省内存的开销。想想也是,一个变量和一个数组所站的内存空间肯定是不一样的。我在后续的Netty中也遇到了很多这种场景,这种在我们日常的开发破CRUD中很难去使用和理解 ,大概高性能就是这样一点一点挤出来的吧

5.RedisClientHandler

public class RedisClientHandler extends ChannelDuplexHandler {
    @Override
    public void write(ChannelHandlerContext ctx, Object msg, ChannelPromise promise) {
        String[] commands = ((String) msg).split("\\s+");
        List<RedisMessage> children = new ArrayList<RedisMessage>(commands.length);
        for (String cmdString : commands) {
            children.add(new FullBulkStringRedisMessage(ByteBufUtil.writeUtf8(ctx.alloc(), cmdString)));
        }
        RedisMessage request = new ArrayRedisMessage(children);
        ctx.write(request, promise);
    }
    @Override
    public void channelRead(ChannelHandlerContext ctx, Object msg) {
        RedisMessage redisMessage = (RedisMessage) msg;
        printAggregatedRedisResponse(redisMessage);
        ReferenceCountUtil.release(redisMessage);
    }
    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
        System.err.print("exceptionCaught: ");
        cause.printStackTrace(System.err);
        ctx.close();
    }
    private static void printAggregatedRedisResponse(RedisMessage msg) {
        if (msg instanceof SimpleStringRedisMessage) {
            System.out.println(((SimpleStringRedisMessage) msg).content());
        } else if (msg instanceof ErrorRedisMessage) {
            System.out.println(((ErrorRedisMessage) msg).content());
        } else if (msg instanceof IntegerRedisMessage) {
            System.out.println(((IntegerRedisMessage) msg).value());
        } else if (msg instanceof FullBulkStringRedisMessage) {
            System.out.println(getString((FullBulkStringRedisMessage) msg));
        } else if (msg instanceof ArrayRedisMessage) {
            for (RedisMessage child : ((ArrayRedisMessage) msg).children()) {
                printAggregatedRedisResponse(child);
            }
        } else {
            throw new CodecException("unknown message type: " + msg);
        }
    }
    private static String getString(FullBulkStringRedisMessage msg) {
        if (msg.isNull()) {
            return "(null)";
        }
        return msg.content().toString(CharsetUtil.UTF_8);
    }
}

这个Handler主要是处理发送Redis的指令和打印从Redis接收到的信息。有兴趣您可以用这两个类实践一下,直接用命令行操作Redis

下一章我们会讲ChannelDupleshadler以及它背后的一些类,这一章已经挺长的了😶‍🌫️

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值