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