Mina 粘包、拆包的实现-网上常见的代码有bug

倾然丶 夕夏残阳落幕 提交于 2020-02-27 14:05:07

mina的粘包拆包其实是蛮简单的,只是一开始没搞清楚原理。

我们要约定数据包的格式,我这里的是(4个字节长度+json的string字符串)

1:写一个

ProtocolCodecFactory类,用来拦截数据包处理

内容如下

public class MessageCodecFactory implements ProtocolCodecFactory {

    private final DataEncoderEx encoder;

    private final DataDecoderEx decoder;

    

    public MessageCodecFactory() {

        encoder = new DataEncoderEx();

        decoder = new DataDecoderEx();

    }

    /* (non-Javadoc)

     * @see org.apache.mina.filter.codec.ProtocolCodecFactory#getDecoder(org.apache.mina.core.session.IoSession)

     */

    @Override

    public ProtocolDecoder getDecoder(IoSession session) throws Exception {

        return decoder;

    }

    /* (non-Javadoc)

     * @see org.apache.mina.filter.codec.ProtocolCodecFactory#getEncoder(org.apache.mina.core.session.IoSession)

     */

    @Override

    public ProtocolEncoder getEncoder(IoSession session) throws Exception {

        return encoder;

    }

}

2:在chain里面注册解码器

chain.addLast("codec", new ProtocolCodecFilter(new MessageCodecFactory()));

注意放在多线程上面,否则会导致解码混乱的情况

3:实现decode和encoder

CumulativeProtocolDecoder 这个类的作用很好,我贴一个网上的总结

A. 你的doDecode()方法返回true 时,CumulativeProtocolDecoder 的decode()方法会首 先判断你是否在doDecode()方法中从内部的IoBuffer 缓冲区读取了数据,如果没有,

则会抛出非法的状态异常,也就是你的doDecode()方法返回true 就表示你已经消费了 本次数据(相当于聊天室中一个完整的消息已经读取完毕),进一步说,也就是此时你 必须已经消费过内部的IoBuffer 缓冲区的数据(哪怕是消费了一个字节的数据)。如果 验证过通过,那么CumulativeProtocolDecoder 会检查缓冲区内是否还有数据未读取, 如果有就继续调用doDecode()方法,没有就停止对doDecode()方法的调用,直到有新 的数据被缓冲。 B. 当你的doDecode()方法返回false 时,CumulativeProtocolDecoder 会停止对doDecode() 方法的调用,但此时如果本次数据还有未读取完的,就将含有剩余数据的IoBuffer 缓 冲区保存到IoSession 中,以便下一次数据到来时可以从IoSession 中提取合并。如果 发现本次数据全都读取完毕,则清空IoBuffer 缓冲区。 简而言之,当你认为读取到的数据已经够解码了,那么就返回true,否则就返回false。这 个CumulativeProtocolDecoder 其实最重要的工作就是帮你完成了数据的累积,因为这个工 作是很烦琐的。

也就是说返回true,那么CumulativeProtocolDecoder会再次调用decoder,并把剩余的数据发下来

返回false就不处理剩余的,当有新数据包来的时候把剩余的和新的拼接在一起然后再调用decoder

public class DataDecoderEx extends CumulativeProtocolDecoder {

@Override

protected boolean doDecode(IoSession session, IoBuffer in,

ProtocolDecoderOutput out) throws Exception {

// TODO Auto-generated method stub

if(in.remaining()<4)//这里很关键,网上很多代码都没有这句,是用来当拆包时候剩余长度小于4的时候的保护,不加就出错咯 

{

return false;

}

        if (in.remaining() > 1) {

        

            in.mark();//标记当前位置,以便reset 

            int length =in.getInt(in.position());

         

            if(length > in.remaining()-4){//如果消息内容不够,则重置,相当于不读取size   

            System.out.println("package notenough  left="+in.remaining()+" length="+length);

                in.reset();   

                return false;//接收新数据,以拼凑成完整数据   

            }else{  

            System.out.println("package ="+in.toString()); 

            in.getInt();

            

                byte[] bytes = new byte[length]; 

                in.get(bytes, 0, length);   

                String str = new String(bytes,"UTF-8");    

                if(null != str && str.length() > 0){   

                String strOut = DateSecret.decryptDES(str);//别看这里的处理,这里是我的数据包解密算法~你可以直接拿str当数据

                out.write(strOut);    

                }   

                if(in.remaining() > 0){//如果读取内容后还粘了包,就让父类再给一次,进行下一次解析  

                //System.out.println("package left="+in.remaining()+" data="+in.toString()); 

                }   

                return true;//这里有两种情况1:没数据了,那么就结束当前调用,有数据就再次调用

            }   

        }   

        return false;//处理成功,让父类进行接收下个包   

}

}

public class DataEncoderEx extends ProtocolEncoderAdapter{

/* (non-Javadoc)

     * @see org.apache.mina.filter.codec.ProtocolEncoder#encode(org.apache.mina.core.session.IoSession, java.lang.Object, org.apache.mina.filter.codec.ProtocolEncoderOutput)

     */

    public void encode(IoSession session, Object message,

            ProtocolEncoderOutput out) throws Exception {

    System.out.println(message);

    IoBuffer buf = IoBuffer.allocate(100).setAutoExpand(true);

    String strOut = DateSecret.encryptDES(message.toString());//别看这里的处理,这里是我的数据包加密算法~你可以直接拿message.toString当数据

    buf.putInt(strOut.getBytes(Charset.forName("utf-8")).length);

buf.putString(strOut,Charset.forName("utf-8").newEncoder());

buf.flip();

out.write(buf);

    }

}

这样基本没神马问题,如有问题我会继续补充 ———————————————— 版权声明:本文为CSDN博主「loseleo」的原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接及本声明。 原文链接:https://blog.csdn.net/loseleo/article/details/9141783

标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!