現象: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