現在已經是World Wide Web的時代,無數的web應用框架被創造出來從而大大的提高了web開發的速度。拋開WWW的這個優勢,我們知道還有很多協議是HTTP協議所無法替代的。有時,我們仍然需要構造c/s應用來實現適當的協議。

 === MINA是什么? ===
 你有沒有曾經使用java或者其他語言實現過某個協議棧?就像你所經歷過的那樣,編寫網絡應用即使對于有經驗的開發者也不是容易的事情。這歸咎于以下幾個方面:
 
   * 沒有為開發者設計的合適的網絡應用框架.
     * 使你無法在有限的時間內創建你的應用.
   * 網絡I/O編碼,消息的編/解碼,業務邏輯常常糾纏在一起.
     * 使程序失去可維護性和可復用性
   * 網絡應用難于進行單元測試
     * 你失去了敏捷性
 
 MINA是一個網絡應用框架,在不犧牲性能和可擴展性的前提下用于解決上面的所有問題。


 == I/O 層: 編寫一個 Echo Server ==
 MINA包含兩層:IO層和協議層。我們首先僅使用IO層來實現一個echo服務,因為協議層通常是建立在IO層之上的。 
 attachment:Arch1.gif
 上面的圖展示了MINA的IO層同客戶端的交互。IoAcceptor執行所有底層IO,將他們翻譯成抽象的IO事件,并把翻譯過的事件和關聯的IoSession
發送給IoHandler。


 === IoSession ===
 attachment:IoSession.gif
  一個代表了IoSession程序同一個遠程實體的IO連接。通過IoSession,你可以寫出message到遠程實體,訪問session的配置,并且更改session的屬性。


 === IoHandler ===
 attachment:IoHandler.gif
   * sessionCreated: 當一個IO連接建立時被調用,這個方法在任何IO操作之前被調用,以便socket參數或session屬性能夠最先被設置。
   * sessionOpened: 在sessionCreated調用之后被調用。
   * sessionClosed: 當IO連接被關閉時被調用。
   * sessionIdle: 當在遠程實體和用戶程序之間沒有數據傳輸的時候被調用。
   * exceptionCaught: 當IoAcceptor 或者你的IoHandler.中出現異常時被調用。
   * messageReceived: 當接收到新的協議消息時被調用。可以在這里實現你的控制流程。
   * messageSent: 當用戶請求的消息通過 IoSession#write(Object) 確實發送后被調用。

 
 下面我們看看如何實現echo協議的IoHandler。
 === 實現 IoHandler 以及啟動代碼 ===
 通常,應用需要繼承IoHandlerAdapter并實現需要的方法:
 

package org.apache.mina.examples.echoserver;
 
 
import org.apache.mina.common.ByteBuffer;
 
import org.apache.mina.common.IoHandlerAdapter;
 
import org.apache.mina.common.IoSession;
 
import org.apache.mina.common.TransportType;
 
import org.apache.mina.transport.socket.nio.SocketSessionConfig;
 
 
public class EchoProtocolHandler extends IoHandlerAdapter
 
{
     
public void sessionCreated( IoSession session )
     
{
         
if (session.getTransportType() == TransportType.SOCKET) {
           ((SocketSessionConfig)session.getConfig()).setReceiveBufferSize(
2048);
     }

 
     
public void exceptionCaught( IoSession session, Throwable cause )
     
{
         session.close();
     }

 
     
public void messageReceived( IoSession session, Object message )
     
{
         
if (!(message instanceof ByteBuffer))
             
return;
      
         ByteBuffer rb 
= (ByteBuffer)message;
         
// Write the received data back to remote peer
         ByteBuffer wb = ByteBuffer.allocate( rb.remaining() );
         wb.put( rb );
         wb.flip();
         session.write( wb );
     }

 }

  剛剛我們使用MINA實現echo協議,現在我們將handler綁定到一個server端口上。
import java.net.InetSocketAddress;
 
import org.apache.mina.transport.socket.nio.SocketAcceptor;
 
import org.apache.mina.transport.socket.nio.SocketAcceptorConfig;
 
 
public class Main
 
{
     
/** Choose your favorite port number. */
     
private static final int PORT = 8080;
     
 
     
public static void main( String[] args ) throws Exception
     
{
         SocketAcceptor acceptor 
= new SocketAcceptor();
         SocketAcceptorConfig defaultConfig 
= new SocketAcceptorConfig();
         defaultConfig.setReuseAddress(
true);
      
         
// Bind
         acceptor.bind(new InetSocketAddress(PORT), new EchoProtocolHandler(), defaultConfig);
 
         System.out.println( 
"Listening on port " + PORT );
     }

 }


=== 添加IoFilters ===
 IoFilter提供了更加有力的方式來擴展MINA。它攔截所有的IO事件進行事件的預處理和后處理。你可以把它想象成Servlet的filters。IoFilter能夠實現以下幾種目的:
 
   * 事件日志
   * 性能檢測
   * 數據轉換(e.g. SSL support)
   * 防火墻…等等
 attachment:Arch2.gif
 我們的echo協議handler不對任何IO事件進行日志。我們可以通過添加一個filter來增加日志能力。MINA提供了IoLoggingFilter來進行日志。我們只要添加日志filter到ServiceRegistry即可。
 
 .
 DefaultIoFilterChainBuilder chain 
= config.getFilterChain();
 addLogger(chain);
 .
 
 
private static void addLogger( DefaultIoFilterChainBuilder chain ) throws Exception
 
{
     chain.addLast( 
"logger"new LoggingFilter() );
 }


 想使用SSL?MINA也提供了一個SSL的filter,但它需要JDK1.5。
 .
 DefaultIoFilterChainBuilder chain 
= config.getFilterChain();
 addLogger(chain);
 .
 
 
private static void addSSLSupport( DefaultIoFilterChainBuilder chain )
     
throws Exception
 
{
     SSLFilter sslFilter 
=
         
new SSLFilter( BogusSSLContextFactory.getInstance( true ) );
     chain.addLast( 
"sslFilter", sslFilter );
     System.out.println( 
"SSL ON" );
 }

== 協議層: 實現反轉Echo協議 ==
 在上面我們通過簡單的echo server的例子學習了如何使用IO層,但是如果想實現復雜的如LDAP這樣的協議怎么辦呢?它似乎是一個惡夢,因為IO層沒有幫助你分離‘message解析’和‘實際的業務邏輯(比如訪問一個目錄數據庫)’。MINA提供了一個協議層來解決這個問題。協議層將ByteBuffer事件轉換成高層的POJO事件:
 
 attachment:Arch3.gif
 
 使用協議層必須實現5個接口:ProtocolHandler, ProtocolProvider, ProtocolCodecFactory,
ProtocolEncoder, 和 ProtocolDecoder:
 
 attachment:ProtocolClasses.gif
 
 可能看上去有點麻煩,但是請注意ProtocolCodecFactory, ProtocolEncoder, 和
ProtocolDecoder是可以完全復用的;Apache的ASN1項目為MINA提供了ASN.1解碼器,更通用的解碼器如:XML、java對象序列化和簡單的文本將在MINA的下一個版本中提供。一旦你實現了一個靈活的解碼器,你可以在未來的應用中復用它,即使你不打算復用你的解碼器,MINA也提供了一個很簡單的方法來實現復雜的協議。(請參考高級主題)
 在這一章中,我們添加一個‘反轉’server,它用于反轉它接到的所有文本,我們通過它來示范如何編寫一個協議層。



 === ProtocolSession ===
 attachment:ProtocolSession.gif
 ProtocolSession同IO層的IoSession同樣繼承自Session。就像前面提到的,你只需撰寫面向POJO的message而不是ByteBuffer的。ProtocolEncoder
將message對象解釋成ByteBuffers以便IO層能夠將他們輸出到socket。

 === ProtocolHandler ===
 ProtocolHandler類似于IO層的IoHandler.dataRead和dataWritten方法被替換成messageReceived和messageSent。這是因為ProtocolDecoder
已經將IO層接收到的 ByteBuffers轉換成了message對象。
package org.apache.mina.examples.reverser;
 
 
import org.apache.mina.common.IoHandler;
 
import org.apache.mina.common.IoHandlerAdapter;
 
import org.apache.mina.common.IoSession;
 
 
public class ReverseProtocolHandler extends IoHandlerAdapter
 
{
     
public void exceptionCaught( IoSession session, Throwable cause )
     
{
         
// Close connection when unexpected exception is caught.
         session.close();
     }

 
     
public void messageReceived( IoSession session, Object message )
     
{
         
// Reverse received string
         String str = message.toString();
         StringBuffer buf 
= new StringBuffer( str.length() );
         
forint i = str.length()  1; i >= 0; i )
         
{
             buf.append( str.charAt( i ) );
         }

 
         
// and write it back.
         session.write( buf.toString() );
     }

 }


 === ProtocolEncoder 和 ProtocolDecoder ===
 attachment:ProtocolCodec.gif
 ProtocolEncoder 和ProtocolDecoder只有一個方法。ProtocolEncoder將message對象轉換成一個ByteBuffer,而ProtocolDecoder將一個ByteBuffer轉換成message對象。下面我們將學習如何實現這些接口。要實現反轉協議要實作的唯一接口就是ProtocolProvider。它非常簡單:
 (注:Provider用于在一個統一的類中提供該協議相關的Handler、Decoder和Encoder。)

package org.apache.mina.examples.reverser;
 
 
import org.apache.mina.protocol.*;
 
 
/**
  * {
@link ProtocolProvider} implementation for reverser server protocol.
 
*/

 
public class ReverseProtocolProvider implements ProtocolProvider
 
{
     
// Protocol handler is usually a singleton.
     private static ProtocolHandler HANDLER =
 
new ReverseProtocolHandler();
 
     
// Codec factory is also usually a singleton.
     private static ProtocolCodecFactory CODEC_FACTORY =
 
new ProtocolCodecFactory()
     
{
         
public ProtocolEncoder newEncoder()
         
{
             
// Create a new encoder.
             return new TextLineEncoder();
         }

 
         
public ProtocolDecoder newDecoder()
         
{
             
// Create a new decoder.
             return new TextLineDecoder();
         }

     }
;
 
     
public ProtocolCodecFactory getCodecFactory()
     
{
         
return CODEC_FACTORY;
     }

 
     
public ProtocolHandler getHandler()
     
{
         
return HANDLER;
     }

 }


這樣,反轉協議就被完全實現了。啟動的部分同echo server非常相似:
package org.apache.mina.examples.reverser;
 
 
import org.apache.mina.common.*;
 
import org.apache.mina.protocol.*;
 
import org.apache.mina.registry.*;
 
 
/**
  * (<b>Entry point</b>) Reverser server which reverses all text lines from
  * clients.
  * 
  * 
@author Trustin Lee (trustin@apache.org)
  * 
@version $Rev: 165594 $, $Date: 20050502 16:21:22 +0900 $,
  
*/

 
public class Main
 
{
     
private static final int PORT = 8080;
 
     
public static void main( String[] args ) throws Exception
     
{
         ServiceRegistry registry 
= new SimpleServiceRegistry();
 
         
// Bind
         Service service = new Service( "reverse", TransportType.SOCKET, PORT );
         registry.bind( service, 
new ReverseProtocolProvider() );
 
         System.out.println( 
"Listening on port " + PORT );
     }

 }


 === 添加 ProtocolFilters ==
 ProtocolFilter 同IO層的IoFilter類似:
 attachment:Arch4.gif
 
 添加IoLoggingFilter來記錄底層IO事件是為了debug。我們可以用ProtocolLoggingFilter代替它來記錄高層事件:
 private static void addLogger( ServiceRegistry registry )
     
{
         ProtocolAcceptor acceptor 
= registry.getProtocolAcceptor( TransportType.SOCKET );
         acceptor.getFilterChain().addLast( 
"logger"new ProtocolLoggingFilter() );
         System.out.println( 
"Logging ON" );
     }


=== ByteBuffers ===
 
 MINA沒有直接使用使用java NIO的ByteBuffer類。它使用一個自制的ByteBuffer來擴展java
NIO ByteBuffer的功能。
 以下是它們的一些區別:
   * MINA ByteBuffer是一個抽象類,用戶可以自由的擴展它
   * MINA 管理 MINA ByteBuffers 并對其提供對象池. Users can control the point the
buffers are released by providing acquire() and release() methods.
   * MINA ByteBuffer提供很多便利的方法,如:無符號數值的getter和基于String的getter和putter
 如果你使用MINA,你將不需要直接使用NIO buffers,因為僅使用MINA buffers就可以完成大多數buffer操作。
 
 ==== ByteBuffer 池 ====
 
 MINA有一個全局的ByteBuffer池,它被在同一個虛擬機下的所有MINA應用共享。任何分配的buffers將在IO操作或者事件處理方法被執行之后被釋放。所以你可以調用ByteBuffer.allocate()來從池中得到一個ByteBuffer而不需要將它返回到池中。請查閱ByteBuffer
JavaDocs獲得更多信息。
 
 === 線程模式 ===
 
 MINA通過它靈活的filter機制來提供多種線程模型。沒有線程池過濾器被使用時MINA運行在一個單線程模式。如果添加了一個IoThreadPoolFilter
到IoAcceptor,你將得到一個leaderfollower模式的線程池。如果再添加一個ProtocolThreadPoolFilter,你的server將有兩個線程池;一個(IoThreadPoolFilter)被用于對message對象進行轉換,另外一個(ProtocolThreadPoolFilter)被用于處理業務邏輯。
 
 SimpleServiceRegistry加上IoThreadPoolFilter和ProtocolThreadPoolFilter的缺省實現即可適用于需要高伸縮性的應用。如果你想使用自己的線程模型,請查看SimpleServiceRegistry的源代碼,并且自己初始化Acceptor。顯然,這是個繁瑣的工作。

IoThreadPoolFilter threadPool = new IoThreadPoolFilter();
 threadPool.start();
 
 IoAcceptor acceptor 
= new SocketAcceptor();
 acceptor.getFilterChain().addLast( 
"threadPool", threadPool );
 
 ProtocolThreadPoolFilter threadPool2 
= new ProtocolThreadPoolFilter();
 threadPool2.start();
 
 ProtocolAcceptor acceptor2 
= new IoProtocolAcceptor( acceptor );
 acceptor2.getFilterChain().addLast( 
"threadPool", threadPool2 );
 
 
 
 threadPool2.stop();
 threadPool.stop();


 === 更復雜的協議支持 ===
 
 ‘Reverser’示例相對于其他復雜的協議來說仍然過于簡單。要想讓一個server工作,仍然有許多message類型和它們的轉換的工作需要作。MINA提供了一下工具類來提供幫助:
 
   * DemuxingProtocolHandler
   * DemuxingProtocolCodecFactory
 
 更多細節請參考 JavaDocs 。
 
 === VM 內部管道通訊 ===
 
 你一定已經知道協議層是建立在IO層之上的,但是有時也不一定。雖然我們通常使用協議層來包裝IO層,但仍有一種特殊的協議層實現,稱作:’
inVM pipe communication’
 讓我們假設你需要使用MINA實現一個SMTP server和一個Spam Filter server。SMTP
server可能需要同Spam Filter server通訊以便發現spam message或者RBL中列出的客戶端。如果這兩個server是在同一個java虛擬機中,一個IO層是多余的,你可以繞過message對象的編解碼的過程。InVM
pipe communication可以使你使用同樣的代碼而不管spam filter server是否在同一個虛擬機中。
 請查看隨源碼分發的’ Tennis’示例。