`
freemart
  • 浏览: 19996 次
  • 性别: Icon_minigender_1
  • 来自: 北京
文章分类
社区版块
存档分类
最新评论

mina粘包、多包和少包的解决方法

阅读更多
   使用过mina的同学应该都遇到到过,在解码时少包、多包的问题,查阅了很多资料还是迷迷糊糊的,经过

不懈努力,终于解决了。原来解决方法是那样的简单。废话少说,请看列子。
  
   另外建了一个交流群:19702042,大家可以在线交流

   问题:我发送的是xml字符串数据,在发送数据后,接收方在解码的时候可能接到1条,也可能是多条,还

可能是半条或一条半,解决方法就是使用CumulativeProtocolDecoder

   首先,在编码的时候要把前4位设成标志位,标志消息内容的长度。里面的重点是doDecode的返回值,一

定要继承CumulativeProtocolDecoder 哦。

   清看decode的写法:
public class AsResponseDecoder extends CumulativeProtocolDecoder {
	private static Logger LOG = LoggerFactory.getLogger(AsResponseDecoder.class);
	private final Charset charset;
	
	public AsResponseDecoder(Charset charset){
		this.charset = charset;
	}
	

	/**
	 * 这个方法的返回值是重点:
	 * 1、当内容刚好时,返回false,告知父类接收下一批内容
	 * 2、内容不够时需要下一批发过来的内容,此时返回false,这样父类

CumulativeProtocolDecoder
	 *    会将内容放进IoSession中,等下次来数据后就自动拼装再交给本类的doDecode
	 * 3、当内容多时,返回true,因为需要再将本批数据进行读取,父类会将剩余的数据再次推送本

类的doDecode
	 */
	public boolean doDecode(IoSession session, IoBuffer in,
			ProtocolDecoderOutput out) throws Exception {
		
		CharsetDecoder cd = charset.newDecoder();
		if(in.remaining() > 0){//有数据时,读取4字节判断消息长度
			byte [] sizeBytes = new byte[4];
			in.mark();//标记当前位置,以便reset
			in.get(sizeBytes);//读取前4字节
                        //NumberUtil是自己写的一个int转byte[]的一个工具类
			int size = NumberUtil.byteArrayToInt(sizeBytes);
			//如果消息内容的长度不够则直接返回true
			if(size > in.remaining()){//如果消息内容不够,则重置,相当于不读取size
				in.reset();
				return false;//接收新数据,以拼凑成完整数据
			} else{
				byte[] bytes = new byte[size]; 
				in.get(bytes, 0, size);
				String xmlStr = new String(bytes,"UTF-8");
				System.out.println("------------"+xmlStr);
				if(null != xmlStr && xmlStr.length() > 0){
					AsResponse resCmd = new AsResponse();
					AsXmlPacker.parse(resCmd, xmlStr);
					if(resCmd != null){
						out.write(resCmd);
					}
				}
				if(in.remaining() > 0){//如果读取内容后还粘了包,就让父类再给俺

一次,进行下一次解析
					return true;
				}
			}
		}
		return false;//处理成功,让父类进行接收下个包
	}


}


下面附上Encode类
public class AsResponseEncoder extends ProtocolEncoderAdapter {
	private final Charset charset;
	
	public AsResponseEncoder(Charset charset){
		this.charset = charset;
	}
	
	public void encode(IoSession session, Object message,
		ProtocolEncoderOutput out) throws Exception {
		CharsetEncoder ce = charset.newEncoder();
		IoBuffer buffer = IoBuffer.allocate(100).setAutoExpand(true);
		
		AsResponse respCmd = (AsResponse) message;
		
		String xml = AsXmlPacker.pack(respCmd);//将对象转成xml
		byte[] bytes = xml.getBytes();
		byte[] sizeBytes = NumberUtil.intToByteArray(bytes.length);
		
		buffer.put(sizeBytes);//将前4位设置成数据体的字节长度
		buffer.put(bytes);//消息内容
		buffer.flip();
		out.write(buffer);
	}


}

3
0
分享到:
评论
12 楼 c_m_l 2014-08-15  
@Override
	protected boolean doDecode(IoSession session, IoBuffer in,
			ProtocolDecoderOutput out) throws Exception {
		//判断前四字节的整型值是否大于等于整个缓冲区的数据。可以方便的判断一次 
		//messageReceived 过来的数据是否完整。
		
		if (in.prefixedDataAvailable(4, maxSize)) {
                     //在这里面做处理
                }


mina已经提供了这些一些列的做法 了, 可以使用in.prefixedDataAvailable(4, maxSize);这个API,很好用
11 楼 simple1024 2013-06-26  
kennyZhong 写道
if(in.remaining() > 0){//有数据时,读取4字节判断消息长度 
            byte [] sizeBytes = new byte[4]; 
            in.mark();//标记当前位置,以便reset 
            in.get(sizeBytes);//读取前4字节 
                        //NumberUtil是自己写的一个int转byte[]的一个工具类 
            int size = NumberUtil.byteArrayToInt(sizeBytes); 
            //如果消息内容的长度不够则直接返回true 
            if(size > in.remaining()){//如果消息内容不够,则重置,相当于不读取size 
                in.reset(); 
                return false;//接收新数据,以拼凑成完整数据 
            } else{ 
                byte[] bytes = new byte[size];  
                in.get(bytes, 0, size); 
                String xmlStr = new String(bytes,"UTF-8"); 
                System.out.println("------------"+xmlStr); 
                if(null != xmlStr && xmlStr.length() > 0){ 
                    AsResponse resCmd = new AsResponse(); 
                    AsXmlPacker.parse(resCmd, xmlStr); 
                    if(resCmd != null){ 
                        out.write(resCmd); 
                    } 
                } 
                if(in.remaining() > 0){//如果读取内容后还粘了包,就让父类再给俺 
 
一次,进行下一次解析 
                    return true; 
                } 
            } 
        } 
        return false;//处理成功,让父类进行接收下个包 





这段有问题吧?
那如果我的包是分成两段以上,后面读取的数据不就乱了吗?



怎么会乱呢?
如果数据不完整(断包了)那不就是返回false,继续接收下个包的数据,然后拼装起来,再次进行doDecode解析,如果还不够就会继续接收,直到出现一个完整的包(非size > in.remaining()),就会执行第一个else中的代码,封装对象,然后write出去。

在这之后再判断是不是有多余的数据(粘包了),如果有,返回true再次继续doDecode方法进行对buffer的解析。

只要你每次解析的数据是按照你所定义的,就不会出现问题。
如果出1个字节的错误,那可就全错了。

看完这些注释我恍然大悟。
谢谢哥们儿的帖子。
10 楼 kennyZhong 2013-06-25  
if(in.remaining() > 0){//有数据时,读取4字节判断消息长度 
            byte [] sizeBytes = new byte[4]; 
            in.mark();//标记当前位置,以便reset 
            in.get(sizeBytes);//读取前4字节 
                        //NumberUtil是自己写的一个int转byte[]的一个工具类 
            int size = NumberUtil.byteArrayToInt(sizeBytes); 
            //如果消息内容的长度不够则直接返回true 
            if(size > in.remaining()){//如果消息内容不够,则重置,相当于不读取size 
                in.reset(); 
                return false;//接收新数据,以拼凑成完整数据 
            } else{ 
                byte[] bytes = new byte[size];  
                in.get(bytes, 0, size); 
                String xmlStr = new String(bytes,"UTF-8"); 
                System.out.println("------------"+xmlStr); 
                if(null != xmlStr && xmlStr.length() > 0){ 
                    AsResponse resCmd = new AsResponse(); 
                    AsXmlPacker.parse(resCmd, xmlStr); 
                    if(resCmd != null){ 
                        out.write(resCmd); 
                    } 
                } 
                if(in.remaining() > 0){//如果读取内容后还粘了包,就让父类再给俺 
 
一次,进行下一次解析 
                    return true; 
                } 
            } 
        } 
        return false;//处理成功,让父类进行接收下个包 





这段有问题吧?
那如果我的包是分成两段以上,后面读取的数据不就乱了吗?
9 楼 liuwenbo200285 2013-04-13  
不需要同步吗?
8 楼 xiaogezq0 2012-08-01  
解决我打问题了,谢谢啊
7 楼 tianlovv 2012-06-01  
实现MessageDecoder也可以实现拆分包,个人感觉mina解码器这块有点乱。另外楼主的if(in.remaining() > 0)换成while是不更好些
6 楼 wszyquan 2012-03-11  
  
强烈支持楼主,写的很好,处理数据包的方式也很得当,很有帮助
感谢
5 楼 jiecooly 2011-12-21  
谢谢分享,这个是我参照你的改的。(给需要的人)
@Override
protected boolean doDecode(IoSession iosession, IoBuffer in, ProtocolDecoderOutput out) throws Exception {
CharsetDecoder charsetDecoder = charset.newDecoder();
int msgLen = ServerConfiguration.MSG_HEADER_LEN;//4字节判断消息长度 
        if(in.remaining() > msgLen){
            in.mark();
            int length = in.getInt();
            if(length - msgLen > in.remaining()){
                in.reset(); 
                return false;
            } else{
            int limit = in.limit();
            in.limit(in.position() + length - msgLen);
String content = in.getString(charsetDecoder);
                out.write(content);
                in.limit(limit);
                if(in.remaining() > 0){
                    return true; 
                } 
            } 
        } 
        return false;
}
4 楼 jiangpingcmt1 2011-09-14  
这段程序确实写的好 粘包position用的精妙,我感觉应该加上if (sizeBytes[5] == 0) {// 长度小于4位要 接收新数据,以拼凑成完整数据。
logger.info("数据长度小于6位,接收新数据,以拼凑成完整数据.");
in.reset();
return false;// 接收新数据,以拼凑成完整数据
}
3 楼 qingming.com 2011-08-21  
字节流通讯,跟语言无关吧。
另:只是不知道CumulativeProtocolDecoder怎么处理粘包的,如果放在IoSession里,需要多线程同步吗?
2 楼 choclover 2011-07-26  
思路是不错的,只是如果客户端是C/Java socket直接连接等,不知道你server端这样的协议,如何办?
1 楼 leaya00 2011-07-14  
  
解决我的大问题了。呵呵。再次称赞下mina

相关推荐

Global site tag (gtag.js) - Google Analytics