现象:
先连续发几十个很小很小的包(<10 byte)
再突然发一个大小64byte的包
这时你会发现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)
经过对mina的分析,这是由对包长度不对做成的(即,我们发的包长是大于64byte的,但他的byteBuffer大小只有64byte,当我们尝试读取第65个byte就会出现这个错误)
mina怎会出现这种错误的呢??是不是有什么配置可以调整
找到mina读取byte的方法AbstractPollingIoProcessor类的read(T session)
此方法源代码如下
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