工作中的一个项目,我们的一个应用与银行系统进行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. postman 使用 - 连接不到接口

  2. MySQL基本操作之命令行操作

    MySQL基础操作 MySQL基础操作--命令行操作

  3. 在不用重做系统下,把硬盘IDE模式修改为AHCI模式

    Win10自2015年7月29日诞生以来已经3年多了,虽然截至到现在Win7用户使用者仍然比Win10用户多,但是Win10用户也在逐渐增加 所使用的硬件--硬盘   用户为了更好的体验,也逐步的从H ...

  4. linux引导系统

    一.linux引导系统 1.选择操作系统 /etc/grub.conf 设置grub引导装载程序口令,使用单用户模式时必须输入此密码 password --md5 md5后的密码字符串(可以通过gru ...

  5. maven项目工程目录约定

    使用maven创建的工程我们称它为maven工程,maven工程具有一定的目录规范,如下: src/main/java —— 存放项目的.java文件 src/main/resources —— 存放 ...

  6. 『SpringMVC』<context:include-filter>&<context:exclude-filter>爬坑

    原文地址:https://segmentfault.com/a/1190000012972619 大家好,我是猪弟,猪在我心中从来不是蠢的代名词,而是懒的代名词,本次准备记录一个在开发测试过程中遇到的 ...

  7. bat批量重命名文件

    @echo off setlocal enabledelayedexpansion set prefix="mai" set /a num=000 rem 排序/o:? -代表逆序 ...

  8. 构建ASP.NET网站十大必备工具

    最近使用ASP.NET为公司构建了一个简单的公共网站(该网站的地址:http://superexpert.com/).在这个过程中,我们使用了数量很多的免费工具,如果把构建ASP.NET网站的必备工具 ...

  9. 构造函数constructor 与析构函数destructor(三)

    (1)构造函数初始化列表: 1 class Test{ 2 int i; 3 public: 4 Test(int vi):i(vi){}//这里的从冒号开始,到右大括号结束,这一段是构造函数初始化列 ...

  10. 2018.08.16 洛谷P3607 [USACO17JAN]序列反转(线性dp)

    传送门 一道感觉比较简单的dp. 注意是要求翻转一个子序列而不是一段连续的数(被坑了很多次啊)... 看到数据范围果断开一个四维数组来dp一波. 我们显然可以用f[i][j][k][t]表示下标在[l ...