Mina中的编解码器通过过滤器ProtocolCodecFilter构造,这个过滤器有3个构造器,其中可以分为两类,一类需要一个ProtoCodecFactory,这个接口有两个方法,分别是getDecoder()和getEncoder(),我们需要实现这两个方法,实现这两个方法就需要一个自定义的Decoder和一个自定义的Encoder
public class CmccSipcCodecFactory implements ProtocolCodecFactory{
private final ProtocolEncoder encoder;
private final ProtocolDecoder decoder;
public CmccSipcCodecFactory(Charset charset) {
this.decoder = new CmccSipcDecoder(charset);
this.encoder = new CmccSipcEncoder(charset);
}
@Override
public ProtocolDecoder getDecoder(IoSession session) throws Exception {
return decoder;
}
@Override
public ProtocolEncoder getEncoder(IoSession session) throws Exception {
return encoder;
}
}
其中CmccSipcDecoder和CmccSipcEncoder是我们自定义的编辑码器,编辑码器需要跟据我们的具体业务来实现,下面是一个编辑码器的例子,发送端和接收端按照约定好的格式将数据组装起来,比如下面的例子中我们是以@作为数据之间的分割符,解码的时候我们也要根据@来进行解码
public class CmccSipcEncoder extends ProtocolEncoderAdapter{
private final Charset charset;
public CmccSipcEncoder(Charset charset) {
super();
this.charset = charset;
}
public void encode(IoSession session, Object message,
ProtocolEncoderOutput out) throws Exception {
SmsObject sms =(SmsObject) message;
CharsetEncoder ce=charset.newEncoder();
IoBuffer buffer =IoBuffer.allocate(100).setAutoExpand(true);
String statusLine="M sip:wap.fetion.com.cn SIP-C/2.0";
String sender = sms.getSender();
String receiver = sms.getReceiver();
String smsContent = sms.getMessage();
buffer.putString(statusLine+"@",ce);
buffer.putString("S:"+sender+'@',ce);
buffer.putString("R:"+receiver+"@", ce);
buffer.putString("L:"+(smsContent.getBytes(charset).length)+"@", ce);
buffer.putString(smsContent, ce);
buffer.flip();
out.write(buffer);
}
}
我们继承了ProtocolEncoderAdapter,这样只实现我们需要的方法就可以了,解码器也是一样,关于解码器主要有两点需要注意
第一点:我们不知道数据发送过来的规模是多大,所以我们也不知道应该接收多少次才可以将数据接收完整,Mina提供了一个CumulativeProtocolDecoder类,这个类的作用是只要有数据发送过来,它就会读取数据,然后累积到内部的IoBuffer缓冲区,这样我们只要负责拆包就可以了。下面是一个解码器的例子
public class CmccSipcDecoder extends CumulativeProtocolDecoder{
private final Charset charset;
private AttributeKey CONTEXT = new AttributeKey(this.getClass(), "context");
private class Context{
int i=1;
int matchCount=0;
String statusLine = "";
String sender ="";
String receiver = "";
String length ="";
String sms = "";
IoBuffer buffer = IoBuffer.allocate(100).setAutoExpand(true);
public int getI() {
return i;
}
public void setI(int i) {
this.i = i;
}
public int getMatchCount() {
return matchCount;
}
public void setMatchCount(int matchCount) {
this.matchCount = matchCount;
}
public IoBuffer getBuffer() {
return buffer;
}
public String getStatusLine() {
return statusLine;
}
public void setStatusLine(String statusLine) {
this.statusLine = statusLine;
}
public String getSender() {
return sender;
}
public void setSender(String sender) {
this.sender = sender;
}
public String getReceiver() {
return receiver;
}
public void setReceiver(String receiver) {
this.receiver = receiver;
}
public String getLength() {
return length;
}
public void setLength(String length) {
this.length = length;
}
public String getSms() {
return sms;
}
public void setSms(String sms) {
this.sms = sms;
}
}
public CmccSipcDecoder(Charset charset){
this.charset=charset;
}
@Override
protected boolean doDecode(IoSession session, IoBuffer in,
ProtocolDecoderOutput out) throws Exception {
System.err.println("********************************");
CharsetDecoder cd = charset.newDecoder();
if(session.getAttribute(CONTEXT)==null){
session.setAttribute(CONTEXT, new Context());
}
Context context = (Context)session.getAttribute(CONTEXT);
IoBuffer buffer = context.getBuffer();
while(in.hasRemaining()){
byte b = in.get();
buffer.put(b);
if(b==64 && context.getI()<5){
context.setMatchCount(context.getMatchCount()+1);
if(context.getI()==1){
buffer.flip();
context.setStatusLine(buffer.getString(context.getMatchCount(),cd));
context.setStatusLine(context.getStatusLine().substring(0, context.getStatusLine().length()-1));
context.setMatchCount(0);
buffer.clear();
}
if(context.getI()==2){
buffer.flip();
context.setSender(buffer.getString(context.getMatchCount(),cd));
context.setSender(context.getSender().substring(0,context.getSender().length()-1));
context.setMatchCount(0);
buffer.clear();
}
if(context.getI()==3){
buffer.flip();
context.setReceiver(buffer.getString(context.getMatchCount(),cd));
context.setReceiver(context.getReceiver().substring(0,context.getReceiver().length()));
context.setMatchCount(0);
buffer.clear();
}
if(context.getI()==4){
buffer.flip();
context.setLength(buffer.getString(context.getMatchCount(),cd));
context.setLength(context.getLength().substring(0, context.getLength().length()-1));
context.setMatchCount(0);
}
context.setI(context.getI()+1);
}else if(context.getI()==5){
context.setMatchCount(context.getMatchCount()+1);
if(context.getMatchCount() == Long.parseLong(context.getLength().split(":")[1])){
buffer.flip();
buffer.position(context.getLength().indexOf(":")+4);
context.setSms(buffer.getString(context.getMatchCount(),cd));
context.setI(context.getI()+1);
session.removeAttribute(CONTEXT);
break;
}
}else{
context.setMatchCount(context.getMatchCount()+1);
}
}
if(session.getAttribute(CONTEXT)!=null){
return true;
}
SmsObject smsObject=new SmsObject();
smsObject.setSender(context.getSender().split(":")[1]);
smsObject.setReceiver(context.getReceiver().split(":")[1]);
smsObject.setMessage(context.getSms());
out.write(smsObject);
return false;
}
}
那么doDecode方法和CumulativeProtocolDecoder类之间到底是怎么配合的呢?在创建编解码过滤器ProtocolCodecFilter的时候我们创建了一个ProtocolCodecFactory工厂类,该类中包含了我们自定义的编解码器,在创建ProtocolCodecFilter的时候ProtocolCodecFactory是作为构造参数传进去的,在收到客户端的消息时,会依次调用所有过滤器的messageReceived()方法,当然也会调用编解码过滤器ProtocolCodecFilter的messageReceived()方法,在messageReceived()方法中,会调用ProtoCodecFactory的getDecoder()方法获得解码器,也就是我们上面的解码器CmccSipcDecoder,拿到解码器后会调用该解码器的
decode()方法,我们注意到在CmccSipcDecoder中并没有decode方法,decode是CmccSipcDecoder的父类中的一个方法,让我们看看这个方法是如何实现的
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;
}
一旦IoBuffer中有数据decode会不断的调用子类(也就是我们自定义的解码器)的doDecode方法,如果doDecode()方法返回true会不断的进行调用,知道IoBuffer中没有数据时或当子类doDecode()方法返回false时会跳出while循环,
所以我们在实现doDecode方法时,如果我们判断已经解析了一条完整的数据但是在buffer中还有多余的数据就要返回true这样就可以继续解析下一条数据,如果我们判断接收的数据包不是一个完整的包就要返回false,这样在下次数据进来时会继续解析直到接收一个完整的包为止。
第二点:在解码的时候,假设一条完整数据的大小是100K,但是我们的IoBuffer的大小为10K,这个时候需要执行4次(IoBuffer会以2被的速度自动扩容)doDecode才能完整的接收一条数据,但是这样存在一个问题第1,2,3次接收的数据要存放到什么地方呢,存到方法里肯定是不行的,因为每次调用方法时都会新创建一个方法栈,用完即被销毁。最好的办法就是讲数据存放到Session中,上面的例子程序演示了数据是如何存放到session中的。
搞清楚了这两个问题,编写编解码器就轻而易举了。
下面的链接中有完整的例子程序
http://download.csdn.net/detail/u010031673/9499864