工作中的一个项目,我们的一个应用与银行系统进行tcp通信的时候,银行下送的报文有时会分多次返回。在tcp中这种数据包分多次小数据包发送的情况成为拆包问题。

其中一个,也是最常见的思路就是在报文的报文头部分规定某一段代表本次发送的完整报文的长度,这样接收方就会心中有数,在没有接收到这个长度的报文之前,认为本次通信未完成,数据包还不完整,从而继续等待下去。之前曾经遇到过这样的问题,那时候是用的java socket逐个字节对报文进行接收,直到看到结尾符为止。

只是这次项目原来的程序员用的netty框架,一开始没有注意到如何在netty正确处理拆包问题。导致后续投产后,银行返回的报文出现没有完整接收的情况,截断在中文汉字处产生乱码,导致异常。

下面介绍如何在netty中处理拆包问题。

server端代码:

public class NettyServer {
public static void main(String[] args) {
ServerBootstrap bootstrap = new ServerBootstrap(new NioServerSocketChannelFactory(Executors.newCachedThreadPool(), Executors.newCachedThreadPool())); // Set up the default event pipeline.
bootstrap.setPipelineFactory(new ChannelPipelineFactory() {
public ChannelPipeline getPipeline() throws Exception {
return Channels.pipeline(new StringDecoder(), new StringEncoder(), new ServerHandler());
}
}); // Bind and start to accept incoming connections.
Channel bind = bootstrap.bind(new InetSocketAddress(8000));
System.out.println("Server已经启动,监听端口: " + bind.getLocalAddress() + ", 等待客户端注册。。。");
} private static class ServerHandler extends SimpleChannelHandler {
@Override
public void messageReceived(ChannelHandlerContext ctx, MessageEvent e) throws Exception {
if (e.getMessage() instanceof String) {
String message = (String) e.getMessage();
System.out.println("Client发来:" + message); // e.getChannel().write("Server已收到刚发送的:" + message+"\n");
e.getChannel().write("000287<?xml version=\"1.0\" encoding=\"GB18030\"?><root><head><TransCode>1002</TransCode><TransDate>20161025</TransDate><TransTime>092745</TransTime>"+
"<SeqNo>2016110542160157</SeqNo><ZoneCode>HZCQ</ZoneCode><TransRltCode>-25330</TransRltCode><TransRltMsg>000</TransRltMsg></head><body></body></root>"); System.out.println("\n等待客户端输入。。。");
} super.messageReceived(ctx, e);
} @Override
public void exceptionCaught(ChannelHandlerContext ctx, ExceptionEvent e) throws Exception {
super.exceptionCaught(ctx, e);
} @Override
public void channelConnected(ChannelHandlerContext ctx, ChannelStateEvent e) throws Exception {
System.out.println("有一个客户端注册上来了。。。");
System.out.println("Client:" + e.getChannel().getRemoteAddress());
System.out.println("Server:" + e.getChannel().getLocalAddress());
System.out.println("\n等待客户端输入。。。");
super.channelConnected(ctx, e);
}
}
}

client端代码:

public class NettyClient {

    public static void main(String[] args) {
// Configure the client.
ClientBootstrap bootstrap = new ClientBootstrap(new NioClientSocketChannelFactory(Executors.newCachedThreadPool(), Executors.newCachedThreadPool())); // Set up the default event pipeline.
bootstrap.setPipelineFactory(new ChannelPipelineFactory() {
public ChannelPipeline getPipeline() throws Exception {
return Channels.pipeline(new DeliDecoder(),
new StringEncoder(),
new ClientHandler());
}
}); // Start the connection attempt.
ChannelFuture future = bootstrap.connect(new InetSocketAddress("localhost", 8000)); // Wait until the connection is closed or the connection attempt fails.
future.getChannel().getCloseFuture().awaitUninterruptibly(); // Shut down thread pools to exit.
bootstrap.releaseExternalResources();
} private static class ClientHandler extends SimpleChannelHandler {
private BufferedReader sin = new BufferedReader(new InputStreamReader(System.in)); @Override
public void messageReceived(ChannelHandlerContext ctx, MessageEvent e) throws Exception {
if (e.getMessage() instanceof String) {
String message = (String) e.getMessage();
System.out.println(message);
e.getChannel().write(sin.readLine());
} super.messageReceived(ctx, e);
} @Override
public void channelConnected(ChannelHandlerContext ctx, ChannelStateEvent e) throws Exception {
System.out.println("已经与Server建立连接。。。。");
System.out.println("\n请输入要发送的信息:");
super.channelConnected(ctx, e); e.getChannel().write("ddddd");
}
}
}

拆包问题解决的关键:

public class DeliDecoder extends FrameDecoder{

    private static final Logger LOG = Logger.getLogger(DeliDecoder.class);
private final int headLen = 6; //银行回传的报文前6位为报文长度,前6位不计算在长度内 @Override
protected Object decode(ChannelHandlerContext chc, Channel channel,
ChannelBuffer buffer) throws Exception {
LOG.info("进入DeliDecoder.decode()"); if (buffer.readableBytes() < headLen) {
return null; //return null表示继续读取,下同
}
LOG.info("buffer copy...");
ChannelBuffer buffer2 = buffer.copy(); //直接用buffer.array()可能会报UnsupportedOperationException,故使用其copy
LOG.info("buffer copy done");
byte[] arr = buffer2.array();
LOG.info("buffer array init");
String temStr = new String(arr, "GB18030");
LOG.info(temStr);
int dataLength = Integer.parseInt(temStr.substring(0, 6));
LOG.info("dataLength : " + dataLength); if (buffer.readableBytes() < dataLength + headLen) {
return null;
} buffer.skipBytes(headLen); //从第7位开始读取报文正文
byte[] decoded = new byte[dataLength];
buffer.readBytes(decoded);
String msg = new String(decoded, "GB18030");
return msg;
}
}

netty网络通信中的tcp拆包问题的更多相关文章

  1. 深入了解Netty【八】TCP拆包、粘包和解决方案

    1.TCP协议传输过程 TCP协议是面向流的协议,是流式的,没有业务上的分段,只会根据当前套接字缓冲区的情况进行拆包或者粘包: 发送端的字节流都会先传入缓冲区,再通过网络传入到接收端的缓冲区中,最终由 ...

  2. Netty处理TCP拆包、粘包

    Netty实践(二):TCP拆包.粘包问题-学海无涯 心境无限-51CTO博客 http://blog.51cto.com/zhangfengzhe/1890577 2017-01-09 21:56: ...

  3. Netty(二)——TCP粘包/拆包

    转载请注明出处:http://www.cnblogs.com/Joanna-Yan/p/7814644.html 前面讲到:Netty(一)--Netty入门程序 主要内容: TCP粘包/拆包的基础知 ...

  4. Netty系列(四)TCP拆包和粘包

    Netty系列(四)TCP拆包和粘包 一.拆包和粘包问题 (1) 一个小的Socket Buffer问题 在基于流的传输里比如 TCP/IP,接收到的数据会先被存储到一个 socket 接收缓冲里.不 ...

  5. 深入学习Netty(5)——Netty是如何解决TCP粘包/拆包问题的?

    前言 学习Netty避免不了要去了解TCP粘包/拆包问题,熟悉各个编解码器是如何解决TCP粘包/拆包问题的,同时需要知道TCP粘包/拆包问题是怎么产生的. 在此博文前,可以先学习了解前几篇博文: 深入 ...

  6. 手把手教你在netty中使用TCP协议请求DNS服务器

    目录 简介 DNS传输协议简介 DNS的IP地址 Do53/TCP在netty中的使用 搭建DNS netty client 发送DNS查询消息 DNS查询的消息处理 总结 简介 DNS的全称doma ...

  7. 网络通信中tcp多客户端连接

    网络编程中的tcp实例太多了,自己也写了好几次(羞愧),今天在想一对一的TCP知道怎么写了,可是一对多的怎么办呢?服务器是如何知道要给那个发送数据呢?做开发的同学应该经常听说uid这个属性.可以为什么 ...

  8. Netty解决粘包和拆包问题的四种方案

    在RPC框架中,粘包和拆包问题是必须解决一个问题,因为RPC框架中,各个微服务相互之间都是维系了一个TCP长连接,比如dubbo就是一个全双工的长连接.由于微服务往对方发送信息的时候,所有的请求都是使 ...

  9. Netty(三) 什么是 TCP 拆、粘包?如何解决?

    前言 记得前段时间我们生产上的一个网关出现了故障. 这个网关逻辑非常简单,就是接收客户端的请求然后解析报文最后发送短信. 但这个请求并不是常见的 HTTP ,而是利用 Netty 自定义的协议. 有个 ...

随机推荐

  1. MongoDB的文档、集合、数据库(二)

    为了理解MongoDB的名词,可以将其于关系型数据库进行对比: 一.文档 概述 文档是MongoDB的核心概念,是数据的基本单元,非常类似于关系数据库中的行.在MongoDB中,文档表示为键值对的一个 ...

  2. win2003上传文件限制

    Windows2003系统下,上传较大的文件时,会出现“Request 对象 错误 'ASP 0104 : 80004005'”错误或者出现空白页面的时候,此时,不要轻易的去找程序的问题,有可能是wi ...

  3. 如何定义一个高逼格的原生JS插件

    插件的需求 我们写代码,并不是所有的业务或者逻辑代码都要抽出来复用.首先,我们得看一下是否需要将一部分经常重复的代码抽象出来,写到一个单独的文件中为以后再次使用.再看一下我们的业务逻辑是否可以为团队服 ...

  4. Golang之Mysql操作

    话说当年武大郎对着电脑一顿噼里啪啦,,,对mysql增删改查 增加insert package main import ( "fmt" "github.com/jmoir ...

  5. Codeforces 689C. Mike and Chocolate Thieves 二分

    C. Mike and Chocolate Thieves time limit per test:2 seconds memory limit per test:256 megabytes inpu ...

  6. 我的UI启蒙之路

    为什么叫UI启蒙之路呢? 我没有学过美术,也不懂设计,但是有的时候也许就是一种命中注定吧,让我知道了UI,并且一发不可收拾的爱上了它. 具体情况是这样的: 我毕业于电力学校,是一名不折不扣的工科生,专 ...

  7. OSGi 系列(一)之什么是 OSGi :Java 语言的动态模块系统

    OSGi 系列(一)之什么是 OSGi :Java 语言的动态模块系统 OSGi 的核心:模块化.动态.基于 OSGi 就可以模块化的开发 java 应用,模块化的部署 java 应用,还可以动态管理 ...

  8. 关于IBatisNet的配置文件中数据库连接字符串加密处理

    我们通常在IBatisNet配置文件 properties.config 加入数据库连接字符串.数据库连接字符串直接放在里面,没有被加密,很不安全.如果我们把 properties.config 文件 ...

  9. 冒泡排序java语言实现

    class bubbleSort { public static void main(String[] args){ int[] a={49,38,65,97,76,13,27,49,78,34,12 ...

  10. android 网站上下的 adt 不能显示没有安装的

    问题描述 使用SDK Manager更新时出现问题Failed to fetch URL https://dl-ssl.google.com/android/repository/repository ...