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

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:

 1 ClientHandler clientHandler = new ClientHandler(this);
 2             bootstrap.group(eventLoop)
 3                     .channel(NioSocketChannel.class)
 4                     .option(ChannelOption.TCP_NODELAY, true)
 5                     .option(ChannelOption.CONNECT_TIMEOUT_MILLIS, 3000)
 6                     .option(ChannelOption.MAX_MESSAGES_PER_READ, Integer.MAX_VALUE)
 7                     .handler(new ClientChannelInitializer(clientHandler));
 8
 9             ChannelFuture f = bootstrap.connect(host, port).sync();
10
11             f.channel().closeFuture().sync();
 1 private class ClientChannelInitializer extends ChannelInitializer<SocketChannel> {
 2         private ClientHandler clientHandler;
 3
 4         public ClientChannelInitializer(ClientHandler clientHandler) {
 5             this.clientHandler = clientHandler;
 6         }
 7
 8         @Override
 9         protected void initChannel(SocketChannel socketChannel) throws Exception {
10
11             socketChannel.pipeline().addLast(new SplDecoder());
12             socketChannel.pipeline().addLast(clientHandler);
13             channel = socketChannel;
14         }
15     }
// 解决问题前 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 {
 1 @Override
 2     public void channelActive(ChannelHandlerContext context) throws Exception {
 3         logger.info("Ready to send request...");
 4         ByteBuffer result = getByteBuffer();
 5         ByteBuf buf = Unpooled.buffer(result.remaining());
 6         buf.writeBytes(result);
 7
 8         context.writeAndFlush(buf);
 9     }
10
11     @Override
12     public void channelRead(ChannelHandlerContext context, Object msg) throws Exception {
13         logger.info("Get server response...");
14
15         String[] result = (String[]) msg;
16
17         logger.debug("response xml is : " + result[1]);
18         client.setResponse(result);
19
20         ok = true;
21     }
22
23     @Override
24     public void channelReadComplete(ChannelHandlerContext ctx) throws Exception {
25         ctx.flush();
26         if (ok) {
27             ctx.close();
28         } else {
29             ctx.read();
30         }
31     }

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()前面添加打印错误信息“****** 读取数据不完整,再次读取......”

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

 1 Connected to the target VM, address: ‘127.0.0.1:62194‘, transport: ‘socket‘
 2 log4j:WARN No appenders could be found for logger (io.netty.util.internal.logging.InternalLoggerFactory).
 3 log4j:WARN Please initialize the log4j system properly.
 4 读取数据:本次1024;累计1024;总共574842
 5 读取数据:本次1024;累计2048;总共574842
 6 读取数据:本次1024;累计3072;总共574842
 7 ......
 8 读取数据:本次1024;累计572416;总共574842
 9 读取数据:本次1024;累计573440;总共574842
10 读取数据:本次1024;累计574464;总共574842
11 读取数据:本次378;累计574842;总共574842
12 0 ~~ null
13 Disconnected from the target VM, address: ‘127.0.0.1:62194‘, transport: ‘socket‘
14
15 Process finished with exit code 0

正常时结果

 1 Connected to the target VM, address: ‘127.0.0.1:62068‘, transport: ‘socket‘
 2 log4j:WARN No appenders could be found for logger (io.netty.util.internal.logging.InternalLoggerFactory).
 3 log4j:WARN Please initialize the log4j system properly.
 4 读取数据:本次1024;累计1024;总共574842
 5 读取数据:本次1024;累计2048;总共574842
 6 读取数据:本次1024;累计3072;总共574842
 7 读取数据:本次1024;累计4096;总共574842
 8 读取数据:本次1024;累计5120;总共574842
 9 读取数据:本次1024;累计6144;总共574842
10 读取数据:本次1024;累计7168;总共574842
11 读取数据:本次1024;累计8192;总共574842
12 读取数据:本次1024;累计9216;总共574842
13 读取数据:本次1024;累计10240;总共574842
14 读取数据:本次1024;累计11264;总共574842
15 读取数据:本次1024;累计12288;总共574842
16 读取数据:本次1024;累计13312;总共574842
17 读取数据:本次576;累计13888;总共574842
18 ****** 读取数据不完整,再次读取......
19 读取数据:本次16384;累计30272;总共574842
20 读取数据:本次16384;累计46656;总共574842
21 读取数据:本次16384;累计63040;总共574842
22 ......
23 读取数据:本次16384;累计554560;总共574842
24 读取数据:本次16384;累计570944;总共574842
25 读取数据:本次3898;累计574842;总共574842
26 0 ~~ null
27 Disconnected from the target VM, address: ‘127.0.0.1:62068‘, transport: ‘socket‘
28
29 Process finished with exit code 0

出错时结果

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

附:

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

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

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

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

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

时间: 2024-10-25 08:54:43

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

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

原文:大数据量传输时配置WCF的注意事项 WCF传输数据量的能力受到许多因素的制约,如果程序中出现因需要传输的数据量较大而导致调用WCF服务失败的问题,应注意以下配置: 1.MaxReceivedMessageSize:获取或设置配置了此绑定的通道上可以接收的消息的最大大小. basicHttpBinding等预定义的绑定一般具有MaxReceivedMessageSize属性,CustomBinding则需要在Transport中定义. 示例代码: <bindings> <custom

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

当访问WebSerivice时,如果数据量很大,传输数据时就会很慢.为了提高速度,我们就会想到对数据进行压缩.首先我们来分析一下. 当在webserice中传输数据时,一般都采用Dataset进行数据传输.执行的过程就是先把Dataset转化为xml进行传输,Dataset转化为xml的格式如下: [html] view plaincopy <DataSetName> <DataTableName> <Column1Name>.......</Column1Nam

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

转自:http://www.cnblogs.com/xuxm2007/archive/2011/09/01/2161929.html http://www.geminikwok.com/2011/04/02/hadoop-job解å?³å¤§æ?°æ?®é??å?³è??æ—¶æ?°æ?®å?¾æ??ç??ä¸?ç§?å??æ³?/ 数据倾斜是指,map /reduce程序执行时,reduce节点大部分执行完毕,但是有一个或者几个reduce节点运行很慢,导致整个程序的处理时间很长,这是因为

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

1.Bloom filter 适用范围:可以用来实现数据字典,进行数据的判重,或者集合求交集 基本原理及要点:对于原理来说很简单,位数组+k个独立hash函数.将hash函数对应的值的位数组置1,查找时如果发现所有hash函数对应位都是1说明存在,很明显这个过程并不保证查找的结果是100%正确的.同时也不支持删除一个已经插入的关键字,因为该关键字对应的位会牵动到其他的关键字.所以一个简单的改进就是 counting Bloom filter,用一个counter数组代替位数组,就可以支持删除了.

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

http://hantayi.blog.51cto.com/1100843/383578 引言 当我们需要在用户区显示一些图形时,先把图形在客户区画上,虽然已经画好但此时我们还无法看到,还要通过 程序主动地刷新用户区,强制Windows发送一条WM_PAINT消息,这将引发视类OnDraw函数简单地将所有的图形对象重画,这样才完成了图形的 显示工作,但在刷新的同时会引起较明显的闪烁尤其是当画面面积较大.图像元素过多时尤为明显甚至达到无法正常工作的地步.因此,我们需要做相应的处理.本 文介绍了采用

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

前言 导出功能几乎是所有应用系统必不可少功能,今天我们来谈一谈,如何使用内存映射文件MMF进行内存优化,本文重点介绍使用方法,相关原理可以参考文末的连接 实现 我们以单次导出一个excel举例(csv同理),excel包含1~n个sheet,在每个sheet中存储的按行和列的坐标在单元格存储具体数据,如果我们要使用MMF,第一个要考虑的就是如何将整个excel合理的存储到MMF中.这里我们引入MMF两个对象: MemoryMappedFile --表示内存映射文件 MemoryMappedVie

tomcat优化---大数据量提交tomcat时,tomcat无法接收导致页面无反应

关于tomcat的一个优化问题: 有时候保存大数据量的数据时.tomcat不优化的话,页面会没反应.tomcat后台并不报错,仅仅是提示以下内容: 警告: More than the maximum number of request parameters (GET plus POST) for a s ingle request ([10,000]) were detected. Any parameters beyond this limit have be en ignored. To c

WCF 大数据量如何从服务端传到客户端

当数据量很大时,想要从服务端传到客户端可能会遇到这几个问题:基础连接意外关闭:内存溢出:或时间过长. 下面是我在项目中实现客户端导出txt文件时解决方法.txt有150M左右. namespace XSGLSRV900 //服务端接口实现 { public class XSGLService : IXSGLService { //这些变量只能是静态的,如果是非静态的,在客户端每一次调用服务端都认为是新建一个服务对象,这些值将不再能读取到.关于WCF服务端的三种模式可参考http://www.cn

MySQL数据库如何解决大数据量存储问题

利用MySQL数据库如何解决大数据量存储问题? 各位高手您们好,我最近接手公司里一个比较棘手的问题,关于如何利用MySQL存储大数据量的问题,主要是数据库中的两张历史数据表,一张模拟量历史数据和一张开关量历史数据表,这两张表字段设计的很简单(OrderNo,Value,DataTime).基本上每张表每天可以增加几千万条数据,我想问如何存储数据才能不影响检索速度呢?需不需要换oracle数据库呢?因为我是数据库方面的新手,希望可以说的详细一点,万分感谢!!?-0-#暂时可以先考虑用infobri