转http://www.open-open.com/lib/view/open1351557171910.html
网络的传输使用需要遵循一定的规则,这些规则我们称为协议。如在互联网请求HTML页面的时候,我们要遵循HTTP协议,HTTP头的格式就是我们要遵守的规则:
03 | text/html,application/xhtml+xml,application/xml;q= 0.9 ,*/*;q= 0.8 |
05 | GBK,utf- 8 ;q= 0.7 ,*;q= 0.3 |
比如我们在做一些第三方开发的时候,经常会按照固定的格式向服务器端发送请求,服务器验证消息后,返回相应的结果。在Mina的开发中,我们也会用到这种模式。首先我们先来描述下这种方式的使用情形:
这样的方式,目前,我所在的项目组中主要用于通信和传输。为了将文件从客户端传到云端,我们先在客户端将数据根据一定的规则切片,然后通过一种特定的格式传输到服务器端。Mina,作为这中间的桥梁,秉承了框架很好的优点,快速开发。由于我们的服务器端采用的是C写的,所以我只给出客户端的编码过程,解码过程原理和开发都一样。
用Mina执行一定协议的传输,主要有以下几个步骤:
1. 设计通信协议;
2. 编写请求(返回)对象和业务对象;
3. 编写解码(编码)器;
4. 编写客户端、服务器端。
下面根据代码,一步步介绍这个过程,由于是前提的测试代码,所以没有考虑线程安全等问题,只是一个思路:
首先是通信的协议,根据mina的构建规则,将协议设计成抽象类,由请求对象和返回对象分别去继承,协议格式如下:
01 | package com.a2.desktop.example5.mina.potocol; |
04 | import org.apache.mina.core.buffer.IoBuffer; |
11 | public abstract class AbsMessage { |
16 | * tag | header length | Filename | File length | offset | checksum | temps | data |
20 | /** 请求或访问类型 请求Tag:0x00 返回Tag:0x01 共 8 bit */ |
21 | public abstract byte getTag(); |
23 | /** 头文件长度 共 2^16 可表示 65535 */ |
24 | public abstract short getHeaderlen(); |
26 | /** 根据UUID生成文件唯一标识,共 8*36=288 bit */ |
27 | public abstract byte [] getFilename(); |
29 | /** 获取文件长度 2^32=4GB 共 32 bit */ |
30 | public abstract int getFileLen(); |
32 | /** 获取文件的偏移量offset 共 32 bit */ |
33 | public abstract int getOffset(); |
35 | /** 获取文件的MD5校验码 共 32 bit */ |
36 | public abstract byte [] getChecksum(); |
38 | /** 预留字段 长度不超过 128 bit */ |
39 | public abstract byte [] getTmp(); |
41 | /**data 方式传输内容 不超过1024bit*/ |
42 | public abstract IoBuffer getData(); |
下面是请求对象(返回)和业务对象,这里的业务对象主要功能是将文件切片:
01 | package com.a2.desktop.example5.mina.potocol; |
03 | import java.io.IOException; |
04 | import java.nio.charset.Charset; |
06 | import org.apache.mina.core.buffer.IoBuffer; |
07 | import org.slf4j.Logger; |
08 | import org.slf4j.LoggerFactory; |
16 | public class InfoRequest extends AbsMessage { |
18 | Logger logger = LoggerFactory.getLogger(InfoRequest. class ); |
24 | public InfoRequest(FilePiece piece) { |
33 | public byte getTag() { |
38 | public short getHeaderlen() { |
39 | if (getTmp() == null ) { |
40 | short len = ( short ) ( 1 + 2 + 36 + 4 + 4 + 4 ); |
43 | short len = ( short ) ( 1 + 2 + 36 + 4 + 4 + 4 + ( short ) getTmp().length); |
49 | public int getFileLen() { |
52 | return ( int ) piece.getFc().size(); |
54 | } catch (IOException e) { |
61 | public int getOffset() { |
63 | return piece.getOffset(); |
68 | public byte [] getFilename() { |
70 | /** check the bits of name */ |
71 | byte [] name = new byte [ 36 ]; |
72 | name = piece.getFilename().getBytes(); |
79 | public byte [] getChecksum() { |
81 | byte [] checksum = new byte [ 4 ]; |
82 | checksum = piece.getChecksum().getBytes(); |
87 | public byte [] getTmp() { |
93 | public IoBuffer getData() { |
94 | return piece.getBuf(); |
业务对象代码,RandomAccessFile可用于随机读写,用于文件的切片,这里还用了管道,主要目的是为了加开读写速度:
01 | package com.a2.desktop.example5.mina.potocol; |
04 | import java.io.RandomAccessFile; |
05 | import java.nio.ByteBuffer; |
06 | import java.nio.channels.FileChannel; |
09 | import org.apache.mina.core.buffer.IoBuffer; |
10 | import org.slf4j.Logger; |
11 | import org.slf4j.LoggerFactory; |
19 | public class FilePiece { |
21 | Logger logger = LoggerFactory.getLogger(FilePiece. class ); |
23 | private ByteBuffer[] dsts; |
27 | private String filename; |
29 | private FileChannel fc; |
31 | private RandomAccessFile raf; |
35 | private String checksum; |
38 | public FilePiece(String path, int offset) throws Exception { |
40 | raf = new RandomAccessFile( new File(path), "rw" ); |
41 | fc = raf.getChannel(); |
45 | dsts = new ByteBuffer[ 1024 ]; |
47 | for ( int i = 0 ; i < dsts.length; i++) { |
48 | dsts[i] = ByteBuffer.allocate( 1024 ); |
51 | fc.read(dsts, offset, 1024 ); |
54 | buf=IoBuffer.allocate( 1024 ); |
56 | filename = UUID.randomUUID().toString(); |
57 | logger.info( "has built:" + filename + " filename size" |
61 | /**这个方法还有点儿问题,数据取的不对*/ |
62 | public IoBuffer getBuf(){ |
64 | while (dsts[ 0 ].hasRemaining()){ |
65 | buf.putChar(dsts[ 0 ].getChar()); |
71 | public String getFilename() { |
75 | public FileChannel getFc() { |
79 | public RandomAccessFile getRaf() { |
83 | public int getOffset() { |
87 | public String getChecksum() { |
再接下来是编码器
,编码器的作用就是讲数据装换成用于传输的流,在Mina中这种流就是IoBuffer:
01 | package com.a2.desktop.example5.mina.potocol; |
03 | import java.nio.charset.Charset; |
05 | import org.apache.mina.core.buffer.IoBuffer; |
06 | import org.apache.mina.core.session.IoSession; |
07 | import org.apache.mina.filter.codec.ProtocolEncoderOutput; |
08 | import org.apache.mina.filter.codec.demux.MessageEncoder; |
15 | public class InfoEncoder implements MessageEncoder<absmessage>{ |
17 | private Charset charset; |
19 | public InfoEncoder(Charset charset){ |
24 | public void encode(IoSession session, AbsMessage message, |
25 | ProtocolEncoderOutput out) throws Exception { |
27 | IoBuffer buf=IoBuffer.allocate( 1024 ).setAutoExpand( true ); |
29 | if (message instanceof InfoRequest){ |
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()); |
41 | } else if (message instanceof InfoResponse){ |
解码器与之类似,解码器在这里的作用主要用户服务器端解码:
01 | package com.a2.desktop.example5.mina.potocol; |
03 | import java.nio.charset.Charset; |
05 | import org.apache.mina.core.buffer.IoBuffer; |
06 | import org.apache.mina.core.session.IoSession; |
07 | import org.apache.mina.filter.codec.ProtocolDecoderOutput; |
08 | import org.apache.mina.filter.codec.demux.MessageDecoder; |
09 | import org.apache.mina.filter.codec.demux.MessageDecoderResult; |
15 | public class InfoDecoder implements MessageDecoder { |
17 | private Charset charset; |
19 | public InfoDecoder(Charset charset) { |
20 | this .charset = charset; |
24 | public MessageDecoderResult decodable(IoSession session, IoBuffer in) { |
28 | if (in.remaining() < 56 ) { |
29 | return MessageDecoderResult.NEED_DATA; |
33 | short head_len=in.getShort(); |
35 | if (tag == ( short ) 0x01 ) { |
36 | System.out.println( "请求标识符:" +tag+ " head length:" +head_len); |
39 | return MessageDecoderResult.NOT_OK; |
42 | return MessageDecoderResult.OK; |
46 | public MessageDecoderResult decode(IoSession session, IoBuffer in, |
47 | ProtocolDecoderOutput out) throws Exception { |
51 | InfoReqContainer irc= new InfoReqContainer(); |
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())); |
64 | return MessageDecoderResult.OK; |
68 | public void finishDecode(IoSession session, ProtocolDecoderOutput out) |
为了解码方便,我这里设计了一个辅助类InfoReqContainer,主要用户辅助操作:
01 | package com.a2.desktop.example5.mina.potocol; |
03 | import org.apache.mina.core.buffer.IoBuffer; |
11 | public class InfoReqContainer { |
13 | private short headlen; |
14 | private String filename; |
18 | private String checksum; |
19 | private IoBuffer data; |
21 | public byte getTag() { |
25 | public void setTag( byte tag) { |
29 | public short getHeadlen() { |
33 | public void setHeadlen( short headlen) { |
34 | this .headlen = headlen; |
37 | public String getFilename() { |
41 | public void setFilename(String filename) { |
42 | this .filename = filename; |
45 | public int getFilelen() { |
49 | public void setFilelen( int filelen) { |
50 | this .filelen = filelen; |
53 | public int getOffset() { |
57 | public void setOffset( int offset) { |
61 | public String getTemp() { |
65 | public void setTemp(String temp) { |
69 | public String getChecksum() { |
73 | public void setChecksum(String checksum) { |
74 | this .checksum = checksum; |
77 | public IoBuffer getData() { |
81 | public void setData(IoBuffer data) { |
在mina中,要将解码器和编码器绑定到协议工厂类中,才能被过滤器使用:
01 | package com.a2.desktop.example5.mina.potocol; |
03 | import org.apache.mina.filter.codec.demux.DemuxingProtocolCodecFactory; |
04 | import org.apache.mina.filter.codec.demux.MessageDecoder; |
05 | import org.apache.mina.filter.codec.demux.MessageEncoder; |
07 | public class InfoCodecFactory extends DemuxingProtocolCodecFactory { |
08 | private MessageDecoder decoder; |
10 | private MessageEncoder<absmessage> encoder; |
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); |
19 | }</absmessage></absmessage> |
做完了这些,编码解码的工作都完成了,最后就是写客户端和服务器端进行测试,注意文件位置,这里没有做提示:
01 | package com.a2.desktop.example5.mina.potocol; |
03 | import java.net.InetSocketAddress; |
04 | import java.nio.charset.Charset; |
06 | import org.apache.mina.core.future.ConnectFuture; |
07 | import org.apache.mina.core.service.IoConnector; |
08 | import org.apache.mina.core.session.IoSession; |
09 | import org.apache.mina.filter.codec.ProtocolCodecFilter; |
10 | import org.apache.mina.transport.socket.nio.NioSocketConnector; |
12 | public class TestClient { |
14 | private static String HOST = "127.0.0.1" ; |
16 | private static int PORT = 8082 ; |
18 | public static void main(String[] args) { |
20 | IoConnector connector = new NioSocketConnector(); |
22 | connector.setConnectTimeout( 30000 ); |
24 | connector.getFilterChain().addLast( |
26 | new ProtocolCodecFilter( new InfoCodecFactory( |
27 | new InfoDecoder(Charset.forName( "utf-8" )), |
28 | new InfoEncoder(Charset.forName( "utf-8" ))))); |
30 | connector.setHandler( new ClientHandler()); |
31 | IoSession session = null ; |
33 | ConnectFuture future = connector.connect( new InetSocketAddress( |
35 | future.awaitUninterruptibly(); |
36 | session = future.getSession(); |
38 | FilePiece piece = new FilePiece( |
39 | "D:\\Develop Libs Tar\\apache-mina-2.0.7-bin.zip" , 0 ); |
41 | InfoRequest ir = new InfoRequest(piece); |
46 | } catch (Exception e) { |
48 | System.out.println( "客户端链接异常..." ); |
51 | session.getCloseFuture().awaitUninterruptibly(); |
客户端的Handler没有做任何处理:
01 | package com.a2.desktop.example5.mina.potocol; |
03 | import org.apache.mina.core.service.IoHandler; |
04 | import org.apache.mina.core.session.IdleStatus; |
05 | import org.apache.mina.core.session.IoSession; |
07 | public class ClientHandler implements IoHandler { |
10 | public void sessionCreated(IoSession session) throws Exception { |
16 | public void sessionOpened(IoSession session) throws Exception { |
22 | public void sessionClosed(IoSession session) throws Exception { |
28 | public void sessionIdle(IoSession session, IdleStatus status) |
35 | public void exceptionCaught(IoSession session, Throwable cause) |
42 | public void messageReceived(IoSession session, Object message) |
48 | public void messageSent(IoSession session, Object message) throws Exception { |
服务器端:
01 | package com.a2.desktop.example5.mina.potocol; |
03 | import java.net.InetSocketAddress; |
04 | import java.nio.charset.Charset; |
06 | import org.apache.mina.core.service.IoAcceptor; |
07 | import org.apache.mina.core.session.IdleStatus; |
08 | import org.apache.mina.core.session.IoSessionConfig; |
09 | import org.apache.mina.filter.codec.ProtocolCodecFilter; |
10 | import org.apache.mina.filter.logging.LogLevel; |
11 | import org.apache.mina.filter.logging.LoggingFilter; |
12 | import org.apache.mina.transport.socket.nio.NioSocketAcceptor; |
16 | public class TestServer{ |
18 | private static int PORT = 8082 ; |
20 | public static void main(String[] args) { |
21 | IoAcceptor acceptor = null ; |
24 | acceptor = new NioSocketAcceptor(); |
27 | acceptor.getFilterChain().addLast( |
29 | new ProtocolCodecFilter( new InfoCodecFactory( |
30 | new InfoDecoder(Charset.forName( "utf-8" )), |
31 | new InfoEncoder(Charset.forName( "utf-8" ))))); |
33 | LoggingFilter lf = new LoggingFilter(); |
34 | lf.setMessageReceivedLogLevel(LogLevel.DEBUG); |
35 | acceptor.getFilterChain().addLast( "logger" , lf); |
37 | IoSessionConfig cfg = acceptor.getSessionConfig(); |
39 | cfg.setIdleTime(IdleStatus.BOTH_IDLE, 100 ); |
42 | acceptor.setHandler( new ServerHandler()); |
44 | acceptor.bind( new InetSocketAddress(PORT)); |
45 | System.out.println( "成功开启服务器端..." ); |
47 | } catch (Exception e) { |
服务器端的Handler:
01 | package com.a2.desktop.example5.mina.potocol; |
03 | import org.apache.mina.core.service.IoHandler; |
04 | import org.apache.mina.core.session.IdleStatus; |
05 | import org.apache.mina.core.session.IoSession; |
07 | public class ServerHandler implements IoHandler { |
10 | public void sessionCreated(IoSession session) throws Exception { |
16 | public void sessionOpened(IoSession session) throws Exception { |
22 | public void sessionClosed(IoSession session) throws Exception { |
28 | public void sessionIdle(IoSession session, IdleStatus status) |
35 | public void exceptionCaught(IoSession session, Throwable cause) |
42 | public void messageReceived(IoSession session, Object message) |
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()); |
50 | session.write( "success rescive" ); |
53 | System.out.println( "获取失败" ); |
59 | public void messageSent(IoSession session, Object message) throws Exception { |