現在已經是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() );
for( int 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’示例。