Mina实现自定义协议的通信

来源:互联网 发布:罗马军团到中国 知乎 编辑:程序博客网 时间:2024/05/16 00:57

转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 abstractclass 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    publicabstract byte getTag();
22 
23    /** 头文件长度 共 2^16 可表示 65535 */
24    publicabstract short getHeaderlen();
25 
26    /** 根据UUID生成文件唯一标识,共 8*36=288 bit */
27    publicabstract byte[] getFilename();//需要設計一個算法
28 
29    /** 获取文件长度 2^32=4GB 共 32 bit */
30    publicabstract int getFileLen();
31 
32    /** 获取文件的偏移量offset 共 32 bit */
33    publicabstract int getOffset();
34 
35    /** 获取文件的MD5校验码 共 32 bit */
36    publicabstract byte[] getChecksum();
37 
38    /** 预留字段 长度不超过 128 bit */
39    publicabstract byte[] getTmp();
40     
41    /**data 方式传输内容 不超过1024bit*/
42    publicabstract 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 classInfoRequest extends AbsMessage {
17 
18    Logger logger = LoggerFactory.getLogger(InfoRequest.class);
19 
20    FilePiece piece;
21 
22    Charset charset;
23 
24    publicInfoRequest(FilePiece piece) {
25        this.piece = piece;
26    }
27     
28    publicInfoRequest(){
29        //empty
30    }
31 
32    @Override
33    publicbyte getTag() {// 0x01 请求包
34        return(byte)0x01;
35    }
36 
37    @Override
38    publicshort getHeaderlen() {
39        if(getTmp() == null) {
40            shortlen = (short) (1+ 2 + 36 + 4+ 4 + 4 );
41            returnlen;
42        } else {
43            shortlen = (short) (1+ 2 + 36 + 4+ 4 + 4 + (short) getTmp().length);
44            returnlen;
45        }
46    }
47 
48    @Override
49    publicint getFileLen() {// 文件总长度
50 
51        try{
52            return(int) piece.getFc().size();
53 
54        } catch (IOException e) {
55            e.printStackTrace();
56        }
57        return0;
58    }
59 
60    @Override
61    publicint getOffset() {// 传输 偏移量
62 
63        returnpiece.getOffset();
64 
65    }
66 
67    @Override
68    publicbyte[] getFilename() {// 文件名称
69 
70        /** check the bits of name */
71        byte[] name =new byte[36];
72        name = piece.getFilename().getBytes();
73 
74        returnname;
75 
76    }
77 
78    @Override
79    publicbyte[] getChecksum() {// checksum
80 
81        byte[] checksum =new byte[4];
82        checksum = piece.getChecksum().getBytes(); 
83        returnchecksum;
84    }
85 
86    @Override
87    publicbyte[] getTmp() {
88        byte[] b=newbyte[5];
89        returnb;
90    }
91 
92    @Override
93    publicIoBuffer getData() {
94        returnpiece.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 classFilePiece {
20 
21    Logger logger = LoggerFactory.getLogger(FilePiece.class);
22 
23    privateByteBuffer[] dsts;
24     
25    privateIoBuffer buf;
26 
27    privateString filename;
28 
29    privateFileChannel fc;
30 
31    privateRandomAccessFile raf;
32 
33    privateint offset;
34 
35    privateString checksum;
36 
37    /** 构建文件的基本信息 */
38    publicFilePiece(String path, intoffset) throws Exception {
39 
40        raf =new RandomAccessFile(newFile(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    publicIoBuffer getBuf(){
63        dsts[0].flip();
64        while(dsts[0].hasRemaining()){
65            buf.putChar(dsts[0].getChar());
66        }
67        buf.flip();
68        returnbuf;
69    }
70 
71    publicString getFilename() {
72        returnfilename;
73    }
74 
75    publicFileChannel getFc() {
76        returnfc;
77    }
78 
79    publicRandomAccessFile getRaf() {
80        returnraf;
81    }
82 
83    publicint getOffset() {
84        returnoffset;
85    }
86 
87    publicString 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 classInfoEncoder implements MessageEncoder<absmessage>{
16 
17    privateCharset charset;
18     
19    publicInfoEncoder(Charset charset){
20        this.charset=charset;
21    }
22     
23    @Override
24    publicvoid encode(IoSession session, AbsMessage message,
25            ProtocolEncoderOutput out)throws Exception {
26         
27        IoBuffer buf=IoBuffer.allocate(1024).setAutoExpand(true);
28         
29        if(messageinstanceof 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        }elseif(message instanceofInfoResponse){
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 classInfoDecoder implements MessageDecoder {
16 
17    privateCharset charset;
18 
19    publicInfoDecoder(Charset charset) {
20        this.charset = charset;
21    }
22 
23    @Override
24    publicMessageDecoderResult decodable(IoSession session, IoBuffer in) {
25 
26        //System.out.println("package size:"+in.remaining());
27        // 报头长度<56
28        if(in.remaining() < 56) {
29            returnMessageDecoderResult.NEED_DATA;
30        }
31 
32        bytetag = in.get();
33        shorthead_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            returnMessageDecoderResult.NOT_OK;
40        }
41 
42        returnMessageDecoderResult.OK;
43    }
44 
45    @Override
46    publicMessageDecoderResult decode(IoSession session, IoBuffer in,
47            ProtocolDecoderOutput out)throws Exception {
48        bytetag=in.get();
49     
50        if(tag==0x01){
51            InfoReqContainer irc=newInfoReqContainer();
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        returnMessageDecoderResult.OK;
65    }
66 
67    @Override
68    publicvoid finishDecode(IoSession session, ProtocolDecoderOutput out)
69            throwsException {
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 classInfoReqContainer {
12    privatebyte tag;
13    privateshort headlen;
14    privateString filename;
15    privateint filelen;
16    privateint offset;
17    privateString temp;
18    privateString checksum;
19    privateIoBuffer data;
20 
21    publicbyte getTag() {
22        returntag;
23    }
24 
25    publicvoid setTag(bytetag) {
26        this.tag = tag;
27    }
28 
29    publicshort getHeadlen() {
30        returnheadlen;
31    }
32 
33    publicvoid setHeadlen(shortheadlen) {
34        this.headlen = headlen;
35    }
36 
37    publicString getFilename() {
38        returnfilename;
39    }
40 
41    publicvoid setFilename(String filename) {
42        this.filename = filename;
43    }
44 
45    publicint getFilelen() {
46        returnfilelen;
47    }
48 
49    publicvoid setFilelen(intfilelen) {
50        this.filelen = filelen;
51    }
52 
53    publicint getOffset() {
54        returnoffset;
55    }
56 
57    publicvoid setOffset(intoffset) {
58        this.offset = offset;
59    }
60 
61    publicString getTemp() {
62        returntemp;
63    }
64 
65    publicvoid setTemp(String temp) {
66        this.temp = temp;
67    }
68 
69    publicString getChecksum() {
70        returnchecksum;
71    }
72 
73    publicvoid setChecksum(String checksum) {
74        this.checksum = checksum;
75    }
76 
77    publicIoBuffer getData() {
78        returndata;
79    }
80 
81    publicvoid 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 classInfoCodecFactory extends DemuxingProtocolCodecFactory {
08    privateMessageDecoder decoder;
09 
10    privateMessageEncoder<absmessage> encoder;
11 
12    publicInfoCodecFactory(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 classTestClient {
13     
14    privatestatic String HOST = "127.0.0.1";
15 
16    privatestatic int PORT = 8082;
17 
18    publicstatic void main(String[] args) {
19        // 创建一个非阻塞的客户端程序
20        IoConnector connector =new NioSocketConnector();
21        // 设置链接超时时间
22        connector.setConnectTimeout(30000);
23        // 添加过滤器
24        connector.getFilterChain().addLast(
25                "codec",
26                newProtocolCodecFilter(new InfoCodecFactory(
27                        newInfoDecoder(Charset.forName("utf-8")),
28                        newInfoEncoder(Charset.forName("utf-8")))));
29        // 添加业务逻辑处理器类
30        connector.setHandler(newClientHandler());
31        IoSession session =null;
32        try{
33            ConnectFuture future = connector.connect(newInetSocketAddress(
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 classClientHandler implements IoHandler {
08 
09    @Override
10    publicvoid sessionCreated(IoSession session)throws Exception {
11        // TODO Auto-generated method stub
12 
13    }
14 
15    @Override
16    publicvoid sessionOpened(IoSession session)throws Exception {
17        // TODO Auto-generated method stub
18 
19    }
20 
21    @Override
22    publicvoid sessionClosed(IoSession session)throws Exception {
23        // TODO Auto-generated method stub
24 
25    }
26 
27    @Override
28    publicvoid sessionIdle(IoSession session, IdleStatus status)
29            throwsException {
30        // TODO Auto-generated method stub
31 
32    }
33 
34    @Override
35    publicvoid exceptionCaught(IoSession session, Throwable cause)
36            throwsException {
37        // TODO Auto-generated method stub
38 
39    }
40 
41    @Override
42    publicvoid messageReceived(IoSession session, Object message)
43            throwsException {
44        //System.out.println(message.toString());
45    }
46 
47    @Override
48    publicvoid 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 classTestServer{
17 
18    privatestatic int PORT = 8082;
19 
20    publicstatic 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                    newProtocolCodecFilter(new InfoCodecFactory(
30                            newInfoDecoder(Charset.forName("utf-8")),
31                            newInfoEncoder(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(newServerHandler());
43            // 绑定端口
44            acceptor.bind(newInetSocketAddress(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 classServerHandler implements IoHandler {
08 
09    @Override
10    publicvoid sessionCreated(IoSession session)throws Exception {
11        // TODO Auto-generated method stub
12 
13    }
14 
15    @Override
16    publicvoid sessionOpened(IoSession session)throws Exception {
17        // TODO Auto-generated method stub
18 
19    }
20 
21    @Override
22    publicvoid sessionClosed(IoSession session)throws Exception {
23        // TODO Auto-generated method stub
24 
25    }
26 
27    @Override
28    publicvoid sessionIdle(IoSession session, IdleStatus status)
29            throwsException {
30        // TODO Auto-generated method stub
31 
32    }
33 
34    @Override
35    publicvoid exceptionCaught(IoSession session, Throwable cause)
36            throwsException {
37        // TODO Auto-generated method stub
38 
39    }
40 
41    @Override
42    publicvoid messageReceived(IoSession session, Object message)
43            throwsException {
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    publicvoid messageSent(IoSession session, Object message)throws Exception {
60        // TODO Auto-generated method stub
61 
62    }
63 
64}
原创粉丝点击