mina read方法出現BufferUnderflowException異常的解決辦法

現象:java

先連續發幾十個很小很小的包(<10 byte)apache

再忽然發一個大小64byte的包session

這時你會發現mina就會出現如下錯誤
java.nio.BufferUnderflowException
 at java.nio.HeapByteBuffer.get(Unknown Source)
 at org.apache.mina.core.buffer.AbstractIoBuffer.get(AbstractIoBuffer.java:419)
 at org.apache.mina.core.buffer.AbstractIoBuffer.get(AbstractIoBuffer.java:827)
 at com.labox.common.net.ProtocolHandler.messageReceived(ProtocolHandler.java:81)
 at org.apache.mina.core.filterchain.DefaultIoFilterChain$TailFilter.messageReceived(DefaultIoFilterChain.java:752)
 at org.apache.mina.core.filterchain.DefaultIoFilterChain.callNextMessageReceived(DefaultIoFilterChain.java:414)
 at org.apache.mina.core.filterchain.DefaultIoFilterChain.access$5(DefaultIoFilterChain.java:411)
 at org.apache.mina.core.filterchain.DefaultIoFilterChain$EntryImpl$1.messageReceived(DefaultIoFilterChain.java:832)
 at org.apache.mina.core.filterchain.DefaultIoFilterChain$HeadFilter.messageReceived(DefaultIoFilterChain.java:616)
 at org.apache.mina.core.filterchain.DefaultIoFilterChain.callNextMessageReceived(DefaultIoFilterChain.java:414)
 at org.apache.mina.core.filterchain.DefaultIoFilterChain.fireMessageReceived(DefaultIoFilterChain.java:408)
 at org.apache.mina.core.polling.AbstractPollingIoProcessor.read(AbstractPollingIoProcessor.java:582)
 at org.apache.mina.core.polling.AbstractPollingIoProcessor.process(AbstractPollingIoProcessor.java:542)
 at org.apache.mina.core.polling.AbstractPollingIoProcessor.process(AbstractPollingIoProcessor.java:534)
 at org.apache.mina.core.polling.AbstractPollingIoProcessor.access$7(AbstractPollingIoProcessor.java:532)
 at org.apache.mina.core.polling.AbstractPollingIoProcessor$Worker.run(AbstractPollingIoProcessor.java:861)socket

通過對mina的分析,這是由對包長度不對作成的(即,咱們發的包長是大於64byte的,但他的byteBuffer大小隻有64byte,當咱們嘗試讀取第65個byte就會出現這個錯誤).net

mina怎會出現這種錯誤的呢??是否是有什麼配置能夠調整線程

找到mina讀取byte的方法AbstractPollingIoProcessor類的read(T session)ip

此方法源代碼以下get

private void read(T session) {
        IoSessionConfig config = session.getConfig();同步

        System.out.println("cap buffer size"+config.getReadBufferSize());//這句我本身加的
        IoBuffer buf = IoBuffer.allocate(config.getReadBufferSize());源碼

        final boolean hasFragmentation =
            session.getTransportMetadata().hasFragmentation();

        try {
            int readBytes = 0;
            int ret;

            try {
                if (hasFragmentation) {
                    while ((ret = read(session, buf)) > 0) {
                        readBytes += ret;
                        if (!buf.hasRemaining()) {
                            break;
                        }
                    }
                } else {
                    ret = read(session, buf);
                    if (ret > 0) {
                        readBytes = ret;
                    }
                }
            } finally {
                buf.flip();
            }

            if (readBytes > 0) {
                session.getFilterChain().fireMessageReceived(buf);
                buf = null;

                if (hasFragmentation) {
                    if (readBytes << 1 < config.getReadBufferSize()) {
                        session.decreaseReadBufferSize();
                    } else if (readBytes == config.getReadBufferSize()) {
                        session.increaseReadBufferSize();
                    }
                }
            }
            if (ret < 0) {
                scheduleRemove(session);
            }
        } catch (Throwable e) {
            if (e instanceof IOException) {
                scheduleRemove(session);
            }
            session.getFilterChain().fireExceptionCaught(e);
        }
    }

 

通過對這段代碼的分析終於發現問題所在了

你們注意if (readBytes > 0) 這個塊下的代碼

你不難發現

if (hasFragmentation) {
                    if (readBytes << 1 < config.getReadBufferSize()) {
                        session.decreaseReadBufferSize();
                    } else if (readBytes == config.getReadBufferSize()) {
                        session.increaseReadBufferSize();
                    }
                }

意思是if hasFragmentation==true

if 當前配置初始化ByteBuffer大小 > 當前讀取包的平方 爲 true 就把配置中初始化byteBuffer大小減半

else if 當前已讀取字節==配置包初始化大小  爲true時 把配置中初始化byteBuffer大小加倍 

接下來結合我出錯的現象看看

當我接連發幾十個小於10byte的包時,這時配置中的初始化ByteBuffer大小就爲取小,默認最小爲64byte

當我再發一個大於64byte的包,但整個ByteBuffer只有64byte,那就出錯了。

接下來咱們來修正這個問題

方法一:不要改變默認初始化byteBuffer大小,要修改mina的源碼

找到org.apache.mina.transport.socket.nio.NioSocketSession 這個類的METADATA變量

把 new DefaultTransportMetadata()的第四個參數改爲false就ok了

方法二:本身寫read()方法中獲得byteBuffer實例的方法

從read()方法看出,他獲得byteBuffer實例是每次去請求的,若是咱們在這裏作一個cache,每次從cache中獲得,天然byteBuffer的大小也是固定的,只要按本身業務最大包大小去開就能夠了。

每一個線程用一個本身的ByteBuffer實例,這樣就不會有同步問題.

找到org.apache.mina.core.polling.AbstractPollingIoProcessor類中的read(T session)方法改爲

static ThreadLocal readCache=new ThreadLocal();//這個是放ByteBuffer實例的cache

private void read(T session) {

IoBuffer buf=readCache.get();
if(buf==null){
 buf=IoBuffer.allocate(512);//512爲包默認大小
 readCache.set(buf);
}else{
 buf.clear();
}

try {
    int readBytes = 0;
    int ret;

    try {
            ret = read(session, buf);
            if (ret > 0) {
                readBytes = ret;
            }
    } finally {
        buf.flip();
    }

    if (readBytes > 0) {
        session.getFilterChain().fireMessageReceived(buf);
    }
    if (ret < 0) {
        scheduleRemove(session);
    }
} catch (Throwable e) {
    if (e instanceof IOException) {
        scheduleRemove(session);
    }
    session.getFilterChain().fireExceptionCaught(e);
}

}

搞定了

ps:不知這個是否是mina的bug,是否是還有別的方法配置的呢???

請教那位兄弟有更好的解決方法.

qq:85529766