前面有写过一篇简单的关于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内容解决拆包问题。好吧,其实是一个很简单的问题。本文完。。。
发表回复