工作中的一个项目,我们的一个应用与银行系统进行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;
}
}

最新文章

  1. CS: Marshalling and Unmarshalling, Serialization and Unserialization
  2. nodejs 相关
  3. adb使用
  4. Function---hdu5875(大连网选,区间连续求余)
  5. 自定义 404 与 500 错误页面,URL 地址不会重定向(一)
  6. XTUOJ1250 Super Fast Fourier Transform 暴力
  7. CMDB处理数据流程
  8. php运用curl触发后台脚本不超时执行某项任务
  9. 从一般分布式设计看HDFS设计思想与架构
  10. AngularJS -- 提供者(Providers)
  11. Python数据分析(二): Numpy技巧 (2/4)
  12. 畅通工程 HDU - 1863
  13. Tensorflow笔记一
  14. 软件测试:1.Describe An Error
  15. python 字典 拼接SQL语句
  16. 大数据-将MP3保存到数据库并读取出来《黑马程序员_超全面的JavaWeb视频教程vedio》day17
  17. beta阶段测试基本概况对应机型硬件信息
  18. pyppeteer使用笔记
  19. 什么是Less、typescript与webpack?
  20. 如何新建一个datatable,并往表里赋值

热门文章

  1. loadrunner12.5-添加检查点
  2. UEdit百度富文本编辑器
  3. wcf服务契约的重载
  4. jQuery Datepicker 插件遇到问题
  5. 2018.09.19 atcoder Snuke's Subway Trip(最短路)
  6. 处理jQuery选择器中的特殊符号,如(、#等
  7. C语言之控制台读取上下左右方向键指令
  8. UVa 11367 Full Tank? (DP + Dijkstra)
  9. jar 包和 mysql 服务器部署
  10. MATLAB实现截位的问题