1、先说问题

背景:服务是运行在Linux上的安全网关提供的,TCP协议发送 通过二进制编码的xml字符串 报文,报文头的第一个字段是int类型的表示字节序标记,第二个字段是int类型的表示整个报文长度。

现象:数据量较小时完全可以正常收发报文,当服务端发送的报文数据量较大时(本例是将近600k)概率性出现接收数据直接调用readComplete()方法而没有走channelRead()

跟踪:跟踪代码发现出问题时context 的 read() 方法执行中读取到一百多k(有时两百多也可能三百多,总之是还没读取到全部数据)时某次读到的数据本应该是1024字节(填满默认分配的ByteBuf)却只读到了576字节;

netty框架代码中判断如果当前读到的字节数小于ByteBuf的size则认为是读取完成,因此调用了readComplete()方法,出错。。。

解决方案:在ClientHandler类添加一个标记flag,用于是否正常读取数据判断。channelRead()方法正常调用则将其置为true;readComplete方法中添加一个判断只有当flag为true时关闭context否则继续调用ctx.read()。

2、再上核心代码

Client:

 ClientHandler clientHandler = new ClientHandler(this);
bootstrap.group(eventLoop)
.channel(NioSocketChannel.class)
.option(ChannelOption.TCP_NODELAY, true)
.option(ChannelOption.CONNECT_TIMEOUT_MILLIS, 3000)
.option(ChannelOption.MAX_MESSAGES_PER_READ, Integer.MAX_VALUE)
.handler(new ClientChannelInitializer(clientHandler)); ChannelFuture f = bootstrap.connect(host, port).sync(); f.channel().closeFuture().sync();
 private class ClientChannelInitializer extends ChannelInitializer<SocketChannel> {
private ClientHandler clientHandler; public ClientChannelInitializer(ClientHandler clientHandler) {
this.clientHandler = clientHandler;
} @Override
protected void initChannel(SocketChannel socketChannel) throws Exception { socketChannel.pipeline().addLast(new SplDecoder());
socketChannel.pipeline().addLast(clientHandler);
channel = socketChannel;
}
}
// 解决问题前 initChannel的实现是这样的,使用了netty内部的长度字段解码器
@Override
protected void initChannel(SocketChannel ch) throws Exception {
ch.pipeline().addLast(new LengthFieldBasedFrameDecoder(Integer.MAX_VALUE,4,4,-8,0));
ch.pipeline().addLast(clientHandler);
}

ClientHandler:

public class ClientHandler extends ChannelInboundHandlerAdapter {
 @Override
public void channelActive(ChannelHandlerContext context) throws Exception {
logger.info("Ready to send request...");
ByteBuffer result = getByteBuffer();
ByteBuf buf = Unpooled.buffer(result.remaining());
buf.writeBytes(result); context.writeAndFlush(buf);
} @Override
public void channelRead(ChannelHandlerContext context, Object msg) throws Exception {
logger.info("Get server response..."); String[] result = (String[]) msg; logger.debug("response xml is : " + result[1]);
client.setResponse(result); ok = true;
} @Override
public void channelReadComplete(ChannelHandlerContext ctx) throws Exception {
ctx.flush();
if (ok) {
ctx.close();
} else {
ctx.read();
}
}

3、最后说解决过程

起初我怀疑是使用netty的定长字段解码器LengthFieldBasedFrameDecoder参数不当引起的,因为自认为对它理解不深;于是自己写了一个继承byteToMessageDecoder的解码器可以实现解决拆包问题和解码功能,但是问题依然概率性出现...

后来抱着试试看的态度在ClientHandler里面添加了一个实例属性ok(默认false),在正常执行channelRead()方法后将其置为true,readComplete()方法中做判断如果ok==false调用ctx.read(),运行发现完美解决问题

因为调用read()方法是继续读取数据而不是重新读取(因为此时ctx和channel、pipline等数据状态都没变)!

在SplDecoder类中添加当前读取数据打印信息:“logger.debug("读取数据:本次" + readableBytes + ";累计" + currentLength + ";总共" + total);”;

在ctx.read()前面添加打印错误信息“****** 读取数据不完整,再次读取......”

运行正常和出错时的控制台打印信息如下(由于实际打印行数太多,我用"......"代替了部分重复行):

 Connected to the target VM, address: '127.0.0.1:62194', transport: 'socket'
log4j:WARN No appenders could be found for logger (io.netty.util.internal.logging.InternalLoggerFactory).
log4j:WARN Please initialize the log4j system properly.
读取数据:本次1024;累计1024;总共574842
读取数据:本次1024;累计2048;总共574842
读取数据:本次1024;累计3072;总共574842
......
读取数据:本次1024;累计572416;总共574842
读取数据:本次1024;累计573440;总共574842
读取数据:本次1024;累计574464;总共574842
读取数据:本次378;累计574842;总共574842
0 ~~ null
Disconnected from the target VM, address: '127.0.0.1:62194', transport: 'socket' Process finished with exit code 0

正常时结果

 Connected to the target VM, address: '127.0.0.1:62068', transport: 'socket'
log4j:WARN No appenders could be found for logger (io.netty.util.internal.logging.InternalLoggerFactory).
log4j:WARN Please initialize the log4j system properly.
读取数据:本次1024;累计1024;总共574842
读取数据:本次1024;累计2048;总共574842
读取数据:本次1024;累计3072;总共574842
读取数据:本次1024;累计4096;总共574842
读取数据:本次1024;累计5120;总共574842
读取数据:本次1024;累计6144;总共574842
读取数据:本次1024;累计7168;总共574842
读取数据:本次1024;累计8192;总共574842
读取数据:本次1024;累计9216;总共574842
读取数据:本次1024;累计10240;总共574842
读取数据:本次1024;累计11264;总共574842
读取数据:本次1024;累计12288;总共574842
读取数据:本次1024;累计13312;总共574842
读取数据:本次576;累计13888;总共574842
****** 读取数据不完整,再次读取......
读取数据:本次16384;累计30272;总共574842
读取数据:本次16384;累计46656;总共574842
读取数据:本次16384;累计63040;总共574842
......
读取数据:本次16384;累计554560;总共574842
读取数据:本次16384;累计570944;总共574842
读取数据:本次3898;累计574842;总共574842
0 ~~ null
Disconnected from the target VM, address: '127.0.0.1:62068', transport: 'socket' Process finished with exit code 0

出错时结果

结果最后打印0~~null表示正常结束(返回code为0错误信息为null)。

附:

问题处理前控制台打印结果

 "C:\Program Files...
log4j:WARN No appenders could be found for logger (io.netty.util.internal.logging.InternalLoggerFactory).
log4j:WARN Please initialize the log4j system properly.
-1 ~~ 服务异常;Detail:java.lang.NullPointerException Process finished with exit code 0

问题处理前控制台打印信息

由于没有执行channelRead()方法,所以我获取到的数据没能执行赋值操作,报了空指针异常。

结果最后打印 -1 ~~ 服务异常;Detail:java.lang.NullPointerException 表示发生了异常(返回code为-1;错误信息为"服务异常;Detail:java.lang.NullPointerException")

使用netty4.x客户端接收较大数据量报文时发生的读取不完整bug修复记录的更多相关文章

  1. 大数据量传输时配置WCF的注意事项

    原文:大数据量传输时配置WCF的注意事项 WCF传输数据量的能力受到许多因素的制约,如果程序中出现因需要传输的数据量较大而导致调用WCF服务失败的问题,应注意以下配置: 1.MaxReceivedMe ...

  2. 【转载】大数据量传输时配置WCF的注意事项

    WCF传输数据量的能力受到许多因素的制约,如果程序中出现因需要传输的数据量较大而导致调用WCF服务失败的问题,应注意以下配置: 1.MaxReceivedMessageSize:获取或设置配置了此绑定 ...

  3. hadoop job解决大数据量关联时数据倾斜的一种办法

    转自:http://www.cnblogs.com/xuxm2007/archive/2011/09/01/2161929.html http://www.geminikwok.com/2011/04 ...

  4. java处理大数据量任务时的可用思路--未验证版,具体实现方法有待实践

    1.Bloom filter适用范围:可以用来实现数据字典,进行数据的判重,或者集合求交集基本原理及要点:对于原理来说很简单,位数组+k个独立hash函数.将hash函数对应的值的位数组置1,查找时如 ...

  5. 关于webservice大数据量传输时的压缩和解压缩

    当访问WebSerivice时,如果数据量很大,传输数据时就会很慢.为了提高速度,我们就会想到对数据进行压缩.首先我们来分析一下. 当在webserice中传输数据时,一般都采用Dataset进行数据 ...

  6. VC++大数据量绘图时无闪烁刷屏技术实现(我的理解是,在内存上作画,然后手动显示,而不再直接需要经过WM_PAINT来处理了)

    http://hantayi.blog.51cto.com/1100843/383578 引言 当我们需要在用户区显示一些图形时,先把图形在客户区画上,虽然已经画好但此时我们还无法看到,还要通过 程序 ...

  7. 使用内存映射文件MMF实现大数据量导出时的内存优化

    前言 导出功能几乎是所有应用系统必不可少功能,今天我们来谈一谈,如何使用内存映射文件MMF进行内存优化,本文重点介绍使用方法,相关原理可以参考文末的连接 实现 我们以单次导出一个excel举例(csv ...

  8. c# 大数据量比较时-方案

    1.当面临千万条数据量的比较时,从技术的角度来说应该用泛型键值(c#键值由于用了散列算法速度很快).例如前几天我需要查的是 航空公司.出发.到达.返点可以将 航空公司-出发-到达做一个键,返点作为值. ...

  9. 大数据量冲击下Windows网卡异常分析定位

    背景 mqtt的服务端ActiveMQ在windows上,多台PC机客户端不停地向MQ发送消息. 现象 观察MQ自己的日志data/activemq.log里显示,TCP链接皆异常断开.此时尝试从服务 ...

随机推荐

  1. javascript入门:prototype和面向对象的实现

    由于工作需要,需要大量使用javascript,于是对其进行了一下学习. 学习任何一个语言,最重要的是掌握其和其他语言不同的关键特性.对javascript来说,我总结就是prototype.就像me ...

  2. Vim,极简使用教程,让你瞬间脱离键鼠切换的痛苦

    注:看大家对Vim仇恨极大,其实它只是一种文本操作方式,可以减少键鼠的切换,从而让编辑文本的操作更迅捷.并不等同于IDE,在我看来,它们是两个是包含关系,IDE可以有Vim编辑模式.Vim或许可以通过 ...

  3. CSS基础1

    CSS是层叠样式表(Cascading Style Sheets)的缩写,它有以下优点:①提高页面浏览速度:②缩短改版时间,降低维护费用:③更好的控制页面布局:④实现表现和结构.内容相分离:⑤更方便搜 ...

  4. Struts2与Ajax的整合

    整合: 导入jar包 sturts2-json-plugin-2.1.8.1.jar 说明: 在该jar包中有struts-plugin.xml文件 <struts>            ...

  5. 小技巧,关于OC打印指针地址和arc下的retaincount

    CFGetRetainCount((__bridge CFTypeRef)self  : 打印retainCount 打印指针的地址(不是指针指向对象的地址):NSLog(@"aStr指针内 ...

  6. 6. support vector machine

    1. 了解SVM 1. Logistic regression 与SVM超平面 给定一些数据点,它们分别属于两个不同的类,现在要找到一个线性分类器把这些数据分成两类.如果用x表示数据点,用y表示类别( ...

  7. Python学习【第十一篇】模块(1)

    模块 模块让你能够有逻辑地组织你的Python代码段. 把相关的代码分配到一个模块里能让你的代码更好用,更易懂. 模块也是Python对象,具有随机的名字属性用来绑定或引用. 简单地说,模块就是一个保 ...

  8. installing mysql,this may take a few minutes,hold on plz wdcp卡住解决办法

    centos6安装wdcp时make in progress卡住的解决办法 今天在一台centos6的vps上安装wdcp出现的这个问题,到安装程序滚动至下面这里时出现"卡死". ...

  9. [CC]点云密度计算

    包括两种计算方法:精确计算和近似计算(思考:local density=单位面积的点数 vs  local density =1/单个点所占的面积) 每种方法可以实现三种模式的点云密度计算,CC里面的 ...

  10. 【CSS 杂记】

    1.CSS达到截取效果 地方卡机了会计师的立法及  =>  地方卡机了... max-width: 400px; overflow: hidden; white-space: nowrap; t ...