Apache Mina使用手記(四) 收藏
上一篇中,我們介紹瞭如何在mina中編寫自己的日誌過濾器,這一篇我們自己實現一個編解器。
實際應用當,很多應用系統應用的都不是標準的web service或XML等,比如象中國移動/聯通/電信的短信網關程序,都有自己不同的協議實現,並且都是基於TCP/IP的字節流。Mina自帶的編解 碼器實現了TextLineEncoder和TextLineDecoder,可以進行按行的字符串處理,對於象短信網關程序,就要自己實現編解碼過濾器 了。
我們定義一個簡單的基於TCP/IP字節流的協議,實現在客戶端和服務端之間的數據包傳輸。數據包MyProtocalPack有消息頭和消息體組 成,消息頭包括:length(消息包的總長度,數據類型int),flag(消息包標誌位,數據類型byte),消息體content是一個字符串,實 際實現的時候按byte流處理。源代碼如下:
- package com.gftech.mytool.mina;
- import com.gftech.util.GFCommon;
- public class MyProtocalPack {
- private int length;
- private byte flag;
- private String content;
- public MyProtocalPack(){
- }
- public MyProtocalPack( byte flag,String content){
- this .flag=flag;
- this .content=content;
- int len1=content== null ? 0 :content.getBytes().length;
- this .length= 5 +len1;
- }
- public MyProtocalPack( byte [] bs){
- if (bs!= null && bs.length>= 5 ){
- length=GFCommon.bytes2int(GFCommon.bytesCopy(bs, 0 , 4 ));
- flag=bs[4 ];
- content=new String(GFCommon.bytesCopy(bs, 5 , length- 5 ));
- }
- }
- 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;
- }
- public String toString(){
- StringBuffer sb=new StringBuffer();
- sb.append(" Len:" ).append(length);
- sb.append(" flag:" ).append(flag);
- sb.append(" content:" ).append(content);
- return sb.toString();
- }
- }
回過頭來,我們先看一下在MinaTimeServer中,如何使用一個文本的編解碼過濾器,它是在過濾器鏈中添加了一個叫ProtocalCodecFilter的類,其中它調用 了一個工廠方法TextLineCodecFactory的工廠類,創建具休的TextLineEncoder和TextLineDecoder編碼和解 碼器。我們看一下具體的源代碼:
- acceptor.getFilterChain().addLast( "codec" , new ProtocolCodecFilter( new TextLineCodecFactory(Charset.forName( "GBK" ))));
- package org.apache.mina.filter.codec.textline;
- import java.nio.charset.Charset;
- import org.apache.mina.core.buffer.BufferDataException;
- 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;
- /**
- * A {@link ProtocolCodecFactory} that performs encoding and decoding between
- * a text line data and a Java string object. This codec is useful especially
- * when you work with a text-based protocols such as SMTP and IMAP.
- *
- * @author The Apache MINA Project ([email protected])
- * @version $Rev$, $Date$
- */
- public class TextLineCodecFactory implements ProtocolCodecFactory {
- private final TextLineEncoder encoder;
- private final TextLineDecoder decoder;
- /**
- * Creates a new instance with the current default {@link Charset}.
- */
- public TextLineCodecFactory() {
- this (Charset.defaultCharset());
- }
- /**
- * Creates a new instance with the specified {@link Charset}. The
- * encoder uses a UNIX {@link LineDelimiter} and the decoder uses
- * the AUTO {@link LineDelimiter}.
- *
- * @param charset
- * The charset to use in the encoding and decoding
- */
- public TextLineCodecFactory(Charset charset) {
- encoder = new TextLineEncoder(charset, LineDelimiter.UNIX);
- decoder = new TextLineDecoder(charset, LineDelimiter.AUTO);
- }
- /**
- * Creates a new instance of TextLineCodecFactory. This constructor
- * provides more flexibility for the developer.
- *
- * @param charset
- * The charset to use in the encoding and decoding
- * @param encodingDelimiter
- * The line delimeter for the encoder
- * @param decodingDelimiter
- * The line delimeter for the decoder
- */
- public TextLineCodecFactory(Charset charset,
- String encodingDelimiter, String decodingDelimiter) {
- encoder = new TextLineEncoder(charset, encodingDelimiter);
- decoder = new TextLineDecoder(charset, decodingDelimiter);
- }
- /**
- * Creates a new instance of TextLineCodecFactory. This constructor
- * provides more flexibility for the developer.
- *
- * @param charset
- * The charset to use in the encoding and decoding
- * @param encodingDelimiter
- * The line delimeter for the encoder
- * @param decodingDelimiter
- * The line delimeter for the decoder
- */
- public TextLineCodecFactory(Charset charset,
- LineDelimiter encodingDelimiter, LineDelimiter decodingDelimiter) {
- encoder = new TextLineEncoder(charset, encodingDelimiter);
- decoder = new TextLineDecoder(charset, decodingDelimiter);
- }
- public ProtocolEncoder getEncoder(IoSession session) {
- return encoder;
- }
- public ProtocolDecoder getDecoder(IoSession session) {
- return decoder;
- }
- /**
- * Returns the allowed maximum size of the encoded line.
- * If the size of the encoded line exceeds this value, the encoder
- * will throw a {@link IllegalArgumentException}. The default value
- * is {@link Integer#MAX_VALUE}.
- * <p>
- * This method does the same job with {@link TextLineEncoder#getMaxLineLength()}.
- */
- public int getEncoderMaxLineLength() {
- return encoder.getMaxLineLength();
- }
- /**
- * Sets the allowed maximum size of the encoded line.
- * If the size of the encoded line exceeds this value, the encoder
- * will throw a {@link IllegalArgumentException}. The default value
- * is {@link Integer#MAX_VALUE}.
- * <p>
- * This method does the same job with {@link TextLineEncoder#setMaxLineLength(int)}.
- */
- public void setEncoderMaxLineLength( int maxLineLength) {
- encoder.setMaxLineLength(maxLineLength);
- }
- /**
- * Returns the allowed maximum size of the line to be decoded.
- * If the size of the line to be decoded exceeds this value, the
- * decoder will throw a {@link BufferDataException}. The default
- * value is <tt>1024</tt> (1KB).
- * <p>
- * This method does the same job with {@link TextLineDecoder#getMaxLineLength()}.
- */
- public int getDecoderMaxLineLength() {
- return decoder.getMaxLineLength();
- }
- /**
- * Sets the allowed maximum size of the line to be decoded.
- * If the size of the line to be decoded exceeds this value, the
- * decoder will throw a {@link BufferDataException}. The default
- * value is <tt>1024</tt> (1KB).
- * <p>
- * This method does the same job with {@link TextLineDecoder#setMaxLineLength(int)}.
- */
- public void setDecoderMaxLineLength( int maxLineLength) {
- decoder.setMaxLineLength(maxLineLength);
- }
- }
TextLineFactory實現了ProtocalCodecFactory接口,該接口主要有一個編碼的方法getEncoder()和一個解碼的方法getDecoder():
- package org.apache.mina.filter.codec;
- import org.apache.mina.core.session.IoSession;
- /**
- * Provides {@link ProtocolEncoder} and {@link ProtocolDecoder} which translates
- * binary or protocol specific data into message object and vice versa.
- * <p>
- * Please refer to
- * <a href="../../../../../xref-examples/org/apache/mina/examples/reverser/ReverseProtocolProvider.html" mce_href="xref-examples/org/apache/mina/examples/reverser/ReverseProtocolProvider.html"><code>ReverserProtocolProvider</code></a>
- * example.
- *
- * @author The Apache MINA Project ([email protected])
- * @version $Rev$, $Date$
- */
- public interface ProtocolCodecFactory {
- /**
- * Returns a new (or reusable) instance of {@link ProtocolEncoder} which
- * encodes message objects into binary or protocol-specific data.
- */
- ProtocolEncoder getEncoder(IoSession session) throws Exception;
- /**
- * Returns a new (or reusable) instance of {@link ProtocolDecoder} which
- * decodes binary or protocol-specific data into message objects.
- */
- ProtocolDecoder getDecoder(IoSession session) throws Exception;
- }
我們主要是仿照TextLineEncoder實現其中的encode()方法,仿照TextLineDecoder實現其中的decode()即 可,它們分別實現了ProtocalEncoder和ProtocalDecoder接口。我們要編寫三個類分別 是:MyProtocalCodecFactory,MyProtocalEncoder,MyProtocalDecoder對應 TextLineCodecFactory,TextLineEncoder,TextLineDecoder。
- package com.gftech.mytool.mina;
- 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;
- public class MyProtocalCodecFactory implements ProtocolCodecFactory {
- private final MyProtocalEncoder encoder;
- private final MyProtocalDecoder decoder;
- public MyProtocalCodecFactory(Charset charset) {
- encoder=new MyProtocalEncoder(charset);
- decoder=new MyProtocalDecoder(charset);
- }
- public ProtocolEncoder getEncoder(IoSession session) {
- return encoder;
- }
- public ProtocolDecoder getDecoder(IoSession session) {
- return decoder;
- }
- }
- package com.gftech.mytool.mina;
- 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;
- public class MyProtocalEncoder extends ProtocolEncoderAdapter {
- private final Charset charset;
- public MyProtocalEncoder(Charset charset) {
- this .charset = charset;
- }
- //在此處實現對MyProtocalPack包的編碼工作,並把它寫入輸出流中
- public void encode(IoSession session, Object message, ProtocolEncoderOutput out) throws Exception {
- MyProtocalPack value = (MyProtocalPack) 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);
- }
- public void dispose() throws Exception {
- }
- }
- package com.gftech.mytool.mina;
- 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;
- public class MyProtocalDecoder implements ProtocolDecoder {
- private final AttributeKey CONTEXT = new AttributeKey(getClass(), "context" );
- private final Charset charset;
- private int maxPackLength = 100 ;
- public MyProtocalDecoder() {
- this (Charset.defaultCharset());
- }
- public MyProtocalDecoder(Charset charset) {
- this .charset = charset;
- }
- public int getMaxLineLength() {
- return maxPackLength;
- }
- public void setMaxLineLength( int maxLineLength) {
- if (maxLineLength <= 0 ) {
- throw new IllegalArgumentException( "maxLineLength: " + maxLineLength);
- }
- this .maxPackLength = maxLineLength;
- }
- private Context getContext(IoSession session) {
- Context ctx;
- 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 = getContext(session);
- // 先把當前buffer中的數據追加到Context的buffer當中
- ctx.append(in);
- //把position指向0位置,把limit指向原來的position位置
- IoBuffer buf = ctx.getBuffer();
- buf.flip();
- // 然後按數據包的協議進行讀取
- while (buf.remaining() >= packHeadLength) {
- buf.mark();
- // 讀取消息頭部分
- int length = buf.getInt();
- byte flag = buf.get();
- //檢查讀取的包頭是否正常,不正常的話清空buffer
- if (length< 0 ||length > maxPackLength) {
- buf.clear();
- break ;
- }
- //讀取正常的消息包,並寫入輸出流中,以便IoHandler進行處理
- else if (length >= packHeadLength && length - packHeadLength <= buf.remaining()) {
- int oldLimit2 = buf.limit();
- buf.limit(buf.position() + length - packHeadLength);
- String content = buf.getString(ctx.getDecoder());
- buf.limit(oldLimit2);
- MyProtocalPack pack = new MyProtocalPack(flag, content);
- out.write(pack);
- } else {
- // 如果消息包不完整
- // 將指針重新移動消息頭的起始位置
- buf.reset();
- break ;
- }
- }
- if (buf.hasRemaining()) {
- // 將數據移到buffer的最前面
- IoBuffer temp = IoBuffer.allocate(maxPackLength).setAutoExpand(true );
- temp.put(buf);
- temp.flip();
- buf.clear();
- buf.put(temp);
- } else { // 如果數據已經處理完畢,進行清空
- buf.clear();
- }
- }
- 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 int matchCount = 0 ;
- private int overflowPosition = 0 ;
- private Context() {
- decoder = charset.newDecoder();
- buf = IoBuffer.allocate(80 ).setAutoExpand( true );
- }
- public CharsetDecoder getDecoder() {
- return decoder;
- }
- public IoBuffer getBuffer() {
- return buf;
- }
- public int getOverflowPosition() {
- return overflowPosition;
- }
- public int getMatchCount() {
- return matchCount;
- }
- public void setMatchCount( int matchCount) {
- this .matchCount = matchCount;
- }
- public void reset() {
- overflowPosition = 0 ;
- matchCount = 0 ;
- decoder.reset();
- }
- public void append(IoBuffer in) {
- getBuffer().put(in);
- }
- }
- }
在MyProtocalServer中,添加自己實現的Log4jFilter和編解碼過濾器:
- package com.gftech.mytool.mina;
- import java.io.IOException;
- import java.net.InetSocketAddress;
- import java.nio.charset.Charset;
- import org.apache.log4j.Logger;
- import org.apache.log4j.PropertyConfigurator;
- 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;
- public class MyProtocalServer {
- private static final int PORT = 2500 ;
- static Logger logger = Logger.getLogger(MyProtocalServer. class );
- public static void main(String[] args) throws IOException {
- PropertyConfigurator.configure("conf//log4j.properties" );
- IoAcceptor acceptor = new NioSocketAcceptor();
- Log4jFilter lf = new Log4jFilter(logger);
- acceptor.getFilterChain().addLast("logger" , lf);
- acceptor.getFilterChain().addLast("codec" , new ProtocolCodecFilter( new MyProtocalCodecFactory(Charset.forName( "GBK" ))));
- acceptor.getSessionConfig().setReadBufferSize(1024 );
- acceptor.getSessionConfig().setIdleTime(IdleStatus.BOTH_IDLE, 10 );
- acceptor.setHandler(new MyHandler());
- acceptor.bind(new InetSocketAddress(PORT));
- System.out.println("start server ..." );
- }
- }
- class MyHandler extends IoHandlerAdapter {
- static Logger logger = Logger.getLogger(MyHandler. class );
- @Override
- public void exceptionCaught(IoSession session, Throwable cause) throws Exception {
- cause.printStackTrace();
- }
- @Override
- public void messageReceived(IoSession session, Object message) throws Exception {
- MyProtocalPack pack=(MyProtocalPack)message;
- logger.debug("Rec:" + pack);
- }
- @Override
- public void sessionIdle(IoSession session, IdleStatus status) throws Exception {
- logger.debug("IDLE " + session.getIdleCount(status));
- }
- }
編寫一個客戶端程序進行測試:
- package com.gftech.mytool.mina;
- import java.io.DataOutputStream;
- import java.net.Socket;
- public class MyProtocalClient {
- public static void main(String[] args) {
- try {
- Socket socket = new Socket( "127.0.0.1" , 2500 );
- DataOutputStream out = new DataOutputStream( socket.getOutputStream() ) ;
- for ( int i = 0 ; i < 1000 ; i++) {
- MyProtocalPack pack=new MyProtocalPack(( byte )i,i+ "測試MyProtocalaaaaaaaaaaaaaa" );
- out.writeInt(pack.getLength());
- out.write(pack.getFlag());
- out.write(pack.getContent().getBytes());
- out.flush();
- System.out.println(i + " sended" );
- }
- Thread.sleep(1000 );
- } catch (Exception e) {
- e.printStackTrace();
- }
- }
- }
也可以用IoConnector實現自己的客戶端: