MINA学习之自定义协议

一.自定义协议介绍

1、自定义的编解码工厂

要实现编解码工厂就要实现ProtocolCodecFactory这个接口。

2、实现自定义编解码器

(1)实现自定义解码器:实现ProtocolDecoder接口。

(2)实现自定义的编码器:实现ProtocolEncoder接口。

(3)最后就可以根据我们的自定义编解码工厂获得我们的编解码对象。

3、为什么要使用自定义的编码器?

因为实际工作中往往不是通过一个字符串就可以传输所有的信息,我们传输的是自定义的协议包。并且能在应用程序和网络通信中存在对象和二进制之间转化关系。所以我们需要结合业务编写自定义的编解码器。

4、常用的自定义协议的方法

(1)定长的方式:比如两个字节aa,bb,ok,no等这样的通信方式;

(2)定界符: helloworld | hahaha|......|......通过特殊的符号来区别消息。这样的方式会出现黏包、半包等现象。

比如hello    world|watchemen放进了缓冲区,带来了不正确的消息,这样就应该丢弃数据。

(3)自定义的协议包:包头 + 包体

  • 包头:数据包的版本号/信息,以及整个数据包(包头+包体)的长度
  • 包体:实际数据

总结:自定义协议数据包分析

我们完成通过客户端不断发送指定数目的自定义数据包,然后在服务端解析,这个过程中我们要解决半包问题。

二.Mina小项目Demo

第一步:自定义协议包

package minaStady.com.mina.protocal;
 
/** 
 * @author
 * @date 创建时间:2018年10月12日 上午10:48:40 
 * @Description 自定义协议包
 */
public class ProtocalPack {
	private int length;
	private byte flag;
	private String content;
	
	public ProtocalPack(byte flag,String content){
		this.flag = flag;
		this.content = content;
		int len1 = content == null?0:content.getBytes().length;
		this.length = 5+len1;//包头(length+版本信息) + 包体
	}
	
	public int getLength() {
		return length;
	}
	public void setLength(int length) {
		this.length = length;
	}
	public byte getFlag() {
		return flag;
	}
	public void setFlag(byte flag) {
		this.flag = flag;
	}
	public String getContent() {
		return content;
	}
	public void setContent(String content) {
		this.content = content;
	}

	@Override
	public String toString() {
		StringBuffer sb = new StringBuffer();
		sb.append("length:").append(length);
		sb.append("flag:").append(flag);
		sb.append("content:").append(content);
		return sb.toString();
	}
}

第二步:编码器

package minaStady.com.mina.protocal;

import java.nio.charset.Charset;

import org.apache.mina.core.buffer.IoBuffer;
import org.apache.mina.core.session.IoSession;
import org.apache.mina.filter.codec.ProtocolEncoderAdapter;
import org.apache.mina.filter.codec.ProtocolEncoderOutput;
 
/** 
 * @author 
 * @date 创建时间:2018年10月12日 下午1:38:04 
 * @Description 编码器(将对象转成字节流)
 */
public class ProtocalEncoder extends ProtocolEncoderAdapter{

	private final Charset charset;//定义编码型
	
	public ProtocalEncoder(Charset charset){
		this.charset = charset;
	}
	
	public void encode(IoSession session, Object message,
			ProtocolEncoderOutput out) throws Exception {
		ProtocalPack value = (ProtocalPack) message;//报文信息
		IoBuffer buf = IoBuffer.allocate(value.getLength());//设置缓冲区
		buf.setAutoExpand(true);//自动增长
		buf.putInt(value.getLength());//设置包头
		buf.put(value.getFlag());
		if(value.getContent() != null){//设置内容
			buf.put(value.getContent().getBytes());
		}
		buf.flip();
		out.write(buf);//发送出去
	}
}

第三步:解码器

package minaStady.com.mina.protocal;

import java.nio.charset.Charset;
import java.nio.charset.CharsetDecoder;

import org.apache.mina.core.buffer.IoBuffer;
import org.apache.mina.core.session.AttributeKey;
import org.apache.mina.core.session.IoSession;
import org.apache.mina.filter.codec.ProtocolDecoder;
import org.apache.mina.filter.codec.ProtocolDecoderOutput;
 
/** 
 * @author
 * @date 创建时间:2018年10月12日 下午1:56:45 
 * @Description 解码器(将字节流转成对象)
 */
public class ProtocalDecoder implements ProtocolDecoder{

	private final AttributeKey CONTEXT = new AttributeKey(this.getClass(),"context");//设置上下文存储
	private final Charset charset;
	private int maxPackLength = 100;//设置最大长度,过滤
	
	public int getMaxPackLength() {
		return maxPackLength;
	}

	public void setMaxPackLength(int maxPackLength) {
		if(maxPackLength<0){
			throw new IllegalArgumentException("maxPackLength参数:"+maxPackLength);
		}
		this.maxPackLength = maxPackLength;
	}

	//默认
	public ProtocalDecoder(){
		this(Charset.defaultCharset());
	}
	
	public ProtocalDecoder(Charset charset){
		this.charset = charset;
	}
	 
	public Context getContext(IoSession session){
		Context ctx = (Context)session.getAttribute(CONTEXT);
		if(ctx == null){
			ctx = new Context();
			session.setAttribute(CONTEXT, ctx);
		}
		return ctx;
	}
	
	public void decode(IoSession session, IoBuffer in, ProtocolDecoderOutput out)
			throws Exception {
		final int packHeadlength = 5;//代表包头的长度
		Context ctx = this.getContext(session);
		ctx.append(in);
		IoBuffer buf = ctx.getBuf();
		buf.flip();//缓冲区的指针从0开始
		while(buf.remaining() >= packHeadlength){
			buf.mark();
			int length = buf.getInt();
			byte flag = buf.get();
			if(length < 0 || length > maxPackLength ){
				buf.reset();
				break;
			}else if(length>=packHeadlength && length-packHeadlength<=buf.remaining()){
				int oldLimit = buf.limit();
				buf.limit(buf.position()+ length - packHeadlength);
				String content = buf.getString(ctx.getDecoder());
				buf.limit(oldLimit);
				ProtocalPack pakeage = new ProtocalPack(flag, content);
				out.write(pakeage);//发送数据包
			}else { //半包
				buf.clear();
				break;
			}
		}
		
		//读完是否还有数据
		if(buf.hasRemaining()){
			IoBuffer temp = IoBuffer.allocate(maxPackLength).setAutoExpand(true);
			temp.put(buf);
			temp.flip();
			buf.reset();
			buf.put(temp);
			
		}else{
			buf.reset();//清空
		}
	}

	public void finishDecode(IoSession session, ProtocolDecoderOutput out)
			throws Exception {
		
	}

	public void dispose(IoSession session) throws Exception {
		Context ctx = (Context)session.getAttribute(CONTEXT);
		if(ctx != null){
			session.removeAttribute(CONTEXT);
		}
	}
	
	private class Context { //上下文对象
		private final CharsetDecoder decoder;
		private IoBuffer buf;
		
		private Context(){
			decoder = charset.newDecoder();
			buf = IoBuffer.allocate(80).setAutoExpand(true);
		}
		
		public void append(IoBuffer in){
			this.getBuf().put(in);
		}
		
		public void rest(){
			decoder.reset();
		}
		
		public IoBuffer getBuf() {
			return buf;
		}

		public void setBuf(IoBuffer buf) {
			this.buf = buf;
		}

		public CharsetDecoder getDecoder() {
			return decoder;
		}
	}
}

第四步:编解码工厂

package minaStady.com.mina.protocal;

import java.nio.charset.Charset;

import org.apache.mina.core.session.IoSession;
import org.apache.mina.filter.codec.ProtocolCodecFactory;
import org.apache.mina.filter.codec.ProtocolDecoder;
import org.apache.mina.filter.codec.ProtocolEncoder;

import minaStady.com.mina.protocal.ProtocalDecoder;
import minaStady.com.mina.protocal.ProtocalEncoder;
 
/** 
 * @author 
 * @date 创建时间:2018年10月12日 下午2:41:35 
 * @Description 编解码工厂
 */
public class ProtocalFactory implements ProtocolCodecFactory{
	
	private final ProtocalDecoder decoder;
	private final ProtocalEncoder encoder;
	
	public ProtocalFactory(Charset charset){
		encoder = new ProtocalEncoder(charset);
		decoder = new ProtocalDecoder(charset);
	}

	public ProtocolEncoder getEncoder(IoSession session) throws Exception {
		return encoder;
	}

	public ProtocolDecoder getDecoder(IoSession session) throws Exception {
		return decoder;
	}
}

第五步:服务端实例

package minaStady.com.mina.protocal;

import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.charset.Charset;

import org.apache.mina.core.service.IoAcceptor;
import org.apache.mina.core.service.IoHandlerAdapter;
import org.apache.mina.core.session.IdleStatus;
import org.apache.mina.core.session.IoSession;
import org.apache.mina.filter.codec.ProtocolCodecFilter;
import org.apache.mina.transport.socket.nio.NioSocketAcceptor;
 
/** 
 * @author 
 * @date 创建时间:2018年10月12日 下午2:49:24 
 * @Description 服务端实例
 */
public class ProtocalServer {

	private static final int port = 7080;
	
	public static void main(String[] args) throws IOException {
		
		IoAcceptor acceptor = new NioSocketAcceptor();
		acceptor.getFilterChain().addLast("coderc", new ProtocolCodecFilter(
				new ProtocalFactory(Charset.forName("UTF-8"))));//设置编解码器
		acceptor.getSessionConfig().setReadBufferSize(1024);
		acceptor.getSessionConfig().setIdleTime(IdleStatus.BOTH_IDLE, 10);
		acceptor.setHandler(new Myhandler());
		acceptor.bind(new InetSocketAddress(port));
		System.out.println("server start......");
	}
}

class Myhandler extends IoHandlerAdapter{

	@Override
	public void sessionIdle(IoSession session, IdleStatus status)
			throws Exception {
		System.out.println("server->sessionIdle");
	}

	@Override
	public void exceptionCaught(IoSession session, Throwable cause)
			throws Exception {
		System.out.println("server->exceptionCaught");
	}

	@Override
	public void messageReceived(IoSession session, Object message)
			throws Exception {
		ProtocalPack pack = (ProtocalPack)message;
		System.out.println("服务端接收: " + pack);
	}
}

第六步:客户端实例

package minaStady.com.mina.protocal;

import java.net.InetSocketAddress;
import java.nio.charset.Charset;

import org.apache.mina.core.future.ConnectFuture;
import org.apache.mina.core.future.IoFutureListener;
import org.apache.mina.core.service.IoConnector;
import org.apache.mina.core.service.IoHandlerAdapter;
import org.apache.mina.core.session.IdleStatus;
import org.apache.mina.core.session.IoSession;
import org.apache.mina.filter.codec.ProtocolCodecFilter;
import org.apache.mina.transport.socket.nio.NioSocketConnector;
 
/** 
 * @author 
 * @date 创建时间:2018年10月12日 下午3:06:14 
 * @Description 客户端实例
 */
public class ProtocalClient {

    private static final String HOST = "127.0.0.1";
    private static final int PORT = 7080;
    static long counter = 0;
    final static int fil = 100;
    static long start = 0;
	
	public static void main(String[] args) {
		start = System.currentTimeMillis();//获取当前时间
		IoConnector connector = new NioSocketConnector();
		connector.getFilterChain().addLast("coderc", new ProtocolCodecFilter(
				new ProtocalFactory(Charset.forName("UTF-8"))));//设置编解码器
		connector.getSessionConfig().setReadBufferSize(1024);
		connector.getSessionConfig().setIdleTime(IdleStatus.BOTH_IDLE, 10);
		connector.setHandler(new MyHandlers());
		
		ConnectFuture connectfuture = connector.connect(new InetSocketAddress(HOST,PORT));
		connectfuture.addListener(new IoFutureListener<ConnectFuture>() {

			public void operationComplete(ConnectFuture future) {
				if(future.isConnected()){
					IoSession session = future.getSession();
					sendata(session);
				}
			}
		});
	}
	
	public static void sendata(IoSession session){
		for (int i = 0; i < fil; i++) {
			String content = "watchmen:"+i;
			ProtocalPack pack = new ProtocalPack((byte)i, content);
			session.write(pack);
			System.out.println("客户端发送数据:"+pack);
		}
	}
}

class MyHandlers extends IoHandlerAdapter{

	@Override
	public void sessionIdle(IoSession session, IdleStatus status)
			throws Exception {
		if(status == IdleStatus.READER_IDLE){
			session.close(true);
		}
	}

	@Override
	public void messageReceived(IoSession session, Object message)
			throws Exception {
		ProtocalPack pack = (ProtocalPack)message;
		System.out.println("client->"+pack);
	}
}

第七步:运行结果

猜你喜欢

转载自blog.csdn.net/wang_snake/article/details/79601828