Mina框架断包、粘包问题解决方案

Mina框架断包、粘包问题解决方案

Apache Mina Server 是一个网络通信应用框架,也就是说,它主要是对基于TCP/IP、UDP/IP协议栈的通信框架(当然,也可以提供JAVA 对象的序列化服务、虚拟机管道通信服务等),Mina 可以帮助我们快速开发高性能、高扩展性的网络通信应用,Mina 提供了事件驱动、异步(Mina 的异步IO 默认使用的是JAVA NIO 作为底层支持)操作的编程模型。

在mina中,一般的应用场景用TextLine的Decode和Encode就够用了(TextLine的默认分割符虽然是\r\n,但其实分隔符是可以自己指定的,如:newTextLineDecoder(charset, decodingDelimiter);)

但默认解码器每次读取缓冲的数据是有限制的,即ReadBufferSize的大小,默认是2048个字节,当数据包比较大时将被分成多次读取,造成断包。虽然可以通过acceptor.getSessionConfig().setReadBufferSize(newsize)这种方式来增加默认容量,但毕竟不是王道(太大了浪费空间,肯定会降低数据的处理效率)。

所以,当我们接收的数据的大小不是很固定,且容易偏大的时候,默认的TextLine就不适合了。这时我们在解析之前就需要判断数据包是否完整,这样处理起来就会非常麻烦。那么Mina 中幸好提供了CumulativeProtocolDecoder

类,从名字上可以看出累积性的协议解码器,也就是说只要有数据发送过来,这个类就会去读取数据,然后累积到内部的IoBuffer 缓冲区,但是具体的拆包(把累积到缓冲区的数据解码为JAVA 对象)交由子类的doDecode()方法完成,实际上CumulativeProtocolDecoder就是在decode()反复的调用暴漏给子类实现的doDecode()方法。

具体执行过程如下所示:

A. 你的doDecode()方法返回true 时,CumulativeProtocolDecoder 的decode()方法会首先判断你是否在doDecode()方法中从内部的IoBuffer 缓冲区读取了数据,如果没有,则会抛出非法的状态异常,也就是你的doDecode()方法返回true 就表示你已经消费了本次数据(相当于聊天室中一个完整的消息已经读取完毕),进一步说,也就是此时你必须已经消费过内部的IoBuffer 缓冲区的数据(哪怕是消费了一个字节的数据)。如果验证通过,那么CumulativeProtocolDecoder会检查缓冲区内是否还有数据未读取,如果有就继续调用doDecode()方法,没有就停止对doDecode()方法的调用,直到有新的数据被缓冲。

B. 当你的doDecode()方法返回false 时,CumulativeProtocolDecoder 会停止对doDecode()方法的调用,但此时如果本次数据还有未读取完的,就将含有剩余数据的IoBuffer 缓冲区保存到IoSession 中,以便下一次数据到来时可以从IoSession 中提取合并。如果发现本次数据全都读取完毕,则清空IoBuffer 缓冲区。

简而言之,当你认为读取到的数据已经够解码了,那么就返回true,否则就返回false。这个CumulativeProtocolDecoder其实最重要的工作就是帮你完成了数据的累积,因为这个工作是很烦琐的。

一、    实现解码器

CumulativeProtocolDecoder是一个抽象类,必须继承并实现其doDecode方法,用户自定义协议的拆分就应该写在doDecode方法中,下面的MyDecoder类是一个其子类的实现:

public
class
MyDecoder extends CumulativeProtocolDecoder {

public
static
Logger log = Logger.getLogger(MyDecoder.class);

/**

* 包解码器组件

*/

private PacketComponent
packetComponent;

/**

* 这个方法的返回值是重点:

* 1、当内容刚好时,返回false,告知父类接收下一批内容

* 2、内容不够时需要下一批发过来的内容,此时返回false,这样父类 CumulativeProtocolDecoder

*   会将内容放进IoSession中,等下次来数据后就自动拼装再交给本类的doDecode

* 3、当内容多时,返回true,因为需要再将本批数据进行读取,父类会将剩余的数据再次推送本

* 类的doDecode

*/

public
boolean
doDecode(IoSession session,IoBuffer in,

ProtocolDecoderOutput out) throws Exception {

log.info("in.remaining : "+in.remaining());

if(in.remaining() > 0){//有数据时,读取前8字节判断消息长度

byte [] sizeBytes =
new byte[8];

in.mark();//标记当前位置,以便reset

//因为我的前数据包的长度是保存在第4-8字节中,

in.get(sizeBytes,0,8);//读取4字节

//DataTypeChangeHelper是自己写的一个byte[]转int的一个工具类

int size = (int) DataTypeUtil.bytesToInt(sizeBytes,4);

log.info("size : "+size);

in.reset();

if(size > in.remaining()){//如果消息内容不够,则重置,相当于不读取size

return
false
;//父类接收新数据,以拼凑成完整数据

} else{

byte[] bytes =
new byte[size];

in.get(bytes, 0, size);

//把字节转换为Java对象的工具类

PackageData pack =
packetComponent.getDataFromBuffer(IoBuffer.wrap(bytes));

out.write(pack);

if(in.remaining() > 0){//如果读取内容后还粘了包,就让父类再重读 
一次,进行下一次解析

return
true
;

}

}

}

return
false
;//处理成功,让父类进行接收下个包

}

getter();

    Setter();

}

二、    实现编解码工厂和解码器

我们还需要一个编解码工厂,用来为编解码过滤器提供编码器和解码器,解码器此处我们用不到,但是也必须提供,所以可以提供一个空的实现。

/**

*

* 编解码工厂

*

*/

public
class
MyCodecFcatory implements ProtocolCodecFactory {

private ProtocolEncoder
encoder = null;

private ProtocolDecoder
decoder = null;

public MyCodecFcatory(ProtocolEncoder encoder, ProtocolDecoderdecoder) {

this.encoder = encoder;

this.decoder = decoder;

}

@Override

public ProtocolEncoder getEncoder(IoSession session)
throws Exception {

return
this
.encoder;

}

@Override

public ProtocolDecoder getDecoder(IoSession session)
throws Exception {

return
this
.decoder;

}

}

/**

*

* 编码器:不做任何操作,数据已是约定好的格式,按原格式编码

*

*/

public
class
MyEncoder extends ProtocolEncoderAdapter {

@Override

public
void
encode(IoSession session, Object message,

ProtocolEncoderOutput out) throws Exception {

// TODO Do nothing

}

}

三、    配置编解码过滤器

下面就可以配置编解码过滤器了:

<!-- 累加数据包解码器:解断丢包、粘包问题 -->

<bean
id="codec"
class="org.apache.mina.filter.codec.ProtocolCodecFilter">

<constructor-arg>

<bean
class="com.mina.codec.MyCodecFcatory">

<constructor-arg
index="0">

<bean
class="com.mina.codec.MyEncoder"></bean>

</constructor-arg>

<constructor-arg
index="1">

<bean
class="com.mina.codec.MyDecoder">

<property
name="packetComponent">

<bean
class="com. mina.component.RootComponent">

</bean>

</property>

</bean>

</constructor-arg>

</bean>

</constructor-arg>

</bean>

<bean
id="filterChainBuilder"
class="org.apache.mina.core.filterchain.DefaultIoFilterChainBuilder">

<property
name="filters">

<map>

<entry
key="codec"
value-ref="codec"/>

<entry
key="logger"
value-ref="loggerFilter"/>

<entry
key="executors"
value-ref="executors"/>

</map>

</property>

</bean>

需要注意的是:在doDecode中通过out.write(pack) 把数据输出后,官方的说明文档中说接下来会继续执行后面的过滤器,然后是IoHandle。如果你是只用了一个编解码过滤器的话,这可能完全没问题,但是如果使用了两个编解码过滤器(可能很少有人会这样做,但本人由于前期使用了另外一个自定义的编解码过滤器,后来想加上这个可累加的解码器,为了图省事就在原过滤器的前面新增加了一个编解码过滤器,后来数据流就不走我原来的编解码过滤器了,out.write()之后直接到了IoHandle里面,搞了我好久,无奈最后把两个编解码过滤器合二为一啦,其中原因我还没时间去搞个清楚,为防止大家和我犯同一个错误,特此提醒!)

Mina框架断包、粘包问题解决方案

时间: 2024-11-07 04:38:08

Mina框架断包、粘包问题解决方案的相关文章

NetworkComms c#通信框架与Java的Netty框架通信 解决粘包问题

上次写了一篇文章  基于networkcomms V3通信框架的c#服务器与java客户端进行通信之Protobuf探讨 其中没有解决粘包问题,抛砖引玉,文章得到了失足程序员 老师的点评,并给出了解决方案:[最多评论]java netty socket库和自定义C#socket库利用protobuf进行通信完整实例(10/591) » 于是马上开始学习,并把c#服务器端换成了我比较熟悉的networkcomms v3 c#通信框架(商业版,本文并不提供) ,以方便与已经存在的系统进行整合. 客户

[编织消息框架][设计协议]解决粘包半包(下)

接下来介绍netty如何切割分包 学习目的,了解处理业务,方便以后脱离依赖 读者如果不感兴趣或看不懂可以先忽略,难度比较大 LengthFieldBasedFrameDecoder.class public LengthFieldBasedFrameDecoder( ByteOrder byteOrder, //大小端模式 默认大端 ByteOrder BIG_ENDIAN int maxFrameLength, //包Frame netty叫帧概念 最大上限 int lengthFieldOf

[Go] 轻量服务器框架tcp的粘包问题 封包与拆包

tcp传输的数据是以流的形式传输的,因此就没有办法判断到哪里结束算是自己的一个消息,这样就会出现粘包问题,多个包粘在一起了 可以使用这样一个自定义的形式来解决,一个消息分为 head+body  head包括数据的长度和数据编号 , 长度和编号都是uint32类型 也就是32位 占有4个字节 , 总共head占有8个字节 封装一个消息的结构体,作为一个数据实体,比如下面这个,编号 数据 数据长度  三个属性 package znet type Message struct { Id uint32

NIO框架之MINA源码解析(四):粘包与断包处理及编码与解码

1.粘包与段包 粘包:指TCP协议中,发送方发送的若干包数据到接收方接收时粘成一包,从接收缓冲区看,后一包数据的头紧接着前一包数据的尾.造成的可能原因: 发送端需要等缓冲区满才发送出去,造成粘包 接收方不及时接收缓冲区的包,造成多个包接收 断包:也就是数据不全,比如包太大,就把包分解成多个小包,多次发送,导致每次接收数据都不全. 2.消息传输的格式 消息长度+消息头+消息体  即前N个字节用于存储消息的长度,用于判断当前消息什么时候结束. 消息头+消息体    即固定长度的消息,前几个字节为消息

MINA粘包断包专题研究

一.前述: 近期做项目用到了MINA,其中遇到了一个断包与粘包的问题,困扰了我一天一夜,经过一天一夜的思索与查看其他大牛分享的资料,现将我在解决这一问题过程中的一些心得与解决问题的方法记录下来,供广大IT兄弟姐妹们参考,如有不对或欠妥之处,请指证.请不要吝惜分享您的技术,作为中国IT软件工程师,一定要想到多一个人掌握IT技术,不会给你增加一个竞争对手,如果认为会给你增加竞争对手,这种想法是非常狭隘的,自私自利的.只有分享,大家共同的技术提高了,才能激发出更多的思维解决更加棘手的技术难点,希望大家

Netty解决粘包和拆包问题的四种方案

在RPC框架中,粘包和拆包问题是必须解决一个问题,因为RPC框架中,各个微服务相互之间都是维系了一个TCP长连接,比如dubbo就是一个全双工的长连接.由于微服务往对方发送信息的时候,所有的请求都是使用的同一个连接,这样就会产生粘包和拆包的问题.本文首先会对粘包和拆包问题进行描述,然后介绍其常用的解决方案,最后会对Netty提供的几种解决方案进行讲解.这里说明一下,由于oschina将"jie ma qi"认定为敏感文字,因而本文统一使用"解码一器"表示该含义 粘包

Socket编程实践(6) --TCP粘包原因与解决

流协议与粘包 粘包的表现 Host A 发送数据给 Host B; 而Host B 接收数据的方式不确定 粘包产生的原因 说明 TCP 字节流,无边界 对等方,一次读操作,不能保证完全把消息读完 UDP 数据报,有边界 对方接受数据包的个数是不确定的 产生粘包问题的原因分析 1.SQ_SNDBUF 套接字本身有缓冲区 (发送缓冲区.接受缓冲区) 2.tcp传送的端 mss大小限制 3.链路层也有MTU大小限制,如果数据包大于>MTU要在IP层进行分片,导致消息分割. 4.tcp的流量控制和拥塞控

DELPHI高性能大容量SOCKET并发(四):粘包、分包、解包

DELPHI高性能大容量SOCKET并发(四):粘包.分包.解包 粘包 使用TCP长连接就会引入粘包的问题,粘包是指发送方发送的若干包数据到接收方接收时粘成一包,从接收缓冲区看,后一包数据的头紧接着前一包数据的尾.粘包可能由发送方造成,也可能由接收方造成.TCP为提高传输效率,发送方往往要收集到足够多的数据后才发送一包数据,造成多个数据包的粘连.如果接收进程不及时接收数据,已收到的数据就放在系统接收缓冲区,用户进程读取数据时就可能同时读到多个数据包. 粘包一般的解决办法是制定通讯协议,由协议来规

socket之粘包

什么是粘包 粘包是一种现象  这种现象只出现在TCP中而不会出现在UDP中(TCP和UDP都是传输层中的协议) 粘包:粘包问题主要还是因为接收方不知道消息之间的界限,不知道一次性提取多少字节的数据所造成的 粘包概念详解: 当发送网络数据时,tcp协议会根据Nagle算法将时间间隔短,数据量小的多个数据包打包成一个数据包,先发送到自己操作系统的缓存中,然后操作系统将数据包发送到目标程序所对应操作系统的缓存中,最后将目标程序从缓存中取出,而第一个数据包的长度,应用程序并不知道,所以会直接取出数据或者