Mina丢包/断包问题解决方法

Mina提供的TextLineCodecFactory编码器,默认读取字节是2048,在一般场景下是可以的。当数据包比较大时,可能会分多次读取,导致断包,造成后面的IoHandler处理异常。

当接收到的数据包大小不是很固定,TextLineCodecFactory就不适合了。Mina框架提供了一个CumulativeProtocolDecoder。只要有数据包发送过来,这个decoder就会读取,然后累积到IoSession内部的IoBuffer缓冲区中,而具体的拆包动作,则交由子类的doDecode方法完成。

1 - 源码看处理过程

先来看一下*CumulativeProtocolDecoder#decode()*方法:

for (;;) {
    int oldPos = buf.position();
    boolean decoded = doDecode(session, buf, out);
    if (decoded) {
        if (buf.position() == oldPos) {
            throw new IllegalStateException("doDecode() can't return true when buffer is not consumed.");
        }

        if (!buf.hasRemaining()) {
            break;
        }
    } else {
        break;
    }
}

// if there is any data left that cannot be decoded, we store
// it in a buffer in the session and next time this decoder is
// invoked the session buffer gets appended to
if (buf.hasRemaining()) {
    if (usingSessionBuffer && buf.isAutoExpand()) {
        buf.compact();
    } else {
        storeRemainingInSession(buf, session);
    }
} else {
    if (usingSessionBuffer) {
        removeSessionBuffer(session);
    }
}
  1. oldPos:内部的IoBuffer缓冲区的当前位置
  2. 然后调用子类的doDecode()方法
  3. doDecode()方法返回true, 并且调用之后buf的position和oldPos一样,也就是说子类并没有消费buf,这时候会抛出异常
  4. 缓冲区如果消费完毕了,则跳出循环,不在调用子类的doDecode()方法
  5. doDecode()方法返回false,直接跳出循环,不在调用子类的doDecode()方法
  6. 跳出循环后,缓冲区还是有没被消费的字节,就会将剩余的字节放到缓冲区
  7. 跳出循环且全部被消费,就会清空字节缓冲区

再看一下for循环前面的一段代码:

boolean usingSessionBuffer = true;
IoBuffer buf = (IoBuffer) session.getAttribute(BUFFER);
// If we have a session buffer, append data to that; otherwise
// use the buffer read from the network directly.
if (buf != null) {
    boolean appended = false;
    // Make sure that the buffer is auto-expanded.
    if (buf.isAutoExpand()) {
        try {
            buf.put(in);
            appended = true;
        } catch (IllegalStateException e) {
            // A user called derivation method (e.g. slice()),
            // which disables auto-expansion of the parent buffer.
        } catch (IndexOutOfBoundsException e) {
            // A user disabled auto-expansion.
        }
    }

    if (appended) {
        buf.flip();
    } else {
        // Reallocate the buffer if append operation failed due to
        // derivation or disabled auto-expansion.
        buf.flip();
        IoBuffer newBuf = IoBuffer.allocate(buf.remaining() + in.remaining()).setAutoExpand(true);
        newBuf.order(buf.order());
        newBuf.put(buf);
        newBuf.put(in);
        newBuf.flip();
        buf = newBuf;

        // Update the session attribute.
        session.setAttribute(BUFFER, buf);
    }
} else {
    buf = in;
    usingSessionBuffer = false;
}
  1. 数据包到来时,首先看一下IoSession内部的缓冲区buf中是否有数据
  2. 没有数据,则读取本次收到的数据。就是执行上面for循环的逻辑
  3. 有数据,把buf中的和本次接受到的数据合并,然后就是for循环了,去调用子类doDecode()方法

稍微总结一下 CumulativeProtocolDecoder子类的doDecode()方法返回值:

  1. true:本次数据已经全部消费完毕,可以解包
  2. false: 本次数据接收到的数据还不够解包,返回false,等待下次数据到来时继续解包

2 - 编写自己的解码器

public class IMessageCodecFactory implements ProtocolCodecFactory {

    private final IMessageEncoder encoder;

    private final IMessageDecoder decoder;
 
    public IMessageCodecFactory() {
        this.encoder = new IMessageEncoder();
        this.decoder = new IMessageDecoder();
    }
 
    public ProtocolEncoder getEncoder(IoSession session) throws Exception {
        return encoder;
    }
 
    public ProtocolDecoder getDecoder(IoSession session) throws Exception {
        return decoder;
    }

}

3 - 解包

public class IMessageDecoder extends CumulativeProtocolDecoder {

	private static final Logger LOGGER = LoggerFactory.getLogger(IMessageDecoder.class);

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

	@Override
	public boolean doDecode(IoSession session, IoBuffer ioBuf, ProtocolDecoderOutput out) throws Exception {
		boolean complete = false;

		IoBuffer buff = IoBuffer.allocate(320).setAutoExpand(true);

		while (ioBuf.hasRemaining()) {
			byte b = ioBuf.get();
			if (b == CIMConstant.MESSAGE_SEPARATE) {
				complete = true;
				break;
			}
			buff.put(b);
		}

		buff.flip();
		byte[] bytes = new byte[buff.limit()];
		buff.get(bytes);
		String message = new String(bytes, CHARSET);

		if (!complete) {
			int position = ioBuf.position();
			int limit = ioBuf.limit();
			LOGGER.warn("消息不够消费, {} => :: 当前位置 => {} :: 长度 => {} ", message, position, limit);
			ioBuf.flip();
			return false;
		}

		buff.clear();
		// 消息读取完毕, 解包为高级对象
		// out.write(obj);
		return true;
	}
}

转载于:https://my.oschina.net/iepac/blog/749102

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值