文档章节

mina read方法出现BufferUnderflowException异常的解决办法

JavaGG
 JavaGG
发布于 2009/02/17 22:53
字数 1529
阅读 76816
收藏 5

现象: 先连续发几十个很小很小的包(<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);
}

}

 

另外发现一篇说mina处理断包和粘包处理的

一.  解码方法
mina中有个内置类CumulativeProtocolDecoder是专门用来处理断包和粘包的。该类的api文档中有个实现的例子。
类org.apache.mina.filter.codec.CumulativeProtocolDecoder
public abstract class CumulativeProtocolDecoder extends ProtocolDecoderAdapter {
    private final AttributeKey BUFFER = new AttributeKey(getClass(), "buffer");

    public void decode(IoSession session, IoBuffer in,
            ProtocolDecoderOutput out) throws Exception {
        if (!session.getTransportMetadata().hasFragmentation()) {       //用来判断是否还有分帧(断包)
            while (in.hasRemaining()) {
                if (!doDecode(session, in, out)) {
                    break;
                }
            }
            return;
        }
       
      ////处理断包,省略
      ............................................

    }

    //需要实现的方法
    protected abstract boolean doDecode(IoSession session, IoBuffer in,
            ProtocolDecoderOutput out) throws Exception;
}

CumulativeProtocolDecoder是一个抽象类,必须继承并实现其doDecode方法,用户自定义协议的拆分就应该写在doDecode方法中,下面的类MessageDecoder是一个实现的例子。MessageDecoder解码网络数据到一种有两字节长度头的自定义消息协议格式。
/**
 * 断包和粘包处理,处理后的消息为一个或多个完整的数据消息
 * @author blc
 */
public class MessageDecoder extends CumulativeProtocolDecoder {
    /*
     * (non-Javadoc)
     * 
     * @see
     * org.apache.mina.filter.codec.CumulativeProtocolDecoder#doDecode(org.apache
     * .mina.core.session.IoSession, org.apache.mina.core.buffer.IoBuffer,
     * org.apache.mina.filter.codec.ProtocolDecoderOutput)
     */
    @Override
    protected boolean doDecode(IoSession session, IoBuffer in,
            ProtocolDecoderOutput out) throws Exception {
        
        in.order(ServerConfig.ByteEndian);    //字节序, ServerConfig.ByteEndian = ByteOrder.LITTLE_ENDIAN
        
        //消息buf
        IoBuffer buf = IoBuffer.allocate(ServerConfig.MessageMaxByte);   //ServerConfig.MessageMaxByte 最大消息字节数
        buf.order(ServerConfig.ByteEndian);
        
        //考虑以下几种情况:
        //    1. 一个ip包中只包含一个完整消息
        //    2. 一个ip包中包含一个完整消息和另一个消息的一部分
        //    3. 一个ip包中包含一个消息的一部分
        //    4. 一个ip包中包含两个完整的数据消息或更多(循环处理在父类的decode中)

        if (in.remaining() > 1) {
            int length = in.getShort(in.position());
if (length < 4) {
                throw new ServerException("Error net message. (Message Length="+length+")");
            }
            if (length > ServerConfig.MessageMaxByte) {
                throw new ServerException("Error net message. Message Length("+length+") > MessageMaxByte("+ServerConfig.MessageMaxByte+")");
            }
            if (length > in.remaining()) return false;
            //复制一个完整消息
            byte[] bytes = new byte[length];
            in.get(bytes);
            buf.put(bytes);
            
            buf.flip();
            out.write(buf);
            return true;
        } else {
            return false;
        }
    }
}

二.  使用
将上面的解码器作为一个过滤器配置到mina中即可,在spring中的配置方法如下:
    <!-- 协议过滤器,包括解码和译码 -->
    <bean id="protocolCodecFilter" class="org.apache.mina.filter.codec.ProtocolCodecFilter">
        <constructor-arg>
            <bean id="factory" class="server.ClientConnServer.MessageCodecFactory"></bean>
        </constructor-arg>
    </bean>
    <!-- 将协议过滤器配置到mina的过滤链中 -->
    <bean id="filterChainBuilder" class="org.apache.mina.core.filterchain.DefaultIoFilterChainBuilder">
        <property name="filters">
            <map>
                <entry key="protocolCodecFilter" value-ref="protocolCodecFilter" />
            </map>
        </property>
    </bean>
    <!-- 处理器 -->
    <bean id="clientConnHandler" class="server.ClientConnServer.ClientConnHandler" />
    <!-- socket接收器,接收客户端连接 -->
    <bean id="ioAcceptor" class="org.apache.mina.transport.socket.nio.NioSocketAcceptor" destroy-method="unbind">
        <!--        <property name="defaultLocalAddress" value=":161" />-->
        <property name="handler" ref="clientConnHandler" />
        <property name="reuseAddress" value="true" />
        <property name="filterChainBuilder" ref="filterChainBuilder" />
    </bean>


要配置协议过滤器,必须使用一个ProtocolCodecFactory ,下面是简单实现
public class MessageCodecFactory implements ProtocolCodecFactory {
    private final MessageEncoder encoder;
    private final MessageDecoder decoder;
    
    public MessageCodecFactory() {
        encoder = new MessageEncoder();
        decoder = new MessageDecoder();
    }

    /* (non-Javadoc)
     * @see org.apache.mina.filter.codec.ProtocolCodecFactory#getDecoder(org.apache.mina.core.session.IoSession)
     */
    @Override
    public ProtocolDecoder getDecoder(IoSession session) throws Exception {
        return decoder;
    }

    /* (non-Javadoc)
     * @see org.apache.mina.filter.codec.ProtocolCodecFactory#getEncoder(org.apache.mina.core.session.IoSession)
     */
    @Override
    public ProtocolEncoder getEncoder(IoSession session) throws Exception {
        return encoder;
    }
}

/**
 * 译码器,不做任何事情
 */
public class MessageEncoder extends ProtocolEncoderAdapter {

    /* (non-Javadoc)
     * @see org.apache.mina.filter.codec.ProtocolEncoder#encode(org.apache.mina.core.session.IoSession, java.lang.Object, org.apache.mina.filter.codec.ProtocolEncoderOutput)
     */
    @Override
    public void encode(IoSession session, Object message,
            ProtocolEncoderOutput out) throws Exception {
        //Do nothing
    }

}

转自http://blianchen.blog.163.com/blog/static/1310562992010101891522100/

搞定了

ps:不知这个是不是mina的bug,是不是还有别的方法配置的呢???

请教那位兄弟有更好的解决方法.

qq:85529766

© 著作权归作者所有

上一篇: Nginx身份验证
下一篇: Ansible 快速入门
JavaGG

JavaGG

粉丝 404
博文 185
码字总数 35279
作品 0
广州
私信 提问
加载中

评论(1)

f
fulibao
请问一下,如何修改Mina的源码,然后进行编译,并得到新的jar文件?
mina read方法出现BufferUnderflowException异常的解决办法

现象: 先连续发几十个很小很小的包(<10 byte) 再突然发一个大小64byte的包 这时你会发现mina就会出现以下错误 经过对mina的分析,这是由对包长度不对做成的(即,我们发的包长是大于64byte的...

JavaGG
2009/02/16
7.6K
4
mina read方法出现BufferUnderflowException异常的解决办法

现象: 先连续发几十个很小很小的包(<10 byte) 再突然发一个大小64byte的包 这时你会发现mina就会出现以下错误 java.nio.BufferUnderflowException at java.nio.HeapByteBuffer.get(Unknown...

JavaGG
2010/03/24
1K
0
使用 Apache MINA 开发高性能网络应用程序 

本文将通过一个简单的问候程序 HelloServer 来介绍 MINA 的基础架构的同时演示如何使用 MINA 开发网络应用程序。 Apache MINA(Multipurpose Infrastructure for Network Applications) 是 Ap...

红薯
2008/10/05
2.2K
1
Jmemcached 的小bug!

Jmemcached 对于memcache协议那块处理有个bug! 当启动memcached,客户端连接到jmemcached后,随便输入一个错误的命令比如(set sdfsd) 回车, 发现客户端返回Client_ERROR,这步是正常的。如...

王全
2009/05/22
891
2
Spring 事务

Spring 使用注解方式进行事务管理 使用步骤: 步骤一、在spring配置文件中引入tx:命名空间 步骤二、具有@Transactional 注解的bean自动配置为声明式事务支持 <!-- 事务管理器配置, Hibernate...

Q317075064
2016/09/29
68
0

没有更多内容

加载失败,请刷新页面

加载更多

如何使用ssh工具便于远程管理

前几天亲眼经历了Linux服务器运维过程,眼看着别人熟练运用Linux管理工具,自个心里不是滋味,所以自己特意整理了一篇“专题”:使用ssh服务远程管理主机。 首先在使用ssh服务工具之前,先熟...

linux-tao
9分钟前
1
0
生存还是毁灭?一文读懂挖矿木马的战略战术

前言 比特币等虚拟货币在2019年迎来了久违的大幅上涨,从最低3000美元上涨至7月份的14000美元,涨幅达300%,巨大的金钱诱惑使得更多的黑产团伙加入了恶意挖矿的行列。阿里云安全团队通过对云...

阿里云官方博客
11分钟前
1
0
jQuery中的DOM多样操作

常用状态码及其含义: 404 没找到页面(not found) 403 禁止访问(forbidden) 500 内部服务器出错(internal service error) 200 一切正常(ok) 304 没有被修改(not modified) jQuery中的...

智简
12分钟前
1
0
初学者不会画男头像?男头像怎么画?

人物头像怎样画?男性头像怎样画?学习绘画难吗?怎样才干学好绘画?想必这些都是绘画初学者们经常在想的问题吧,就是不知道如何才干绘画好人物的头像,比如说男性的头像,男性的头像与女人的...

热爱画画的我
12分钟前
1
0
lopatkin俄大神精简中文系统Windows 10 Enterprise 2016 LTSB 14393.729 x64 ZH-CN PIPvm v2

Microsoft Windows 10 Enterprise 2016 LTSB 14393.729 x64 ZH-CN PIPvm v2 评分挺高的 出厂年份:2017 版本:Windows 10 Enterprise 2016 LTSB 14393.729 平台:x64 系统要求: CPU-1 gz R......

xiaogg
13分钟前
4
1

没有更多内容

加载失败,请刷新页面

加载更多

返回顶部
顶部