关于mina的java.nio.BufferUnderflowException

在使用mina进行同步开发时遇到java.nio.BufferUnderflowException的问题。通过调整设置暂时解决了问题,但真正的解决方案在于正确使用CumulativeProtocolDecoder。CumulativeProtocolDecoder在doDecode方法返回true时会检查是否已消费数据,返回false则保留未读取的数据,等待下次合并解码。返回true的关键在于确认数据已足够解码。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

最近在使用mina做一个同步,在开发调试过程中总是会出现java.nio.BufferUnderflowException 确实是很郁闷啊,没办法,百度一下,一开始看到这篇文章http://my.oschina.net/javagg/blog/2,但是发现和我情况是不一样的,我是每个数据包几乎大小不会相差很大。那么还有什么问题呢?



接着找,发现有人说需要设置这个东西。

acceptor.getSessionConfig().setReadBufferSize(256);
acceptor.getSessionConfig().setReceiveBufferSize(256);

我肯定也尝试了一下,试着把这个值调大一点

acceptor.getSessionConfig().setReadBufferSize(102400);
acceptor.getSessionConfig().setReceiveBufferSize(102400);

咦,好像真的没有报underflow了,BUT,过了一会后问题还是出现了,那么肯定也不是解决办法。


接下来就是真正的办法了...  啰嗦一点。。。


解决的核心在于CumulativeProtocolDecoder


  1. 需要一个ProtocolCodecFactory拦截数据包解
public class CharsetCodecFactory implements ProtocolCodecFactory {

	@Override
	public ProtocolDecoder getDecoder(IoSession session) throws Exception {
		return new CharsetDecoder();
	}

	@Override
	public ProtocolEncoder getEncoder(IoSession session) throws Exception {
		return new CharsetEncoder();
	}
}

2.然后在启动时的chain里注册
<span style="white-space:pre">	</span>DefaultIoFilterChainBuilder chain = acceptor.getFilterChain();
	chain.addLast("codec", new ProtocolCodecFilter(new CharsetCodecFactory()));
注意放在多线程上面,否则会导致解码混乱的情况
3.再实现编码和解码

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 CharsetDecoder extends CumulativeProtocolDecoder {

	private final static Logger log = Logger.getLogger(CharsetDecoder.class);

	private final static Charset charset = Charset.forName("UTF-8");

	private IoBuffer cache = IoBuffer.allocate(100).setAutoExpand(true);

	/**
	 * @param session
	 * @param in
	 * @param out
	 * @return
	 * @throws Exception
	 * @see org.apache.mina.filter.codec.CumulativeProtocolDecoder#doDecode(org.apache.mina.core.session.IoSession,
	 *      org.apache.mina.core.buffer.IoBuffer,
	 *      org.apache.mina.filter.codec.ProtocolDecoderOutput)
	 */
	@Override
	protected boolean doDecode(IoSession session, IoBuffer in,
			ProtocolDecoderOutput out) throws Exception {
		//如果长度不够协议头的长度,那么直接将剩余的包放入下一个buffer进行下一次解析
<span style="white-space:pre">		</span>//为什么是4?这个就看你们自己约定的数据包格式了。
		if (in.remaining() < 4){
			return false;
		}
		if (in.remaining() > 0) {// 有数据时,读取4字节判断消息长度
			byte[] sizeBytes = new byte[4];
			in.mark();// 标记当前位置,以便reset
			in.get(sizeBytes);// 读取前4字节
			int size = ReverseUtil.bytes2int(sizeBytes);
			if (size - 4 > in.remaining()) {// 如果消息内容不够,则重置,相当于不读取size
				in.reset();
				return false;// 接收新数据,以拼凑成完整数据
			} else {
<span style="white-space:pre">				</span>//以下是我的业务,可以忽略
				byte[] versionBytes = new byte[4];
				in.get(versionBytes, 0, 4);
				int version = ReverseUtil.bytes2int(versionBytes);

				byte[] commandBytes = new byte[2];
				in.get(commandBytes, 0, 2);
				short command = ReverseUtil.getShort(commandBytes);

				byte idLength = in.get();
				int id = ReverseUtil.unsignedByteToInt(idLength);

				byte[] idStringBytes = new byte[id];
				in.get(idStringBytes, 0, id);

				String idStr = new String(idStringBytes, charset);

				if (size - id - 11 == 0) {
					MessageCommand messageCommand = new MessageCommand(
							getCommant(command), idStr);
					out.write(messageCommand);
					return false;
				}
				byte[] messageBytes = new byte[size - id - 11];
				in.get(messageBytes, 0, size - id - 11);

				MessageCommand messageCommand = new MessageCommand(
						getCommant(command), idStr, messageBytes);
				out.write(messageCommand);
				if (in.remaining() > 0) {// 如果读取内容后还粘了包,进行下一次解析
					return true;
				}
			}
		}
		return false;// 处理成功,让父类进行接收下个包
	}

	@Override
	public void dispose(IoSession session) throws Exception {
		log.info(session.getCurrentWriteMessage());
	}

	@Override
	public void finishDecode(IoSession session, ProtocolDecoderOutput out)
			throws Exception {
	}<pre name="code" class="java">public class CharsetEncoder implements ProtocolEncoder {
	private final static Logger log = Logger.getLogger(CharsetEncoder.class);
	private final static Charset charset = Charset.forName("UTF-8");

	@Override
	public void dispose(IoSession session) throws Exception {
	}

	@Override
	public void encode(IoSession session, Object message,
			ProtocolEncoderOutput out) {
		IoBuffer buff = IoBuffer.allocate(100).setAutoExpand(true);

		try {
			RetMsg retMsg = (RetMsg) message;
			String deviceId = retMsg.getDeviceId();
			int command = retMsg.getCommand();

			byte[] data = retMsg.getData();
			int version = 1;
			int deviceLength = deviceId.length();
			int totalLength = 11 + deviceLength;
			byte leng = (byte) deviceLength;
			if (null != data) {
				totalLength += data.length;
			}

			buff.put(ReverseUtil.intToBytes(totalLength));
			buff.put(ReverseUtil.intToBytes(version));
			buff.put(ReverseUtil.shortToByteArray((short) command));
			buff.put(leng);
			buff.putString(deviceId, charset.newEncoder());

			if (data != null) {
				buff.put(data);
			}
			// 为下一次读取数据做准备
			buff.flip();

			out.write(buff);
		} catch (NumberFormatException e) {
			e.printStackTrace();
		} catch (CharacterCodingException e) {
			e.printStackTrace();
		} catch (Exception e) {
			e.printStackTrace();
		}
	}
}

 
 

这样做完后,亲测有效,后续如果有问题,持续更新

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值