MINA拆包问题的简单讨论

本文出自:【InTheWorld的博客】

mina

前面有写过一篇简单的关于Netty的粘包和拆包问题,这里扯扯Mina。讲真,我对Mina其实不熟悉,但作为网络框架,和Netty什么的还是挺相似的。使用Mina处理报文数据的时候,也是通过添加自定义filter来实现的。对应与本文所讨论的拆包问题,就是ProtocolCodecFilter。

而ProtocolCodecFilter的构造函数有两个,分别是:

public ProtocolCodecFilter(ProtocolCodecFactory factory) 

public ProtocolCodecFilter(final ProtocolEncoder encoder, final ProtocolDecoder decoder) 

如果使用的是第一个构造函数,就需要注意一些问题了。这个工厂类的作用就是产生encoder和decoder类。这里所说的协议使用了length字段来解决拆包和粘包问题。在拆包的情况下,会出现不完整的包,解决这种问题的方法是在每次的decode过程中,首先判断一次报文长度够不够。如果buffer中的长度是够的,则进行一次报文解析,反之就先返回,等待下一次处理。这里的思路都是没有问题的,但如果decoder使用变量来存储length,就可能会出问题。如果使用静态变量,大概的情况是下面这样:

//这里是报文格式
message {
    length : int;
    content : byte[length - 4];
}
//这里是decoder实现
public class CustomDecoder extends CumulativeProtocolDecoder {

    private static int previousLengh = 0;

    @Override
    protected boolean doDecode(IoSession ioSession, IoBuffer ioBuffer, ProtocolDecoderOutput protocolDecoderOutput) throws Exception {
        ioBuffer.order(ByteOrder.LITTLE_ENDIAN);
        int length;
        if (previousLength != 0) {
            length = previousLength;
        } else {
            length = ioBuffer.getInt();
        }
        if (ioBuffer.remaining() >= length - 4) {
            byte[] byteArray = new byte[length];
            ioBuffer.get(byteArray, 0, length - 4);
            //parse the message body
            return true;
        } else {
            //wait for next time
            return false;
        }
    }
}

在单个连接的情况下,这样做是没有问题的,但是在多个连接的情况下是不能工作的,原因不需多说,因为这个length是Decoder的静态变量。多个连接会相互干扰,导致出现问题。既然静态变量不行,那非静态成员变量应该没有问题了吧?答案是可以的,不过需要ProtocolCodecFactory的一些支持。如果,你的ProtocolCodecFactory可以保证对同一个session始终返回同一个decoder,那么使用非静态成员变量也是可以的。倘若你按照下面的方式实现ProtocolCodecFactory,就会出问题了。

public class CustomCodecFactory implements ProtocolCodecFactory {
    @Override
    public ProtocolDecoder getDecoder(IoSession session) throws Exception {
        return new CustomDecoder();
    }
}

因为这个ProtocolCodecFilter的getDecoder方法每次都会返回一个新的decoder,所以非静态成员变量就被重置了,从而导致了错误。在ProtocolCodecFilter的第二个构造函数是以如下方式实现的,这种方式可以简单的保证一个session始终得到同一个Decoder:

    public ProtocolCodecFilter(final ProtocolEncoder encoder, final ProtocolDecoder decoder) {
        if(encoder == null) {
            throw new IllegalArgumentException("encoder");
        } else if(decoder == null) {
            throw new IllegalArgumentException("decoder");
        } else {
            this.factory = new ProtocolCodecFactory() {
                public ProtocolEncoder getEncoder(IoSession session) {
                    return encoder;
                }

                public ProtocolDecoder getDecoder(IoSession session) {
                    return decoder;
                }
            };
        }
    }

虽然使用非静态成员变量的方法可以解决这种拆包问题,我觉得不依赖于Decoder纪录状态的实现方式更好一些。其实也很简单,用代码说话吧!

public class CustomDecoder extends CumulativeProtocolDecoder {

    @Override
    protected boolean doDecode(IoSession ioSession, IoBuffer ioBuffer, ProtocolDecoderOutput protocolDecoderOutput) throws Exception {
        ioBuffer.order(ByteOrder.LITTLE_ENDIAN);
        int length = 0;
        if (ioBuffer.remaining() >= 4) {
            ioBuffer.mark();
            length = ioBuffer.getInt();
            if (ioBuffer.remaining() >= length - 4) {
                byte[] byteArray = new byte[length];
                ioBuffer.get(byteArray, 0, length - 4);
                //parse the message body
                return true;
            } else {
                //wait for next time
                return false;
            }
        } else {
            ioBuffer.reset();
            return
        }
    }
}

不使用成员变量的方法其实就是“试读”加“回退”,decoder通过每次重新分析buffer内容解决拆包问题。好吧,其实是一个很简单的问题。本文完。。。

发表评论