Mina 粘包断包解码 CumulativeProtocolDecoder 源码解读

来源:互联网 发布:数据库概论第四版 编辑:程序博客网 时间:2024/05/16 07:47


这个解码器就是处理粘包和断包的。主要思路:多个包顺序处理。直到全部处理完毕。如果不够解包的数据,则存储到会话中,下次再收到数据合并再处理。

看完源代码后,对自定义的子类的实现方法就有方向了。具体怎么解码自定义包和业务逻辑相关,不解释。


public abstract class CumulativeProtocolDecoder extends ProtocolDecoderAdapter {    private final AttributeKey BUFFER = new AttributeKey(getClass(), "buffer");    /**     * Creates a new instance.     */    protected CumulativeProtocolDecoder() {        // Do nothing    }    /**     * Cumulates content of <tt>in</tt> into internal buffer and forwards     * decoding request to {@link #doDecode(IoSession, IoBuffer, ProtocolDecoderOutput)}.     * <tt>doDecode()</tt> is invoked repeatedly until it returns <tt>false</tt>     * and the cumulative buffer is compacted after decoding ends.     *     * @throws IllegalStateException if your <tt>doDecode()</tt> returned     *                               <tt>true</tt> not consuming the cumulative buffer.     * 累积型处理解码,此处理已包含粘包和断包的情况。     */    public void decode(IoSession session, IoBuffer in, ProtocolDecoderOutput out) throws Exception {    /**     * 没有断包设置时走这块代码。     * 此时认为此包中没有断包的情况,比如同时N个包。一直处理直到解码不成功直接退出,不管还有没有包数据。     * NioSocketSession设置的变量为true,其他session实现类不清楚。也就是不走这块。继续看下面代码吧。     * */        if (!session.getTransportMetadata().hasFragmentation()) {            while (in.hasRemaining()) {                if (!doDecode(session, in, out)) {                    break;                }            }            return;        }                boolean usingSessionBuffer = true;//使用了上次session中存储的剩余包数据。默认为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()) {//如果剩余包为自动扩展的,那好,把这次的包装到后面,并置为标识为true.                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 {//没有剩余包,直接使用这次的包,并将其标识置为false。            buf = in;            usingSessionBuffer = false;        }        /**         * 循环处理这个包,直到不够解码时,或此包已读完。         * */        for (;;) {            int oldPos = buf.position();//记录下这次处理的初始位置,看看后面的程序到底有没有动过我。            /**             * 这个方法要真正解码处理。原则:1.读这个大包,如果包中的数据已满足可以解出自定义对象,你就先解出此对象,并通过out.write先存起来。然后返回true告诉这里,我再检查下这个包还有没有数据,如果有再调用此方法再解码。             * 2.如果这个包中数据不够解码的了,你也别动后面的数据了,直接返回false到这里。然后这个循就退出了。因为没有可用的数据了,这次的工作已经结束,然后收拾收拾场地,该存的存起来。等下次了。             * 总之:这个方法就是你看下这个包,要是够你用的,你就用,然后返回true。如果这次不够用,你最好什么也别动,或者动了也要给我弄好。返回false。 不然咱们没有下回了。             * */            boolean decoded = doDecode(session, buf, out);            if (decoded) {//这次成功解码。                if (buf.position() == oldPos) {//如果没有使用这个包的数据,为什么要返回true。你骗我。这时应该返回false.                    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()) {        //这个&&的两个条件是同时成立的。不会出现第一个为true,而第二个条件为false。是由上面的代码保证的。各位不要太纠结这块。写这个的大神已考虑到了。            if (usingSessionBuffer && buf.isAutoExpand()) {//已经存到会话里了,就不存了,不过要看下如果这个包是自动扩展的。要压缩下容量,不然这个包容量太大,而里面又没装多少东西。也好省点内存哈。                buf.compact();            } else {                storeRemainingInSession(buf, session);//直接存到会话里。            }        } else {            if (usingSessionBuffer) {//如果以前有会话残留,就清理掉。                removeSessionBuffer(session);            }        }    }    /**     * Implement this method to consume the specified cumulative buffer and     * decode its content into message(s).     *     * @param in the cumulative buffer     * @return <tt>true</tt> if and only if there's more to decode in the buffer     *         and you want to have <tt>doDecode</tt> method invoked again.     *         Return <tt>false</tt> if remaining data is not enough to decode,     *         then this method will be invoked again when more data is cumulated.     * @throws Exception if cannot decode <tt>in</tt>.     */    protected abstract boolean doDecode(IoSession session, IoBuffer in, ProtocolDecoderOutput out) throws Exception;    /**     * Releases the cumulative buffer used by the specified <tt>session</tt>.     * Please don't forget to call <tt>super.dispose( session )</tt> when     * you override this method.     */    @Override    public void dispose(IoSession session) throws Exception {        removeSessionBuffer(session);    }    private void removeSessionBuffer(IoSession session) {        session.removeAttribute(BUFFER);    }    private void storeRemainingInSession(IoBuffer buf, IoSession session) {        final IoBuffer remainingBuf = IoBuffer.allocate(buf.capacity()).setAutoExpand(true);        remainingBuf.order(buf.order());        remainingBuf.put(buf);        session.setAttribute(BUFFER, remainingBuf);    }}


0 0
原创粉丝点击