mina 使用手記 4

原創   Apache Mina使用手記(四) 收藏

上一篇中,我們介紹瞭如何在mina中編寫自己的日誌過濾器,這一篇我們自己實現一個編解器。

實際應用當,很多應用系統應用的都不是標準的web service或XML等,比如象中國移動/聯通/電信的短信網關程序,都有自己不同的協議實現,並且都是基於TCP/IP的字節流。Mina自帶的編解 碼器實現了TextLineEncoder和TextLineDecoder,可以進行按行的字符串處理,對於象短信網關程序,就要自己實現編解碼過濾器 了。

我們定義一個簡單的基於TCP/IP字節流的協議,實現在客戶端和服務端之間的數據包傳輸。數據包MyProtocalPack有消息頭和消息體組 成,消息頭包括:length(消息包的總長度,數據類型int),flag(消息包標誌位,數據類型byte),消息體content是一個字符串,實 際實現的時候按byte流處理。源代碼如下:

  1. package  com.gftech.mytool.mina;  
  2. import  com.gftech.util.GFCommon;  
  3. public   class  MyProtocalPack {  
  4.     private   int  length;  
  5.     private   byte  flag;  
  6.     private  String content;  
  7.       
  8.     public  MyProtocalPack(){  
  9.           
  10.     }  
  11.       
  12.     public  MyProtocalPack( byte  flag,String content){  
  13.         this .flag=flag;  
  14.         this .content=content;  
  15.         int  len1=content== null ? 0 :content.getBytes().length;  
  16.         this .length= 5 +len1;  
  17.     }  
  18.       
  19.     public  MyProtocalPack( byte [] bs){  
  20.         if (bs!= null  && bs.length>= 5 ){  
  21.             length=GFCommon.bytes2int(GFCommon.bytesCopy(bs, 0 4 ));  
  22.             flag=bs[4 ];  
  23.             content=new  String(GFCommon.bytesCopy(bs,  5 , length- 5 ));  
  24.         }  
  25.     }  
  26.       
  27.     public   int  getLength() {  
  28.         return  length;  
  29.     }  
  30.     public   void  setLength( int  length) {  
  31.         this .length = length;  
  32.     }  
  33.     public   byte  getFlag() {  
  34.         return  flag;  
  35.     }  
  36.     public   void  setFlag( byte  flag) {  
  37.         this .flag = flag;  
  38.     }  
  39.     public  String getContent() {  
  40.         return  content;  
  41.     }  
  42.     public   void  setContent(String content) {  
  43.         this .content = content;  
  44.     }  
  45.       
  46.     public  String toString(){  
  47.         StringBuffer sb=new  StringBuffer();  
  48.         sb.append(" Len:" ).append(length);  
  49.         sb.append(" flag:" ).append(flag);  
  50.         sb.append(" content:" ).append(content);  
  51.         return  sb.toString();  
  52.     }  
  53. }  

回過頭來,我們先看一下在MinaTimeServer中,如何使用一個文本的編解碼過濾器,它是在過濾器鏈中添加了一個叫ProtocalCodecFilter的類,其中它調用 了一個工廠方法TextLineCodecFactory的工廠類,創建具休的TextLineEncoder和TextLineDecoder編碼和解 碼器。我們看一下具體的源代碼:

  1. acceptor.getFilterChain().addLast( "codec" new  ProtocolCodecFilter( new  TextLineCodecFactory(Charset.forName( "GBK" ))));  

  1. package  org.apache.mina.filter.codec.textline;  
  2. import  java.nio.charset.Charset;  
  3. import  org.apache.mina.core.buffer.BufferDataException;  
  4. import  org.apache.mina.core.session.IoSession;  
  5. import  org.apache.mina.filter.codec.ProtocolCodecFactory;  
  6. import  org.apache.mina.filter.codec.ProtocolDecoder;  
  7. import  org.apache.mina.filter.codec.ProtocolEncoder;  
  8. /**  
  9.  * A {@link ProtocolCodecFactory} that performs encoding and decoding between  
  10.  * a text line data and a Java string object.  This codec is useful especially  
  11.  * when you work with a text-based protocols such as SMTP and IMAP.  
  12.  *  
  13.  * @author The Apache MINA Project ([email protected])  
  14.  * @version $Rev$, $Date$  
  15.  */   
  16. public   class  TextLineCodecFactory  implements  ProtocolCodecFactory {  
  17.     private   final  TextLineEncoder encoder;  
  18.     private   final  TextLineDecoder decoder;  
  19.     /**  
  20.      * Creates a new instance with the current default {@link Charset}.  
  21.      */   
  22.     public  TextLineCodecFactory() {  
  23.         this (Charset.defaultCharset());  
  24.     }  
  25.     /**  
  26.      * Creates a new instance with the specified {@link Charset}.  The  
  27.      * encoder uses a UNIX {@link LineDelimiter} and the decoder uses  
  28.      * the AUTO {@link LineDelimiter}.  
  29.      *  
  30.      * @param charset  
  31.      *  The charset to use in the encoding and decoding  
  32.      */   
  33.     public  TextLineCodecFactory(Charset charset) {  
  34.         encoder = new  TextLineEncoder(charset, LineDelimiter.UNIX);  
  35.         decoder = new  TextLineDecoder(charset, LineDelimiter.AUTO);  
  36.     }  
  37.     /**  
  38.      * Creates a new instance of TextLineCodecFactory.  This constructor  
  39.      * provides more flexibility for the developer.  
  40.      *  
  41.      * @param charset  
  42.      *  The charset to use in the encoding and decoding  
  43.      * @param encodingDelimiter  
  44.      *  The line delimeter for the encoder  
  45.      * @param decodingDelimiter  
  46.      *  The line delimeter for the decoder  
  47.      */   
  48.     public  TextLineCodecFactory(Charset charset,  
  49.             String encodingDelimiter, String decodingDelimiter) {  
  50.         encoder = new  TextLineEncoder(charset, encodingDelimiter);  
  51.         decoder = new  TextLineDecoder(charset, decodingDelimiter);  
  52.     }  
  53.     /**  
  54.      * Creates a new instance of TextLineCodecFactory.  This constructor  
  55.      * provides more flexibility for the developer.  
  56.      *  
  57.      * @param charset  
  58.      *  The charset to use in the encoding and decoding  
  59.      * @param encodingDelimiter  
  60.      *  The line delimeter for the encoder  
  61.      * @param decodingDelimiter  
  62.      *  The line delimeter for the decoder  
  63.      */   
  64.     public  TextLineCodecFactory(Charset charset,  
  65.             LineDelimiter encodingDelimiter, LineDelimiter decodingDelimiter) {  
  66.         encoder = new  TextLineEncoder(charset, encodingDelimiter);  
  67.         decoder = new  TextLineDecoder(charset, decodingDelimiter);  
  68.     }  
  69.     public  ProtocolEncoder getEncoder(IoSession session) {  
  70.         return  encoder;  
  71.     }  
  72.     public  ProtocolDecoder getDecoder(IoSession session) {  
  73.         return  decoder;  
  74.     }  
  75.        /**  
  76.      * Returns the allowed maximum size of the encoded line.  
  77.      * If the size of the encoded line exceeds this value, the encoder  
  78.      * will throw a {@link IllegalArgumentException}.  The default value  
  79.      * is {@link Integer#MAX_VALUE}.  
  80.      * <p>  
  81.      * This method does the same job with {@link TextLineEncoder#getMaxLineLength()}.  
  82.      */   
  83.     public   int  getEncoderMaxLineLength() {  
  84.         return  encoder.getMaxLineLength();  
  85.     }  
  86.     /**  
  87.      * Sets the allowed maximum size of the encoded line.  
  88.      * If the size of the encoded line exceeds this value, the encoder  
  89.      * will throw a {@link IllegalArgumentException}.  The default value  
  90.      * is {@link Integer#MAX_VALUE}.  
  91.      * <p>  
  92.      * This method does the same job with {@link TextLineEncoder#setMaxLineLength(int)}.  
  93.      */   
  94.     public   void  setEncoderMaxLineLength( int  maxLineLength) {  
  95.         encoder.setMaxLineLength(maxLineLength);  
  96.     }  
  97.     /**  
  98.      * Returns the allowed maximum size of the line to be decoded.  
  99.      * If the size of the line to be decoded exceeds this value, the  
  100.      * decoder will throw a {@link BufferDataException}.  The default  
  101.      * value is <tt>1024</tt> (1KB).  
  102.      * <p>  
  103.      * This method does the same job with {@link TextLineDecoder#getMaxLineLength()}.  
  104.      */   
  105.     public   int  getDecoderMaxLineLength() {  
  106.         return  decoder.getMaxLineLength();  
  107.     }  
  108.     /**  
  109.      * Sets the allowed maximum size of the line to be decoded.  
  110.      * If the size of the line to be decoded exceeds this value, the  
  111.      * decoder will throw a {@link BufferDataException}.  The default  
  112.      * value is <tt>1024</tt> (1KB).  
  113.      * <p>  
  114.      * This method does the same job with {@link TextLineDecoder#setMaxLineLength(int)}.  
  115.      */   
  116.     public   void  setDecoderMaxLineLength( int  maxLineLength) {  
  117.         decoder.setMaxLineLength(maxLineLength);  
  118.     }  
  119. }  

TextLineFactory實現了ProtocalCodecFactory接口,該接口主要有一個編碼的方法getEncoder()和一個解碼的方法getDecoder():

  1. package  org.apache.mina.filter.codec;  
  2. import  org.apache.mina.core.session.IoSession;  
  3. /**  
  4.  * Provides {@link ProtocolEncoder} and {@link ProtocolDecoder} which translates  
  5.  * binary or protocol specific data into message object and vice versa.  
  6.  * <p>  
  7.  * Please refer to  
  8.  * <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>  
  9.  * example.  
  10.  *  
  11.  * @author The Apache MINA Project ([email protected])  
  12.  * @version $Rev$, $Date$  
  13.  */   
  14. public   interface  ProtocolCodecFactory {  
  15.     /**  
  16.      * Returns a new (or reusable) instance of {@link ProtocolEncoder} which  
  17.      * encodes message objects into binary or protocol-specific data.  
  18.      */   
  19.     ProtocolEncoder getEncoder(IoSession session) throws  Exception;  
  20.     /**  
  21.      * Returns a new (or reusable) instance of {@link ProtocolDecoder} which  
  22.      * decodes binary or protocol-specific data into message objects.  
  23.      */   
  24.     ProtocolDecoder getDecoder(IoSession session) throws  Exception;  
  25. }  

我們主要是仿照TextLineEncoder實現其中的encode()方法,仿照TextLineDecoder實現其中的decode()即 可,它們分別實現了ProtocalEncoder和ProtocalDecoder接口。我們要編寫三個類分別 是:MyProtocalCodecFactory,MyProtocalEncoder,MyProtocalDecoder對應 TextLineCodecFactory,TextLineEncoder,TextLineDecoder。

  1. package  com.gftech.mytool.mina;  
  2. import  java.nio.charset.Charset;  
  3. import  org.apache.mina.core.session.IoSession;  
  4. import  org.apache.mina.filter.codec.ProtocolCodecFactory;  
  5. import  org.apache.mina.filter.codec.ProtocolDecoder;  
  6. import  org.apache.mina.filter.codec.ProtocolEncoder;  
  7. public   class  MyProtocalCodecFactory    implements  ProtocolCodecFactory {  
  8.         private   final  MyProtocalEncoder encoder;  
  9.         private   final  MyProtocalDecoder decoder;  
  10.           
  11.         public  MyProtocalCodecFactory(Charset charset) {  
  12.             encoder=new  MyProtocalEncoder(charset);  
  13.             decoder=new  MyProtocalDecoder(charset);  
  14.         }  
  15.            
  16.         public  ProtocolEncoder getEncoder(IoSession session) {  
  17.             return  encoder;  
  18.         }  
  19.         public  ProtocolDecoder getDecoder(IoSession session) {  
  20.             return  decoder;  
  21.         }  
  22.           
  23. }  

  1. package  com.gftech.mytool.mina;  
  2. import  java.nio.charset.Charset;  
  3. import  org.apache.mina.core.buffer.IoBuffer;  
  4. import  org.apache.mina.core.session.IoSession;  
  5. import  org.apache.mina.filter.codec.ProtocolEncoderAdapter;  
  6. import  org.apache.mina.filter.codec.ProtocolEncoderOutput;  
  7. public   class  MyProtocalEncoder  extends  ProtocolEncoderAdapter {  
  8.     private   final  Charset charset;  
  9.     public  MyProtocalEncoder(Charset charset) {  
  10.         this .charset = charset;  
  11.     }  
  12.     //在此處實現對MyProtocalPack包的編碼工作,並把它寫入輸出流中   
  13.     public   void  encode(IoSession session, Object message, ProtocolEncoderOutput out)  throws  Exception {  
  14.         MyProtocalPack value = (MyProtocalPack) message;  
  15.         IoBuffer buf = IoBuffer.allocate(value.getLength());  
  16.         buf.setAutoExpand(true );  
  17.         buf.putInt(value.getLength());  
  18.         buf.put(value.getFlag());  
  19.         if  (value.getContent() !=  null )  
  20.             buf.put(value.getContent().getBytes());  
  21.         buf.flip();  
  22.         out.write(buf);  
  23.     }  
  24.     public   void  dispose()  throws  Exception {  
  25.     }  
  26. }  

  1. package  com.gftech.mytool.mina;  
  2. import  java.nio.charset.Charset;  
  3. import  java.nio.charset.CharsetDecoder;  
  4. import  org.apache.mina.core.buffer.IoBuffer;  
  5. import  org.apache.mina.core.session.AttributeKey;  
  6. import  org.apache.mina.core.session.IoSession;  
  7. import  org.apache.mina.filter.codec.ProtocolDecoder;  
  8. import  org.apache.mina.filter.codec.ProtocolDecoderOutput;  
  9. public   class  MyProtocalDecoder  implements  ProtocolDecoder {  
  10.     private   final  AttributeKey CONTEXT =  new  AttributeKey(getClass(),  "context" );  
  11.     private   final  Charset charset;  
  12.     private   int  maxPackLength =  100 ;  
  13.     public  MyProtocalDecoder() {  
  14.         this (Charset.defaultCharset());  
  15.     }  
  16.     public  MyProtocalDecoder(Charset charset) {  
  17.         this .charset = charset;  
  18.     }  
  19.     public   int  getMaxLineLength() {  
  20.         return  maxPackLength;  
  21.     }  
  22.     public   void  setMaxLineLength( int  maxLineLength) {  
  23.         if  (maxLineLength <=  0 ) {  
  24.             throw   new  IllegalArgumentException( "maxLineLength: "  + maxLineLength);  
  25.         }  
  26.         this .maxPackLength = maxLineLength;  
  27.     }  
  28.     private  Context getContext(IoSession session) {  
  29.         Context ctx;  
  30.         ctx = (Context) session.getAttribute(CONTEXT);  
  31.         if  (ctx ==  null ) {  
  32.             ctx = new  Context();  
  33.             session.setAttribute(CONTEXT, ctx);   
  34.         }   
  35.         return  ctx;  
  36.     }  
  37.     public   void  decode(IoSession session, IoBuffer in, ProtocolDecoderOutput out)  throws  Exception {  
  38.         final   int  packHeadLength =  5 ;  
  39.         //先獲取上次的處理上下文,其中可能有未處理完的數據   
  40.         Context ctx = getContext(session);  
  41.         // 先把當前buffer中的數據追加到Context的buffer當中    
  42.         ctx.append(in);   
  43.         //把position指向0位置,把limit指向原來的position位置   
  44.         IoBuffer buf = ctx.getBuffer();  
  45.         buf.flip();   
  46.         // 然後按數據包的協議進行讀取   
  47.         while  (buf.remaining() >= packHeadLength) {  
  48.             buf.mark();  
  49.             // 讀取消息頭部分   
  50.             int  length = buf.getInt();  
  51.             byte  flag = buf.get();  
  52.             //檢查讀取的包頭是否正常,不正常的話清空buffer   
  53.             if  (length< 0  ||length > maxPackLength) {  
  54.                 buf.clear();   
  55.                 break ;  
  56.             }   
  57.             //讀取正常的消息包,並寫入輸出流中,以便IoHandler進行處理   
  58.             else   if  (length >= packHeadLength && length - packHeadLength <= buf.remaining()) {  
  59.                 int  oldLimit2 = buf.limit();  
  60.                 buf.limit(buf.position() + length - packHeadLength);  
  61.                 String content = buf.getString(ctx.getDecoder());  
  62.                 buf.limit(oldLimit2);  
  63.                 MyProtocalPack pack = new  MyProtocalPack(flag, content);  
  64.                 out.write(pack);  
  65.             } else  {  
  66.                 // 如果消息包不完整   
  67.                 // 將指針重新移動消息頭的起始位置    
  68.                 buf.reset();   
  69.                 break ;  
  70.             }  
  71.         }  
  72.         if  (buf.hasRemaining()) {  
  73.             // 將數據移到buffer的最前面    
  74.                 IoBuffer temp = IoBuffer.allocate(maxPackLength).setAutoExpand(true );  
  75.                 temp.put(buf);  
  76.                 temp.flip();  
  77.                 buf.clear();  
  78.                 buf.put(temp);  
  79.                    
  80.         } else  { // 如果數據已經處理完畢,進行清空   
  81.             buf.clear();   
  82.         }  
  83.           
  84.           
  85.     }  
  86.     public   void  finishDecode(IoSession session, ProtocolDecoderOutput out)  throws  Exception {  
  87.     }  
  88.     public   void  dispose(IoSession session)  throws  Exception {   
  89.         Context ctx = (Context) session.getAttribute(CONTEXT);  
  90.         if  (ctx !=  null ) {  
  91.             session.removeAttribute(CONTEXT);  
  92.         }  
  93.     }  
  94.     //記錄上下文,因爲數據觸發沒有規模,很可能只收到數據包的一半   
  95.     //所以,需要上下文拼起來才能完整的處理   
  96.     private   class  Context {  
  97.         private   final  CharsetDecoder decoder;  
  98.         private  IoBuffer buf;  
  99.         private   int  matchCount =  0 ;  
  100.         private   int  overflowPosition =  0 ;  
  101.         private  Context() {  
  102.             decoder = charset.newDecoder();  
  103.             buf = IoBuffer.allocate(80 ).setAutoExpand( true );  
  104.         }  
  105.         public  CharsetDecoder getDecoder() {  
  106.             return  decoder;  
  107.         }  
  108.         public  IoBuffer getBuffer() {  
  109.             return  buf;  
  110.         }  
  111.         public   int  getOverflowPosition() {  
  112.             return  overflowPosition;  
  113.         }  
  114.         public   int  getMatchCount() {  
  115.             return  matchCount;  
  116.         }  
  117.         public   void  setMatchCount( int  matchCount) {  
  118.             this .matchCount = matchCount;  
  119.         }  
  120.         public   void  reset() {  
  121.             overflowPosition = 0 ;  
  122.             matchCount = 0 ;  
  123.             decoder.reset();  
  124.         }  
  125.         public   void  append(IoBuffer in) {   
  126.             getBuffer().put(in);  
  127.             
  128.         }  
  129.    
  130.     }  
  131. }  

在MyProtocalServer中,添加自己實現的Log4jFilter和編解碼過濾器:

  1. package  com.gftech.mytool.mina;  
  2. import  java.io.IOException;  
  3. import  java.net.InetSocketAddress;  
  4. import  java.nio.charset.Charset;  
  5. import  org.apache.log4j.Logger;  
  6. import  org.apache.log4j.PropertyConfigurator;  
  7. import  org.apache.mina.core.service.IoAcceptor;  
  8. import  org.apache.mina.core.service.IoHandlerAdapter;  
  9. import  org.apache.mina.core.session.IdleStatus;  
  10. import  org.apache.mina.core.session.IoSession;  
  11. import  org.apache.mina.filter.codec.ProtocolCodecFilter;  
  12. import  org.apache.mina.transport.socket.nio.NioSocketAcceptor;  
  13. public   class  MyProtocalServer {  
  14.     private   static   final   int  PORT =  2500 ;  
  15.     static  Logger logger = Logger.getLogger(MyProtocalServer. class );  
  16.     public   static   void  main(String[] args)  throws  IOException {  
  17.         PropertyConfigurator.configure("conf//log4j.properties" );  
  18.         IoAcceptor acceptor = new  NioSocketAcceptor();  
  19.         Log4jFilter lf = new  Log4jFilter(logger);  
  20.         acceptor.getFilterChain().addLast("logger" , lf);  
  21.       
  22.         acceptor.getFilterChain().addLast("codec" new  ProtocolCodecFilter( new  MyProtocalCodecFactory(Charset.forName( "GBK" ))));  
  23.         acceptor.getSessionConfig().setReadBufferSize(1024 );  
  24.         acceptor.getSessionConfig().setIdleTime(IdleStatus.BOTH_IDLE, 10 );  
  25.         acceptor.setHandler(new  MyHandler());  
  26.         acceptor.bind(new  InetSocketAddress(PORT));  
  27.         System.out.println("start server ..." );  
  28.     }  
  29. }  
  30. class  MyHandler  extends  IoHandlerAdapter {  
  31.     static  Logger logger = Logger.getLogger(MyHandler. class );  
  32.     @Override   
  33.     public   void  exceptionCaught(IoSession session, Throwable cause)  throws  Exception {  
  34.         cause.printStackTrace();  
  35.     }  
  36.     @Override   
  37.     public   void  messageReceived(IoSession session, Object message)  throws  Exception {  
  38.         MyProtocalPack pack=(MyProtocalPack)message;  
  39.         logger.debug("Rec:"  + pack);  
  40.     }  
  41.     @Override   
  42.     public   void  sessionIdle(IoSession session, IdleStatus status)  throws  Exception {  
  43.         logger.debug("IDLE "  + session.getIdleCount(status));  
  44.     }  
  45. }  

編寫一個客戶端程序進行測試:

  1. package  com.gftech.mytool.mina;  
  2. import  java.io.DataOutputStream;  
  3. import  java.net.Socket;  
  4. public   class  MyProtocalClient {  
  5.       
  6.     public   static   void  main(String[] args) {  
  7.         try  {  
  8.             Socket socket = new  Socket( "127.0.0.1" 2500 );  
  9.             DataOutputStream out =  new  DataOutputStream( socket.getOutputStream() ) ;  
  10.             for  ( int  i =  0 ; i <  1000 ; i++) {  
  11.                 MyProtocalPack pack=new  MyProtocalPack(( byte )i,i+ "測試MyProtocalaaaaaaaaaaaaaa" );  
  12.                 out.writeInt(pack.getLength());  
  13.                 out.write(pack.getFlag());  
  14.                 out.write(pack.getContent().getBytes());  
  15.                 out.flush();  
  16.                 System.out.println(i + " sended" );  
  17.             }  
  18.             Thread.sleep(1000  );  
  19.         } catch  (Exception e) {  
  20.             e.printStackTrace();  
  21.         }  
  22.     }  
  23. }  

也可以用IoConnector實現自己的客戶端:

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章