本文將通過一個簡單的問候程序 HelloServer 來介紹 MINA 的基礎架構的同時演示如何使用 MINA 開發網絡應用程序。
Apache MINA(Multipurpose Infrastructure for Network Applications) 是 Apache 組織一個較新的項目,它為開發高性能和高可用性的網絡應用程序提供了非常便利的框架。當前發行的 MINA 版本支持基于 Java NIO 技術的 TCP/UDP 應用程序開發、串口通訊程序(只在最新的預覽版中提供),MINA 所支持的功能也在進一步的擴展中。
目 前正在使用 MINA 的軟件包括有:Apache Directory Project、AsyncWeb、AMQP(Advanced Message Queuing Protocol)、RED5 Server(Macromedia Flash Media RTMP)、ObjectRADIUS、Openfire 等等。
本文將通過一個簡單的問候程序 HelloServer 來介紹 MINA 的基礎架構的同時演示如何使用MINA 開發網絡應用程序。
- 首先到官方網站下載最新的 MINA 版本,地址是:http://mina.apache.org/downloads.html。 下載之前先介紹一下 MINA 的兩個版本:1.0.x 適合運行環境為 JDK1.4,1.1.x 適合 JDK1.5 的版本,兩者的編譯環境都需要 JDK1.5。JDK1.5 已經是非常普遍了,本文中使用 1.1.5 版本的 MINA,編譯和運行所需的文件是 mina-core-1.1.5.jar。
- 下載 MINA 的依賴包 slf4j。MINA 使用此項目作為日志信息的輸出,而 MINA 本身并不附帶此項目包,請到http://www.slf4j.org/download.html 地址下載 slf4j 包,slf4j 項目解壓后有很多的文件,本例中只需要其中的 slf4j-api-1.4.3.jar 和 slf4j-simple-1.4.3.jar 這兩個 jar 文件。如果沒有這兩個文件就會導致啟動例子程序的時候報 org/slf4j/LoggerFactory 類沒找到的錯誤。
- 當然要求機器上必須裝有 1.5 或者更新版本的 JDK。
- 最好你應該選擇一個順手的 Java 開發環境例如 Eclipse 或者 NetBeans 之類的,可以更方便的編碼和調試,雖然我們的最低要求只是一個簡單的文本編輯器而已。
![]() ![]() |
![]()
|
- 編寫代碼 HelloServer.java 如下
-
package demo.mina.echo;
import java.io.IOException;
import java.net.InetSocketAddress;
import org.apache.mina.common.*;
import org.apache.mina.transport.socket.nio.*;
import org.apache.mina.filter.codec.ProtocolCodecFilter;
import org.apache.mina.filter.codec.textline.TextLineCodecFactory;
/**
* HelloServer演示程序
* @author liudong (
http://www.dlog.cn/javayou
)
*/
public class HelloServer {
private static final int PORT = 8080;
/**
* @param args
* @throws IOException
*/
public static void main(String[] args) throws IOException {
IoAcceptor acceptor = new SocketAcceptor();
IoAcceptorConfig config = new SocketAcceptorConfig();
DefaultIoFilterChainBuilder chain = config.getFilterChain();
//使用字符串編碼
chain.addLast("codec",
new ProtocolCodecFilter(new TextLineCodecFactory()));
//啟動HelloServer
acceptor.bind(new InetSocketAddress(PORT), new HelloHandler(), config);
System.out.println("HelloServer started on port " + PORT);
}
}
/**
* HelloServer的處理邏輯
* @author liudong
*/
class HelloHandler extends IoHandlerAdapter {
/**
* 當有異常發生時觸發
*/
@Override
public void exceptionCaught(IoSession ssn, Throwable cause) {
cause.printStackTrace();
ssn.close();
}
/**
* 有新連接時觸發
*/
@Override
public void sessionOpened(IoSession ssn) throws Exception {
System.out.println("session open for " + ssn.getRemoteAddress());
}
/**
* 連接被關閉時觸發
*/
@Override
public void sessionClosed(IoSession ssn) throws Exception {
System.out.println("session closed from " + ssn.getRemoteAddress());
}
/**
* 收到來自客戶端的消息
*/
public void messageReceived(IoSession ssn, Object msg) throws Exception {
String ip = ssn.getRemoteAddress().toString();
System.out.println("===> Message From " + ip +" : " + msg);
ssn.write("Hello " + msg);
}
}
- 編譯執行
先不用試著去讀懂每一行代碼的具體意思,用你順手的編譯器編譯 HelloServer.java,如果報錯請確認是否已將前面提到的三個 jar 文件添加至類路徑中。如果一切順利接著就可以啟動HelloServer 程序,啟動后提示:HelloServer started on port 8080
表示啟動成功,如果啟動失敗,問題無外乎是類沒找到或者端口占用。如果端口被占用的話,換一個羅,修改 PORT
常量值后再次編譯并啟動。
- 測試服務器
打開命令行窗口,輸入 telnet localhost 8080 后,輸入您的英文名或者其他一些亂七八糟的字符后回車再去看看剛啟動的服務程序有何反應。我的反應如下:
HelloServer started on port 8080 |
好了,一切正常,恭喜你的第一個使用 MINA
開發的網絡程序已經成功運行了。
![]() ![]() |
![]()
|
在介紹架構之前先認識幾個接口:
IoAccepter 相當于網絡應用程序中的服務器端
IoConnector 相當于客戶端
IoSession 當前客戶端到服務器端的一個連接實例
IoHandler 業務處理邏輯
IoFilter 過濾器用于懸接通訊層接口與業務層接口
![]() ![]() |
![]()
|
下圖是 MINA
的架構圖,
圖 1:MINA 的架構圖

在 圖中的模塊鏈中,IoService 便是應用程序的入口,相當于我們前面代碼中的 IoAccepter,IoAccepter 便是 IoService 的一個擴展接口。IoService 接口可以用來添加多個 IoFilter,這些 IoFilter 符合責任鏈模式并由 IoProcessor 線程負責調用。而 IoAccepter 在 ioService 接口的基礎上還提供綁定某個通訊端口以及取消綁定的接口。在上面的例子中,我們是這樣使用 IoAccepter 的:
IoAcceptor acceptor = new SocketAcceptor(); |
相當于我們使用了 Socket 通訊方式作為服務的接入,當前版本的 MINA 還提供了除 SocketAccepter 外的基于數據報文通訊的 DatagramAccepter 以及基于管道通訊的 VmPipeAccepter。另外還包括串口通訊接入方式,目前基于串口通訊的接入方式已經在最新測試版的 MINA 中提供。你也可以自行實現 IoService 接口來使用自己的通訊方式。
而在上圖中最右端也就是 IoHandler,這便是業務處理模塊。相當于前面例子中的 HelloHandler 類。在業務處理類中不需要去關心實際的通訊細節,只管處理客戶端傳輸過來的信息即可。編寫 Handler 類就是使用 MINA 開發網絡應用程序的重心所在,相當于 MINA 已經幫你處理了所有的通訊方面的細節問題。為了簡化 Handler 類,MINA 提供了 IoHandlerAdapter 類,此類僅僅是實現了 IoHandler 接口,但并不做任何處理。
一個 IoHandler 接口中具有如下一些方法(摘自 MINA 的 API 文檔):
void exceptionCaught(IoSession session, Throwable cause) 當接口中其他方法拋出異常未被捕獲時觸發此方法 |
void messageReceived(IoSession session, Object message) 當接收到客戶端的請求信息后觸發此方法. |
void messageSent(IoSession session, Object message) 當信息已經傳送給客戶端后觸發此方法. |
void sessionClosed(IoSession session) 當連接被關閉時觸發,例如客戶端程序意外退出等等. |
void sessionCreated(IoSession session) 當一個新客戶端連接后觸發此方法. |
void sessionIdle(IoSession session, IdleStatus status) 當連接空閑時觸發此方法. |
void sessionOpened(IoSession session) 當連接后打開時觸發此方法,一般此方法與 sessionCreated 會被同時觸發 |
前面我們提到 IoService 是負責底層通訊接入,而 IoHandler 是負責業務處理的。那么 MINA 架構圖中的 IoFilter 作何用途呢?答案是你想作何用途都可以。但是有一個用途卻是必須的,那就是作為 IoService 和 IoHandler 之間的橋梁。IoHandler 接口中最重要的一個方法是 messageReceived,這個方法的第二個參數是一個 Object 型的消息,總所周知,Object 是所有 Java 對象的基礎,那到底誰來決定這個消息到底是什么類型呢?答案也就在這個 IoFilter 中。在前面使用的例子中,我們添加了一個 IoFilter 是 new ProtocolCodecFilter(new TextLineCodecFactory()),這個過濾器的作用是將來自客戶端輸入的信息轉換成一行行的文本后傳遞給 IoHandler,因此我們可以在 messageReceived 中直接將 msg 對象強制轉換成 String 對象。
而 如果我們不提供任何過濾器的話,那么在 messageReceived 方法中的第二個參數類型就是一個 byte 的緩沖區,對應的類是 org.apache.mina.common.ByteBuffer。雖然你也可以將解析客戶端信息放在 IoHandler 中來做,但這并不是推薦的做法,使原來清晰的模型又模糊起來,變得 IoHandler 不只是業務處理,還得充當協議解析的任務。
MINA自身帶有一些常用的過濾器,例如LoggingFilter(日志記錄)、BlackListFilter(黑名單過濾)、CompressionFilter(壓縮)、SSLFilter(SSL加密)等。
![]() ![]() |
![]()
|
MINA 不僅僅是用來開發網絡服務器端應用程序,它一樣可以使用 IoConnector 來連接到各種各樣的網絡服務程序。
通 過本文中 HelloServer 這個例子,我們在驚嘆 MINA 可以帶來多么大便利的同時,還不得不為其卓越的性能而驕傲,據稱使用MINA開發服務器程序的性能已經逼近使用 C/C++ 語言開發的網絡服務。作為 MINA 的入門文章,性能問題不在本文討論范圍內。
另外在 MINA 壓縮包中附帶有不少比 HelloServer 要好得多的例子,通過這些例子可以進一步的了解并掌握 MINA。
http://mina.apache.org MINA 官方網站
MINA 框架簡介
1。MINA 框架簡介
當客戶首次訪問采用MINA編寫的程序時,IoAcceptor作為線程運行,負責接受來自客戶的請求。當有客戶請求連接時,創建一個 Session,該Session與IoProcessor、SocketChannel以及IOService聯系起來。IoProcessor也作為 另外一個線程運行,定時檢查客戶是否有數據到來,并對客戶請求進行處理,依次調用在IOService注冊的各個IoFilter,最后調用 IoHandler進行最終的邏輯處理,再將處理后的結果Filter后返回給客戶端。
2。IoSession
Session可以理解為服務器與客戶端的特定連接,該連接由服務器地址、端口以及客戶端地址、端口來決定。客戶端發起請求時,指定服務器地址和端口,客戶端也會指定或者根據網絡路由信息自動指定一個地址、自動分配一個端口。這個地址、端口對構成一個Session。
Session是服務器端對這種連接的抽象,MINA對其進行了封裝,定義了IoSession接口,用來代表客戶端與服務器的連接,在服務器端來 指代客戶端,實現對客戶端的操作、綁定與客戶端有關的信息與對象。通過利用Session的這個概念,編寫程序時就可以在服務器端非常方便地區分出是當前 處理的是哪個客戶端的請求、維持客戶端的狀態信息、可以實現客戶端之間相互通訊。
IoSession提供以下一些常用方法:
(1)setAttribute(Object key, Object value) getAttribute(Object key)
設置/獲取用戶定義的屬性。
將該屬性與session聯系起來,方便以后處理用戶請求時使用。比如如果要求用戶登錄后才能繼續進行操作,那么在用戶成功登陸后,可以通過 setAttribute()設置一個屬性,當用戶以后繼續請求時,可以通過getAttribute()獲取該屬性來判斷用戶是否登錄。
(2)getRemoteAddress()
獲取遠程客戶端地址。
(3)getId() getCreationTime() getLastIoTime() getConfig()
獲取Session的Id、創建時間、上次IO時間、配置信息。
(4)write(Object message)
將數據發送給客戶端。
(5)close()
關閉Session。
說明:可以在Session中發送數據,但是Session沒有提供讀取數據的方法,讀取數據通過另一套機制在IoHandler的messageReceived()中實現。
3。Event
MINA可以看成是事件驅動的。通常在網絡通訊中,可以將整個過程劃分為幾個基本的階段,如建立連接、數據通信、關閉連接。
數據通信一般包括數據的發送和接收,由于在通信過程中,可能要多次發送和接收數據,以進行不同的業務交互。
不可能一直都接收和發送數據,因此就有Idle出現,在MINA中,如果在設定的時間內沒有數據發送或接收,那么就會觸發一個Idle事件。
由于某種原因,可能會發生錯誤,導致系統異常發生,引發exception。
因此,如果從事件發生的角度看的話,就可以在MINA中將通信看成由一個建立鏈接(sessionCreated 和 sessionOpened )、多個數據接收和發送、一個關閉連接事件以及多個Idle事件等7種事件組成的過程。
Session是對雙方相互通信的抽象,因此通信的過程就是一系列與Session相關的事件。
在MINA現在對TCP的實現中,sessionCreated 和 sessionOpened 沒有區別。因此嚴格來說,有6種類型的事件。
4。IoHandler
從以上MINA框架簡圖可以看出,對來自客戶端數據最終處理是在IoHandler中處理的。IoHandler封裝了來自客戶端不同事件的處理, 如果對某個事件感興趣,可以實現相應的方法,當該事件發生時,IoHandler中的方法就會被觸發執行。IoHandler總共有7個方法對應7個事 件:
(1)void exceptionCaught(IoSession session, Throwable cause)
有異常發生時被觸發。
(2)void messageReceived(IoSession session, Object message)
有消息到達時被觸發,message代表接收到的消息。
(3)void messageSent(IoSession session, Object message)
發送消息時時被觸發,即在調用IoSession.write()時被觸發,message代表將要發送的消息。
(4)void sessionClosed(IoSession session)
當連接關閉時被觸發,即Session終止時被觸發。
(5)void sessionCreated(IoSession session)
當創建一個新連接時被觸發,即當開始一個新的Session時被觸發。
(6)void sessionIdle(IoSession session, IdleStatus status)
當連接空閑時被觸發。使用IoSessionConfig中的setIdleTime(IdleStatus status, int idleTime)方法可以設置session的空閑時間。如果該Session的空閑時間超過設置的值,該方法被觸發,可以通過 session.getIdleCount(status)來獲取sessionIdle被觸發的次數。
(7)void sessionOpened(IoSession session)
當打開一個連接時被觸發。在目前的實現中,好像 sessionOpened 和 sessionCreated 沒有太大區別,sessionCreated 在 sessionOpened 之前被觸發。
IoHandler是一個接口,一般情況沒有必要直接實現該接口的每一個方法。MINA提供了一個IoHandlerAdapter類,該類實現了 IoHandler要求的方法,但是都沒有做任何處理。當我們要編寫自己的Handler時,可以擴展IoHandlerAdapter,重寫我們關心的 事件方法即可。比如,一般情況,我們比較關心是否接收到數據這個時間,那么我們就可以覆蓋messageReceived方法,不用管其他方法。
5。IoFilter
IoFilter用來對客戶的請求或發送給客戶的數據進行filter。與IoHandler一樣,Filter也是基于事件的,通過實現IoFilter接口,就可以對通信過程中的Session的事件進行處理。
Filter是一種鏈式結構,與IoHandler不同,處理每一種Session事件的函數中,除了傳入session對象外,還傳入了 NextFilter對象,用來代表下一個Filter。一般情況,在處理結束后,調用下一個filter的相應方法作進一步處理。Filter也可以針 對特定的通信或數據,不進行進一步處理,就可以不用調用NextFilter的相應方法。
除了與Session相應的7種事件外,在IoFilter中還可以對Filter的init、destroy以及add、remove等時間愛女作出處理。
MINA提供了一個IoFilterAdapter類,我們要編寫自己的Filter時,可以擴展IoFilterAdapter,不用直接實現IoFilter接口。
Apache MINA提供一個LoggingFilter類,用來log通信過程。