一、包與命名
所有的類和方法嚴格使用駝峰法命名。
例如SSLFilter被更名為SslFilter,其它很多類也是如此。
所有NIO傳輸類在命名時增加‘Nio’前綴。
因為NIO並不只是socket/datagram傳輸的實現,所有‘Nio’前綴加在了所有的NIO傳輸類上。
改變之前:
- SocketAcceptor acceptor = new SocketAcceptor();
改變之后:
- SocketAcceptor acceptor = new NioSocketAcceptor();
Filter類被重新整理進多重子包內。
隨着框架自帶的filter實現的數量的增加,所有的filter都被移動到適當的子包中(例如,StreamWriteFilter移至org.apache.mina.filter.stream)。
*.support的所有包被移動到了其父包(或者其他包)中。
為了避免循環依賴,*.support包中的所有類都被移至其父包或者其他包中。你可以在IDE(例如Eclipse)中簡單的修正這些包的導入從而避免編譯錯誤。
二、Buffers
MINA ByteBuffer被重命名為IoBuffer。
因為MINA ByteBuffer與JDK中NIO ByteBuffer同名,很多用戶發現與其組員溝通時存在很多困難。根據用戶的反饋,我們將MINA ByteBuffer重命名為IoBuffer,這不僅使類名稱簡化,也是類名稱更加明晰。
放棄Buffer池,默認使用IoBuffer.allocate(int)來分配heap buffer。
- acquire()與release()兩個方法將不再是容易發生錯誤的。如果你願意,你可以調用free()方法,但這是可選的。請自己承擔使用這個方法的風險。
- 在大多數JVM中,框架內置的IoBuffer性能更加強勁、穩定。
Direct buffer池是MINA早期版本所標榜的眾多特性之一。然而根據當今的尺度,在主流的JVM中direct buffers的表現要比heap buffers差。此外,當direct buffer memory的最大值沒有被正確設定時,不可預期的OutOfMemoryError也經常出現。
為了使系統內置的IoBuffer性能更加強勁、穩定,Apache MINA項目組將默認的buffer類型由direct改為heap。因為heap buffers並不需要池化,PooledByteBufferAllocator也被移除掉了。由於沒有了池的概念,ByteBuffer.acquire() 和 ByteBuffer.release()也被移除掉了。
然而,如果使用的速度太快,分配heap buffers也會成為瓶頸。這是因為分配字節數據如要將所有的元素都置為0,這個操作是消耗內存帶寬的。CachedBufferAllocator是針對這種情況使用的,但是在大多數情況下,你還是應該使用默認的SimpleBufferAllocator。
三、啟動和配置
IoService的配置被簡化了。
在1.x版本中,有很多種方式來配置IoService和它的子接口(例如 IoAcceptor 和 IoConnector)。基本上,有兩種配置方法:
在調用bind() 或 connect()時,具體指定一個IoServiceConfig
- SocketAcceptor acceptor = new SocketAcceptor();
- SocketAcceptorConfig myServiceConfig = new SocketAcceptorConfig();
- myServiceConfig.setReuseAddress(true);
- acceptor.bind(myHandler, myServiceConfig);
使用IoService.defaultConfig屬性,此時不需要指定一個IoServiceConfig
- SocketAcceptor acceptor = new SocketAcceptor();
- acceptor.getDefaultConfig().setReuseAddress(true);
- acceptor.bind(new InetSocketAddress(8080), myHandler);
配置IoFilterChain是另一個令人頭痛的問題,因為除了IoServiceConfig內的IoFilterChainBuilder外,還有一個全局的IoFilterChainBuilder,這就意味着使用兩個IoFilterChainBuilders來配置一個IoFilterChain。大多數用戶使用全局的IoFilterChainBuilder來配置IoFilterChain,並且這就足夠了。
針對這種復雜情況,MINA 2.0簡化了網絡應用程序的啟動,請比較下面的代碼與前面代碼的不同
- SocketAcceptor acceptor = new SocketAcceptor();
- acceptor.setReuseAddress(true);
- acceptor.getFilterChain().addLast("myFilter1", new MyFirstFilter());
- acceptor.getFilterChain().addLast("myFilter2", new MySecondFilter());
- acceptor.getSessionConfig().setTcpNoDelay(true);
- // You can specify more than one addresses to bind to multiple addresses or interface cards.
- acceptor.setLocalAddress(new InetSocketAddress(8080));
- acceptor.setHandler(myHandler);
- acceptor.bind();
- // New API restricts one bind per acceptor, and you can't bind more than once.
- // The following statement will raise an exception.
- acceptor.bind();
你也許意識到與Spring框架整合也將變得更加簡單。
四、線程
ThreadModel被移除了。
最初引入ThreadModel的概念為的是簡化一個IoService預定義的線程模式。然而,配置線程模式卻變得非常簡單以至於不能引入新的組建。與其易用性相比,線程模式帶了更多的混亂。在2.x中,當你需要的時候,你必須明確的增加一個ExecutorFilter。
ExecutorFilter使用一個特定的Executor實現來維系事件順序。
在1.x中,可以使用任意的Executor實現來來維系事件順序,但2.x提供了兩個新的ThreadPoolExecutor實現,OrderedThreadPoolExecutor和UnorderedThreadPoolExecutor,ExecutorFilter維系事件順序,當以下兩種情況:當使用默認構造方法時,ExecutorFilter創建一個OrderedThreadPoolExecutor,或者
明確指明使用OrderedThreadPoolExecutor時
OrderedThreadPoolExecutor 和 UnorderedThreadPoolExecutor內部使用了一些架構來防止發生OutOfMemoryError,所以你應該盡量使用這兩個類而不是其他Executor的實現。
五、協議編解碼
DemuxingProtocolCodecFactory被重寫了。
新增了DemuxingProtocolEncoder和DemuxingProtocolDecoder兩個類,DemuxingProtocolCodecFactory只是這兩個類的外殼。register() 方法被重命名為addMessageEncoder() 和addMessageDecoder(),這個變化使混合使用多個encoders和decoders變得更加自由。
MessageEncoder接口也發生了改變,MessageEncoder.getMessageTypes()被移除了,當你調用addMessageEncoder(),你只需要指明信息的類型,encoder就可以進行正確的編碼了。
六、集成
JMX集成被重新設計了。
Sping集成被簡化了。
七、其他方面的改變
TransportType更名為TransportMetadata。
TransportType改名是因為它的角色是元數據而不僅僅是一種枚舉。
IoSessionLogger被重新設計了。
IoSessionLogger現在實現了SLF4J Logger接口,所以你可以像聲明簡單SLF4J logger實例一樣聲明它,這個變化使你不必向其他不必要的部分暴露IoSessionLogger對象。另外,在使用MDC時,請考慮使用簡單的MdcInjectionFilter,這時IoSessionLogger是沒有必要的。
改變之前:
- IoSessionLogger.debug(session, ...);
改變之后:
- Logger logger = IoSessionLogger.getLogger(session);
- logger.debug(...);
BroadcastIoSession被合並到IoSession中。
ReadThrottleFilterBuilder被ReadThrottleFilter替代並最終移除。
(2) 快速上手指南
一、介紹
該教程通過構建一個time server,帶你走進給予MINA的應用程序開發的大門,但在開始之前我們需要具備下面的必要條件:
- MINA 2.x的核心包
- JDK 1.5 或更高版本
- SLF4J 1.3.0 或更高版本
- Log4J 1.2的用戶:slf4j-api.jar, slf4j-log4j12.jar, and Log4J 1.2.x
- Log4J 1.3的用戶:slf4j-api.jar, slf4j-log4j13.jar, and Log4J 1.3.x
- java.util.logging的用戶:slf4j-api.jar and slf4j-jdk14.jar
注意:請務必確認你所使用的slf4j-*.jar要與你的日志框架相匹配。例如,slf4j-log4j12.jar 和 log4j-1.3.x.jar不能在一起使用,否則會引起混亂。
我已經在Windows? 2000 professional 和 linux平台上測試了這個程序,如果你在運行這個程序的過程中遇到了問題,請立即聯系我們的開發人員。
當然,這個程序是與開發環境(IDE, editors等等)無關的,它可以在任何你熟悉的平台中運行。另外,為了簡化,編譯命令與運行腳本都沒有體現,如果你需要學習如何編譯並運行java程序,請參考Java tutorial。
二、編寫基於MINA框架的time server
我們從創建一個名為MinaTimeServer.java的文件開始,最初的代碼如下:
- public class MinaTimeServer {
- public static void main(String[] args) {
- // code will go here next
- }
- }
對所有人來說,這段代碼應該是簡單易懂的,我們只是簡單的定義了一個main方法是這個程序能夠正常運行起來。從現在開始,我們將逐步加入代碼是其最終成為一個可用的server。首先,我們需要一個可以監聽連接到來的對象,既然我們的程序是基於TCP/IP的,所以我們在程序中加入一個SocketAcceptor。
- import org.apache.mina.core.service.IoAcceptor;
- import org.apache.mina.transport.socket.nio.NioSocketAcceptor;
- public class MinaTimeServer
- {
- public static void main( String[] args )
- {
- IoAcceptor acceptor = new NioSocketAcceptor();
- }
- }
加入NioSocketAcceptor之后,我們可以繼續定義一個handler類,並將其與NioSocketAcceptor綁定到一個端口上。
下面,我們在配置中增加一個filter,這個filter將把二進制數據或是協議相關的數據轉換成為一個消息對象,反之亦然。我們使用現有的TextLine工廠類,以為它可以處理基於文本的信息(你不需要自己來實現編解碼部分)。
- import java.nio.charset.Charset;
- import org.apache.mina.core.service.IoAcceptor;
- import org.apache.mina.filter.codec.ProtocolCodecFilter;
- import org.apache.mina.filter.codec.textline.TextLineCodecFactory;
- import org.apache.mina.filter.logging.LoggingFilter;
- import org.apache.mina.transport.socket.nio.NioSocketAcceptor;
- public class MinaTimeServer
- {
- public static void main( String[] args )
- {
- IoAcceptor acceptor = new NioSocketAcceptor();
- acceptor.getFilterChain().addLast( "logger", new LoggingFilter() );
- acceptor.getFilterChain().addLast( "codec", new ProtocolCodecFilter( new TextLineCodecFactory( Charset.forName( "UTF-8" ))));
- }
- }
然后,我們定義一個handler,這個handler將對客戶端的連接以及過去當前時間的請求做出服務。handler類必須實現IoHandler接口。對於大多數基於MINA的應用程序,這個操作無疑是一個很大的負擔,因為它將處理客戶端說有的請求。在這個教程中,我們的handler將繼承自IoHandlerAdapter,這個類依照適配器模式來簡化實現IoHandler接口所帶來的代碼量。
- import java.io.IOException;
- import java.nio.charset.Charset;
- import org.apache.mina.core.service.IoAcceptor;
- import org.apache.mina.filter.codec.ProtocolCodecFilter;
- import org.apache.mina.filter.codec.textline.TextLineCodecFactory;
- import org.apache.mina.filter.logging.LoggingFilter;
- import org.apache.mina.transport.socket.nio.NioSocketAcceptor;
- public class MinaTimeServer
- {
- public static void main( String[] args ) throws IOException
- {
- IoAcceptor acceptor = new NioSocketAcceptor();
- acceptor.getFilterChain().addLast( "logger", new LoggingFilter() );
- acceptor.getFilterChain().addLast( "codec", new ProtocolCodecFilter( new TextLineCodecFactory( Charset.forName( "UTF-8" ))));
- acceptor.setHandler( new TimeServerHandler() );
- }
- }
現在,我們在NioSocketAcceptor增加一些Socket相關的配置:
- import java.io.IOException;
- import java.nio.charset.Charset;
- import org.apache.mina.core.session.IdleStatus;
- import org.apache.mina.core.service.IoAcceptor;
- import org.apache.mina.filter.codec.ProtocolCodecFilter;
- import org.apache.mina.filter.codec.textline.TextLineCodecFactory;
- import org.apache.mina.filter.logging.LoggingFilter;
- import org.apache.mina.transport.socket.nio.NioSocketAcceptor;
- public class MinaTimeServer
- {
- public static void main( String[] args ) throws IOException
- {
- IoAcceptor acceptor = new NioSocketAcceptor();
- acceptor.getFilterChain().addLast( "logger", new LoggingFilter() );
- acceptor.getFilterChain().addLast( "codec", new ProtocolCodecFilter( new TextLineCodecFactory( Charset.forName( "UTF-8" ))));
- acceptor.setHandler( new TimeServerHandler() );
- idle sessions
- acceptor.getSessionConfig().setReadBufferSize( 2048 );
- acceptor.getSessionConfig().setIdleTime( IdleStatus.BOTH_IDLE, 10 );
- }
- }
在MinaTimeServer增加了兩行新的內容,這些set方法分別設置了IoHandler、input buffer size和session對象上的idle屬性。buffer size指明了底層操作系統應該給與新到來的數據分配多少空間;第二行指明了什么時候應該檢測idle sessions。在setIdleTime這個方法中,第一參數指明了在檢測session是否idle時,應該關心那一種活動,第二個參數指明了session變為idle狀態時需要經過多長的時間。
handler的代碼如下:
- import java.util.Date;
- import org.apache.mina.core.session.IdleStatus;
- import org.apache.mina.core.service.IoHandlerAdapter;
- import org.apache.mina.core.session.IoSession;
- public class TimeServerHandler extends IoHandlerAdapter
- {
- @Override
- public void exceptionCaught( IoSession session, Throwable cause ) throws Exception
- {
- cause.printStackTrace();
- }
- @Override
- public void messageReceived( IoSession session, Object message ) throws Exception
- {
- String str = message.toString();
- if( str.trim().equalsIgnoreCase("quit") ) {
- session.close();
- return;
- }
- Date date = new Date();
- session.write( date.toString() );
- System.out.println("Message written...");
- }
- @Override
- public void sessionIdle( IoSession session, IdleStatus status ) throws Exception
- {
- System.out.println( "IDLE " + session.getIdleCount( status ));
- }
- }
該類用到的方法有exceptionCaught、messageReceived和sessionIdle。在handler中,一定要定義exceptionCaught方法,該方法用來處理在遠程連接中處理過程中發生的各種異常,如果這個方法沒有被定義,我們可能不能發現這些異常。
在這個handler中,exceptionCaught方法只是簡單地打印出異常堆棧信息並關閉連接,對於大多數程序來說,這是一種比較標准的操作,除非連接可以在異常條件下恢復。
messageReceived方法會接收客戶端的數據並返回當前的的系統時間,如果從客戶端接收到了消息‘quit’,則session會被關閉。與調用session.write(Object)的情況相同,不同的協議編解碼器決定了傳入該方法的對象(第二個參數)也是不同的。如果你沒有指定協議編解碼器,你最有可能接收到一個IoBuffer對象,當然,調用session.write(Object)也是一個IoBuffer對象。
當session持續idle的時間與acceptor.getSessionConfig().setIdleTime( IdleStatus.BOTH_IDLE, 10 )設置的時間一致時,sessionIdle方法將被調用。
現在剩下的工作只是定義一個server監聽的地址和端口了,當然還需要啟動服務。代碼如下:
正如你所見,這里調用了acceptor.setLocalAddress( new InetSocketAddress(PORT) );方法,該方法指明了server將在哪個IP和端口上監聽。最后一步調用了IoAcceptor.bind()方法,該方法將端口與具體的客戶端進程綁定在一起。
三、驗證Time server
現在,我們編譯上面的程序,編譯完成后就可以運行並查看運行結果了。最簡單的測試途徑就是啟動程序,並使用telnet與之建立連接:
Client Output |
Server Output |
user@myhost:~> telnet 127.0.0.1 9123 |
MINA Time server started. |
四、接下來
獲取更多資源,請瀏覽MINA的Documentation。你也可以閱讀其他教程。
(3) MINA的應用程序架構
一、簡介
有個問題經常被提出:基於MINA的應用程序應該是什么樣的呢?這篇文章將給出一個答案。我們已經收集了很多基於MINA的描述信息。下面是架構圖:
讓我們在來關於一下細節
這張圖片選取自Trustin Lee在JavaOne 2008上的報告"Rapid Network Application Development with Apache MINA“
從廣義上講,基於MINA的應用程序分為3層
- I/O Service - 完成實際的I/O操作
- I/O Filter Chain - 將字節過濾或轉換成為預想的數據結構,反之亦然
- I/O Handler - 完成實際的業務邏輯操作
那我們如何創建一個基於MINA的應用程序呢?
- Create I/O service - 從現有的Services (*Acceptor)中選擇一個或者創建自己的
- Create Filter Chain - 從現有的Filters中選擇或者創建一個傳輸request/response的自定義Filter
- Create I/O Handler - 編寫業務邏輯, 處理不同的報文
創建MINA程序就如上文所述的一樣。
(4) 日志配置
一、背景
MINA框架允許開發人員在編寫基於MINA的應用程序時使用自己熟悉的日志系統。
二、SLF4J
MINA框架使用Simple Logging Facade for Java (SLF4J)。你可以在這里 獲取到更多關於SLF4J的信息,這種日志系統兼容各種日志系統的實現。你可能會使用log4j、java.util.logging或其他的日志系統,使用這種日志框架的好處在於如果你在開發過程中,將日志系統從java.util.logging改為log4j,你根本需要修改你的代碼。
選擇正確的jar包
Logging framework |
Required JARs |
Log4J 1.2.x | slf4j-api.jar , slf4j-log4j12.jar |
Log4J 1.3.x | slf4j-api.jar , slf4j-log4j13.jar |
java.util.logging | slf4j-api.jar , slf4j-jdk14.jar |
Commons Logging | slf4j-api.jar , slf4j-jcl.jar |
下面幾點還需要注意:
- 對於任意一種日志系統,slf4j-api.jar是必須的;
- 重要:在classpath上不能放置多於一個日志系統實現jar包(例如slf4j-log4j12.jar and slf4j-jdk14.jar),這將導致日志出席不可預知的行為;
- slf4j-api.jar 和 slf4j-<impl>.jar的版本應該是一致的。
如果SLF4J配置正確,你可以繼續配置你真正使用的日志系統(例如修改log4j.properties )。
重載Jakarta Commons Logging
SLF4J提供了一種機制可以使現有的應用程序從使用Jakarta Commons Logging變更為SLF4J而不需要修改代碼,只需要將commons-loggong JAR文件充classpath中除去,並將jcl104-over-slf4j.jar 加入到classpath中。
三、log4j范例
我們以log4j為例,然后將下面的代碼片段加入到log4j.properties中:
- # Set root logger level to DEBUG and its only appender to A1.
- log4j.rootLogger=DEBUG, A1
- # A1 is set to be a ConsoleAppender.
- log4j.appender.A1=org.apache.log4j.ConsoleAppender
- # A1 uses PatternLayout.
- log4j.appender.A1.layout=org.apache.log4j.PatternLayout
- log4j.appender.A1.layout.ConversionPattern=%-4r [%t] %-5p %c{1} %x - %m%n
我們將這個文件放置在工程的src目錄中,如果你使用IDE,當你測試代碼是,你實際上是想把這個文件放置在classpath上。
注意 :這里只是在IoAcceptor 上設置了日志,但slf4j 可以在程序中廣泛使用,有了它的幫助,你可以根據需要獲取到有用的信息。
下面我們編寫一個簡單的server從而生成一些日志信息,這里我們使用EchoServer 的范例工程來增加日志:
- public static void main(String[] args) throws Exception {
- IoAcceptor acceptor = new SocketAcceptor();
- DefaultIoFilterChainBuilder chain = acceptor.getFilterChain();
- LoggingFilter loggingFilter = new LoggingFilter();
- chain.addLast("logging", loggingFilter);
- acceptor.setLocalAddress(new InetSocketAddress(PORT));
- acceptor.setHandler(new EchoProtocolHandler());
- acceptor.bind();
- System.out.println("Listening on port " + PORT);
- }
正如你所見,在EchoServer 范例中,我們刪除了addLogger方法並新增加了兩行代碼。通過LoggingFilter 的引用,你可以在這里設置與IoAcceptor 相關的所有事件的日志級別。在這里,可以使用LoggingFilter 中的setLogLevel(IoEventType, LogLevel)方法來區分觸發IoHandler日志的時間以及對應的日志級別,下面是這個方法選項:
IoEventType |
Description |
SESSION_CREATED | 一個新的session被創建時觸發 |
SESSION_OPENED | 一個新的session打開時觸發 |
SESSION_CLOSED | 一個session被關閉時觸發 |
MESSAGE_RECEIVED | 接收到數據時觸發 |
MESSAGE_SENT | 數據被發送后觸發 |
SESSION_IDLE | 一個session空閑了一定時間后觸發 |
EXCEPTION_CAUGHT | 當有異常拋出時觸發 |
下面是日志級別的描述:
LogLevel |
Description |
NONE | 無論如何配置,日志都不會產生 |
TRACE | 在日志系統中創建一個TRACE事件 |
DEBUG | 在日志系統中生成debug信息 |
INFO | 在日志系統中生成提示信息 |
WARN | 在日志系統中生成警告信息 |
ERROR | 在日志系統中生成錯誤信息 |
根據這些信息,你應該可以擴展這些范例來構建一個使用日志的簡單系統,這些日志將為你提供有用的信息。
(5) 基本概念之IoBuffer
簡介
IoBuffer是MINA應用程序中使用的一種字節緩沖區,它是JDK中ByteBuffer類的替代品。MINA框架出於下面兩個原因沒有直接使用JDK中nio包內的ByteBuffe:
- 沒有提供可用的getters和putters方法,例如fill, get/putString, 和get/putAsciiInt();
- 由於它的容量是固定的,所以不利於存儲變長數據。
MINA 3 將改變這種情況。MINA框架對nio ByteBuffer做了一層封裝的最主要原因是希望能夠擁有一種可擴展的緩沖區。這並不是一個很好的決定。緩沖區就是緩沖區:一個用於存儲臨時數據的臨時空間,直到這些數據被使用。其實還有些其他的解決方案,例如可以對一組nio ByteBuffer進行包裝來避免數據從一個緩沖區向兩個容量更大的緩沖區復制,從而得到一個容量可擴展的緩沖區。
或許在filter之間傳遞數據時使用InputStrea來代替字節緩沖區會更加舒適,因為這不需要提供一種可以存儲數據的特性,這種數據結構可以使字節數組、字符串或者其他類型的消息等等。
最后,但並非最不重要的一點是,當前的實現並沒有達成一個目標:零拷貝策略(例如當我們從socket中讀取了一些數據,我們希望避免持續的數據拷貝)。如果我們使用了可以擴展的字節緩沖區,那么我們只需要在管理大數據消息時進行數據拷貝。請記住MINA ByteBuffer只不過是NIO ByteBuffer的頂層封裝,當我們使用direct buffers時,很可能是一個很嚴重的問題。
IoBuffer 操作
分配一個新的Buffer
IoBuffer 是一個抽象類,所以它不能直接被實例化。分配IoBuffer,我們可以使用兩種allocate()方法。
- // Allocates a new buffer with a specific size, defining its type (direct or heap)
- public static IoBuffer allocate(int capacity, boolean direct)
- // Allocates a new buffer with a specific size
- public static IoBuffer allocate(int capacity)
allocate()方法是用一個或兩個參數。第一種形式使用兩個參數:
- capacity - buffer的容量
- direct -buffer的類型。true 意味着得到一個direct buffer,false 意味着得到一個heap buffer
默認的buffer分配是由SimpleBufferAllocator 處理的。
可選的, 下面的形式也可以使用:
- IoBuffer buffer = IoBuffer.allocate(8);
- buffer.setAutoExpand(true);
- buffer.putString("12345678", encoder);
- // Add more to this buffer
- buffer.put((byte)10);
按照上面的例子,如果數據的長度大於8byte的話,IoBuffe會根據情況重新分配其內置的ByteBuffer,它的容量會被加倍,它的limit會增長到String被寫入時的最后position。這種行為與StringBuffer工作的方式十分類似。
注意:這種程序結構在MINA3.0時會被廢棄,因為這並不是增長buffer容量的最好方式。這種方式很可能被一種類似InputStream的方式所替代,在InputStream的背后很可能是一組固定長度的ByteBuffers。
創建自動收縮的Buffer
為了節省內存,在有些情形下我們需要釋放被額外分配的內存,IoBuffer提供了autoShrink 屬性來達到此目的。如果autoShrink屬性被打開,當compact()方法被調用時,IoBuffer回將部分的回收其容量,只使用四分之一或是更少的容量。如果需要手動控制收縮行為,請使用shrink()方法。
讓我們實踐一下:
- IoBuffer buffer = IoBuffer.allocate(16);
- buffer.setAutoShrink(true);
- buffer.put((byte)1);
- System.out.println("Initial Buffer capacity = "+buffer.capacity());
- buffer.shrink();
- System.out.println("Initial Buffer capacity after shrink = "+buffer.capacity());
- buffer.capacity(32);
- System.out.println("Buffer capacity after incrementing capacity to 32 = "+buffer.capacity());
- buffer.shrink();
- System.out.println("Buffer capacity after shrink= "+buffer.capacity());
我們初始化分配一個容量為16的buffer,並將自動收縮設置為true。
讓我們看一下輸出的結果:
- Initial Buffer capacity = 16
- Initial Buffer capacity after shrink = 16
- Buffer capacity after incrementing capacity to 32 = 32
- Buffer capacity after shrink= 16
讓我們分析一下輸出:
- 初始化buffer的容量為16,因為我們使用16指定了該buffer的容量,16也就成了該buffer的最小容量
- 調用shrink()方法后,容量仍舊為16,所以無論怎么調用緊縮方法,容量都不好小於其初始容量
- 增加該buffer的容量至32,該buffer的容量達到32
- 調用 shrink()方法,容量回收至16,從而剔除了冗余的容量
再次強調,這種方式是一種默認的行為,我們不需要明確指明一個buffer是否能被收縮。
Buffer分配
IoBufferAllocater負責分配並管理buffer,如果你希望使用你的方式精確控制分配行為,請自己實現IoBufferAllocater 接口。
MINA提供了IoBufferAllocater的兩種實現,如下:
- SimpleBufferAllocator (默認) - 每次創建一個新的buffer
- CachedBufferAllocator - 緩存buffer,使buffer在擴展時可以被重用
注意:在新版本的JVM中,使用cached IoBuffer並不能明顯提高性能。
你可以自己實現IoBufferAllocator接口並在IoBuffer上調用setAllocator()方法來指定使用你的實現。
(6) 基本概念之IoHandler
簡介
Handler用來處理MINA觸發的I/O事件。IoHandler是一個核心接口,它定義了Filter鏈末端需要的所有行為。IoHandler接口包含以下方法:
- sessionCreated
- sessionOpened
- sessionClosed
- sessionIdle
- exceptionCaught
- messageReceived
- messageSent
sessionCreated事件
一個新的connection被創建時,會觸發SessionCreated事件。對於TCP來說,這個事件代表連接的建立;對於UDP來說,它代表收到了一個UDP數據包。這個方法可以用作初始化session的各種屬性,也可以用來在一個新建的connection上觸發一些一次性的行為。
I/O processor線程會調用這個方法,所以在實現該方法時,只加入一些耗時較少的操作,因為I/O processor線程是用來處理多會話的。
sessionOpened事件
當一個connection打開時會觸發sessionOpened事件,這個事件永遠在sessionCreated之后觸發。如果配置了線程模式,那么這個方法會被非I/O processor線程調用。
sessionClosed事件
當一個session關閉的時候會觸發sessionClosed事件。可以將session的清理操作放在這個方法里進行。
sessionIdle事件
當一個session空閑的時候會觸發sessionIdle事件。當使用UDP時該方法將不會被調用。
exceptionCaught事件
當用戶代碼或MINA框架拋出異常時,會觸發事件事件。如果該異常是一個IOException,那么connection會被關閉。
messageReceived事件
當接收到消息的時候會觸發messageReceived事件。所有的業務處理代碼應該寫在這里,但要留心你所要的消息類型。
messageSent事件
當消息已被遠端接收到的時候,會觸發messageSent事件(調用IoSession.write()發送消息)。
(7)傳輸特性之串口
使用MINA2.0,你可以像編寫基於TCP/IP的程序那樣編寫基於串口的程序。
獲得MINA2.0
MINA 2.0的最終版本還沒有release,但是你可以下載最新的版本。如果你希望從trunk構建代碼,可以參考開發者指南。
前提
在訪問串口之前,Java應用程序需要一個native庫。MINA使用ftp://ftp.qbang.org/pub/rxtx/rxtx-2.1-7-bins-r2.zip,請把它放到你的JDK或JRE的lib/i386/下,並在程序啟動的命令行中加入-Djava.library.path=來指定你的native庫的位置。
連接到串口
串口通訊通過IoConnector來實現,這是有通訊媒介的點對點特性來決定的。我們假定你已經通過MINA的教程了解到了IoConnector的相關知識。連接到串口需要SerialConnector:
- // create your connector
- IoConnector connector = new SerialConnector()
- connector.setHandler( ... here your buisness logic IoHandler ... );
與SocketConnector,並沒有什么不同。讓我們創建一個地址來連接串口:
- SerialAddress portAddress=new SerialAddress( "/dev/ttyS0", 38400, 8, StopBits.BITS_1, Parity.NONE, FlowControl.NONE );
第一個參數代表串口的標識符。對於Windows系統,串口一般叫做"COM1"、"COM2"以此類推,對於Linux或者一些Unix系統,通常由"/dev/ttyS0"、"/dev/ttyS1"、"/dev/ttyUSB0"來表示。
剩下的參數取決於你的硬件設備的連接特性。
- 波特率
- 數據位數
- 奇偶校驗
- 流控制機制
當這些都具備,就可以連接到該地址:
- ConnectFuture future = connector.connect( portAddress );
- future.await();
- IoSession sessin = future.getSession();
其他的事情和使用TCP協議等一樣,你可以加入你的filters和codecs。
(8)傳輸特性之UDP
該教程可以幫助你使用MINA框架編寫基於UDP的Socket應用程序。在這篇教程中,我們將編寫一個server端程序,server可以通過連接該程序來展現client端程序的內存使用情況。現實中的很多程序都已經具備與該程序類似的功能,可以監控程序來內存使用情況。
構建代碼
MINA 2.0的最終版本還沒有release,但是你可以下載最新的版本。如果你希望從trunk構建代碼,可以參考開發者指南。
應用程序
如上圖所示,該server在端口18567監聽,client端連接server端並向server端發送內存使用數據。在上圖所展示應用中,共有兩個client連接到了server。
程序執行流程
- server在18567端口監聽客戶端請求;
- client連接server端,server會在Session Created事件處理時增加一個Tab頁;
- clients向server發送內存使用數據;
- server端把接收到的數據展現在Tab上。
Server端代碼分析
我們可以在MINA的示例代的org.apache.mina.example.udp包下找到這些代碼。對於每個實例,我們只需要關系MINA相關的代碼。
要想構建該server,我們需要做如下操作:
- 創建一個數據報socket,監聽client端請求。(請參考MemoryMonitor.java);
- 創建一個IoHandler來處理MINA框架生成的各種事件。(請參考MemoryMonitorHandler.java)。
第一步可以通過該程序片來實現:
- NioDatagramAcceptor acceptor = new NioDatagramAcceptor();
- acceptor.setHandler(new MemoryMonitorHandler(this));
這里,我們創建一個NioDatagramAcceptor來監聽client端請求,並且設置它的IoHandler。變量"PORT"是一個int值。下一步我們家filter鏈中加入了一個logging filter。LoggingFilter是一個非常不錯的記錄日志的方式,它可以在很多節點處生成日志信息,這些熱值能夠展現出MINA框架的工作方式。
- DefaultIoFilterChainBuilder chain = acceptor.getFilterChain();
- chain.addLast("logger", new LoggingFilter());
下一步我們深入一下UDP傳輸所特有的代碼。我們設置acceptor可以重用address。
- DatagramSessionConfig dcfg = acceptor.getSessionConfig();
- dcfg.setReuseAddress(true);acceptor.bind(new InetSocketAddress(PORT));
當然,最后一件事情是調用bind()方法來綁定端口。
IoHandler實現
Server端主要關心如下三個事件:
- Session創建
- Message接收
- Session關閉
詳細代碼如下:
Session Created Event
- @Override
- public void sessionCreated(IoSession session) throws Exception {
- SocketAddress remoteAddress = session.getRemoteAddress();
- server.addClient(remoteAddress);
- }
在session創建事件中,我們調用addClient()方法來增加一個Tab頁。
Message Received Event
- @Override
- public void messageReceived(IoSession session, Object message) throws Exception {
- if (message instanceof IoBuffer) {
- IoBuffer buffer = (IoBuffer) message;
- SocketAddress remoteAddress = session.getRemoteAddress();
- server.recvUpdate(remoteAddress, buffer.getLong());
- }
- }
在message接收事件中,我們從接收到的消息中得到所關心的內存使用的數據;應用程序還需要發送響應信息。在這個方法中,處理消息和發送響應都是通過session完成的。
Session Closed Event
- @Override
- public void sessionClosed(IoSession session) throws Exception {
- System.out.println("Session closed...");
- SocketAddress remoteAddress = session.getRemoteAddress();
- server.removeClient(remoteAddress);
- }
在session關閉事件中,我們需要刪除對應的Tab頁。
Client端代碼分析
在這一節,我們將解釋一下客戶端代碼。實現客戶端我們需要進行如下操作:
- 創建Socket並連接到server端
- 設置IoHandler
- 收集內存使用信息
- 發送數據到server端
我們從MemMonClient.java開始,可以在org.apache.mina.example.udp.client包下找到它。開始的幾行代碼非常簡單:
- connector = new NioDatagramConnector();
- connector.setHandler( this );
- ConnectFuture connFuture = connector.connect( new InetSocketAddress("localhost", MemoryMonitor.PORT ));
這里我們創建了一個NioDatagramConnector,設置了它的handler並且連接到server端。我們必須在InetSocketAddress對象中設定host,否則程序不能正常運行。該程序是在Windows XP環境下開發運行的,所以與其他環境可能存在差別。下一步我們等待確認client已經連接到server端,一旦連接建立,我們可以開始向server端發送數據。代碼如下:
- connFuture.addListener( new IoFutureListener(){
- public void operationComplete(IoFuture future) {
- ConnectFuture connFuture = (ConnectFuture)future;
- if( connFuture.isConnected() ){
- session = future.getSession();
- try {
- sendData();
- } catch (InterruptedException e) {
- e.printStackTrace();
- }
- } else {
- log.error("Not connected...exiting");
- }
- }
- });
這里我們在ConnectFuture對象中加入一個listener,當client連接到server端時,operationComplete方法將被回調,這時我們開始發送數據。我們通過調用sendData方法向server端發送數據,該方法如下:
- private void sendData() throws InterruptedException {
- for (int i = 0; i < 30; i++) {
- long free = Runtime.getRuntime().freeMemory();
- IoBuffer buffer = IoBuffer.allocate(8);
- buffer.putLong(free);
- buffer.flip();
- session.write(buffer);
- try {
- Thread.sleep(1000);
- } catch (InterruptedException e) {
- e.printStackTrace();
- throw new InterruptedException(e.getMessage());
- }
- }
- }
該方法會在30秒內每秒向server端發送當前的剩余內存Cincinnati。你可以看到我們分配了一個足夠大的IoBuffer來裝載一個long型的變量。最后這個buffer被flipped並發送至server端。
(9)傳輸特性之APR
簡介
APR(Apache portable Run-time libraries,Apache可移植運行庫)的目的如其名稱一樣,主要為上層的應用程序提供一個可以跨越多操作系統平台使用的底層支持接口庫。MINA目前也能夠支持APR。本章我們將討論一下使用MINA進行APR傳輸的基本過程。我們使用Time Server為例。
前提
APR傳輸依賴於下列組件:
- APR運行庫 - 從http://www.apache.org/dist/tomcat/tomcat-connectors/native/處下載並安裝適當版本。
- JNI封裝 - tomcat-apr-5.5.23.jar包含該release,將本地庫加入路徑。
使用APR傳輸
請參考Time Server例子的完整代碼。基於NIO的Time server實現如下列代碼所示:
- IoAcceptor acceptor = new NioSocketAcceptor();
- acceptor.getFilterChain().addLast( "logger", new LoggingFilter() );
- acceptor.getFilterChain().addLast( "codec", new ProtocolCodecFilter( new TextLineCodecFactory( Charset.forName( "UTF-8" ))));
- acceptor.setHandler( new TimeServerHandler() );
- acceptor.getSessionConfig().setReadBufferSize( 2048 );
- acceptor.getSessionConfig().setIdleTime( IdleStatus.BOTH_IDLE, 10 );
- acceptor.bind( new InetSocketAddress(PORT) );
如何使用APR傳輸如下列代碼所示:
- IoAcceptor acceptor = new AprSocketAcceptor();
- acceptor.getFilterChain().addLast( "logger", new LoggingFilter() );
- acceptor.getFilterChain().addLast( "codec", new ProtocolCodecFilter( new TextLineCodecFactory( Charset.forName( "UTF-8" ))));
- acceptor.setHandler( new TimeServerHandler() );
- acceptor.getSessionConfig().setReadBufferSize( 2048 );
- acceptor.getSessionConfig().setIdleTime( IdleStatus.BOTH_IDLE, 10 );
- acceptor.bind( new InetSocketAddress(PORT) );
我們只是將NioSocketAcceptor改為AprSocketAcceptor,只通過這一個小改動,我們的程序就可以支持APR傳輸,其余的代碼與之前都是相同的。
(10)與Spring整合
我們通過這篇文章來介紹如何與Spring框架整合MINA應用。
程序結構
我們將編寫一個簡單的MINA應用程序,其組成包括:
- 一個Handler
- 兩個Filter - Logging Filter和ProtocolCodec Filter
- 數據報Socket
初始化代碼
讓我們先看一下代碼。為了簡化,我們做了一些省略。
- public void initialize() throws IOException {
- // Create an Acceptor
- NioDatagramAcceptor acceptor = new NioDatagramAcceptor();
- // Add Handler
- acceptor.setHandler(new ServerHandler());
- acceptor.getFilterChain().addLast("logging", new LoggingFilter());
- acceptor.getFilterChain().addLast("codec", new ProtocolCodecFilter(new SNMPCodecFactory()));
- // Create Session Configuration
- DatagramSessionConfig dcfg = acceptor.getSessionConfig();
- dcfg.setReuseAddress(true);
- logger.debug("Starting Server......");
- // Bind and be ready to listen
- acceptor.bind(new InetSocketAddress(DEFAULT_PORT));
- logger.debug("Server listening on "+DEFAULT_PORT);
- }
整合過程
與Spring框架整合,我們需要以下操作:
- 設置IO handler
- 創建Filters並將它們加入到Filter鏈
- 創建Socket並設置相關參數
注意 :如同MINA之前的release,最近release的版本中並沒有Spring特定的包,目前這個包叫做Integration Beans,它用來實現與所有的DI框架整合而不僅限於Spring。
讓我們看一下Spring的xml文件。我刪除了通用部分,只保留了與我們實現整合相關的內容。這個例子脫胎於MINA實例中的Chat應用,請參考該實例中完整的xml文件。現在我們開始整合,首先是定義IO Handler:
- <!-- The IoHandler implementation -->
- <bean id="trapHandler" class="com.ashishpaliwal.udp.mina.server.ServerHandler" />
然后創建Filter鏈:
- <bean id="snmpCodecFilter" class="org.apache.mina.filter.codec.ProtocolCodecFilter">
- <constructor-arg>
- bean class="com.ashishpaliwal.udp.mina.snmp.SNMPCodecFactory" />
- </constructor-arg>
- </bean>
- <bean id="loggingFilter" class="org.apache.mina.filter.logging.LoggingFilter" />
- <!-- The filter chain. -->
- <bean id="filterChainBuilder" class="org.apache.mina.core.filterchain.DefaultIoFilterChainBuilder">
- <property name="filters">
- <map>
- <entry key="loggingFilter" value-ref="loggingFilter"/>
- <entry key="codecFilter" value-ref="snmpCodecFilter"/>
- </map>
- </property>
- </bean>
這里,我們創佳了自己的IoFilter實例。對於ProtocolCodec來說,注入SNMPCodecFactory時我們使用了構造注入。Logging Filter是被直接創建的,沒有注入其他屬性。一旦我們定義了所有filters的bean定義,我們就可以將它們組裝成Filter鏈。我們定義一個id為FilterChainBuidler的bean,然后將定義好的filter bean注入其中。萬事俱備了,我們只差創建Socket並調用bind()方法。
讓我們完成最后一部分,創建Socket並使用Filter鏈:
- <bean class="org.springframework.beans.factory.config.CustomEditorConfigurer">
- <property name="customEditors">
- <map>
- <entry key="java.net.SocketAddress">
- <bean class="org.apache.mina.integration.beans.InetSocketAddressEditor" />
- </entry>
- </map>
- </property>
- </bean>
- <!-- The IoAcceptor which binds to port 161 -->
- <bean id="ioAcceptor" class="org.apache.mina.transport.socket.nio.NioDatagramAcceptor" init-method="bind" destroy-method="unbind">
- <property name="defaultLocalAddress" value=":161" />
- <property name="handler" ref="trapHandler" />
- <property name="filterChainBuilder" ref="filterChainBuilder" />
- </bean>
我們創建了ioAcceptor,注入了IO handler和Filter鏈。現在我們需要編寫一個方法去讀取Spring的xml文件並啟動應用,代碼如下:
- public void initializeViaSpring() throws Exception {
- new ClassPathXmlApplicationContext("trapReceiverContext.xml");
- }
現在我們需要從main方法運行程序,MINA應用便可以初始化。