Apache mina 入门(五) —— 断包,粘包问题解决
来源:互联网 发布:java调用通用短信接口 编辑:程序博客网 时间:2024/06/05 14:39
通过前面的文章Apache mina 入门(一)— 基础知识,我们可以知道:Apache Mina Server 是一个网络通信应用框架,也就是说,它主要是对基于TCP/IP、UDP/IP协议栈的通信框架(当然,也可以提供JAVA 对象的序列化服务、虚拟机管道通信服务等),Mina 可以帮助我们快速开发高性能、高扩展性的网络通信应用,Mina 提供了事件驱动、异步(Mina 的异步IO 默认使用的是JAVA NIO 作为底层支持)操作的编程模型。
通过Apache Mina 入门 (二)—— 异步通信机制
Apache mina 入门(三) —— 客户端同步通讯
Apache mina 入门(四) —— 客户端长连接方式实现断线重连监听
我们可以熟练的处理程序中出现的问题,但Mina 其实还有一个严重的问题,那就是断包问题 —— 自定义或者默认的解码器每次读取缓冲的数据是有限制的,即ReadBufferSize的大小,默认是2048个字节,当数据包比较大时将被分成多次读取,造成断包。虽然我们有一种粗暴的解决方案,那就是通过acceptor.getSessionConfig().setReadBufferSize(newsize)这种方式来增加默认容量【这样导致的后果就是数据的处理效率变慢】
在mina中,一般的应用场景用TextLine的Decode和Encode就够用了(TextLine的默认分割符虽然是\r\n,但其实分隔符是可以自己指定的,如:newTextLineDecoder(charset, decodingDelimiter);)
所以,当我们接收的数据的大小不是很固定,且容易偏大的时候,默认的TextLine就不适合了。这时我们在解析之前就需要判断数据包是否完整,这样处理起来就会非常麻烦。那么Mina 中幸好提供了CumulativeProtocolDecoder
类,从名字上可以看出累积性的协议解码器,也就是说只要有数据发送过来,这个类就会去读取数据,然后累积到内部的IoBuffer 缓冲区,但是具体的拆包(把累积到缓冲区的数据解码为JAVA 对象)交由子类的doDecode()方法完成,实际上CumulativeProtocolDecoder就是在decode()方法中反复的调用暴漏给子类实现的doDecode()方法。
具体执行过程如下所示:
A. 你的doDecode()方法返回true 时,CumulativeProtocolDecoder 的decode()方法会首先判断你是否在doDecode()方法中从内部的IoBuffer 缓冲区读取了数据【源码是根据当前数据的读取位置与之前的位置进行比对,如果相等,则代表未从缓存中读取数据,反之,则已从缓存区读取数据】,如果没有,则会抛出非法的状态异常【throw new IllegalStateException(“doDecode() can’t return true when buffer is not consumed.”);】,也就是你的doDecode()方法返回true 就表示已经消费过内部的IoBuffer 缓冲区的数据。如果验证通过,那么CumulativeProtocolDecoder会检查缓冲区内是否还有数据未读取,如果有就继续调用doDecode()方法【从当前读取位置继续往下读取】,没有就停止对doDecode()方法的调用。
B. 当你的doDecode()方法返回false 时,CumulativeProtocolDecoder 会停止对doDecode()方法的调用,但此时如果本次数据还有未读取完的,就将含有剩余数据的IoBuffer 缓冲区保存到IoSession 中,以便下一次数据到来时可以从IoSession 中提取合并。如果发现本次数据全都读取完毕,则清空IoBuffer 缓冲区。
简而言之,当你认为读取到的数据已经够解码了,则将该部分数据先进行解码,然后在判断缓存去是否还有数据,如果有就返回true,否则就返回false。这个CumulativeProtocolDecoder其实最重要的工作就是帮你完成了数据的累积,因为这个工作是很烦琐的。
主要代码为解码器:
/** * 解码器 * 继承CumulativeProtocolDecoder 类,实现对mina 断包,粘包问题解决 * 主要思路: * 1、判断当前缓存去中是否存在数据,如果存在,则进行后续处理。 * 2、获取报文数据【报文规范为:长度 (2个字节) + 方法编号(1个字节) + 内容】,先获取长度,判断缓存区剩余数据长度与报文长度是否相等,如果不相等,代表报文数据不完整, * 返回 false,需要再次从缓存去读取 * 3、相等,则获取方法编号,内容,获取到该内容后,则先将该部分完整数据送给handler处理, * 4、判断缓存区是否还存在数据,如果存在,代表该报文后面还粘包了。则返回true. * @author liuc * @date 2017-12-22 * */public class ByteArrayDecoder extends CumulativeProtocolDecoder { private static final Logger logger = Logger.getLogger(NSProtocalDecoder.class); private final Charset charset = Charset.forName("GBK"); // 请求报文的最大长度 100k private int maxPackLength = 102400; public int getMaxPackLength() { return maxPackLength; } public void setMaxPackLength(int maxPackLength) { if (maxPackLength <= 0) { throw new IllegalArgumentException("请求报文最大长度:" + maxPackLength); } this.maxPackLength = maxPackLength; } @Override protected boolean doDecode(IoSession session, IoBuffer in, ProtocolDecoderOutput out) throws Exception { //1、先判断数据是否存在 if(in.remaining() > 0){ in.mark(); //1、获取长度 byte[] sizeBytes = new byte[2]; in.get(sizeBytes,0,2);//读取2字节 byte[] length_byte_arr = new byte[]{0,0,0,0}; length_byte_arr[2] = sizeBytes[0]; length_byte_arr[3] = sizeBytes[1]; //获取长度 int length = ByteTools.byteArrayToInt(length_byte_arr); //length -2 中 减2是因为读取了两个字节的长度 if(in.remaining() < length -2 ){ in.reset(); //代表报文不完整,需要再次读取缓冲区的数据 return false; } //获取方法编号 byte[] funcidBytes = {in.get()}; byte[] funcid_byte_arr = new byte[]{0,0,0,0}; funcid_byte_arr[3] = funcidBytes[0]; //获取长度 int funcid = ByteTools.byteArrayToInt(funcid_byte_arr); System.out.println("3=================================="+in.remaining()); //获取内容 //读取报文正文内容 int oldLimit = in.limit(); logger.debug("limit:" + (in.position() + length)); //当前读取的位置 + 总长度 - 前面读取的字节长度 in.limit(in.position() + length - 3); String content = in.getString(charset.newDecoder()); in.limit(oldLimit); logger.debug("报文正文内容:" + content); BaseMessageForClient message = new BaseMessageForClient(); message.setLength(length); message.setFuncid(funcid); message.setContent(content); out.write(message); //代表着后续还有包,可以重新进行读取,但前一个包已经传送给handler进行处理了 //该问题称为粘包 if(in.remaining() > 0){ return true; } } return false; }}
编码器代码如下:
public class ByteArrayEncoder extends ProtocolEncoderAdapter { public void encode(IoSession session, Object message, ProtocolEncoderOutput out) throws Exception { // TODO Auto-generated method stub BaseMessageForServer basemessage = (BaseMessageForServer)message; IoBuffer buffer = IoBuffer.allocate(256); buffer.setAutoExpand(true); buffer.put(ByteTools.intToByteArray(basemessage.getLength()+3, 2));//包长 buffer.put(ByteTools.intToByteArray(basemessage.getFuncid(), 1));//方法编号 buffer.put(basemessage.getContent().getBytes());//内容 buffer.flip(); out.write(buffer); out.flush(); buffer.free(); }}
源码下载地址:http://download.csdn.net/download/u012151597/10168974
- Apache mina 入门(五) —— 断包,粘包问题解决
- 深入解析Apache Mina源码(4)——Mina编解码以及对粘包和断包的处理
- Mina框架断包、粘包问题解决方案
- mina处理断包和粘包
- mina处理断包和粘包
- mina学习 粘包,断包处理
- mina处理断包和粘包
- mina IoBuffer 处理断包、粘包
- Mina 粘包、断包、半包解决
- Apache MINA 连续自动发送心跳包
- Apache mina 入门(一)— 基础知识
- mina 粘包解决方案之一
- Mina通信粘包处理
- Mina中的粘包处理
- mina粘包、多包和少包的解决方法
- Apache mina 2.0.1 和 AS3 Socket 进行通讯(处理粘包问题)
- 解决apache mina在网络环境慢下的粘包问题
- TCP粘包问题解决
- Linux安装java8
- JS——worker
- Android6.0动态权限
- nodejs crud
- DSL的诞生 | 复杂sql转成Elasticsearch DSL深入详解
- Apache mina 入门(五) —— 断包,粘包问题解决
- Xcode中加载OpenCV
- swift4
- 补基础之javascript面向对象-封装
- 调用WebService的几种方式
- android事件分发总结
- Pixhawk驱动下载
- 技术网站
- 12.22