日韩黑丝制服一区视频播放|日韩欧美人妻丝袜视频在线观看|九九影院一级蜜桃|亚洲中文在线导航|青草草视频在线观看|婷婷五月色伊人网站|日本一区二区在线|国产AV一二三四区毛片|正在播放久草视频|亚洲色图精品一区

分享

使用 Apache MINA 2 開(kāi)發(fā)網(wǎng)絡(luò)應(yīng)用

 gyb98 2010-12-11

 

簡(jiǎn)介: Apache MINA 2 是一個(gè)開(kāi)發(fā)高性能和高可伸縮性網(wǎng)絡(luò)應(yīng)用程序的網(wǎng)絡(luò)應(yīng)用框架。它提供了一個(gè)抽象的事件驅(qū)動(dòng)的異步 API,可以使用 TCP/IP、UDP/IP、串口和虛擬機(jī)內(nèi)部的管道等傳輸方式。Apache MINA 2 可以作為開(kāi)發(fā)網(wǎng)絡(luò)應(yīng)用程序的一個(gè)良好基礎(chǔ)。本文將介紹 Apache MINA 2 的基本概念和 API,包括 I/O 服務(wù)、I/O 會(huì)話(huà)、I/O 過(guò)濾器和 I/O 處理器。另外還將介紹如何使用狀態(tài)機(jī)。本文包含簡(jiǎn)單的計(jì)算器服務(wù)和復(fù)雜的聯(lián)機(jī)游戲兩個(gè)示例應(yīng)用。

發(fā)布日期: 2009 年 12 月 03 日
級(jí)別: 中級(jí)
訪(fǎng)問(wèn)情況 792 次瀏覽
建議: 0 (添加評(píng)論)

1 star2 stars3 stars4 stars5 stars 平均分 (共 0 個(gè)評(píng)分 )

Apache MINA 2 是一個(gè)開(kāi)發(fā)高性能和高可伸縮性網(wǎng)絡(luò)應(yīng)用程序的網(wǎng)絡(luò)應(yīng)用框架。它提供了一個(gè)抽象的事件驅(qū)動(dòng)的異步 API,可以使用 TCP/IP、UDP/IP、串口和虛擬機(jī)內(nèi)部的管道等傳輸方式。Apache MINA 2 可以作為開(kāi)發(fā)網(wǎng)絡(luò)應(yīng)用程序的一個(gè)良好基礎(chǔ)。下面將首先簡(jiǎn)單介紹一下 Apache MINA 2。

Apache MINA 2 介紹

Apache MINA 是 Apache 基金會(huì)的一個(gè)開(kāi)源項(xiàng)目,目前最新的版本是 2.0.0-RC1。本文中使用的版本是 2.0.0-M6。從 參考資料 中可以找到相關(guān)的下載信息。下面首先介紹基于 Apache MINA 的網(wǎng)絡(luò)應(yīng)用的一般架構(gòu)。

基于 Apache MINA 的網(wǎng)絡(luò)應(yīng)用的架構(gòu)

基于 Apache MINA 開(kāi)發(fā)的網(wǎng)絡(luò)應(yīng)用,有著相似的架構(gòu)。圖 1 中給出了架構(gòu)的示意圖。


圖 1. 基于 Apache MINA 的網(wǎng)絡(luò)應(yīng)用的架構(gòu)
基于 apache mina 的網(wǎng)絡(luò)應(yīng)用的架構(gòu)

圖 1 所示,基于 Apache MINA 的網(wǎng)絡(luò)應(yīng)用有三個(gè)層次,分別是 I/O 服務(wù)、I/O 過(guò)濾器和 I/O 處理器:

  • I/O 服務(wù):I/O 服務(wù)用來(lái)執(zhí)行實(shí)際的 I/O 操作。Apache MINA 已經(jīng)提供了一系列支持不同協(xié)議的 I/O 服務(wù),如 TCP/IP、UDP/IP、串口和虛擬機(jī)內(nèi)部的管道等。開(kāi)發(fā)人員也可以實(shí)現(xiàn)自己的 I/O 服務(wù)。
  • I/O 過(guò)濾器:I/O 服務(wù)能夠傳輸?shù)氖亲止?jié)流,而上層應(yīng)用需要的是特定的對(duì)象與數(shù)據(jù)結(jié)構(gòu)。I/O 過(guò)濾器用來(lái)完成這兩者之間的轉(zhuǎn)換。I/O 過(guò)濾器的另外一個(gè)重要作用是對(duì)輸入輸出的數(shù)據(jù)進(jìn)行處理,滿(mǎn)足橫切的需求。多個(gè) I/O 過(guò)濾器串聯(lián)起來(lái),形成 I/O 過(guò)濾器鏈。
  • I/O 處理器:I/O 處理器用來(lái)執(zhí)行具體的業(yè)務(wù)邏輯。對(duì)接收到的消息執(zhí)行特定的處理。

創(chuàng)建一個(gè)完整的基于 Apache MINA 的網(wǎng)絡(luò)應(yīng)用,需要分別構(gòu)建這三個(gè)層次。Apache MINA 已經(jīng)為 I/O 服務(wù)和 I/O 過(guò)濾器提供了不少的實(shí)現(xiàn),因此這兩個(gè)層次在大多數(shù)情況下可以使用已有的實(shí)現(xiàn)。I/O 處理器由于是與具體的業(yè)務(wù)相關(guān)的,一般來(lái)說(shuō)都是需要自己來(lái)實(shí)現(xiàn)的。

事件驅(qū)動(dòng)的 API

Apache MINA 提供的是事件驅(qū)動(dòng)的 API。它把與網(wǎng)絡(luò)相關(guān)的各種活動(dòng)抽象成事件。網(wǎng)絡(luò)應(yīng)用只需要對(duì)其感興趣的事件進(jìn)行處理即可。事件驅(qū)動(dòng)的 API 使得基于 Apache MINA 開(kāi)發(fā)網(wǎng)絡(luò)應(yīng)用變得比較簡(jiǎn)單。應(yīng)用不需要考慮與底層傳輸相關(guān)的具體細(xì)節(jié),而只需要處理抽象的 I/O 事件。比如在實(shí)現(xiàn)一個(gè)服務(wù)端應(yīng)用的時(shí)候,如果有新的連接進(jìn)來(lái),I/O 服務(wù)會(huì)產(chǎn)生 sessionOpened這樣一個(gè)事件。如果該應(yīng)用需要在有連接打開(kāi)的時(shí)候,執(zhí)行某些特定的操作,只需要在 I/O 處理器中此事件處理方法 sessionOpened中添加相應(yīng)的代碼即可。

在介紹 Apache MINA 中的基本概念的細(xì)節(jié)之前,首先通過(guò)一個(gè)簡(jiǎn)單的應(yīng)用來(lái)熟悉上面提到的三個(gè)層次的具體職責(zé)。


從簡(jiǎn)單應(yīng)用開(kāi)始

在使用 Apache MINA 開(kāi)發(fā)復(fù)雜的應(yīng)用之前,首先將介紹一個(gè)簡(jiǎn)單的應(yīng)用。通過(guò)此應(yīng)用可以熟悉上面提到的三個(gè)層次,即 I/O 服務(wù)、I/O 過(guò)濾器和 I/O 處理器。該應(yīng)用是一個(gè)簡(jiǎn)單的計(jì)算器服務(wù),客戶(hù)端發(fā)送要計(jì)算的表達(dá)式給服務(wù)器,服務(wù)器返回計(jì)算結(jié)果。比如客戶(hù)端發(fā)送 2+2,服務(wù)器返回 4.0作為結(jié)果。

在實(shí)現(xiàn)此計(jì)算器的時(shí)候,首先需要考慮的是 I/O 服務(wù)。該計(jì)算器使用 TCP/IP 協(xié)議,需要在指定端口監(jiān)聽(tīng),接受客戶(hù)端的連接。Apache MINA 提供了基于 Java NIO 的套接字實(shí)現(xiàn),可以直接使用。其次要考慮的是 I/O 過(guò)濾器。I/O 過(guò)濾器過(guò)濾所有的 I/O 事件和請(qǐng)求,可以用來(lái)處理橫切的需求,如記錄日志、壓縮等。最后就是 I/O 處理器。I/O 處理器用來(lái)處理業(yè)務(wù)邏輯。具體到該應(yīng)用來(lái)說(shuō),就是在接收到消息之后,把該消息作為一個(gè)表達(dá)式來(lái)執(zhí)行,并把結(jié)果發(fā)送回去。I/O 處理器需要實(shí)現(xiàn) org.apache.mina.core.service.IoHandler接口或者繼承自 org.apache.mina.core.service.IoHandlerAdapter。該應(yīng)用的 I/O 處理器的實(shí)現(xiàn)如 清單 1 所示。


清單 1. 計(jì)算器服務(wù)的 I/O 處理器 CalculatorHandler
  public class CalculatorHandler extends IoHandlerAdapter {
            private static final Logger LOGGER = LoggerFactory
            .getLogger(CalculatorHandler.class);
            private ScriptEngine jsEngine = null;
            public CalculatorHandler() {
            ScriptEngineManager sfm = new ScriptEngineManager();
            jsEngine = sfm.getEngineByName("JavaScript");
            if (jsEngine == null) {
            throw new RuntimeException("找不到 JavaScript 引擎。");
            }
            }
            public void exceptionCaught(IoSession session, Throwable cause)
            throws Exception {
            LOGGER.warn(cause.getMessage(), cause);
            }
            public void messageReceived(IoSession session, Object message)
            throws Exception {
            String expression = message.toString();
            if ("quit".equalsIgnoreCase(expression.trim())) {
            session.close(true);
            return;
            }
            try {
            Object result = jsEngine.eval(expression);
            session.write(result.toString());
            } catch (ScriptException e) {
            LOGGER.warn(e.getMessage(), e);
            session.write("Wrong expression, try again.");
            }
            }
            }
            

清單 1 中,messageReceivedIoHandler 接口聲明。當(dāng)接收到新的消息的時(shí)候,該方法就會(huì)被調(diào)用。此處的邏輯是如果傳入了“quit”,則通過(guò) session.close關(guān)閉當(dāng)前連接;如果不是的話(huà),就執(zhí)行該表達(dá)式并把結(jié)果通過(guò) session.write發(fā)送回去。此處執(zhí)行表達(dá)式用的是 JDK 6 中提供的 JavaScript 腳本引擎。此處使用到了 I/O 會(huì)話(huà)相關(guān)的方法,會(huì)在下面進(jìn)行說(shuō)明。

接下來(lái)只需要把 I/O 處理器和 I/O 過(guò)濾器配置到 I/O 服務(wù)上就可以了。具體的實(shí)現(xiàn)如 清單 2 所示。


清單 2. 計(jì)算器服務(wù)主程序 CalculatorServer
public class CalculatorServer {
            private static final int PORT = 10010;
            private static final Logger LOGGER = LoggerFactory
            .getLogger(CalculatorServer.class);
            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 CalculatorHandler());
            acceptor.bind(new InetSocketAddress(PORT));
            LOGGER.info("計(jì)算器服務(wù)已啟動(dòng),端口是" + PORT);
            }
            }
            

清單 2 中,首先創(chuàng)建一個(gè) org.apache.mina.transport.socket.nio.NioSocketAcceptor 的實(shí)例,由它提供 I/O 服務(wù);接著獲得該 I/O 服務(wù)的過(guò)濾器鏈,并添加兩個(gè)新的過(guò)濾器,一個(gè)用來(lái)記錄相關(guān)日志,另外一個(gè)用來(lái)在字節(jié)流和文本之間進(jìn)行轉(zhuǎn)換;最后配置 I/O 處理器。完成這些之后,通過(guò) bind 方法來(lái)在特定的端口進(jìn)行監(jiān)聽(tīng),接收連接。服務(wù)器啟動(dòng)之后,可以通過(guò)操作系統(tǒng)自帶的 Telnet 工具來(lái)進(jìn)行測(cè)試,如 圖 2 所示。在輸入表達(dá)式之后,計(jì)算結(jié)果會(huì)出現(xiàn)在下面一行。


圖 2. 使用 Telnet 工具測(cè)試計(jì)算器服務(wù)
使用 telnet 工具測(cè)試計(jì)算器服務(wù)

在介紹了簡(jiǎn)單的計(jì)算器服務(wù)這個(gè)應(yīng)用之后,下面說(shuō)明本文中會(huì)使用的復(fù)雜的聯(lián)機(jī)游戲應(yīng)用。


聯(lián)機(jī)游戲示例說(shuō)明

上一節(jié)中給出了一個(gè)簡(jiǎn)單的基于 Apache MINA 的網(wǎng)絡(luò)應(yīng)用的實(shí)現(xiàn),可以用來(lái)熟悉基本的架構(gòu)。而在實(shí)際開(kāi)發(fā)中,網(wǎng)絡(luò)應(yīng)用都是有一定復(fù)雜度的。下面會(huì)以一個(gè)比較復(fù)雜的聯(lián)機(jī)游戲作為示例來(lái)詳細(xì)介紹 Apache MINA 的概念、API 和典型用法。

該聯(lián)機(jī)游戲支持兩個(gè)人進(jìn)行俄羅斯方塊的對(duì)戰(zhàn)。這個(gè)游戲借鑒了 QQ 的“火拼俄羅斯”。用戶(hù)在啟動(dòng)客戶(hù)端之后,需要輸入一個(gè)昵稱(chēng)進(jìn)行注冊(cè)。用戶(hù)可以在“游戲大廳”中查看當(dāng)前已注冊(cè)的所有其它用戶(hù)。當(dāng)前用戶(hù)可以選擇另外的一 個(gè)用戶(hù)發(fā)送游戲邀請(qǐng)。邀請(qǐng)被接受之后就可以開(kāi)始進(jìn)行對(duì)戰(zhàn)。在游戲過(guò)程中,當(dāng)前用戶(hù)可以看到對(duì)方的游戲狀態(tài),即方塊的情況。該游戲的運(yùn)行效果如 圖 3 所示。


圖 3. 聯(lián)機(jī)游戲示例運(yùn)行效果圖
聯(lián)機(jī)游戲示例運(yùn)行效果圖

下面開(kāi)始以這個(gè)應(yīng)用為例來(lái)具體介紹 Apache MINA 中的基本概念。先從 I/O 服務(wù)開(kāi)始。


I/O 服務(wù)

I/O 服務(wù)用來(lái)執(zhí)行真正的 I/O 操作,以及管理 I/O 會(huì)話(huà)。根據(jù)所使用的數(shù)據(jù)傳輸方式的不同,有不同的 I/O 服務(wù)的實(shí)現(xiàn)。由于 I/O 服務(wù)執(zhí)行的是輸入和輸出兩種操作,實(shí)際上有兩種具體的子類(lèi)型。一種稱(chēng)為“I/O 接受器(I/O acceptor)”,用來(lái)接受連接,一般用在服務(wù)器的實(shí)現(xiàn)中;另外一種稱(chēng)為“I/O 連接器(I/O connector)”,用來(lái)發(fā)起連接,一般用在客戶(hù)端的實(shí)現(xiàn)中。對(duì)應(yīng)在 Apache MINA 中的實(shí)現(xiàn),org.apache.mina.core.service.IoService是 I/O 服務(wù)的接口,而繼承自它的接口 org.apache.mina.core.service.IoAcceptororg.apache.mina.core.service.IoConnector 則分別表示 I/O 接受器和 I/O 連接器。IoService 接口提供的重要方法如 表 1 所示。


表 1. IoService 中的重要方法
方法說(shuō)明
setHandler(IoHandler handler) 設(shè)置 I/O 處理器。該 I/O 處理器會(huì)負(fù)責(zé)處理該 I/O 服務(wù)所管理的所有 I/O 會(huì)話(huà)產(chǎn)生的 I/O 事件。
getFilterChain() 獲取 I/O 過(guò)濾器鏈,可以對(duì) I/O 過(guò)濾器進(jìn)行管理,包括添加和刪除 I/O 過(guò)濾器。
getManagedSessions() 獲取該 I/O 服務(wù)所管理的 I/O 會(huì)話(huà)。

下面具體介紹 I/O 接受器和 I/O 連接器。

I/O 接受器

I/O 接受器用來(lái)接受連接,與對(duì)等體(客戶(hù)端)進(jìn)行通訊,并發(fā)出相應(yīng)的 I/O 事件交給 I/O 處理器來(lái)處理。使用 I/O 接受器的時(shí)候,只需要調(diào)用 bind方法并指定要監(jiān)聽(tīng)的套接字地址。當(dāng)不再接受連接的時(shí)候,調(diào)用 unbind停止監(jiān)聽(tīng)即可。關(guān)于 I/O 接受器的具體用法,可以參考 清單 2 中給出的計(jì)算器服務(wù)的實(shí)現(xiàn)。

I/O 連接器

I/O 連接器用來(lái)發(fā)起連接,與對(duì)等體(服務(wù)器)進(jìn)行通訊,并發(fā)出相應(yīng)的 I/O 事件交給 I/O 處理器來(lái)處理。使用 I/O 連接器的時(shí)候,只需要調(diào)用 connect方法連接指定的套接字地址。另外可以通過(guò) setConnectTimeoutMillis設(shè)置連接超時(shí)時(shí)間(毫秒數(shù))。

清單 3 中給出了使用 I/O 連接器的一個(gè)示例。


清單 3. I/O 連接器示例
SocketConnector connector = new NioSocketConnector();
            connector.setConnectTimeoutMillis(CONNECT_TIMEOUT);
            connector.getFilterChain().addLast("logger", new LoggingFilter());
            connector.getFilterChain().addLast("protocol",
            new ProtocolCodecFilter(new TetrisCodecFactory()));
            ConnectFuture connectFuture = connector.connect(new InetSocketAddress(host, port));
            connectFuture.awaitUninterruptibly();
            

清單 3 中,首先創(chuàng)建一個(gè) Java NIO 的套接字連接器 NioSocketConnector 的實(shí)例,接著設(shè)置超時(shí)時(shí)間。再添加了 I/O 過(guò)濾器之后,通過(guò) connect 方法連接到指定的地址和端口即可。

在介紹完 I/O 服務(wù)之后,下面介紹 I/O 會(huì)話(huà)。


I/O 會(huì)話(huà)

I/O 會(huì)話(huà)表示一個(gè)活動(dòng)的網(wǎng)絡(luò)連接,與所使用的傳輸方式無(wú)關(guān)。I/O 會(huì)話(huà)可以用來(lái)存儲(chǔ)用戶(hù)自定義的與應(yīng)用相關(guān)的屬性。這些屬性通常用來(lái)保存應(yīng)用的狀態(tài)信息,還可以用來(lái)在 I/O 過(guò)濾器和 I/O 處理器之間交換數(shù)據(jù)。I/O 會(huì)話(huà)在作用上類(lèi)似于 Servlet 規(guī)范中的 HTTP 會(huì)話(huà)。

Apache MINA 中 I/O 會(huì)話(huà)實(shí)現(xiàn)的接口是 org.apache.mina.core.session.IoSession。該接口中比較重要的方法如 表 2 所示。


表 2. IoSession 中的重要方法
方法說(shuō)明
close(boolean immediately) 關(guān)閉當(dāng)前連接。如果參數(shù) immediatelytrue的話(huà),連接會(huì)等到隊(duì)列中所有的數(shù)據(jù)發(fā)送請(qǐng)求都完成之后才關(guān)閉;否則的話(huà)就立即關(guān)閉。
getAttribute(Object key) 從 I/O 會(huì)話(huà)中獲取鍵為 key的用戶(hù)自定義的屬性。
setAttribute(Object key, Object value) 將鍵為 key,值為 value的用戶(hù)自定義的屬性存儲(chǔ)到 I/O 會(huì)話(huà)中。
removeAttribute(Object key) 從 I/O 會(huì)話(huà)中刪除鍵為 key的用戶(hù)自定義的屬性。
write(Object message) 將消息對(duì)象 message發(fā)送到當(dāng)前連接的對(duì)等體。該方法是異步的,當(dāng)消息被真正發(fā)送到對(duì)等體的時(shí)候,IoHandler.messageSent(IoSession,Object)會(huì)被調(diào)用。如果需要的話(huà),也可以等消息真正發(fā)送出去之后再繼續(xù)執(zhí)行后續(xù)操作。

在介紹完 I/O 會(huì)話(huà)之后,下面介紹 I/O 過(guò)濾器。


I/O 過(guò)濾器

從 I/O 服務(wù)發(fā)送過(guò)來(lái)的所有 I/O 事件和請(qǐng)求,在到達(dá) I/O 處理器之前,會(huì)先由 I/O 過(guò)濾器鏈中的 I/O 過(guò)濾器進(jìn)行處理。Apache MINA 中的過(guò)濾器與 Servlet 規(guī)范中的過(guò)濾器是類(lèi)似的。過(guò)濾器可以在很多情況下使用,比如記錄日志、性能分析、訪(fǎng)問(wèn)控制、負(fù)載均衡和消息轉(zhuǎn)換等。過(guò)濾器非常適合滿(mǎn)足網(wǎng)絡(luò)應(yīng)用中各種橫切 的非功能性需求。在一個(gè)基于 Apache MINA 的網(wǎng)絡(luò)應(yīng)用中,一般存在多個(gè)過(guò)濾器。這些過(guò)濾器互相串聯(lián),形成鏈條,稱(chēng)為過(guò)濾器鏈。每個(gè)過(guò)濾器依次對(duì)傳入的 I/O 事件進(jìn)行處理。當(dāng)前過(guò)濾器完成處理之后,由過(guò)濾器鏈中的下一個(gè)過(guò)濾器繼續(xù)處理。當(dāng)前過(guò)濾器也可以不調(diào)用下一個(gè)過(guò)濾器,而提前結(jié)束,這樣 I/O 事件就不會(huì)繼續(xù)往后傳遞。比如負(fù)責(zé)用戶(hù)認(rèn)證的過(guò)濾器,如果遇到未認(rèn)證的對(duì)等體發(fā)出的 I/O 事件,則會(huì)直接關(guān)閉連接。這可以保證這些事件不會(huì)通過(guò)此過(guò)濾器到達(dá) I/O 處理器。

Apache MINA 中 I/O 過(guò)濾器都實(shí)現(xiàn) org.apache.mina.core.filterchain.IoFilter接口。一般來(lái)說(shuō),不需要完整實(shí)現(xiàn) IOFilter接口,只需要繼承 Apache MINA 提供的適配器 org.apache.mina.core.filterchain.IoFilterAdapter,并覆寫(xiě)所需的事件過(guò)濾方法即可,其它方法的默認(rèn)實(shí)現(xiàn)是不做任何處理,而直接把事件轉(zhuǎn)發(fā)到下一個(gè)過(guò)濾器。

IoFilter 接口詳細(xì)說(shuō)明

IoFilter接口提供了 15 個(gè)方法。這 15 個(gè)方法大致分成兩類(lèi),一類(lèi)是與過(guò)濾器的生命周期相關(guān)的,另外一類(lèi)是用來(lái)過(guò)濾 I/O 事件的。第一類(lèi)方法如 表 3 所示。


表 3. IoFilter 中與過(guò)濾器的生命周期相關(guān)的方法
方法說(shuō)明
init() 當(dāng)過(guò)濾器第一次被添加到過(guò)濾器鏈中的時(shí)候,此方法被調(diào)用。用來(lái)完成過(guò)濾器的初始化工作。
onPreAdd(IoFilterChain parent, String name, IoFilter.NextFilter nextFilter) 當(dāng)過(guò)濾器即將被添加到過(guò)濾器鏈中的時(shí)候,此方法被調(diào)用。
onPostAdd(IoFilterChain parent, String name, IoFilter.NextFilter nextFilter) 當(dāng)過(guò)濾器已經(jīng)被添加到過(guò)濾器鏈中之后,此方法被調(diào)用。
onPreRemove(IoFilterChain parent, String name, IoFilter.NextFilter nextFilter) 當(dāng)過(guò)濾器即將被從過(guò)濾器鏈中刪除的時(shí)候,此方法被調(diào)用。
onPostRemove(IoFilterChain parent, String name, IoFilter.NextFilter nextFilter) 當(dāng)過(guò)濾器已經(jīng)被從過(guò)濾器鏈中刪除的時(shí)候,此方法被調(diào)用。
destroy() 當(dāng)過(guò)濾器不再需要的時(shí)候,它將被銷(xiāo)毀,此方法被調(diào)用。

表 3 中給出的方法中,參數(shù) parent 表示包含此過(guò)濾器的過(guò)濾器鏈,參數(shù) name 表示過(guò)濾器的名稱(chēng),參數(shù) nextFilter 表示過(guò)濾器鏈中的下一個(gè)過(guò)濾器。

第二類(lèi)方法如 表 4 所示。


表 4. IoFilter 中過(guò)濾 I/O 事件的方法
方法說(shuō)明
filterClose(IoFilter.NextFilter nextFilter, IoSession session) 過(guò)濾對(duì) IoSessionclose方法的調(diào)用。
filterWrite(IoFilter.NextFilter nextFilter, IoSession session, WriteRequest writeRequest) 過(guò)濾對(duì) IoSessionwrite方法的調(diào)用。
exceptionCaught(IoFilter.NextFilter nextFilter, IoSession session, Throwable cause) 過(guò)濾對(duì) IoHandlerexceptionCaught方法的調(diào)用。
messageReceived(IoFilter.NextFilter nextFilter, IoSession session, Object message) 過(guò)濾對(duì) IoHandlermessageReceived方法的調(diào)用。
messageSent(IoFilter.NextFilter nextFilter, IoSession session, WriteRequest writeRequest) 過(guò)濾對(duì) IoHandlermessageSent方法的調(diào)用。
sessionClosed(IoFilter.NextFilter nextFilter, IoSession session) 過(guò)濾對(duì) IoHandlersessionClosed方法的調(diào)用。
sessionCreated(IoFilter.NextFilter nextFilter, IoSession session) 過(guò)濾對(duì) IoHandlersessionCreated方法的調(diào)用。
sessionIdle(IoFilter.NextFilter nextFilter, IoSession session, IdleStatus status) 過(guò)濾對(duì) IoHandlersessionIdle方法的調(diào)用。
sessionOpened(IoFilter.NextFilter nextFilter, IoSession session) 過(guò)濾對(duì) IoHandlersessionOpened方法的調(diào)用。

對(duì)于 表 4 中給出的與 I/O 事件相關(guān)的方法,它們都有一個(gè)參數(shù)是 nextFilter,表示過(guò)濾器鏈中的下一個(gè)過(guò)濾器。如果當(dāng)前過(guò)濾器完成處理之后,可以通過(guò)調(diào)用 nextFilter 中的方法,把 I/O 事件傳遞到下一個(gè)過(guò)濾器。如果當(dāng)前過(guò)濾器不調(diào)用 nextFilter 中的方法的話(huà),該 I/O 事件就不能繼續(xù)往后傳遞。另外一個(gè)共同的參數(shù)是 session,用來(lái)表示當(dāng)前的 I/O 會(huì)話(huà),可以用來(lái)發(fā)送消息給對(duì)等體。下面通過(guò)具體的實(shí)例來(lái)說(shuō)明過(guò)濾器的實(shí)現(xiàn)。

BlacklistFilter

BlacklistFilter是 Apache MINA 自帶的一個(gè)過(guò)濾器實(shí)現(xiàn),其功能是阻止來(lái)自特定地址的連接,即所謂的“黑名單”功能。BlacklistFilter繼承自 IoFilterAdapter,并覆寫(xiě)了 IoHandler相關(guān)的方法。清單 4 中給出了部分實(shí)現(xiàn)。


清單 4. 阻止來(lái)自特定地址連接的 BlacklistFilter
public void messageReceived(NextFilter nextFilter, IoSession session, Object message) {
            if (!isBlocked(session)) {
            nextFilter.messageReceived(session, message);
            } else {
            blockSession(session);
            }
            }
            private void blockSession(IoSession session) {
            session.close(true);
            }

清單 4messageReceived 方法的實(shí)現(xiàn)中,首先通過(guò) isBlocked 來(lái)判斷當(dāng)前連接是否應(yīng)該被阻止,如果不是的話(huà),則通過(guò) nextFilter.messageReceived 把該 I/O 事件傳遞到下一個(gè)過(guò)濾器;否則的話(huà),則通過(guò) blockSession 來(lái)阻止當(dāng)前連接。

使用 ProtocolCodecFilter

ProtocolCodecFilter 用來(lái)在字節(jié)流和消息對(duì)象之間互相轉(zhuǎn)換。當(dāng)該過(guò)濾器接收到字節(jié)流的時(shí)候,需要首先判斷消息的邊界,然后把表示一條消息的字節(jié)提取出來(lái),通過(guò)一定的邏輯轉(zhuǎn)換成 消息對(duì)象,再把消息對(duì)象往后傳遞,交給 I/O 處理器來(lái)執(zhí)行業(yè)務(wù)邏輯。這個(gè)過(guò)程稱(chēng)為“解碼”。與“解碼”對(duì)應(yīng)的是“編碼”過(guò)程。在“編碼”的時(shí)候,過(guò)濾器接收到的是消息對(duì)象,通過(guò)與“解碼”相反的邏 輯,把消息對(duì)象轉(zhuǎn)換成字節(jié),并反向傳遞,交給 I/O 服務(wù)來(lái)執(zhí)行 I/O 操作。

在“編碼”和“解碼”中的一個(gè)重要問(wèn)題是如何在字節(jié)流中判斷消息的邊界。通常來(lái)說(shuō),有三種辦法解決這個(gè)問(wèn)題:

  • 使用固定長(zhǎng)度的消息。這種方式實(shí)現(xiàn)起來(lái)比較簡(jiǎn)單,只需要每次讀取特定數(shù)量的字節(jié)即可。
  • 使用固定長(zhǎng)度的消息頭來(lái)指明消息主體的長(zhǎng)度。比如每個(gè)消息開(kāi)始的 4 個(gè)字節(jié)的值表示了后面緊跟的消息主體的長(zhǎng)度。只需要首先讀取該長(zhǎng)度,再讀取指定數(shù)量的字節(jié)即可。
  • 使用分隔符。消息之間通過(guò)特定模式的分隔符來(lái)分隔。每次只要遇到該模式的字節(jié),就表示到了一個(gè)消息的末尾。

具體到示例應(yīng)用來(lái)說(shuō),客戶(hù)端和服務(wù)器之間的通信協(xié)議比較復(fù)雜,有不同種類(lèi)的消息。每種消息的格式都不相同,同類(lèi)消息的內(nèi)容也不盡相同。因此,使用固定長(zhǎng)度的消息頭來(lái)指明消息主體的長(zhǎng)度就成了最好的選擇。

示例應(yīng)用中的每種消息主體由兩部分組成,第一部分是固定長(zhǎng)度的消息類(lèi)別名稱(chēng),第二部分是每種消息的主體內(nèi)容。圖 4 中給出了示例應(yīng)用中一條完整的消息的結(jié)構(gòu)。


圖 4. 示例應(yīng)用中消息的結(jié)構(gòu)
示例應(yīng)用中消息的結(jié)構(gòu)

AbstractTetrisCommand用來(lái)描述聯(lián)機(jī)游戲示例應(yīng)用中的消息。它是一個(gè)抽象類(lèi),是所有具體消息的基類(lèi)。其具體實(shí)現(xiàn)如 清單 5 所示。


清單 5. 聯(lián)機(jī)游戲示例應(yīng)用中的消息 AbstractTetrisCommand
public abstract class AbstractTetrisCommand implements TetrisCommand {
            public abstract String getName();
            public abstract byte[] bodyToBytes() throws Exception;
            public abstract void bodyFromBytes(byte[] bytes) throws Exception;
            public byte[] toBytes() throws Exception {
            byte[] body = bodyToBytes();
            int commandNameLength = Constants.COMMAND_NAME_LENGTH;
            int len = commandNameLength + body.length;
            byte[] bytes = new byte[len];
            String name = StringUtils.rightPad(getName(), commandNameLength,
            Constants.COMMAND_NAME_PAD_CHAR);
            name = name.substring(0, commandNameLength);
            System.arraycopy(name.getBytes(), 0, bytes, 0, commandNameLength);
            System.arraycopy(body, 0, bytes, commandNameLength, body.length);
            return bytes;
            }
            }

清單 5 所示,AbstractTetrisCommand 中定義了 3 個(gè)抽象方法:getName、bodyToBytesbodyFromBytes,分別用來(lái)獲取消息的名稱(chēng)、把消息的主體轉(zhuǎn)換成字節(jié)數(shù)組和從字節(jié)數(shù)組中構(gòu)建消息。bodyToBytes對(duì)應(yīng)于前面提到的“編碼”過(guò)程,而 bodyFromBytes對(duì)應(yīng)于“解碼”過(guò)程。每種具體的消息都應(yīng)該實(shí)現(xiàn)這 3 個(gè)方法。AbstractTetrisCommand 中的方法 toBytes 封裝了把消息的主體轉(zhuǎn)換成字節(jié)數(shù)組的邏輯,在字節(jié)數(shù)組中,首先是長(zhǎng)度固定為 Constants.COMMAND_NAME_LENGTH的消息類(lèi)別名稱(chēng),緊接著是每種消息特定的主體內(nèi)容,由 bodyToBytes 方法來(lái)生成。

在介紹完示例應(yīng)用中的消息格式之后,下面將討論具體的“編碼”和“解碼”過(guò)程?!熬幋a”過(guò)程由編碼器來(lái)完成,編碼器需要實(shí)現(xiàn) org.apache.mina.filter.codec.ProtocolEncoder 接口,一般來(lái)說(shuō)繼承自 org.apache.mina.filter.codec.ProtocolEncoderAdapter 并覆寫(xiě)所需的方法即可。清單 6 中給出了示例應(yīng)用中消息編碼器 CommandEncoder 的實(shí)現(xiàn)。


清單 6. 聯(lián)機(jī)游戲示例應(yīng)用中消息編碼器 CommandEncoder
public class CommandEncoder extends ProtocolEncoderAdapter {
            public void encode(IoSession session, Object message,
            ProtocolEncoderOutput out) throws Exception {
            AbstractTetrisCommand command = (AbstractTetrisCommand) message;
            byte[] bytes = command.toBytes();
            IoBuffer buf = IoBuffer.allocate(bytes.length, false);
            buf.setAutoExpand(true);
            buf.putInt(bytes.length);
            buf.put(bytes);
            buf.flip();
            out.write(buf);
            }
            }

清單 6 中,encode 方法封裝了編碼的邏輯。由于 AbstractTetrisCommandtoBytes已經(jīng)完成了到字節(jié)數(shù)組的轉(zhuǎn)換,encode 方法直接使用即可。首先寫(xiě)入消息主體字節(jié)數(shù)組的長(zhǎng)度,再是字節(jié)數(shù)組本身,就完成了編碼的過(guò)程。

與編碼過(guò)程相比,解碼過(guò)程要相對(duì)復(fù)雜一些。具體的實(shí)現(xiàn)如 清單 7 所示。


清單 7. 聯(lián)機(jī)游戲示例應(yīng)用中消息解碼器 CommandDecoder
public class CommandDecoder extends CumulativeProtocolDecoder {
            protected boolean doDecode(IoSession session, IoBuffer in,
            ProtocolDecoderOutput out) throws Exception {
            if (in.prefixedDataAvailable(4, Constants.MAX_COMMAND_LENGTH)) {
            int length = in.getInt();
            byte[] bytes = new byte[length];
            in.get(bytes);
            int commandNameLength = Constants.COMMAND_NAME_LENGTH;
            byte[] cmdNameBytes = new byte[commandNameLength];
            System.arraycopy(bytes, 0, cmdNameBytes, 0, commandNameLength);
            String cmdName = StringUtils.trim(new String(cmdNameBytes));
            AbstractTetrisCommand command = TetrisCommandFactory
            .newCommand(cmdName);
            if (command != null) {
            byte[] cmdBodyBytes = new byte[length - commandNameLength];
            System.arraycopy(bytes, commandNameLength, cmdBodyBytes, 0,
            length - commandNameLength);
            command.bodyFromBytes(cmdBodyBytes);
            out.write(command);
            }
            return true;
            } else {
            return false;
            }
            }
            }

清單 7 中可以看到,解碼器 CommandDecoder 繼承自 CumulativeProtocolDecoder。這是 Apache MINA 提供的一個(gè)幫助類(lèi),它會(huì)自動(dòng)緩存所有已經(jīng)接收到的數(shù)據(jù),直到編碼器認(rèn)為可以開(kāi)始進(jìn)行編碼。這樣在實(shí)現(xiàn)自己的編碼器的時(shí)候,就只需要考慮如何判斷消息的邊界即可。如果一條消息的后續(xù)數(shù)據(jù)還沒(méi)有接收到,CumulativeProtocolDecoder會(huì)自動(dòng)進(jìn)行緩存。在之前提到過(guò),解碼過(guò)程的一個(gè)重要問(wèn)題是判斷消息的邊界。對(duì)于固定長(zhǎng)度的消息來(lái)說(shuō),只需要使用 Apache MINA 的 IoBufferremaining方法來(lái)判斷當(dāng)前緩存中的字節(jié)數(shù)目,如果大于消息長(zhǎng)度的話(huà),就進(jìn)行解碼;對(duì)于使用固定長(zhǎng)度消息頭來(lái)指明消息主體的長(zhǎng)度的情況,IoBuffer提供了 prefixedDataAvailable方法來(lái)滿(mǎn)足這一需求。prefixedDataAvailable會(huì)檢查當(dāng)前緩存中是否有固定長(zhǎng)度的消息頭,并且由此消息頭指定長(zhǎng)度的消息主體是否已經(jīng)全部在緩存中。如果這兩個(gè)條件都滿(mǎn)足的話(huà),說(shuō)明一條完整的消息已經(jīng)接收到,可以進(jìn)行解碼了。解碼的過(guò)程本身并不復(fù)雜,首先讀取消息的類(lèi)別名稱(chēng),然后通過(guò) TetrisCommandFactory.newCommand方法來(lái)生成一個(gè)該類(lèi)消息的實(shí)例,接著通過(guò)該實(shí)例的 bodyFromBytes方法就可以從字節(jié)數(shù)組中恢復(fù)消息的內(nèi)容,得到一個(gè)完整的消息對(duì)象。每次成功解碼一個(gè)消息對(duì)象,需要調(diào)用 ProtocolDecoderOutputwrite把此消息對(duì)象往后傳遞。消息對(duì)象會(huì)通過(guò)過(guò)濾器鏈,最終達(dá)到 I/O 處理器,在 IoHandler.messageReceived中接收到此消息對(duì)象。如果當(dāng)前緩存的數(shù)據(jù)不足以用來(lái)解碼一條消息的話(huà),doDecode只需要返回 false即可。接收到新的數(shù)據(jù)之后,doDecode會(huì)被再次調(diào)用。

過(guò)濾器鏈

過(guò)濾器只有在添加到過(guò)濾器鏈中的時(shí)候才起作用。過(guò)濾器鏈?zhǔn)沁^(guò)濾器的容器。過(guò)濾器鏈與 I/O 會(huì)話(huà)是一一對(duì)應(yīng)的關(guān)系。org.apache.mina.core.filterchain.IoFilterChain是 Apache MINA 中過(guò)濾器鏈的接口,其中提供了一系列方法對(duì)其中包含的過(guò)濾器進(jìn)行操作,包括查詢(xún)、添加、刪除和替換等。如 表 5 所示。


表 5. IoFilterChain 接口的方法
方法說(shuō)明
addFirst(String name, IoFilter filter) 將指定名稱(chēng)的過(guò)濾器添加到過(guò)濾器鏈的開(kāi)頭。
addLast(String name, IoFilter filter) 將指定名稱(chēng)的過(guò)濾器添加到過(guò)濾器鏈的末尾。
contains(String name) 判斷過(guò)濾器鏈中是否包含指定名稱(chēng)的過(guò)濾器。
get(String name) 從過(guò)濾器鏈中獲取指定名稱(chēng)的過(guò)濾器。
remove(String name) 從過(guò)濾器鏈中刪除指定名稱(chēng)的過(guò)濾器。
replace(String name, IoFilter newFilter) 用過(guò)濾器 newFilter替換掉過(guò)濾器鏈中名為 name的過(guò)濾器。
getSession() 獲取與過(guò)濾器鏈一一對(duì)應(yīng)的 I/O 會(huì)話(huà)。

在介紹完 I/O 過(guò)濾器和過(guò)濾器鏈之后,下面介紹 I/O 處理器。


I/O 處理器

I/O 事件通過(guò)過(guò)濾器鏈之后會(huì)到達(dá) I/O 處理器。I/O 處理器中與 I/O 事件對(duì)應(yīng)的方法會(huì)被調(diào)用。Apache MINA 中 org.apache.mina.core.service.IoHandler是 I/O 處理器要實(shí)現(xiàn)的接口,一般情況下,只需要繼承自 org.apache.mina.core.service.IoHandlerAdapter并覆寫(xiě)所需方法即可。IoHandler接口的方法如 表 6 所示。


表 6. IoHandler 接口的方法
方法說(shuō)明
sessionCreated(IoSession session) 當(dāng)有新的連接建立的時(shí)候,該方法被調(diào)用。
sessionOpened(IoSession session) 當(dāng)有新的連接打開(kāi)的時(shí)候,該方法被調(diào)用。該方法在 sessionCreated之后被調(diào)用。
sessionClosed(IoSession session) 當(dāng)連接被關(guān)閉的時(shí)候,此方法被調(diào)用。
sessionIdle(IoSession session, IdleStatus status) 當(dāng)連接變成閑置狀態(tài)的時(shí)候,此方法被調(diào)用。
exceptionCaught(IoSession session, Throwable cause) 當(dāng) I/O 處理器的實(shí)現(xiàn)或是 Apache MINA 中有異常拋出的時(shí)候,此方法被調(diào)用。
messageReceived(IoSession session, Object message) 當(dāng)接收到新的消息的時(shí)候,此方法被調(diào)用。
messageSent(IoSession session, Object message) 當(dāng)消息被成功發(fā)送出去的時(shí)候,此方法被調(diào)用。

對(duì)于 表 6 中的方法,有幾個(gè)需要重點(diǎn)的說(shuō)明一下。首先是 sessionCreatedsessionOpened 的區(qū)別。sessionCreated方法是由 I/O 處理線(xiàn)程來(lái)調(diào)用的,而 sessionOpened 是由其它線(xiàn)程來(lái)調(diào)用的。因此從性能方面考慮,不要在 sessionCreated 方法中執(zhí)行過(guò)多的操作。對(duì)于 sessionIdle,默認(rèn)情況下,閑置時(shí)間設(shè)置是禁用的,也就是說(shuō) sessionIdle 并不會(huì)被調(diào)用??梢酝ㄟ^(guò) IoSessionConfig.setIdleTime(IdleStatus, int) 來(lái)進(jìn)行設(shè)置。

Apache MINA 中的基本概念已經(jīng)介紹完了,下面介紹狀態(tài)機(jī)的使用。


使用狀態(tài)機(jī)

在 I/O 處理器中實(shí)現(xiàn)業(yè)務(wù)邏輯的時(shí)候,對(duì)于簡(jiǎn)單的情況,一般只需要在 messageReceived 方法中對(duì)傳入的消息進(jìn)行處理。如果需要寫(xiě)回?cái)?shù)據(jù)到對(duì)等體的話(huà),用 IoSession.write 方法即可。在另外的一些情況下,客戶(hù)端和服務(wù)器端的通信協(xié)議比較復(fù)雜,客戶(hù)端其實(shí)是有狀態(tài)變遷的。這個(gè)時(shí)候可以用 Apache MINA 提供的狀態(tài)機(jī)實(shí)現(xiàn),可以使得 I/O 處理器的實(shí)現(xiàn)更加簡(jiǎn)單。

狀態(tài)機(jī)中兩個(gè)重要的元素是狀態(tài)以及狀態(tài)之間的遷移。示例應(yīng)用中客戶(hù)端的狀態(tài)以及遷移如 圖 5 所示。


圖 5. 聯(lián)機(jī)游戲示例應(yīng)用中客戶(hù)端的狀態(tài)以及遷移
聯(lián)機(jī)游戲示例應(yīng)用中客戶(hù)端的狀態(tài)以及遷移

客戶(hù)端初始化的時(shí)候,其狀態(tài)為“未連接”,表示客戶(hù)端還沒(méi)有在服務(wù)器上面注冊(cè),此時(shí)還不能進(jìn)行游戲;接著用戶(hù)需要輸入一個(gè)昵稱(chēng)來(lái)注冊(cè)到服務(wù)器 上面,完成之后狀態(tài)遷移到“閑置”。此時(shí)客戶(hù)端會(huì)接收到當(dāng)前在線(xiàn)的所有其它用戶(hù)的列表。當(dāng)前用戶(hù)可以邀請(qǐng)其它用戶(hù)和他一塊游戲,也可以接收來(lái)自其它用戶(hù)的 邀請(qǐng)。邀請(qǐng)發(fā)送出去之后,客戶(hù)端的狀態(tài)遷移到“邀請(qǐng)已發(fā)送”。如果接受了其它用戶(hù)的邀請(qǐng),客戶(hù)端的狀態(tài)遷移到“邀請(qǐng)已接收”。如果某個(gè)用戶(hù)的邀請(qǐng)被另外一 個(gè)用戶(hù)接受的話(huà),兩個(gè)客戶(hù)端的狀態(tài)都會(huì)遷移到“游戲中”。

要實(shí)現(xiàn)這樣較為復(fù)雜的狀態(tài)機(jī)的話(huà),只需要在 I/O 處理器中以聲明式的方式定義狀態(tài)和遷移條件就可以了。首先需要聲明狀態(tài)機(jī)中狀態(tài),如 清單 8 所示。


清單 8. 聯(lián)機(jī)游戲示例應(yīng)用中的狀態(tài)聲明
@State public static final String ROOT = "Root";
            @State(ROOT) public static final String NOT_CONNECTED = "NotConnected";
            @State(ROOT) public static final String IDLE = "Idle";
            @State(ROOT) public static final String INVITATION_SENT = "InvitationSent";
            @State(ROOT) public static final String INVITATION_ACCEPTED = "InvitationAccepted";
            @State(ROOT) public static final String PLAYING = "Playing";
            

清單 8 所示,上面定義了一共六個(gè)狀態(tài)。通過(guò)標(biāo)注 @State就 聲明了一個(gè)狀態(tài)。需要注意的是狀態(tài)之間是可以繼承的。如果狀態(tài)機(jī)接收到一個(gè)事件的時(shí)候,在當(dāng)前狀態(tài)中找不到對(duì)應(yīng)的遷移,就會(huì)在其父狀態(tài)上繼續(xù)查找。狀態(tài)的 繼承在某些情況下是很有用的,比如希望為所有的狀態(tài)都增加同樣的遷移邏輯,就可以直接把遷移條件添加在父狀態(tài)上面。一個(gè)典型的場(chǎng)景就是錯(cuò)誤處理,一般來(lái) 說(shuō),所有的狀態(tài)都需要錯(cuò)誤處理,而錯(cuò)誤處理的邏輯一般都是相同的。把發(fā)生錯(cuò)誤時(shí)候的遷移放在父狀態(tài)中,可以簡(jiǎn)潔的描述這一場(chǎng)景。

定義了狀態(tài)之后,下面應(yīng)該聲明狀態(tài)之間的遷移。如 清單 9 所示。


清單 9. 聯(lián)機(jī)游戲示例應(yīng)用中的狀態(tài)遷移聲明
@IoHandlerTransition(on = MESSAGE_RECEIVED, in = NOT_CONNECTED, next = IDLE)
            public void login(TetrisServerContext context, IoSession session, LoginCommand cmd) {
            String nickName = cmd.getNickName();
            context.nickName = nickName;
            session.setAttribute("nickname", nickName);
            session.setAttribute("status", UserStatus.IDLE);
            sessions.add(session);
            users.add(nickName);
            RefreshPlayersListCommand command = createRefreshPlayersListCommand();
            broadcast(command);
            }
            @IoHandlerTransition(on = EXCEPTION_CAUGHT, in = ROOT, weight = 10)
            public void exceptionCaught(IoSession session, Exception e) {
            LOGGER.warn("Unexpected error.", e);
            session.close(true);
            }
            @IoHandlerTransition(in = ROOT, weight = 100)
            public void unhandledEvent() {
            LOGGER.warn("Unhandled event.");
            }
            

清單 9 中,使用標(biāo)注 @IoHandlerTransition聲明了一個(gè)狀態(tài)遷移。每個(gè)狀態(tài)遷移可以有四個(gè)屬性:on、innextweight,其中屬性 in是必須的,其余是可選的。屬性 on表示觸發(fā)此狀態(tài)遷移的事件名稱(chēng),如果省略該屬性的話(huà),則默認(rèn)為匹配所有事件的通配符。該屬性的值可以是表中給出的 I/O 處理器中能處理的七種事件類(lèi)型。屬性 in表示狀態(tài)遷移的起始狀態(tài)。屬性 next表示狀態(tài)遷移的結(jié)束狀態(tài),如果省略該屬性的話(huà),則默認(rèn)為表示當(dāng)前狀態(tài) 的 _self_。屬性 weight用來(lái)指明狀態(tài)遷移的權(quán)重。一個(gè)狀態(tài)的所有遷移是按照其權(quán)重升序排列的。對(duì)于當(dāng)前狀態(tài),如果有多個(gè)可能的遷移,排序靠前的遷移將會(huì)發(fā)生。代碼中的第一個(gè)標(biāo)注聲明了如果當(dāng)前狀態(tài)是“未連接”,并且接收到了 MESSAGE_RECEIVED事件,而且消息的內(nèi)容是一個(gè) LoginCommand對(duì)象的話(huà),login方法會(huì)被調(diào)用,調(diào)用完成之后,當(dāng)前狀態(tài)遷移到“閑置”。第二個(gè)標(biāo)注聲明了對(duì)于任何的狀態(tài),如果接收到了 EXCEPTION_CAUGHT事件,exceptionCaught方法會(huì)被調(diào)用。最后一個(gè)標(biāo)注聲明了一個(gè)狀態(tài)遷移,其起始狀態(tài)是 ROOT,表示該遷移對(duì)所有的事件都起作用。不過(guò)它的 weight是 100,優(yōu)先級(jí)比較低。該狀態(tài)遷移的作用是處理其它沒(méi)有對(duì)應(yīng)狀態(tài)遷移的事件。

使用了 Apache MINA 提供的狀態(tài)機(jī)之后,創(chuàng)建 I/O 處理器的方式也發(fā)生了變化。I/O 處理器的實(shí)例由狀態(tài)機(jī)來(lái)創(chuàng)建,如 清單 10 所示。


清單 10. 在狀態(tài)機(jī)中創(chuàng)建 I/O 處理器
private static IoHandler createIoHandler() {
            StateMachine sm = StateMachineFactory.getInstance(
            IoHandlerTransition.class).create(ServerHandler.NOT_CONNECTED,
            new ServerHandler());
            return new StateMachineProxyBuilder().setStateContextLookup(
            new IoSessionStateContextLookup(new StateContextFactory() {
            public StateContext create() {
            return new ServerHandler.TetrisServerContext();
            }
            })).create(IoHandler.class, sm);
            }
            

清單 10 中,TetrisServerContext是提供給狀態(tài)機(jī)的上下文對(duì)象,用來(lái)在狀態(tài)之間共享數(shù)據(jù)。當(dāng)然用 IoSession也是可以實(shí)現(xiàn)的,不過(guò)上下文對(duì)象的好處是類(lèi)型安全,不需要做額外的類(lèi)型轉(zhuǎn)換。

在介紹完?duì)顟B(tài)機(jī)后,下面介紹一些高級(jí)話(huà)題,包括異步操作以及 Apache MINA 與 JMX 和 Spring 的集成。


高級(jí)話(huà)題

在前面章節(jié)中介紹了 Apache MINA 的基本概念和具體用法,下面討論一些高級(jí)話(huà)題。

異步操作

Apache MINA 中的很多操作都是異步的,比如連接的建立、連接的關(guān)閉、還有數(shù)據(jù)的發(fā)送等。在編寫(xiě)網(wǎng)絡(luò)應(yīng)用的時(shí)候,需要考慮這一點(diǎn)。比如 IoConnectorconnect方法,其返回值是 org.apache.mina.core.future.ConnectFuture類(lèi)的對(duì)象。通過(guò)此對(duì)象,可以查詢(xún)連接操作的狀態(tài)。清單 3 中已經(jīng)使用了 ConnectFuture。另外一個(gè)常用的是發(fā)送數(shù)據(jù)時(shí)使用的 org.apache.mina.core.future.WriteFuture,如 清單 11 所示。


清單 11. WriteFuture 的使用
IoSession session = ...; // 獲取 I/O 會(huì)話(huà)對(duì)象
            WriteFuture future = session.write("Hello World"); // 發(fā)送數(shù)據(jù)
            future.awaitUninterruptibly(); // 等待發(fā)送數(shù)據(jù)操作完成
            if(future.isWritten())
            {
            // 數(shù)據(jù)已經(jīng)被成功發(fā)送
            }
            else
            {
            // 數(shù)據(jù)發(fā)送失敗
            }
            

由于這樣的需求很常見(jiàn),I/O 處理器中提供了 messageSent方法,當(dāng)數(shù)據(jù)發(fā)送成功的時(shí)候,該方法會(huì)被調(diào)用。

JMX 集成

Apache MINA 可以集成 JMX 來(lái)對(duì)網(wǎng)絡(luò)應(yīng)用進(jìn)行管理和監(jiān)測(cè)。下面通過(guò)對(duì)前面給出的計(jì)算器服務(wù)進(jìn)行簡(jiǎn)單修改,來(lái)說(shuō)明如何集成 JMX。所需的修改如 清單 12 所示。


清單 12. Apache MINA 與 JMX 的集成
MBeanServer mBeanServer = ManagementFactory.getPlatformMBeanServer();
            IoAcceptor acceptor = new NioSocketAcceptor();
            IoServiceMBean acceptorMBean = new IoServiceMBean(acceptor);
            ObjectName acceptorName = new ObjectName(acceptor.getClass()
            .getPackage().getName()
            + ":type=acceptor,name=" + acceptor.getClass().getSimpleName());
            mBeanServer.registerMBean(acceptorMBean, acceptorName);
            

清單 12 所示,首先獲取平臺(tái)提供的受控 bean 的服務(wù)器,接著創(chuàng)建受控 bean(MBean)來(lái)包裝想要管理和監(jiān)測(cè)的對(duì)象,這里使用的是 I/O 連接器對(duì)象。最后把創(chuàng)建出來(lái)的受控 bean 注冊(cè)到服務(wù)器即可。

在啟動(dòng)計(jì)算器服務(wù)應(yīng)用的時(shí)候,添加下面的啟動(dòng)參數(shù):-Dcom.sun.management.jmxremote -Dcom.sun.management.jmxremote.port=8084 -Dcom.sun.management.jmxremote.authenticate=false -Dcom.sun.management.jmxremote.ssl=false,就啟用了 JMX。接著通過(guò) JVM 提供的“Java 監(jiān)視和管理控制臺(tái)”(運(yùn)行 jconsole)就可以連接到此應(yīng)用進(jìn)行管理和監(jiān)測(cè)了。監(jiān)測(cè)的結(jié)果如 圖 6 所示。


圖 6. 通過(guò)“Java 監(jiān)視和管理控制臺(tái)”管理和監(jiān)測(cè)基于 Apache MINA 的應(yīng)用
通過(guò)“Java 監(jiān)視和管理控制臺(tái)”管理和監(jiān)測(cè)基于 apache mina 的應(yīng)用

Spring 集成

Apache MINA 可以和流行的開(kāi)源框架 Spring 進(jìn)行集成,由 Spring 來(lái)管理 Apache MINA 中的對(duì)象。與 Spring 集成的方式也比較簡(jiǎn)單,只需要編寫(xiě)相應(yīng)的 Spring 配置文件即可。清單 13 中給出了與 Spring 集成之后的計(jì)算器服務(wù)的配置文件。


清單 13. Apache MINA 與 Spring 集成的配置文件
 <?xml version="1.0" encoding="UTF-8"?>
            <beans>
            <bean id="calculatorHandler" class="calculator.CalculatorHandler" />
            <bean id="loggingFilter" class="org.apache.mina.filter.logging.LoggingFilter" />
            <bean id="calculatorCodecFilter" class="org.apache.mina.filter.
            codec.ProtocolCodecFilter">
            <constructor-arg>
            <bean class="org.apache.mina.filter.codec.textline.TextLineCodecFactory" />
            </constructor-arg>
            </bean>
            <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="calculatorCodecFilter" />
            </map>
            </property>
            </bean>
            <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>
            <bean id="ioAcceptor" class="org.apache.mina.transport.socket.nio.NioSocketAcceptor"
            init-method="bind" destroy-method="unbind">
            <property name="defaultLocalAddress" value=":10010" />
            <property name="handler" ref="calculatorHandler" />
            <property name="filterChainBuilder" ref="filterChainBuilder" />
            </bean>
            </beans>
            

清單 13 中創(chuàng)建 I/O 處理器和 I/O 過(guò)濾器的方式很直接。由于不能直接從 I/O 接受器獲取過(guò)濾器鏈,這里創(chuàng)建了一個(gè) org.apache.mina.core.filterchain.DefaultIoFilterChainBuilder類(lèi)的 bean,用來(lái)構(gòu)建過(guò)濾器鏈。由 Apache MINA 提供的網(wǎng)絡(luò)地址編輯器 org.apache.mina.integration.beans.InetSocketAddressEditor允許以“主機(jī)名 : 端口”的形式指定網(wǎng)絡(luò)地址。在聲明 I/O 接受器的時(shí)候,通過(guò) init-method指明了當(dāng) I/O 接受器創(chuàng)建成功之后,調(diào)用其 bind方法來(lái)接受連接;通過(guò) destroy-method聲明了當(dāng)其被銷(xiāo)毀的時(shí)候,調(diào)用其 unbind來(lái)停止監(jiān)聽(tīng)。


總結(jié)

Apache MINA 是一個(gè)很好的網(wǎng)絡(luò)應(yīng)用框架,它通過(guò)事件驅(qū)動(dòng)的 API 為開(kāi)發(fā)網(wǎng)絡(luò)應(yīng)用提供了良好的架構(gòu)基礎(chǔ),同時(shí)也提供了豐富的 I/O 服務(wù)和 I/O 過(guò)濾器的實(shí)現(xiàn),使得開(kāi)發(fā)網(wǎng)絡(luò)應(yīng)用變得簡(jiǎn)單。本文詳細(xì)介紹了 Apache MINA 中的基本概念,包括 I/O 服務(wù)、I/O 會(huì)話(huà)、I/O 過(guò)濾器和 I/O 處理器等,同時(shí)介紹了如何利用狀態(tài)機(jī)來(lái)實(shí)現(xiàn)邏輯復(fù)雜的 I/O 處理器。除此之外,還討論了 Apache MINA 如何與 JMX 和 Spring 進(jìn)行集成。本文提供了一個(gè)簡(jiǎn)單的計(jì)算器服務(wù)和復(fù)雜的俄羅斯方塊聯(lián)機(jī)游戲作為示例,可以幫助讀者更好的掌握基于 Apache MINA 的網(wǎng)絡(luò)應(yīng)用開(kāi)發(fā)。


    本站是提供個(gè)人知識(shí)管理的網(wǎng)絡(luò)存儲(chǔ)空間,所有內(nèi)容均由用戶(hù)發(fā)布,不代表本站觀點(diǎn)。請(qǐng)注意甄別內(nèi)容中的聯(lián)系方式、誘導(dǎo)購(gòu)買(mǎi)等信息,謹(jǐn)防詐騙。如發(fā)現(xiàn)有害或侵權(quán)內(nèi)容,請(qǐng)點(diǎn)擊一鍵舉報(bào)。
    轉(zhuǎn)藏 分享 獻(xiàn)花(0

    0條評(píng)論

    發(fā)表

    請(qǐng)遵守用戶(hù) 評(píng)論公約

    類(lèi)似文章 更多