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,防止更多這樣的問題產生。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM