MINA Tutorial 中文版: A Date with MINA
作者: TrustinLee
譯者:
Donald
該手冊為使用中的用戶而寫,用戶可隨意改進該手冊。
目錄
-
MINA Tutorial 中文版: A Date with MINA
-
目錄
-
概述
-
MINA是什么?
-
I/O 層: 編寫一個 Echo Server
-
IoSession
-
IoHandler
-
實現 IoHandler 以及啟動代碼
-
添加IoFilters
-
協議層: 實現反轉Echo協議
-
ProtocolSession
-
ProtocolHandler
-
ProtocolEncoder 和 ProtocolDecoder
-
實現 ProtocolHandler
-
實現 ProtocolProvider 以及啟動代碼
-
添加 ProtocolFilters
-
高級主題
-
ByteBuffers
-
ByteBuffer 池
-
線程模式
-
更復雜的協議支持
-
VM 內部管道通訊
-
How to Contribute
-
How to Contact Us
-
How to Report Bugs
-
How Issues
-
Acknowledgements
概述
現在已經是World Wide Web的時代,無數的web應用框架被創造出來從而大大的提高了web開發的速度。拋開WWW的這個優勢,我們知道還有很多協議是HTTP協議所無法替代的。有時,我們仍然需要構造c/s應用來實現適當的協議。
MINA是什么?
你有沒有曾經使用java或者其他語言實現過某個協議棧?就像你所經歷過的那樣,編寫網絡應用即使對于有經驗的開發者也不是容易的事情。這歸咎于以下幾個方面:
MINA是一個網絡應用框架,在不犧牲性能和可擴展性的前提下用于解決上面的所有問題。
I/O 層: 編寫一個 Echo Server
MINA包含兩層:IO層和協議層。我們首先僅使用IO層來實現一個echo服務,因為協議層通常是建立在IO層之上的。
上面的圖展示了MINA的IO層同客戶端的交互。IoAcceptor執行所有底層IO,將他們翻譯成抽象的IO事件,并把翻譯過的事件和關聯的IoSession 發送給IoHandler。
IoSession
一個代表了IoSession程序同一個遠程實體的IO連接。通過IoSession,你可以寫出message到遠程實體,訪問session的配置,并且更改session的屬性。
IoHandler
sessionCreated: 當一個IO連接建立時被調用,這個方法在任何IO操作之前被調用,以便socket參數或session屬性能夠最先被設置。
sessionOpened: 在sessionCreated調用之后被調用。
sessionClosed: 當IO連接被關閉時被調用。
sessionIdle: 當在遠程實體和用戶程序之間沒有數據傳輸的時候被調用。
exceptionCaught: 當IoAcceptor 或者你的IoHandler.中出現異常時被調用。
dataRead: 當從遠程實體讀取數據時被調用。
dataWritten: 當你想遠程實體發出請求時被調用
下面我們看看如何實現echo協議的IoHandler。
實現 IoHandler 以及啟動代碼
通常,應用需要繼承IoHandlerAdapter并實現需要的方法:
package org.apache.mina.examples.echoserver;
import org.apache.mina.common.*;
import org.apache.mina.io.*;
public class EchoProtocolHandler extends IoHandlerAdapter
{
public void sessionCreated( IoSession session )
{
SessionConfig cfg = session.getConfig();
if( cfg instanceof SocketSessionConfig )
{
SocketSessionConfig scfg = ( SocketSessionConfig ) cfg ) ;
scfg.setSessionReceiveBufferSize( 2048 );
}
}
public void exceptionCaught( IoSession session, Throwable cause )
{
session.close();
}
public void dataRead( IoSession session, ByteBuffer rb )
{
// Write the received data back to remote peer
ByteBuffer wb = ByteBuffer.allocate( rb.remaining() );
wb.put( rb );
wb.flip();
session.write( wb, null );
}
}
剛剛我們使用MINA實現echo協議,現在我們將handler綁定到一個server端口上。
package org.apache.mina.examples.echoserver;
import org.apache.mina.common.*;
import org.apache.mina.io.*;
import org.apache.mina.io.filter.*;
import org.apache.mina.registry.*;
public class Main
{
/** Choose your favorite port number. */
private static final int PORT = 8080;
public static void main( String[] args ) throws Exception
{
ServiceRegistry registry = new SimpleServiceRegistry();
// Bind
Service service = new Service( "echo",
TransportType.SOCKET, PORT );
registry.bind( service, new EchoProtocolHandler() );
System.out.println( "Listening on port " + PORT );
}
}
添加IoFilters
IoFilter提供了更加有力的方式來擴展MINA。它攔截所有的IO事件進行事件的預處理和后處理。你可以把它想象成Servlet的filters。IoFilter能夠實現以下幾種目的:
事件日志
性能檢測
數據轉換(e.g. SSL support)
防火墻…等等
我們的echo協議handler不對任何IO事件進行日志。我們可以通過添加一個filter來增加日志能力。MINA提供了IoLoggingFilter來進行日志。我們只要添加日志filter到ServiceRegistry即可。
private static void addLogger( ServiceRegistry registry )
{
IoAcceptor acceptor =
registry.getIoAcceptor( TransportType.SOCKET );
acceptor.getFilterChain().addLast( "logger",
new IoLoggingFilter() );
System.out.println( "Logging ON" );
}
想使用SSL?MINA也提供了一個SSL的filter,但它需要JDK1.5。
private static void addSSLSupport( ServiceRegistry registry )
throws Exception
{
SSLFilter sslFilter =
new SSLFilter( BogusSSLContextFactory.getInstance( true ) );
IoAcceptor acceptor =
registry.getIoAcceptor( TransportType.SOCKET );
acceptor.getFilterChain().addLast( "sslFilter", sslFilter );
System.out.println( "SSL ON" );
}
協議層: 實現反轉Echo協議
在上面我們通過簡單的echo
server的例子學習了如何使用IO層,但是如果想實現復雜的如LDAP這樣的協議怎么辦呢?它似乎是一個惡夢,因為IO層沒有幫助你分離
‘message解析’和‘實際的業務邏輯(比如訪問一個目錄數據庫)’。MINA提供了一個協議層來解決這個問題。協議層將ByteBuffer事件轉換成高層的POJO事件:
使用協議層必須實現5個接口:ProtocolHandler, ProtocolProvider, ProtocolCodecFactory, ProtocolEncoder, 和 ProtocolDecoder:
可能看上去有點麻煩,但是請注意ProtocolCodecFactory, ProtocolEncoder, 和 ProtocolDecoder是
可以完全復用的;Apache的ASN1項目為MINA提供了ASN.1解碼器,更通用的解碼器如:XML、java對象序列化和簡單的文本將在MINA
的下一個版本中提供。一旦你實現了一個靈活的解碼器,你可以在未來的應用中復用它,即使你不打算復用你的解碼器,MINA也提供了一個很簡單的方法來實現
復雜的協議。(請參考高級主題) 在這一章中,我們添加一個‘反轉’server,它用于反轉它接到的所有文本,我們通過它來示范如何編寫一個協議層。
ProtocolSession
ProtocolSession同IO層的IoSession同樣繼承自Session。就像前面提到的,你只需撰寫面向POJO的message而不是ByteBuffer的。ProtocolEncoder 將message對象解釋成ByteBuffers以便IO層能夠將他們輸出到socket。
ProtocolHandler
ProtocolHandler類似于IO層的IoHandler.dataRead和dataWritten方法被替換成messageReceived和messageSent。這是因為ProtocolDecoder 已經將IO層接收到的 ByteBuffers轉換成了message對象。
ProtocolEncoder 和 ProtocolDecoder
ProtocolEncoder 和ProtocolDecoder只有一個方法。ProtocolEncoder將message對象轉換成一個ByteBuffer,而ProtocolDecoder將一個ByteBuffer轉換成message對象。下面我們將學習如何實現這些接口。
實現 ProtocolHandler
讓我們首先實現一個ProtocolHandler。如同剛才實現IoHandler那樣,我們繼承ProtocolHandlerAdapter:
package org.apache.mina.examples.reverser;
import org.apache.mina.protocol.*;
public class ReverseProtocolHandler extends ProtocolHandlerAdapter
{
public void exceptionCaught( ProtocolSession session,
Throwable cause )
{
// Close connection when unexpected exception is caught.
session.close();
}
public void messageReceived( ProtocolSession session,
Object message )
{
// Reverse reveiced 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() );
}
}
實現 ProtocolProvider 以及啟動代碼
要實現反轉協議要實作的唯一接口就是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: 2005-05-02 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類似:
添加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" );
}
高級主題
在這里我們為MINA的高端用戶講解一些高級話題。
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,你將得到一個leader-follower模式的線程池。如果再添加一個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提供了一下工具類來提供幫助:
更多細節請參考 JavaDocs 。
VM 內部管道通訊
你一定已經知道協議層是建立在IO層之上的,但是有時也不一定。雖然我們通常使用協議層來包裝IO層,但仍有一種特殊的協議層實現,稱作:’
in-VM pipe communication’ 讓我們假設你需要使用MINA實現一個SMTP server和一個Spam Filter
server。SMTP server可能需要同Spam Filter server通訊以便發現spam
message或者RBL中列出的客戶端。如果這兩個server是在同一個java虛擬機中,一個IO層是多余的,你可以繞過message對象的編解
碼的過程。In-VM pipe communication可以使你使用同樣的代碼而不管spam filter
server是否在同一個虛擬機中。 請查看隨源碼分發的’ Tennis’示例。
How to Contribute
We want MINA to evolve actively, reacting to user requests, and
therefore we need as much feedback from you as possible. The Apache
Directory team will strive to satisfy all possible use cases of MINA.
Please feel free to contact us.
How to Contact Us
How to Report Bugs
You can report any bugs found from MINA to our issue tracker page.
Please attach any test cases that can reproduce your issue if possible.
How Issues
Any patches and comments are welcome! You can browse the list of unresolved issues in JIRA:
Or, you could do some performance benchmarks on MINA and tune it.
Acknowledgements
MINA couldn’t exist without strong support of many contributors:
The Apache Directory team for letting me join the team
All users of Netty2 forum and
dev@directory.apache.org for great feedbacks
Jan Andersson and his team for SSLFilter
Vel Pandian for enabling client mode for SSLFilter
Vinod Panicker for performance benchmark and active feedbacks