Mina实现自定义协议的通信

18 篇文章 0 订阅

转http://www.open-open.com/lib/view/open1351557171910.html


网络的传输使用需要遵循一定的规则,这些规则我们称为协议。如在互联网请求HTML页面的时候,我们要遵循HTTP协议,HTTP头的格式就是我们要遵守的规则:

01Request Headers
02Accept:
03text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
04Accept-Charset:
05GBK,utf-8;q=0.7,*;q=0.3
06Accept-Encoding:
07gzip,deflate,sdch
08Accept-Language:
09zh-CN,zh;q=0.8
10Cache-Control:
11max-age=0

比如我们在做一些第三方开发的时候,经常会按照固定的格式向服务器端发送请求,服务器验证消息后,返回相应的结果。在Mina的开发中,我们也会用到这种模式。首先我们先来描述下这种方式的使用情形:

这样的方式,目前,我所在的项目组中主要用于通信和传输。为了将文件从客户端传到云端,我们先在客户端将数据根据一定的规则切片,然后通过一种特定的格式传输到服务器端。Mina,作为这中间的桥梁,秉承了框架很好的优点,快速开发。由于我们的服务器端采用的是C写的,所以我只给出客户端的编码过程,解码过程原理和开发都一样。

 用Mina执行一定协议的传输,主要有以下几个步骤:

1.         设计通信协议;

2.         编写请求(返回)对象和业务对象;

3.         编写解码(编码)器;

4.         编写客户端、服务器端。

Mina实现自定义协议的通信

下面根据代码,一步步介绍这个过程,由于是前提的测试代码,所以没有考虑线程安全等问题,只是一个思路:

首先是通信的协议,根据mina的构建规则,将协议设计成抽象类,由请求对象和返回对象分别去继承,协议格式如下:

01package com.a2.desktop.example5.mina.potocol;
02 
03 
04import org.apache.mina.core.buffer.IoBuffer;
05 
06/**
07 * 通信协议
08 * @author Chen.Hui
09 *
10 */
11public abstract class AbsMessage {
12     
13    /**
14     * 协议格式:
15     *
16     * tag | header length | Filename | File length | offset | checksum | temps | data
17     *
18     */
19 
20    /** 请求或访问类型 请求Tag:0x00 返回Tag:0x01 共 8 bit */
21    public abstract byte getTag();
22 
23    /** 头文件长度 共 2^16 可表示 65535 */
24    public abstract short getHeaderlen();
25 
26    /** 根据UUID生成文件唯一标识,共 8*36=288 bit */
27    public abstract byte[] getFilename();//需要設計一個算法
28 
29    /** 获取文件长度 2^32=4GB 共 32 bit */
30    public abstract int getFileLen();
31 
32    /** 获取文件的偏移量offset 共 32 bit */
33    public abstract int getOffset();
34 
35    /** 获取文件的MD5校验码 共 32 bit */
36    public abstract byte[] getChecksum();
37 
38    /** 预留字段 长度不超过 128 bit */
39    public abstract byte[] getTmp();
40     
41    /**data 方式传输内容 不超过1024bit*/
42    public abstract IoBuffer getData();
43 
44}

下面是请求对象(返回)和业务对象,这里的业务对象主要功能是将文件切片:

01package com.a2.desktop.example5.mina.potocol;
02 
03import java.io.IOException;
04import java.nio.charset.Charset;
05 
06import org.apache.mina.core.buffer.IoBuffer;
07import org.slf4j.Logger;
08import org.slf4j.LoggerFactory;
09 
10/**
11 * 请求对象
12 *
13 * @author Chen.Hui
14 *
15 */
16public class InfoRequest extends AbsMessage {
17 
18    Logger logger = LoggerFactory.getLogger(InfoRequest.class);
19 
20    FilePiece piece;
21 
22    Charset charset;
23 
24    public InfoRequest(FilePiece piece) {
25        this.piece = piece;
26    }
27     
28    public InfoRequest(){
29        //empty
30    }
31 
32    @Override
33    public byte getTag() {// 0x01 请求包
34        return (byte) 0x01;
35    }
36 
37    @Override
38    public short getHeaderlen() {
39        if (getTmp() == null) {
40            short len = (short) (1 + 2 + 36 + 4 + 4 + 4 );
41            return len;
42        } else {
43            short len = (short) (1 + 2 + 36 + 4 + 4 + 4 + (short) getTmp().length);
44            return len;
45        }
46    }
47 
48    @Override
49    public int getFileLen() {// 文件总长度
50 
51        try {
52            return (int) piece.getFc().size();
53 
54        } catch (IOException e) {
55            e.printStackTrace();
56        }
57        return 0;
58    }
59 
60    @Override
61    public int getOffset() {// 传输 偏移量
62 
63        return piece.getOffset();
64 
65    }
66 
67    @Override
68    public byte[] getFilename() {// 文件名称
69 
70        /** check the bits of name */
71        byte[] name = new byte[36];
72        name = piece.getFilename().getBytes();
73 
74        return name;
75 
76    }
77 
78    @Override
79    public byte[] getChecksum() {// checksum
80 
81        byte[] checksum = new byte[4];
82        checksum = piece.getChecksum().getBytes(); 
83        return checksum;
84    }
85 
86    @Override
87    public byte[] getTmp() {
88        byte[] b=new byte[5];
89        return b;
90    }
91 
92    @Override
93    public IoBuffer getData() {
94        return piece.getBuf();
95    }
96}
业务对象代码,RandomAccessFile可用于随机读写,用于文件的切片,这里还用了管道,主要目的是为了加开读写速度:



01package com.a2.desktop.example5.mina.potocol;
02 
03import java.io.File;
04import java.io.RandomAccessFile;
05import java.nio.ByteBuffer;
06import java.nio.channels.FileChannel;
07import java.util.UUID;
08 
09import org.apache.mina.core.buffer.IoBuffer;
10import org.slf4j.Logger;
11import org.slf4j.LoggerFactory;
12 
13/**
14 * 分片文件操作类
15 *
16 * @author Chen.Hui
17 *
18 */
19public class FilePiece {
20 
21    Logger logger = LoggerFactory.getLogger(FilePiece.class);
22 
23    private ByteBuffer[] dsts;
24     
25    private IoBuffer buf;
26 
27    private String filename;
28 
29    private FileChannel fc;
30 
31    private RandomAccessFile raf;
32 
33    private int offset;
34 
35    private String checksum;
36 
37    /** 构建文件的基本信息 */
38    public FilePiece(String path, int offset) throws Exception {
39 
40        raf = new RandomAccessFile(new File(path), "rw");
41        fc = raf.getChannel();
42 
43        this.offset = offset;
44 
45        dsts = new ByteBuffer[1024];
46 
47        for (int i = 0; i < dsts.length; i++) {
48            dsts[i] = ByteBuffer.allocate(1024);
49        }
50 
51        fc.read(dsts, offset, 1024);
52         
53         
54        buf=IoBuffer.allocate(1024);
55 
56        filename = UUID.randomUUID().toString();
57        logger.info("has built:" + filename + " filename size"
58                + filename.length());
59 
60    }
61    /**这个方法还有点儿问题,数据取的不对*/
62    public IoBuffer getBuf(){
63        dsts[0].flip();
64        while(dsts[0].hasRemaining()){
65            buf.putChar(dsts[0].getChar());
66        }
67        buf.flip();
68        return buf;
69    }
70 
71    public String getFilename() {
72        return filename;
73    }
74 
75    public FileChannel getFc() {
76        return fc;
77    }
78 
79    public RandomAccessFile getRaf() {
80        return raf;
81    }
82 
83    public int getOffset() {
84        return offset;
85    }
86 
87    public String getChecksum() {
88        // TODO checksum algorithems
89        return "aaaa";
90    }
91}
再接下来是编码器 ,编码器的作用就是讲数据装换成用于传输的流,在Mina中这种流就是IoBuffer
01package com.a2.desktop.example5.mina.potocol;
02 
03import java.nio.charset.Charset;
04 
05import org.apache.mina.core.buffer.IoBuffer;
06import org.apache.mina.core.session.IoSession;
07import org.apache.mina.filter.codec.ProtocolEncoderOutput;
08import org.apache.mina.filter.codec.demux.MessageEncoder;
09 
10/**
11 * 编码器
12 * @author Chen.Hui
13 *
14 */
15public class InfoEncoder implements MessageEncoder<absmessage>{
16 
17    private Charset charset;
18     
19    public InfoEncoder(Charset charset){
20        this.charset=charset;
21    }
22     
23    @Override
24    public void encode(IoSession session, AbsMessage message,
25            ProtocolEncoderOutput out) throws Exception {
26         
27        IoBuffer buf=IoBuffer.allocate(1024).setAutoExpand(true);
28         
29        if(message instanceof InfoRequest){
30             
31            InfoRequest req=(InfoRequest) message;
32            buf.put(req.getTag());
33            buf.putShort((short)req.getHeaderlen());
34            buf.put(req.getFilename());
35            buf.putInt(req.getFileLen());
36            buf.putInt(req.getOffset());
37            buf.put(req.getChecksum());
38            buf.put(req.getTmp());
39            buf.put(req.getData());
40             
41        }else if(message instanceof InfoResponse){
42            //TODO
43        }
44                 
45        buf.flip();
46         
47        out.write(buf);
48    }
49}</absmessage>

解码器与之类似,解码器在这里的作用主要用户服务器端解码:

01package com.a2.desktop.example5.mina.potocol;
02 
03import java.nio.charset.Charset;
04 
05import org.apache.mina.core.buffer.IoBuffer;
06import org.apache.mina.core.session.IoSession;
07import org.apache.mina.filter.codec.ProtocolDecoderOutput;
08import org.apache.mina.filter.codec.demux.MessageDecoder;
09import org.apache.mina.filter.codec.demux.MessageDecoderResult;
10/**
11 * 解码器
12 * @author ChenHui
13 *
14 */
15public class InfoDecoder implements MessageDecoder {
16 
17    private Charset charset;
18 
19    public InfoDecoder(Charset charset) {
20        this.charset = charset;
21    }
22 
23    @Override
24    public MessageDecoderResult decodable(IoSession session, IoBuffer in) {
25 
26        //System.out.println("package size:"+in.remaining());
27        // 报头长度<56
28        if (in.remaining() < 56) {
29            return MessageDecoderResult.NEED_DATA;
30        }
31 
32        byte tag = in.get();
33        short head_len=in.getShort();
34 
35        if (tag == (short) 0x01) {
36            System.out.println("请求标识符:"+tag+" head length:"+head_len);
37        }else{
38            //System.out.println("未知标识符...");
39            return MessageDecoderResult.NOT_OK;
40        }
41 
42        return MessageDecoderResult.OK;
43    }
44 
45    @Override
46    public MessageDecoderResult decode(IoSession session, IoBuffer in,
47            ProtocolDecoderOutput out) throws Exception {
48        byte tag=in.get();
49     
50        if(tag==0x01){
51            InfoReqContainer irc=new InfoReqContainer();
52            irc.setTag(tag);
53            irc.setHeadlen(in.getShort());
54            irc.setFilename(in.getString(36, charset.newDecoder()));
55            irc.setFilelen(in.getInt());
56            irc.setOffset(in.getInt());
57            irc.setChecksum(in.getString(4, charset.newDecoder()));
58            irc.setTemp(in.getString(5, charset.newDecoder()));//应该用head len-53
59            irc.setData(in);
60             
61            out.write(irc);
62        }
63             
64        return MessageDecoderResult.OK;
65    }
66 
67    @Override
68    public void finishDecode(IoSession session, ProtocolDecoderOutput out)
69            throws Exception {
70        // TODO Auto-generated method stub
71 
72    }
73 
74}
为了解码方便,我这里设计了一个辅助类InfoReqContainer,主要用户辅助操作:
01package com.a2.desktop.example5.mina.potocol;
02 
03import org.apache.mina.core.buffer.IoBuffer;
04 
05/**
06 * 请求对象解析类
07 *
08 * @author Chen.Hui
09 *
10 */
11public class InfoReqContainer {
12    private byte tag;
13    private short headlen;
14    private String filename;
15    private int filelen;
16    private int offset;
17    private String temp;
18    private String checksum;
19    private IoBuffer data;
20 
21    public byte getTag() {
22        return tag;
23    }
24 
25    public void setTag(byte tag) {
26        this.tag = tag;
27    }
28 
29    public short getHeadlen() {
30        return headlen;
31    }
32 
33    public void setHeadlen(short headlen) {
34        this.headlen = headlen;
35    }
36 
37    public String getFilename() {
38        return filename;
39    }
40 
41    public void setFilename(String filename) {
42        this.filename = filename;
43    }
44 
45    public int getFilelen() {
46        return filelen;
47    }
48 
49    public void setFilelen(int filelen) {
50        this.filelen = filelen;
51    }
52 
53    public int getOffset() {
54        return offset;
55    }
56 
57    public void setOffset(int offset) {
58        this.offset = offset;
59    }
60 
61    public String getTemp() {
62        return temp;
63    }
64 
65    public void setTemp(String temp) {
66        this.temp = temp;
67    }
68 
69    public String getChecksum() {
70        return checksum;
71    }
72 
73    public void setChecksum(String checksum) {
74        this.checksum = checksum;
75    }
76 
77    public IoBuffer getData() {
78        return data;
79    }
80 
81    public void setData(IoBuffer data) {
82        this.data = data;
83    }
84 
85}

在mina中,要将解码器和编码器绑定到协议工厂类中,才能被过滤器使用:

01package com.a2.desktop.example5.mina.potocol;
02 
03import org.apache.mina.filter.codec.demux.DemuxingProtocolCodecFactory;
04import org.apache.mina.filter.codec.demux.MessageDecoder;
05import org.apache.mina.filter.codec.demux.MessageEncoder;
06 
07public class InfoCodecFactory extends DemuxingProtocolCodecFactory {
08    private MessageDecoder decoder;
09 
10    private MessageEncoder<absmessage> encoder;
11 
12    public InfoCodecFactory(MessageDecoder decoder,
13            MessageEncoder<absmessage> encoder) {
14        this.decoder = decoder;
15        this.encoder = encoder;
16        addMessageDecoder(this.decoder);
17        addMessageEncoder(AbsMessage.class, this.encoder);
18    }
19}</absmessage></absmessage>

做完了这些,编码解码的工作都完成了,最后就是写客户端和服务器端进行测试,注意文件位置,这里没有做提示:

01package com.a2.desktop.example5.mina.potocol;
02 
03import java.net.InetSocketAddress;
04import java.nio.charset.Charset;
05 
06import org.apache.mina.core.future.ConnectFuture;
07import org.apache.mina.core.service.IoConnector;
08import org.apache.mina.core.session.IoSession;
09import org.apache.mina.filter.codec.ProtocolCodecFilter;
10import org.apache.mina.transport.socket.nio.NioSocketConnector;
11 
12public class TestClient {
13     
14    private static String HOST = "127.0.0.1";
15 
16    private static int PORT = 8082;
17 
18    public static void main(String[] args) {
19        // 创建一个非阻塞的客户端程序
20        IoConnector connector = new NioSocketConnector();
21        // 设置链接超时时间
22        connector.setConnectTimeout(30000);
23        // 添加过滤器
24        connector.getFilterChain().addLast(
25                "codec",
26                new ProtocolCodecFilter(new InfoCodecFactory(
27                        new InfoDecoder(Charset.forName("utf-8")),
28                        new InfoEncoder(Charset.forName("utf-8")))));
29        // 添加业务逻辑处理器类
30        connector.setHandler(new ClientHandler());
31        IoSession session = null;
32        try {
33            ConnectFuture future = connector.connect(new InetSocketAddress(
34                    HOST, PORT));// 创建连接
35            future.awaitUninterruptibly();// 等待连接创建完成
36            session = future.getSession();// 获得session
37 
38            FilePiece piece = new FilePiece(
39                    "D:\\Develop Libs Tar\\apache-mina-2.0.7-bin.zip", 0);
40 
41            InfoRequest ir = new InfoRequest(piece);
42                 
43            session.write(ir);// 发送消息
44             
45             
46        } catch (Exception e) {
47e.printStackTrace();
48            System.out.println("客户端链接异常...");
49        }
50 
51        session.getCloseFuture().awaitUninterruptibly();// 等待连接断开
52        connector.dispose();
53    }
54 
55}

客户端的Handler没有做任何处理:

01package com.a2.desktop.example5.mina.potocol;
02 
03import org.apache.mina.core.service.IoHandler;
04import org.apache.mina.core.session.IdleStatus;
05import org.apache.mina.core.session.IoSession;
06 
07public class ClientHandler implements IoHandler {
08 
09    @Override
10    public void sessionCreated(IoSession session) throws Exception {
11        // TODO Auto-generated method stub
12 
13    }
14 
15    @Override
16    public void sessionOpened(IoSession session) throws Exception {
17        // TODO Auto-generated method stub
18 
19    }
20 
21    @Override
22    public void sessionClosed(IoSession session) throws Exception {
23        // TODO Auto-generated method stub
24 
25    }
26 
27    @Override
28    public void sessionIdle(IoSession session, IdleStatus status)
29            throws Exception {
30        // TODO Auto-generated method stub
31 
32    }
33 
34    @Override
35    public void exceptionCaught(IoSession session, Throwable cause)
36            throws Exception {
37        // TODO Auto-generated method stub
38 
39    }
40 
41    @Override
42    public void messageReceived(IoSession session, Object message)
43            throws Exception {
44        //System.out.println(message.toString());
45    }
46 
47    @Override
48    public void messageSent(IoSession session, Object message) throws Exception {
49        // TODO Auto-generated method stub
50 
51    }
52 
53}
服务器端:
01package com.a2.desktop.example5.mina.potocol;
02 
03import java.net.InetSocketAddress;
04import java.nio.charset.Charset;
05 
06import org.apache.mina.core.service.IoAcceptor;
07import org.apache.mina.core.session.IdleStatus;
08import org.apache.mina.core.session.IoSessionConfig;
09import org.apache.mina.filter.codec.ProtocolCodecFilter;
10import org.apache.mina.filter.logging.LogLevel;
11import org.apache.mina.filter.logging.LoggingFilter;
12import org.apache.mina.transport.socket.nio.NioSocketAcceptor;
13 
14 
15 
16public class TestServer{
17 
18    private static int PORT = 8082;
19 
20    public static void main(String[] args) {
21        IoAcceptor acceptor = null;
22        try {
23            // 创建一个非阻塞的server端的Socket
24            acceptor = new NioSocketAcceptor();
25 
26            // 设置过滤器(添加自带的编解码器)
27            acceptor.getFilterChain().addLast(
28                    "codec",
29                    new ProtocolCodecFilter(new InfoCodecFactory(
30                            new InfoDecoder(Charset.forName("utf-8")),
31                            new InfoEncoder(Charset.forName("utf-8")))));
32            // 设置日志过滤器
33            LoggingFilter lf = new LoggingFilter();
34            lf.setMessageReceivedLogLevel(LogLevel.DEBUG);
35            acceptor.getFilterChain().addLast("logger", lf);
36            // 获得IoSessionConfig对象
37            IoSessionConfig cfg = acceptor.getSessionConfig();
38            // 读写通道10秒内无操作进入空闲状态
39            cfg.setIdleTime(IdleStatus.BOTH_IDLE, 100);
40 
41            // 绑定逻辑处理器
42            acceptor.setHandler(new ServerHandler());
43            // 绑定端口
44            acceptor.bind(new InetSocketAddress(PORT));
45            System.out.println("成功开启服务器端...");
46             
47        } catch (Exception e) {
48             
49            e.printStackTrace();
50        }
51    }
52}        

服务器端的Handler:

01package com.a2.desktop.example5.mina.potocol;
02 
03import org.apache.mina.core.service.IoHandler;
04import org.apache.mina.core.session.IdleStatus;
05import org.apache.mina.core.session.IoSession;
06 
07public class ServerHandler implements IoHandler {
08 
09    @Override
10    public void sessionCreated(IoSession session) throws Exception {
11        // TODO Auto-generated method stub
12 
13    }
14 
15    @Override
16    public void sessionOpened(IoSession session) throws Exception {
17        // TODO Auto-generated method stub
18 
19    }
20 
21    @Override
22    public void sessionClosed(IoSession session) throws Exception {
23        // TODO Auto-generated method stub
24 
25    }
26 
27    @Override
28    public void sessionIdle(IoSession session, IdleStatus status)
29            throws Exception {
30        // TODO Auto-generated method stub
31 
32    }
33 
34    @Override
35    public void exceptionCaught(IoSession session, Throwable cause)
36            throws Exception {
37        // TODO Auto-generated method stub
38 
39    }
40 
41    @Override
42    public void messageReceived(IoSession session, Object message)
43            throws Exception {
44        if (message instanceof InfoReqContainer) {
45            InfoReqContainer irc = (InfoReqContainer) message;
46            System.out.println("服务器端 获取成功 Tag:" + irc.getTag() + "\r\n " + "head len:"
47                    + irc.getHeadlen() + "\r\nfilename: " + irc.getFilename()
48                    + "\r\nfile len:"+irc.getFilelen()+"\r\noffset:"+irc.getOffset()+"\r\nchecksum:"+irc.getChecksum()+"\r\ndata:"+irc.getData().toString());
49             
50            session.write("success rescive");
51             
52        } else {
53            System.out.println("获取失败");
54        }
55 
56    }
57 
58    @Override
59    public void messageSent(IoSession session, Object message) throws Exception {
60        // TODO Auto-generated method stub
61 
62    }
63 
64}

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值