使用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,防止更多这样的问题产生。

Netty4.0.24.Final 版本中 IdleStateHandler 使用时的局限性的更多相关文章

  1. OpenGL2.0及以上版本中glm,glut,glew,glfw,mesa等部件的关系

    OpenGL2.0及以上版本中gl,glut,glew,glfw,mesa等部件的关系 一.OpenGL OpenGL函数库相关的API有核心库(gl),实用库(glu),辅助库(aux).实用工具库 ...

  2. getContext在谷歌浏览器中,使用时要先加载canvas对象,否则会提示'getContext is null'

    <body> <canvas id=" style="border:1px solid #c3c3c3;"> Your browser does ...

  3. 关于电脑安装多个版本JDK后使用时的切换

    描述:刚到新公司,自己安装了jdk1.7和开发工具myeclipse10,但是由于公司项目的需要(具体原因不详细描述了),需要使用myeclipse6.5和jdk1.6.于是在切换jdk1.7和jdk ...

  4. Lua-面向对象中函数使用时冒号(:)和点(.)的区别

    先来看一段简单的代码: local Animal = {} function Animal:Eat( food ) print("Animal:Eat", self, food) ...

  5. vue中keep-alive使用时,注意要点

    <keep-alive exclude="QRCode,NewsInfor,VipRecordDetail"> <router-view></rout ...

  6. Hibernate/JPA中@Where使用时注意

    在使用Hibernate或者JPA时,我们经常会使用@Where注解实现查询过滤,在实体类上.实体属性上.查询语句上都有应用. 例如: @Where(clause = "status != ...

  7. OptaPlanner 7.32.0.Final版本彩蛋 - SolverManager之异步求解

    因为工作和其它原因,很长一段时间没有出新的.关于OptaPlanner的文章了,但工余时间并没有停止对该引擎的学习.与此同时Geoffrey大神带领的KIE项目团队并没有闲下来,尽管在工业可用性.易用 ...

  8. OptaPlanner 7.32.0.Final版本彩蛋 - SolverManager之批量求解

    上一篇介绍了OptaPlanner 7.32.0.Final版本中的SolverManager接口可以实现异步求解功能.本篇将继续介绍SolverManager的另一大特性 - 批量求解. 适用场景 ...

  9. android圆角View实现及不同版本这间的兼容(android3.0过后的版本)

    http://blog.csdn.net/lovecluo/article/details/8710174 在做我们自己的APP的时候,为了让APP看起来更加的好看,我们就需要将我们的自己的View做 ...

随机推荐

  1. linux系统下解决getch()输入数值不回显示

    在linux系统下开发C 程序却会遇到系统不支持conio.h头文件,无法使用getch()不回显函数.下面就演示如何构建函数实现数值输入不回显. #include <stdio.h> # ...

  2. 三元运算符2>1?true:false;

    1.说明: xxx?xxx:xxx; 第一个'xxx'是写条件语句,条件自己根据需求定 第二个'xxx'是当条件为真时会得到的值 第三个'xxx'是当条件为假时会得到的值 2.例子: 代码: bool ...

  3. C#对话框的使用

    [函数] <整型> MessageBox(<字符串> Text, <字符串> Title, <整型> nType,MessageBoxIcon);[函数 ...

  4. 一次触摸,Android到底干了啥

    WeTest 导读 当我们在写带有UI的程序的时候,如果想获取输入事件,仅仅是写一个回调函数,比如(onKeyEvent,onTouchEvent-.),输入事件有可能来自按键的,来自触摸的,也有来自 ...

  5. 前端面试题(3) cookie,sessionStorage和localStorage的区别

    cookie是网站为了标示用户身份存在用户本地终端上的数据(经过加密). cookie数据时钟在同源的http请求中携带(即使不需要),即会在浏览器和服务器之间传递. seeeionStorage和l ...

  6. K-Means和图片压缩

    通俗的介绍这种压缩方式,就是将原来很多的颜色用少量的颜色去表示,这样就可以减小图片大小了.下面首先我先介绍下K-Means,当你了解了K-Means那么你也很容易的可以去理解图片压缩了,最后附上图片压 ...

  7. 31.Linux-wm9876声卡驱动(移植+测试)

    本节学习目的 1)分析Linux中的OSS声卡系统 2)移植wm9876声卡 3)使用madplay应用程序播放mp3 1.声音三要素 采样频率 音频采样率是指录音设备在一秒钟内对声音信号的采样次数, ...

  8. android引入百度地图之最简单的例子-HelloBaiDuMap

    开发工具 我用的是eclipse,目前这是开发android用的最多的一个工具. 开发准备 首先需要在百度地图开发平台上下载所需要的文档和功能,由于最新版的3.0.0的不是很熟悉,我这里用的是稍旧一点 ...

  9. JAVASE 循环 之 计算各位上数字的和

    问题:输入一个整数,计算它各位上数字的和 Scanner sc = new Scanner(System.in); int a = sc.nextInt(); int sum = 0; while(a ...

  10. "虐待"过我的老师们,你们如今还好吗

    当皇城脚下的民生问题都这么难做的时候,其他地方又该如何保障呢? 京城“红黄蓝”出名了,京城发生锅炉大爆炸了,京城发生火灾了…… 聊天中,有一好友突然蹦出了一句话:“你看在皇城脚下都不安全了”. 久久我 ...