`

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

    博客分类:
  • mina
阅读更多

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

分享到:
评论

相关推荐

    mina 2.0.7 (源码)

    因为自己最近做mina相关的二次开发,出现 mina read方法出现BufferUnderflowException错误 后来通过查找资料,发现最简单的办法就是修改源码里面的内容,所以就修改了下,希望能帮助到需要帮助的朋友

    MINA断线重连死锁解决

    当我们遇到"MINA断线重连死锁解决"的问题时,通常涉及到的是网络连接的稳定性和并发处理的复杂性。 MINA框架提供了非阻塞I/O模型,允许处理大量并发连接。然而,在某些情况下,特别是在断线重连的机制中,可能会...

    mina 断包,粘包问题解决

    本文将深入探讨“mina断包”和“粘包”问题,以及如何通过提供的mina_optimize代码解决这些问题。 首先,我们要理解什么是“断包”和“粘包”。在TCP/IP通信中,数据通常会被拆分成多个数据段进行传输,这在传输...

    Android2.2配合Mina时,出现的问题及完美解决方案

    本文将深入探讨在Android 2.2版本上集成Mina框架时可能出现的问题,以及如何找到这些问题的完美解决方案。 Mina(Java Network Application Platform)是一个开源的网络应用框架,它提供了一个高效的、可扩展的网络...

    从Github检出Mina(maven)项目出现问题解决过程

    本文将详细解析“从Github检出Mina(maven)项目出现问题解决过程”的主题,帮助开发者们理解和处理类似的问题。 首先,Mina是一个高性能、轻量级的网络通信框架,常用于构建网络应用,如服务器和客户端。它提供了...

    MINA_API+MINA_DOC+mina

    通过这个文档,开发者可以详细了解到MINA提供的各种接口、类和方法,包括IO服务端和客户端的创建、协议编码和解码、事件驱动模型等核心概念。例如,NioSession代表网络连接会话,FilterChain用于处理数据过滤,...

    Mina+Socket通信

    通过阅读和理解这些代码,你可以更好地掌握Mina与Socket通信的实现方法,并应用于自己的项目中。 总的来说,Mina与Socket结合使用,可以构建出高效、灵活的网络通信解决方案。Mina的高级抽象简化了网络编程的复杂性...

    mina的高级使用,mina文件图片传送,mina发送文件,mina报文处理,mina发送xml和json

    Apache Mina是一个开源的网络通信应用框架,主要应用于Java平台,它为高性能、高可用性的网络应用程序提供了基础架构。在本文中,我们将深入探讨Mina的高级使用,特别是在文件图片传送、文件发送、XML和JSON报文处理...

    mina连接 mina心跳连接 mina断线重连

    Apache Mina是一个开源的网络通信框架,常用于构建高性能、高效率的服务端应用程序,尤其在Java平台上。在本文中,我们将深入探讨Mina的核心概念,包括连接管理、心跳机制以及断线重连策略。 首先,让我们理解"Mina...

    apache-mina-2.0.4.rar_apache mina_mina

    Apache Mina是一个高性能、异步事件驱动的网络应用程序框架,用于快速开发可维护的高性能协议服务器和客户端。这个"apache-mina-2.0.4.rar"压缩包包含的是Apache Mina 2.0.4版本的源代码,是深入理解和定制Mina的...

    Mina开发实例(服务端、客户端)DEMO

    Apache Mina是一个高度可扩展的网络通信框架,它允许开发者创建高性能、高效率的服务端和客户端应用程序。在Java世界中,Mina以其简洁的API和灵活性而受到青睐,尤其适用于处理大量的并发连接,如TCP/IP和UDP协议。...

    mina2 实例程序(socket通讯调用远程方法)

    《mina2实现Socket通信调用远程方法详解》 在当今的分布式系统中,远程方法调用(Remote Method Invocation,RMI)是常见的通信方式之一,它允许程序在不同的网络节点间透明地调用方法,极大地简化了分布式应用的...

    websocket+java服务器(mina)

    7. **异常处理**:处理可能出现的异常,如网络中断、解析错误等,确保服务的稳定运行。 在实际开发中,你可能还需要关注以下几个方面: - 安全性:WebSocket连接可以通过SSL/TLS加密,提高数据传输的安全性。 - ...

    mina自定义编解码器详解

    - 设计编解码器时,通常需要继承mina提供的AbstractDecoder或AbstractEncoder基类,并实现decode()或encode()方法。 - 编解码器应该考虑数据的边界问题,确保正确识别和处理完整的数据包。 4. **示例解析** - ...

    mina新手教程源码 mina+springboot+idea最简单的案例。

    mina新手案例,mina新手教程源码 mina+springboot最简单的案例。用的IDEA * mina服务端 * 1、添加@Controller注解和 @PostConstruct注解,代表启动springboot项目时也调用该类下的该方法, * 启动springboot项目...

    MINA 协议解码过滤器

    `decode()`方法负责实际的解码工作,而`exceptionCaught()`方法则处理在解码过程中出现的异常。 创建自定义解码器时,需要注意以下几点: 1. 非阻塞解码:由于MINA采用非阻塞I/O,解码器必须能够处理数据不完整的...

    Android-MinaSocket一款基于Mina的Socket长连接库

    - **异常处理**:对于可能出现的各种网络异常,如连接中断、数据传输错误等,应有完善的异常处理机制。 总结起来,`Android-MinaSocket` 是一个基于Apache Mina的Android长连接库,它提供了稳定、高效的网络通信...

    一种基于Mina状态机的高性能服务器的设计方法

    Mina 服务器设计方法基于状态机的高性能服务器设计方法 Mina 服务器设计方法是基于状态机的高性能服务器设计方法,旨在提高服务器的性能和可靠性。该方法使用 Mina 网络应用程序框架和有限状态机,对服务器的设计和...

    MINA—TCP案例

    MINA框架同样提供了处理异常的机制。我们需要关注IoHandler的exceptionCaught方法,当发生错误时,该方法会被调用。 6. **关闭连接** 当不再需要连接时,可以调用IoSession的close方法来关闭连接,释放资源。 7....

    mina

    2. **Apache_Mina_Server_2.0中文参考手册V1.0.pdf**:这是MINA服务器的中文版参考手册,涵盖了MINA的核心组件、API使用、配置方法等详细信息,是深入理解MINA不可或缺的资源。 3. **MINA使用手记[1] .shtml** 和 *...

Global site tag (gtag.js) - Google Analytics