1.先解释下什么叫粘包和断包

粘包 就是数据以字节的形式在网络中传输,一个数据包的字节可能经过多次的读取粘合才能形成一个完整的数据包

断包 一次读取的内容可能包含了两个或多个数据包的内容,那么我们必须要把当前正在读取的数据包的内容读完整,后面的内容交给其他的数据包去处理

2.粘包和断包是只针对解码(拆包)而言的,编码不需要考虑这件事

3.如果你是个新手,你要明白拆包封包和序列化和反序列化不是一回事,拆包封包是针对协议格式而言的,而序列化和反序列化是针对包体部分编解码而言的

废话说的不少,但是初接触的人来说,这些概念常常是混淆的,mina在粘包断包处理上做的很完善,直接提供了解决方法,但是我们还是要研究下他是怎么做的

org.apache.mina.filter.codec.CumulativeProtocolDecoder   mina中这个类就是在做粘包和断包处理

采用的原理说明:协议格式中可以在协议的头部使用1,2,4字节定义消息体的中长度,消息体的内容没有读够的时候就一直保存在session中做粘合,直到读取完整[粘包过程],

读取完整的时候,如果后面还有没读取的内容要重复放入session交给后续的包去粘合[断包处理]

例如我项目采用的方式

协议说明:

5个字节协议头+协议体.

协议头1-4字节表示协议长度=协议体长度+协议头长度-4(去掉长度占的4字节),采用网络字节序的整数(高位在前,低位在后)

协议头第5字节为标志字节:该字节的最低位为压缩位:0=协议体未压缩 1=协议体已经压缩,该字节的低2-4位为协议位:000=基于AMF3的协议,001=基于java serial协议 , 5-8位未用,作为以后扩展。

1

2

3

4

5

标志位

数据(AMF3或者java serial)

看看这个类CumulativeProtocolDecoder怎么写的, 参考一篇非常好的文章http://www.blogjava.net/landon/archive/2013/12/02/407122.html

CumulativeProtocolDecoder#decode实现

/**
    * 1.缓存decode中的IoBuffer in至session的attribute
    * 2.循环调用doDecode方法直到其返回false
    * 3.解码结束后缓存的buffer->压缩
    */
    public void decode(IoSession session, IoBuffer in, ProtocolDecoderOutput out) throws Exception {
       // 判断传输层是否存在消息分片,如果不分片则直接doDecode.(可参考TCP/IP详解)
        if (!session.getTransportMetadata().hasFragmentation()) {
            while (in.hasRemaining()) {//hasRemaining()网络中是否还有没读完的内容
                if (!doDecode(session, in, out)) {//这是个抽象方法,调用的是子类中实现的doDecode方法
                    break;
                }
            }

            return;
        }

        boolean usingSessionBuffer = true;
        IoBuffer buf = (IoBuffer) session.getAttribute(BUFFER);//session中的就是你已经读到的所有字节,in中的就是等待读取的内容,这两要粘合在一起
        // 如果session中有BUFFER这个attribute则直接执行追加,否则直接用网络层读到的buffer
        if (buf != null) {
            boolean appended = false;
            // Make sure that the buffer is auto-expanded.
            if (buf.isAutoExpand()) {//这IOBuffer默认可不是自动扩展的,所以要看我们存入session的是否指定为自动扩展,这里当然是了
                try {
                    buf.put(in);//这方法很明了,就是追加
                    appended = true;
                } catch (IllegalStateException e) {
                    // 可能调用了类似slice的方法,会使父缓冲区的自动扩展属性失效(1.可参考AbstractIoBuffer#recapacityAllowed 2.可参考IoBuffer的实现)
                } catch (IndexOutOfBoundsException e) {
                    // 取消了自动扩展属性(可参考IoBuffer实现)
                }
            }

            if (appended) {
    // 追加成功的话,直接flip
                buf.flip();//重置读取状态,准备从头读,position=0,mark=-1,limit=capacity
            } else {
     // 因为用了派生的方法(父子缓冲区)如slice或取消了自动扩展而导致追加失败->重新分配一个Buffer
                buf.flip();
                IoBuffer newBuf = IoBuffer.allocate(buf.remaining() + in.remaining()).setAutoExpand(true);
                newBuf.order(buf.order());
                newBuf.put(buf);
                newBuf.put(in);
                newBuf.flip();
                buf = newBuf;

                // 更新session属性
                session.setAttribute(BUFFER, buf);
            }
        } else {
    // 此else表示session无BUFFER属性,直接赋值
            buf = in;
            usingSessionBuffer = false;
        }

        // 无限循环直到break 1.doDecode返回false 2.doDecode返回true且buf已无数据 3.异常
        for (;;) {
            int oldPos = buf.position();//这时候因为上面的flip,这里肯定是0啊
            boolean decoded = doDecode(session, buf, out);//这是个抽象方法,调用的是子类中实现的doDecode方法
            if (decoded) {//返回true代表已经取到完整的包,也就是读到了>=包头定义的长度的字节
                if (buf.position() == oldPos) {
        //你都已经读取了字节,那这时候的position一定!=0,如果还等于0那就异常了
                    throw new IllegalStateException("doDecode() can't return true when buffer is not consumed.");
                }

                if (!buf.hasRemaining()) {
        //这就是刚刚好的效果,没有没读的数据,正好够完整的包
                    break;
                }
            } else {
                break;
            }
        }

        // 如果经过decode,buffer依然有剩余数据则存储到session->这样下次decode的时候就可以从session取出buffer并执行追加了
        if (buf.hasRemaining()) {
            if (usingSessionBuffer && buf.isAutoExpand()) {
                //后续次就压缩
                buf.compact();
            } else {
                  //首次就直接进session
                storeRemainingInSession(buf, session);
            }
        } else {
            if (usingSessionBuffer) {
                removeSessionBuffer(session);
            }
        }
    }
 
 
 
列出我的解码类

public class MutilDecoder extends CumulativeProtocolDecoder {
private static Logger log = LoggerFactory.getLogger(MutilDecoder.class);

/**
* decoder最大长度
*/
private int maxDecodeLen = 5 * 1024 * 1024;

public void setMaxDecodeLen(int maxDecodeLen) {
  this.maxDecodeLen = maxDecodeLen;
}

@Override
protected boolean doDecode(IoSession session, IoBuffer in, ProtocolDecoderOutput out) throws Exception {
try {
  while (in.remaining() > 0) {// /这层循环实际上不需要,CumulativeProtocolDecoder已经处理了
    boolean dataAvai = in.prefixedDataAvailable(4, maxDecodeLen);
    if (dataAvai) {
      //System.out.println("*****"+in.getInt(in.position()));
      // 正常Encoder中写入的包头制定长度数据
      int len = in.getInt();//读4个字节

      byte flag = in.get();// 标志位//读第5个字节

      // 是否压缩
      boolean compressed = ((flag & 0x1) == MutliEncoderNew.BIT_COMPRESSED);

      // //先把需要的字节数读到数组中,防止decode出错后有剩余的字节保留在IoBuffer,使下一个请求解析不了
      byte bytes[] = new byte[len - 1];
      in.get(bytes, 0, len - 1);//读取包体的字节
      if ((flag & 0xE) == MutliEncoderNew.BIT_JAVA) {
        javaDecode(out, bytes, compressed);//java反序列化
      } else {
        amf3Decode(out, bytes, compressed);//amf3反序列化
      }
      //System.out.println("========1");
    } else {
      // 包长度不正确,等待后续包
      if (log.isDebugEnabled()) {
        log.debug("包长度不正确,等待后续包.......");
      }
      //System.out.println(":::总长度"+in.getInt(in.position())+",本次接收长度:"+in.remaining());
      // System.out.println("length is error");
      return false;
    }

  }
} catch (BufferDataException e) {
  log.error("解码数据长度不在限制范围内,丢弃并关闭session.{}", session);
  session.close(true);
  throw e;
} catch (Exception e) {
  log.error(e.getMessage());
  throw e;
}
 return true;
}
}

【MINA】粘包断包处理的更多相关文章

  1. UNIX网络编程——Socket/TCP粘包、多包和少包, 断包

    为什么TCP 会粘包 前几天,调试mina的TCP通信, 第一个协议包解析正常,第二个数据包不完整.为什么会这样吗,我们用mina这样通信框架,还会出现这种问题? TCP(transport cont ...

  2. Socket/TCP粘包、多包和少包, 断包

    转发: https://blog.csdn.net/pi9nc/article/details/17165171 为什么TCP 会粘包 前几天,调试mina的TCP通信, 第一个协议包解析正常,第二个 ...

  3. 为什么TCP 会粘包断包UDP不会

    TCP(transport control protocol,传输控制协议)是面向连接的,面向流的,提供高可靠性服务.收发两端(客户端和服务器端)都要有一一成对的socket,因此,发送端为了将多个发 ...

  4. Mina框架断包、粘包问题解决方式

    Mina框架断包.粘包问题解决方式 Apache Mina Server 是一个网络通信应用框架,也就是说,它主要是对基于TCP/IP.UDP/IP协议栈的通信框架(当然.也能够提供JAVA 对象的序 ...

  5. mina框架tcpt通讯接收数据断包粘包处理

    用mina做基于tcp,udp有通讯有段时间了,一直对编码解码不是很熟悉,这次做项目的时候碰到了断包情况,贴一下解决过程, 我接受数据格式如下图所示: unit32为c++中数据类型,代表4个字节,由 ...

  6. NIO框架之MINA源码解析(四):粘包与断包处理及编码与解码

    1.粘包与段包 粘包:指TCP协议中,发送方发送的若干包数据到接收方接收时粘成一包,从接收缓冲区看,后一包数据的头紧接着前一包数据的尾.造成的可能原因: 发送端需要等缓冲区满才发送出去,造成粘包 接收 ...

  7. mina websocket 粘包、断包、(丢包)解决心得

    被这3个(其实是2个)问题坑惨了,目前没发现存在丢包问题,之前认为的丢包问题事实是不存在的. 粘包和断包的情况是存在的,这两个问题不怕,只要发送接收到的数据包顺序没有被打乱颠倒,一切都好办. 容易掉的 ...

  8. TCP 的断包和粘包

    以太网中存在一个对于帧的有效数据大小的限制,即 MTU,以太网的 MTU 为 1500 字节. 一.断包 就是说发送端一次发送的消息长度过大,如果超过了 MTU,那么 ip 会对其进行分片. 在网络编 ...

  9. Netty 粘包/半包原理与拆包实战

    Java NIO 粘包 拆包 (实战) - 史上最全解读 - 疯狂创客圈 - 博客园 https://www.cnblogs.com/crazymakercircle/p/9941658.html 本 ...

随机推荐

  1. 【openstack报错】【metadata问题】‘http://169.254.169.254/2009-04-04/meta-data/instance-id’ failed : url error [[Errno 111] Connection refused]

    [时间]2014年2月25日 [平台]ubuntu 12.04.3 openstack havana  with nova-network in multi-host [日志]实例启动时输出的日志内容 ...

  2. POJ 2749--Building roads(2-SAT)

    题意:John有n个牛棚,每个牛棚都住着一些牛,这些牛喜欢串门(drop around, 学到了...),所以John想要建几条路把他们连接起来.他选择的方法是建两个相连中转站,然后每个牛棚连接其中一 ...

  3. Android实例-闪光灯的控制(XE8+小米2)

    unit Unit1; interface uses System.SysUtils, System.Types, System.UITypes, System.Classes, System.Var ...

  4. Google的IP地址一览表,加上代理服务器

    Bulgaria 93.123.23.1 93.123.23.2 93.123.23.3 93.123.23.4 93.123.23.5 93.123.23.6 93.123.23.7 93.123. ...

  5. A Tour of Go Interfaces are satisfied implicitly

    A type implements an interface by implementing the methods. There is no explicit declaration of inte ...

  6. 3.x的触摸响应机制

    第一种是采用函数回调,主要是用于MenuItem [cpp] view plaincopy // a selector callback void menuCloseCallback(Object*  ...

  7. VB操作CAD

    Dim xlapp As Excel.Application            Dim xlbook As Excel.Workbook            Dim sheet As Excel ...

  8. 理解virtual方法

    1.使用场景 virtual方法的使用场景:父类告诉子类,继承接口,修改实现,从而可以面向接口编程. non-virtual方法的使用场景:父类告诉子类,继承接口和实现,从而可以代码复用. 2.成员方 ...

  9. Codeforces Round #324 (Div. 2) A. Olesya and Rodion 水题

    A. Olesya and Rodion Time Limit: 1 Sec Memory Limit: 256 MB 题目连接 http://codeforces.com/contest/584/p ...

  10. File transfer in android with asmack and Openfire

    http://harryjoy.com/2012/08/18/file-transfer-in-android-with-asmack-and-openfire/ http://www.javacod ...