使用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 "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")。
- C#使用HttpWebRequest进行HTTP请求发送和接收的一些小结。(新增修复.NET4.0以下关于cookie的bug)
- AngularJs日常bug修复记录: 使用第三方插件按需加载(oclazyload简单使用)
- “黑马程序员”使用TCP协议完成一个客户端一个服务器。客户端从键盘输入读取一个字符串,发送到服务器。 服务器接收客户端发送的字符串,反转之后发回客户端。客户端接收并打印
- c#如何使用socket发送一条记录再接收一条记录,服务器端及客户端如何编写,初学。。最好有代码,谢谢!
- 10、使用TCP协议完成一个客户端一个服务器。客户端从键盘输入读取一个字符串,发送到服务器。 服务器接收客户端发送的字符串,反转之后发回客户端。客户端接收并打印。
- 博客园win8客户端开发记录7 - 修复bug,发布第二版
- 黑马程序员——使用TCP协议完成一个客户端一个服务器。客户端从键盘输入读取一个字符串,发送到服务器。 服务器接收客户端发送的字符串,反转之后发回客户端。客户端接收并打印。
- geotrellis使用(十二)再记录一次惨痛的伪BUG调试经历(数据导入以及读取瓦片)
- NetCMS使用BUG记录及解决方法
- 数据库操作_连接SQL Server数据库示例;连接ACCESS数据库;连接到 Oracle 数据库示例;SqlCommand 执行SQL命令示例;SqlDataReader 读取数据示例;使用DataAdapter填充数据到DataSet;使用DataTable存储数据库表;将数据库数据填充到 XML 文件;10 使用带输入参数的存储过程;11 使用带输入、输出参数的存储过程示;12 获得数据库中表的数目和名称;13 保存图片到SQL Server数据库示例;14 获得插入记录标识号;Exce
- net控件中数据导到Excel的格式 首先,我们了解一下excel从web页面上导出的原理。当我们把这些数据发送到客户端时,我们想让客户端程序(浏览器)以excel的格式读取它,所以把mime类型设为:application/vnd.ms-excel,当excel读取文件时会以每个cell的格式呈现数据,如果cell没有规定的格式,则excel会以默认的格式去呈现该cell的数据。这样就给我们提供了自定义数据格式的空间,当然我们必须使用excel支持的格式。下面就列出常用的一些格式: 1) 文本
- VC6 下学习使用Teechart8记录 三 数据库读取与鼠标选取数据
- 学习使用dwr 中的push技术 服务器发送一个广播,让每一个客户端都能接收到这个广播。
- 使用DataReader读取记录集 输出全部记录
- 使用XFire开发Web Service客户端完整入门教程
- Sharepoint2010应用开发三:使用客户端对象模型(Client Object Model)读取列表数据
- 使用XFire开发Web Service客户端完整入门教程
- SQL Server2005 xml字段使用方法 1对多表记录读取
- 使用存储过程中的虚拟表解决同时从几个数据库服务器中读取记录的问题
- 使用客户端判断删除记录功能