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

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