通过前面的文章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