netty网络通信中的tcp拆包问题

工作中的一个项目,我们的一个应用与银行系统进行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;
    }
}
时间: 2024-07-29 17:31:49

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

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

Netty系列(四)TCP拆包和粘包 一.拆包和粘包问题 (1) 一个小的Socket Buffer问题 在基于流的传输里比如 TCP/IP,接收到的数据会先被存储到一个 socket 接收缓冲里.不幸的是,基于流的传输并不是一个数据包队列,而是一个字节队列.即使你发送了 2 个独立的数据包,操作系统也不会作为 2 个消息处理而仅仅是作为一连串的字节而言.因此这是不能保证你远程写入的数据就会准确地读取.举个例子,让我们假设操作系统的 TCP/TP 协议栈已经接收了 3 个数据包: 由于基于流传输

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

网络编程中的tcp实例太多了,自己也写了好几次(羞愧),今天在想一对一的TCP知道怎么写了,可是一对多的怎么办呢?服务器是如何知道要给那个发送数据呢?做开发的同学应该经常听说uid这个属性.可以为什么通过UID就知道要发送的数据是给正确的用户的呢? 不怎么忙的时候.仔细的了解了一下TCP的几个API和其中的参数.下面来看一下这几个API和参数: 描述:当创建socket套接字后,该套接字并没有鱼本机地址和端口等信息相连接,而bind函数将完成这些工作 包含的头文件 <sys/types.h>

CocoaAsyncSocket网络通信使用之tcp连接(一)

CocoaAsyncSocket网络通信使用之tcp连接(一) 简述: 在互联网世界中,网络访问是必不可少的一部分,而对于程序员来说,网络编程却是一个比较复杂的存在,特别是socket处理方面. 在android平台中,java类库丰富,封装良好,比如:mina,netty等等. 而在ios平台中,也有出名的socket库,CocoaAsyncSocket. 最近碰到一些朋友在socket的应用上一直不是特别熟悉,自己在接触过socket底层库,使用过mina,netty和CocoaAsyncS

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

在RPC框架中,粘包和拆包问题是必须解决一个问题,因为RPC框架中,各个微服务相互之间都是维系了一个TCP长连接,比如dubbo就是一个全双工的长连接.由于微服务往对方发送信息的时候,所有的请求都是使用的同一个连接,这样就会产生粘包和拆包的问题.本文首先会对粘包和拆包问题进行描述,然后介绍其常用的解决方案,最后会对Netty提供的几种解决方案进行讲解.这里说明一下,由于oschina将"jie ma qi"认定为敏感文字,因而本文统一使用"解码一器"表示该含义 粘包

用java网络编程中的TCP方式上传文本文件及出现的小问题

自己今天刚学java网络编程中的TCP传输,要用TCP传输文件时,自己也是遇到了一些问题,抽空把它整理了一下,供自己以后参考使用. 首先在这个程序中,我用一个客户端,一个服务端,从客户端上传一个文本文件给服务端,服务端接收数据并显示“上传成功”给客户端. 客户端: 1 import java.io.BufferedReader; 2 import java.io.FileReader; 3 import java.io.IOException; 4 import java.io.InputStr

C#中使用TCP通信

TCP通信需要通信双方都在线,所以需要先启动服务端进行监听,客户端才能获得连接,服务端代码: static void Main(string[] args) { TcpClient client = null; NetworkStream stream = null; byte[] buffer = null; string receiveString = null; IPAddress localIP = IPAddress.Parse("127.0.0.1"); int local

关于网络编程中MTU TCP UDP优化设置总结

首先要看TCP/IP协议,涉及到四层:链路层,网络层,传输层,应用层.  其中以太网(Ethernet)的数据帧在链路层 IP包在网络层 TCP或UDP包在传输层 TCP或UDP中的数据(Data)在应用层 它们的关系是 数据帧{IP包{TCP或UDP包{Data}}} --------------------------------------------------------------------------------- 在应用程序中我们用到的Data的长度最大是多少,直接取决于底层的

监控Netstat中的TCP数据

通过netstat命令,我们能获取TCP数据,监控它们有助于了解系统状态. 如果netstat版本比较老的话,那么运行时可能会遇到类似下面的错误信息: error parsing /proc/net/netstat: Success 假设操作系统是CentOS,首先让我们看看如何确认netstat隶属于哪个软件包: shell> rpm -qf $(which netstat) net-tools-<VERSION> 如上所示,可以得知netstat属于net-tools软件包,接着升级

转 gSOAP中使用TCP协议传输数据

一  模型 TCP/IP是一个协议族(Internet protocol suite),包含众多的协议,传输控制协议(TCP)和网际协议(IP)分属不同的层次,是保证数据完整传输的两个基本的重要协议. 在上图TCP/IP模型与SOAP模型中 层级是一一对应的 不同的协议层对数据有不同的称谓,在传输层叫做段(segment),在网络层叫做数据报(datagram),在链路层叫做帧(frame).数据封装成帧后发到传输介质上,到达目的主机后每层协议再剥掉相应的头部,最后将应用层数据交给应用程序处理.