(五)Mina源码解析之编解码器

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



  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值