使用Mina实现数据采集时出现的断包、半包的问题处理

1、之前写了一篇基于Mina实现的一个简单数据采集中间件
在数据采集的多次测试过程中发现有断包、半包的情况
如下:

报文格式错误:68 4e 04 4e 04 68 c8 54 03 27 03 00 0c ef 01 01 01 03 00 00 26 12 17 71 54 04 94 49 01 47 46 01 29 58 01 17 45 80 

上面的报文没结束(我们的协议都是以16结束)

报文格式错误:00 00 00 00 00 00 00 00 00 8f 05 00 00 26 02 aa 16 

上面的报文包头不正确(我们的协议都是以68开头)

2、具体的处理方法
通过百度可以看到很多人使用Mina的过程中也碰到了该问题,我具体看了这两篇blog
作者”第三眼的思绪”的Mina 粘包、断包、半包解决
作者“rchm8519”的Mina框架断包、粘包问题解决方案

3、具体实现:
之前我使用的是

acceptor.getFilterChain().addFirst("codec",new StreamWriteFilter());

我改为

acceptor.getFilterChain().addLast("codec", new ProtocolCodecFilter(new MinaMessageFactory()));  

以下为我的自定义实现ProtocolCodecFactory类,用来拦截数据包。MinaMessageFactory.java:

public class MinaMessageFactory implements ProtocolCodecFactory {

    private MessageDecoder decoder;  

    private MessageEncoder encoder;   

    public MinaMessageFactory() {  
        this.decoder = new MessageDecoder();  
        this.encoder = new MessageEncoder();  
    }  

    @Override  
    public ProtocolDecoder getDecoder(IoSession session) throws Exception {  
        return decoder;  
    }  

    @Override  
    public ProtocolEncoder getEncoder(IoSession session) throws Exception {  
        return encoder;  
    }  

}

自定义编码器MessageEncoder.java:

public class MessageEncoder extends ProtocolEncoderAdapter {

    @Override  
    public void encode(IoSession session, Object message, ProtocolEncoderOutput out)  
            throws Exception {  
        IoBuffer buf = IoBuffer.allocate(100).setAutoExpand(true);  
        //这个判断可根据自己的实际情况而定  
        if("String".equals(message.getClass().getSimpleName())){  
            String msg = (String) message;  
            byte[] bytes = msg.getBytes();  
            //调用方法将int数转为byte数组 
            byte[] heads = intToBytes(bytes.length); 
            buf.put(heads);  
            buf.put(bytes);  
        }else if("byte[]".equals(message.getClass().getSimpleName())){  
            byte[] bytes = (byte[]) message;  
            buf.put(bytes);  
        }  
        //不可缺少
        buf.flip();  
        out.write(buf);  
    }  

    /**
     * 将int数转为byte数组 
     * @param value
     * @return
     */
    public static byte[] intToBytes( int value ){   
        byte[] src = new byte[4];  
        src[3] =  (byte) ((value>>24) & 0xFF);  
        src[2] =  (byte) ((value>>16) & 0xFF);  
        src[1] =  (byte) ((value>>8) & 0xFF);    
        src[0] =  (byte) (value & 0xFF);                  
        return src;   
    }

}

自定义解码器MessageDecoder.java:

public class MessageDecoder extends CumulativeProtocolDecoder {

     /**
      * 1、当内容刚好时,返回false,告知父类接收下一批内容
      * 2、内容不够时需要下一批发过来的内容,此时返回false,这样父类CumulativeProtocolDecoder
      *   会将内容放进IoSession中,等下次来数据后就自动拼装再次给本类的doDecode
      * 3、当内容多时,返回true,因为需要将本批数据进行读取,父类会将剩余的数据再次推送本类的doDecode
      */
     @Override  
     protected boolean doDecode(IoSession session, IoBuffer in,ProtocolDecoderOutput out) throws Exception {  
        System.out.println("=========doDecode==========");

        System.out.println("in.remaining():" + in.remaining());
        if(in.remaining() > 0){
            byte[] sizeBytes = new byte[6];
            //标记当前位置,以便reset
            in.mark();
            in.get(sizeBytes,0,6);

            int len = getLen(sizeBytes);
            System.out.println("len:" + len);
            in.reset();
            //如果消息内容不够,则重置,相当于不读取len
            if(len > in.remaining()){
                //父类接收新数据,以拼凑成完整数据
                System.out.println("lenaaaa:");
                return false;
            }else{
                System.out.println("lenbbbbb:");
                byte[] bytes = new byte[len];
                in.get(bytes,0,len);

                out.write(IoBuffer.wrap(bytes));

                if(in.remaining() > 0){
                    //如果读取内容后还粘了包,就让父类再重读一次,进行下一次解析
                    return true;
                }
            }
        }
        //处理成功,让父类进行接收下这个包
        return false;
     }

    /**
     * 解析获取报文长度 
     * @param sizeBytes
     * @return
     */
    private int getLen(byte[] sizeBytes) {
        //包头从0位开始,1,2两个字节表示数据包长度                              
        String str1 = toHexStr(sizeBytes,1);
        String str2 = toHexStr(sizeBytes,2);
        //计算数据包的长度
        int len = getDataLen(str1, str2);
        //报文总长度 = 数据包的长度 + 包头包尾巴的长度(8个)  
        len = len + 8;
        return len;
    }

     /**
      * 计算报文数据包的长度,参考国网376.1主站通信协议(两个字节)
      * @param arr1
      * @param arr2
      * @return
      */
     public int getDataLen(String arr1,String arr2){
            //将两个字节对应的十六进制字符串转换为二进制
            String str1 = hexString2binaryString(arr1);
            String str2 = hexString2binaryString(arr2);
            //根据协议规定的方法计算数据包长度
            String result = bytobc(str2 + str1.substring(0,6));
            return Integer.parseInt(result);
     }

    /**
     * 二进制转十进制
     **/
    public String bytobc(String str) {
        String outStr = "";
        int sum = 0;
        for (int t = str.length(); t > 0; t--) {
            sum += (Integer.parseInt(str.substring(str.length() - t, str
                    .length()
                    - t + 1)) * (int) Math.pow(2, t - 1));
        }
        outStr = sum + "";
        return outStr;
    }

    /**
     * 将字节转换为十六进制字符中
     * @param sizeBytes
     * @param index
     * @return
     */
    private String toHexStr(byte[] sizeBytes,int index) {
        String getM = Integer.toHexString(sizeBytes[index] & 0xFF)+"";
        if(getM.length()<2){
            getM="0"+getM;
        }
        return getM;
    }

}

4、小结
通过对CumulativeProtocolDecoder详解的了解,以及自定解码器代码的了解,原理和实现方法基本上差不太多,但每个人或每个项目的协义是不同的。这就需要我们在通过代码的基础上将别人的协议改为自已的协义,如别人的长度是在包头的第一、二两个字节,你的协义规定的长度是在包头的第二、三个字节,别人的协议规定的计算数据包长度的算法与你协议规定的计算数据包长度的算法不一样,那么我们就要根据实际情况处理这些。

本人的另外两篇mina文章:
基于Mina实现的一个简单数据采集中间件
基于mina实现一个简单数据采集中间件的多客户端在线测试程序

阅读更多
想对作者说点什么?

博主推荐

换一批

没有更多推荐了,返回首页