• Netty4.0.24.Final 版本中 IdleStateHandler 使用时的局限性


    使用Netty在客户端和服务端建立通讯通道,一般来说,一个连接可能很久没有访问,由于各种各样的网络问题导致连接已经失效,客户端再次发送请求时会产生连接异常。

    基于这个原因,需要在客户端和服务端之间建立ping-pong的心跳机制,我的本意是想通过IdleStateHandler这个netty提供的工具来自动发现连接空闲状态,却出现了以下问题:

    1.客户端和服务端使用 IdleStateHandler 时,原本的请求响应机制失效

    2.IdleStateHandler的空闲通知功能正常,但是却不准确

    最开始排查这个问题的原因是一头雾水,通过断点和分析程序已经走到哪个位置,最终确定IdleStateHandler在输出服务端的响应到channel时,出现了问题

        /**
         * 发送服务端的响应
         * @param messageId
         * @param channel
         * @param messageResult
         */
        protected void sendResponse(long messageId, Channel channel, Object messageResult) {
            MsgHeader msgHeader = new MsgHeader(Constants.RESPONSE_MSG);
            if(messageResult != null) {
                msgHeader.setClz(messageResult.getClass().getName());
            }else {
                msgHeader.setClz(Constants.NULL_RESULT_CLASS);
            }
    
            ResponseMsg responseMsg = new ResponseMsg();
            responseMsg.setReceiveTime(System.currentTimeMillis());
            responseMsg.setResponse(messageResult);
            responseMsg.setMsgHeader(msgHeader);
            responseMsg.getMsgHeader().setMsgId(messageId);
    
            //如果使用voidPromise,则无法和IdleStateHandler同时使用,因为它会触发voidPromise的addListener(...)操作,从而导致write失败
            channel.writeAndFlush(responseMsg, channel.voidPromise());
        }

    在处理完请求进行响应输出时,最后一行在写响应时,使用了空的promise,最终会导致IdleStateHandler的write方法处获得这个空的Promise,进而导致异常

        @Override
        public void write(ChannelHandlerContext ctx, Object msg, ChannelPromise promise) throws Exception {
            promise.addListener(new ChannelFutureListener() {
                @Override
                public void operationComplete(ChannelFuture future) throws Exception {
                    lastWriteTime = System.nanoTime();
                    firstWriterIdleEvent = firstAllIdleEvent = true;
                }
            });
            ctx.write(msg, promise);
        }

    空的Promise的addListener实现方式是简单粗暴的抛出异常,这就导致末行的ctx.write(msg, promise)永远也不能执行到。

    final class VoidChannelPromise extends AbstractFuture<Void> implements ChannelPromise {
    ... @Override
    public VoidChannelPromise addListener(GenericFutureListener<? extends Future<? super Void>> listener) { fail(); return this; } private static void fail() { throw new IllegalStateException("void future"); }
    ... }

    找到这个原因后,立即修改了Server端Handler的发送方法

        /**
         * 发送服务端的响应
         * @param messageId
         * @param channel
         * @param messageResult
         */
        protected void sendResponse(long messageId, Channel channel, Object messageResult) {
            MsgHeader msgHeader = new MsgHeader(Constants.RESPONSE_MSG);
            if(messageResult != null) {
                msgHeader.setClz(messageResult.getClass().getName());
            }else {
                msgHeader.setClz(Constants.NULL_RESULT_CLASS);
            }
    
            ResponseMsg responseMsg = new ResponseMsg();
            responseMsg.setReceiveTime(System.currentTimeMillis());
            responseMsg.setResponse(messageResult);
            responseMsg.setMsgHeader(msgHeader);
            responseMsg.getMsgHeader().setMsgId(messageId);
    
            //如果使用voidPromise,则无法和IdleStateHandler同时使用,因为它会触发voidPromise的addListener(...)操作,从而导致write失败
            channel.writeAndFlush(responseMsg, channel.newPromise());
        }

    末行new一个默认的Promise,执行addListener不会产生异常

    这样server端的问题就解决了

    然后client端却无法解决这个问题,原因时client端接收响应后,netty内部会new一个VoidPromise,这就导致了IdleStateHandler放置于客户端时无论如何都不能正常使用,因为一旦触发write方法就直接异常了,目前还没有找到这两者同时存在的方案。这是Netty4.0.24.Final版本的硬伤,好在的是,Netty4的最新版本已经fix了这个问题

            <dependency>
                <groupId>io.netty</groupId>
                <artifactId>netty-all</artifactId>
                <version>4.1.12.Final</version>
            </dependency>

    IdleStateHandler的write方法已经对此做出了修正

        @Override
        public void write(ChannelHandlerContext ctx, Object msg, ChannelPromise promise) throws Exception {
            // Allow writing with void promise if handler is only configured for read timeout events.
            if (writerIdleTimeNanos > 0 || allIdleTimeNanos > 0) {
                ChannelPromise unvoid = promise.unvoid();  //此处保证返回的一定是非void的
                unvoid.addListener(writeListener);  
                ctx.write(msg, unvoid);
            } else {
                ctx.write(msg, promise);
            }
        }

    总结,一个复杂的框架无论如何会有一些毛病,就像业务系统中的一个bug,两个东西不是同一个人开发的,就会出现使用时的异常情况,找到这个原因进行分析,解决的思路也就跃然于纸上。

    最终,我把netty4的版本升级到了最新的4.1.12.Final,防止更多这样的问题产生。

  • 相关阅读:
    近期前端中的 一些常见的面试题
    一道前端学习题
    前端程序员容易忽视的一些基础知识
    web前端工程师入门须知
    Web前端知识体系精简
    面试分享:一年经验初探阿里巴巴前端社招
    抽象类、抽象函数/抽象方法详解
    C#语法-虚方法详解 Virtual 虚函数
    面向对象语言:继承关系教程
    C#动态创建Xml-LinQ方式
  • 原文地址:https://www.cnblogs.com/windliu/p/7019130.html
Copyright © 2020-2023  润新知