Mina官方教程_中文版_第1頁
Mina官方教程_中文版_第2頁
Mina官方教程_中文版_第3頁
Mina官方教程_中文版_第4頁
Mina官方教程_中文版_第5頁
已閱讀5頁,還剩14頁未讀, 繼續(xù)免費閱讀

下載本文檔

版權說明:本文檔由用戶提供并上傳,收益歸屬內容提供方,若內容存在侵權,請進行舉報或認領

文檔簡介

1、簡介: apache mina 2 是一個開發(fā)高性能和高可伸縮性網(wǎng)絡應用程序的網(wǎng)絡應用框架。它提供了一個抽象的事件驅動的異步 api ,可以使用 tcp/ip 、udp/ip、串口和虛擬機內部的管道等傳輸方式。apache mina 2 可以作為開發(fā)網(wǎng)絡應用程序的一個良好基礎。本文將介紹 apache mina 2 的基本概念和 api ,包括 i/o 服務、 i/o 會話、 i/o 過濾器和 i/o 處理器。另外還將介紹如何使用狀態(tài)機。本文包含簡單的計算器服務和復雜的聯(lián)機游戲兩個示例應用。apache mina 2 是一個開發(fā)高性能和高可伸縮性網(wǎng)絡應用程序的網(wǎng)絡應用框架。它提供了一個抽象的事

2、件驅動的異步 api ,可以使用 tcp/ip 、udp/ip、串口和虛擬機內部的管道等傳輸方式。apache mina 2 可以作為開發(fā)網(wǎng)絡應用程序的一個良好基礎。下面將首先簡單介紹一下 apache mina 2 。apache mina 2 介紹apache mina 是 apache 基金會的一個開源項目,目前最新的版本是 2.0.0-rc1。本文中使用的版本是 2.0.0-m6。從 參考資料 中可以找到相關的下載信息。下面首先介紹基于 apache mina 的網(wǎng)絡應用的一般架構?;?apache mina 的網(wǎng)絡應用的架構基于 apache mina 開發(fā)的網(wǎng)絡應用,有著相似的架

3、構。圖 1 中給出了架構的示意圖。圖 1. 基于 apache mina 的網(wǎng)絡應用的架構如圖 1 所示,基于 apache mina 的網(wǎng)絡應用有三個層次,分別是 i/o 服務、 i/o 過濾器和 i/o 處理器:i/o 服務: i/o 服務用來執(zhí)行實際的 i/o 操作。 apache mina 已經(jīng)提供了一系列支持不同協(xié)議的 i/o 服務,如 tcp/ip 、udp/ip、串口和虛擬機內部的管道等。開發(fā)人員也可以實現(xiàn)自己的 i/o 服務。i/o 過濾器: i/o 服務能夠傳輸?shù)氖亲止?jié)流,而上層應用需要的是特定的對象與數(shù)據(jù)結構。i/o 過濾器用來完成這兩者之間的轉換。i/o 過濾器的另外一個

4、重要作用是對輸入輸出的數(shù)據(jù)進行處理,滿足橫切的需求。多個 i/o 過濾器串聯(lián)起來,形成 i/o 過濾器鏈。i/o 處理器: i/o 處理器用來執(zhí)行具體的業(yè)務邏輯。對接收到的消息執(zhí)行特定的處理。創(chuàng)建一個完整的基于 apache mina 的網(wǎng)絡應用, 需要分別構建這三個層次。apache mina 已經(jīng)為 i/o 服務和 i/o 過濾器提供了不少的實現(xiàn),因此這兩個層次在大多數(shù)情況下可以使用已有的實現(xiàn)。i/o 處理器由于是與具體的業(yè)務相關的,一般來說都是需要自己來實現(xiàn)的。事件驅動的 api apache mina 提供的是事件驅動的 api 。它把與網(wǎng)絡相關的各種活動抽象成事件。網(wǎng)絡應用只需要對其

5、感興趣的事件進行處理即可。事件驅動的 api 使得基于 apache mina 開發(fā)網(wǎng)絡應用變得比較簡單。應用不需要考慮與底層傳輸相關的具體細節(jié),而只需要處理抽象的 i/o 事件。比如在實現(xiàn)一個服務端應用的時候,如果有新的連接進來,i/o 服務會產(chǎn)生sessionopened這樣一個事件。如果該應用需要在有連接打開的時候,執(zhí)行某些特定的操作,只需要在 i/o 處理器中此事件處理方法sessionopened中添加相應的代碼即可。在介紹 apache mina 中的基本概念的細節(jié)之前,首先通過一個簡單的應用來熟悉上面提到的三個層次的具體職責。回頁首從簡單應用開始在使用 apache mina 開

6、發(fā)復雜的應用之前,首先將介紹一個簡單的應用。通過此應用可以熟悉上面提到的三個層次,即 i/o 服務、 i/o 過濾器和 i/o 處理器。該應用是一個簡單的計算器服務,客戶端發(fā)送要計算的表達式給服務器,服務器返回計算結果。比如客戶端發(fā)送2+2,服務器返回4.0作為結果。在實現(xiàn)此計算器的時候,首先需要考慮的是 i/o 服務。該計算器使用 tcp/ip 協(xié)議,需要在指定端口監(jiān)聽,接受客戶端的連接。apache mina 提供了基于 java nio 的套接字實現(xiàn),可以直接使用。其次要考慮的是 i/o 過濾器。i/o 過濾器過濾所有的 i/o 事件和請求, 可以用來處理橫切的需求,如記錄日志、壓縮等。

7、最后就是 i/o 處理器。 i/o 處理器用來處理業(yè)務邏輯。具體到該應用來說,就是在接收到消息之后,把該消息作為一個表達式來執(zhí)行,并把結果發(fā)送回去。i/o 處理器需要實現(xiàn)org.apache.mina.core.service.iohandler接口或者繼承自org.apache.mina.core.service.iohandleradapter。 該應用的 i/o 處理器的實現(xiàn)如清單1所示。清單 1. 計算器服務的 i/o 處理器calculatorhandler public class calculatorhandler extends iohandleradapter private

8、 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(找不到 javas

9、cript 引擎。); 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() ses

10、sion.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 中,messagereceived由iohandler接口聲明。當接收到新的消息的時候,該方法就會被調用。此處的邏輯是如果傳入了“quit ”,則通過session.cl

11、ose關閉當前連接;如果不是的話,就執(zhí)行該表達式并把結果通過session.write發(fā)送回去。此處執(zhí)行表達式用的是 jdk 6 中提供的 javascript 腳本引擎。此處使用到了 i/o 會話相關的方法,會在下面進行說明。接下來只需要把 i/o 處理器和 i/o 過濾器配置到 i/o 服務上就可以了。 具體的實現(xiàn)如清單 2 所示。清單 2. 計算器服務主程序 calculatorserver public class calculatorserver private static final int port = 10010; private static final logger lo

12、gger = 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 textlin

13、ecodecfactory(charset .forname(utf-8); acceptor.sethandler(new calculatorhandler(); acceptor.bind(new inetsocketaddress(port); logger.info(計算器服務已啟動,端口是 + port); 清單 2 中,首先創(chuàng)建一個org.apache.mina.transport.socket.nio.niosocketacceptor的實例,由它提供 i/o 服務;接著獲得該 i/o 服務的過濾器鏈,并添加兩個新的過濾器,一個用來記錄相關日志,另外一個用來在字節(jié)流和文本之間進

14、行轉換;最后配置 i/o 處理器。完成這些之后,通過bind方法來在特定的端口進行監(jiān)聽,接收連接。服務器啟動之后,可以通過操作系統(tǒng)自帶的 telnet 工具來進行測試,如圖 2 所示。在輸入表達式之后,計算結果會出現(xiàn)在下面一行。圖 2. 使用 telnet 工具測試計算器服務在介紹了簡單的計算器服務這個應用之后,下面說明本文中會使用的復雜的聯(lián)機游戲應用。回頁首聯(lián)機游戲示例說明上一節(jié)中給出了一個簡單的基于 apache mina 的網(wǎng)絡應用的實現(xiàn),可以用來熟悉基本的架構。而在實際開發(fā)中,網(wǎng)絡應用都是有一定復雜度的。下面會以一個比較復雜的聯(lián)機游戲作為示例來詳細介紹 apache mina 的概念、

15、 api 和典型用法。該聯(lián)機游戲支持兩個人進行俄羅斯方塊的對戰(zhàn)。這個游戲借鑒了 qq 的“火拼俄羅斯”。用戶在啟動客戶端之后,需要輸入一個昵稱進行注冊。用戶可以在“游戲大廳”中查看當前已注冊的所有其它用戶。當前用戶可以選擇另外的一個用戶發(fā)送游戲邀請。邀請被接受之后就可以開始進行對戰(zhàn)。在游戲過程中, 當前用戶可以看到對方的游戲狀態(tài),即方塊的情況。 該游戲的運行效果如圖 3 所示。圖 3. 聯(lián)機游戲示例運行效果圖下面開始以這個應用為例來具體介紹 apache mina 中的基本概念。先從 i/o 服務開始。回頁首i/o 服務i/o 服務用來執(zhí)行真正的 i/o 操作,以及管理 i/o 會話。根據(jù)所使

16、用的數(shù)據(jù)傳輸方式的不同,有不同的 i/o 服務的實現(xiàn)。由于 i/o 服務執(zhí)行的是輸入和輸出兩種操作,實際上有兩種具體的子類型。一種稱為“ i/o 接受器(i/o acceptor)”,用來接受連接,一般用在服務器的實現(xiàn)中;另外一種稱為“ i/o 連接器(i/o connector)”,用來發(fā)起連接,一般用在客戶端的實現(xiàn)中。對應在apache mina 中的實現(xiàn),org.apache.mina.core.service.ioservice是 i/o 服務的接口,而繼承自它的接口org.apache.mina.core.service.ioacceptor和org.apache.mina.core

17、.service.ioconnector則分別表示 i/o 接受器和 i/o 連接器。ioservice接口提供的重要方法如表 1 所示。表 1. ioservice 中的重要方法方法說明sethandler(iohandler handler) 設置 i/o 處理器。 該 i/o 處理器會負責處理該 i/o 服務所管理的所有 i/o 會話產(chǎn)生的 i/o 事件。getfilterchain() 獲取 i/o 過濾器鏈,可以對 i/o 過濾器進行管理,包括添加和刪除 i/o 過濾器。getmanagedsessions() 獲取該 i/o 服務所管理的 i/o 會話。下面具體介紹 i/o 接受器

18、和 i/o 連接器。i/o 接受器i/o 接受器用來接受連接,與對等體(客戶端)進行通訊,并發(fā)出相應的 i/o 事件交給 i/o 處理器來處理。使用 i/o 接受器的時候,只需要調用bind方法并指定要監(jiān)聽的套接字地址。當不再接受連接的時候,調用unbind停止監(jiān)聽即可。關于 i/o 接受器的具體用法,可以參考清單 2 中給出的計算器服務的實現(xiàn)。i/o 連接器i/o 連接器用來發(fā)起連接,與對等體(服務器)進行通訊,并發(fā)出相應的 i/o 事件交給 i/o 處理器來處理。 使用 i/o 連接器的時候, 只需要調用connect方法連接指定的套接字地址。另外可以通過setconnecttimeout

19、millis設置連接超時時間(毫秒數(shù))。清單 3 中給出了使用 i/o 連接器的一個示例。清單 3. i/o 連接器示例socketconnector connector = new niosocketconnector(); connector.setconnecttimeoutmillis(connect_timeout); connector.getfilterchain().addlast(logger, new loggingfilter(); connector.getfilterchain().addlast(protocol, new protocolcodecfilter(n

20、ew tetriscodecfactory(); connectfuture connectfuture = connector.connect(new inetsocketaddress(host, port); connectfuture.awaituninterruptibly(); 在清單 3 中,首先創(chuàng)建一個 java nio 的套接字連接器niosocketconnector的實例,接著設置超時時間。再添加了 i/o 過濾器之后,通過connect方法連接到指定的地址和端口即可。在介紹完 i/o 服務之后,下面介紹 i/o 會話?;仨撌譱/o 會話i/o 會話表示一個活動的網(wǎng)絡連接

21、,與所使用的傳輸方式無關。i/o 會話可以用來存儲用戶自定義的與應用相關的屬性。這些屬性通常用來保存應用的狀態(tài)信息,還可以用來在 i/o 過濾器和 i/o 處理器之間交換數(shù)據(jù)。i/o 會話在作用上類似于 servlet 規(guī)范中的 http 會話。apache mina 中 i/o 會話實現(xiàn)的接口是org.apache.mina.core.session.iosession。該接口中比較重要的方法如表 2 所示。表 2. iosession 中的重要方法方法說明close(boolean immediately) 關閉當前連接。如果參數(shù)immediately為true的話,連接會等到隊列中所有的

22、數(shù)據(jù)發(fā)送請求都完成之后才關閉;否則的話就立即關閉。getattribute(object key) 從 i/o 會話中獲取鍵為key的用戶自定義的屬性。setattribute(object key, object value) 將鍵為key,值為value的用戶自定義的屬性存儲到 i/o 會話中。removeattribute(object key) 從 i/o 會話中刪除鍵為key的用戶自定義的屬性。write(object message) 將消息對象message發(fā)送到當前連接的對等體。該方法是異步的,當消息被真正發(fā)送到對等體的時候,iohandler.messagesent(iose

23、ssion,object)會被調用。如果需要的話,也可以等消息真正發(fā)送出去之后再繼續(xù)執(zhí)行后續(xù)操作。在介紹完 i/o 會話之后,下面介紹 i/o 過濾器?;仨撌譱/o 過濾器從 i/o 服務發(fā)送過來的所有 i/o 事件和請求,在到達 i/o 處理器之前,會先由 i/o 過濾器鏈中的 i/o 過濾器進行處理。apache mina 中的過濾器與 servlet 規(guī)范中的過濾器是類似的。過濾器可以在很多情況下使用,比如記錄日志、性能分析、訪問控制、負載均衡和消息轉換等。過濾器非常適合滿足網(wǎng)絡應用中各種橫切的非功能性需求。在一個基于 apache mina 的網(wǎng)絡應用中,一般存在多個過濾器。這些過濾器

24、互相串聯(lián),形成鏈條,稱為過濾器鏈。每個過濾器依次對傳入的 i/o 事件進行處理。當前過濾器完成處理之后,由過濾器鏈中的下一個過濾器繼續(xù)處理。當前過濾器也可以不調用下一個過濾器,而提前結束,這樣 i/o 事件就不會繼續(xù)往后傳遞。比如負責用戶認證的過濾器,如果遇到未認證的對等體發(fā)出的 i/o 事件,則會直接關閉連接。這可以保證這些事件不會通過此過濾器到達 i/o 處理器。apache mina 中 i/o 過濾器都實現(xiàn)org.apache.mina.core.filterchain.iofilter接口。一般來說,不需要完整實現(xiàn)iofilter接口,只需要繼承 apache mina 提供的適配器

25、org.apache.mina.core.filterchain.iofilteradapter,并覆寫所需的事件過濾方法即可,其它方法的默認實現(xiàn)是不做任何處理,而直接把事件轉發(fā)到下一個過濾器。iofilter 接口詳細說明iofilter接口提供了 15 個方法。這 15 個方法大致分成兩類,一類是與過濾器的生命周期相關的,另外一類是用來過濾 i/o 事件的。第一類方法如表 3 所示。表 3. iofilter 中與過濾器的生命周期相關的方法方法說明init() 當過濾器第一次被添加到過濾器鏈中的時候,此方法被調用。用來完成過濾器的初始化工作。onpreadd(iofilterchain p

26、arent, string name, 當過濾器即將被添加到過濾器鏈中的時候,此方法被調iofilter.nextfilter nextfilter) 用。onpostadd(iofilterchain parent, string name, iofilter.nextfilter nextfilter) 當過濾器已經(jīng)被添加到過濾器鏈中之后,此方法被調用。onpreremove(iofilterchain parent, string name, iofilter.nextfilter nextfilter) 當過濾器即將被從過濾器鏈中刪除的時候,此方法被調用。onpostremove(io

27、filterchain parent, string name, iofilter.nextfilter nextfilter) 當過濾器已經(jīng)被從過濾器鏈中刪除的時候,此方法被調用。destroy() 當過濾器不再需要的時候,它將被銷毀,此方法被調用。在表 3 中給出的方法中, 參數(shù)parent表示包含此過濾器的過濾器鏈,參數(shù)name表示過濾器的名稱,參數(shù)nextfilter表示過濾器鏈中的下一個過濾器。第二類方法如表 4 所示。表 4. iofilter 中過濾 i/o 事件的方法方法說明filterclose(iofilter.nextfilter nextfilter, iosessio

28、n session) 過濾對iosession的close方法的調用。filterwrite(iofilter.nextfilter nextfilter, iosession session, writerequest writerequest) 過濾對iosession的write方法的調用。exceptioncaught(iofilter.nextfilter nextfilter, iosession session, throwable cause) 過濾對iohandler的exceptioncaught方法的調用。messagereceived(iofilter.nextfilt

29、er nextfilter, iosession session, object message) 過濾對iohandler的messagereceived方法的調用。messagesent(iofilter.nextfilter nextfilter, iosession session, writerequest writerequest) 過濾對iohandler的messagesent方法的調用。sessionclosed(iofilter.nextfilter nextfilter, iosession session) 過濾對iohandler的sessionclosed方法的調用

30、。sessioncreated(iofilter.nextfilter nextfilter, iosession session) 過濾對iohandler的sessioncreated方法的調用。sessionidle(iofilter.nextfilter nextfilter, iosession session, idlestatus status) 過濾對iohandler的sessionidle方法的調用。sessionopened(iofilter.nextfilter nextfilter, iosession session) 過濾對iohandler的sessionope

31、ned方法的調用。對于 表 4 中給出的與 i/o 事件相關的方法,它們都有一個參數(shù)是nextfilter,表示過濾器鏈中的下一個過濾器。如果當前過濾器完成處理之后,可以通過調用nextfilter中的方法,把 i/o 事件傳遞到下一個過濾器。如果當前過濾器不調用nextfilter中的方法的話, 該 i/o 事件就不能繼續(xù)往后傳遞。 另外一個共同的參數(shù)是session,用來表示當前的 i/o 會話, 可以用來發(fā)送消息給對等體。下面通過具體的實例來說明過濾器的實現(xiàn)。blacklistfilter blacklistfilter是 apache mina 自帶的一個過濾器實現(xiàn),其功能是阻止來自特

32、定地址的連接,即所謂的“黑名單”功能。blacklistfilter繼承自iofilteradapter,并覆寫了iohandler相關的方法。清單 4 中給出了部分實現(xiàn)。清單 4. 阻止來自特定地址連接的 blacklistfilter public void messagereceived(nextfilter nextfilter, iosession session, object message) if (!isblocked(session) nextfilter.messagereceived(session, message); else blocksession(sessio

33、n); private void blocksession(iosession session) session.close(true); 在清單 4 中messagereceived方法的實現(xiàn)中, 首先通過isblocked來判斷當前連接是否應該被阻止, 如果不是的話, 則通過nextfilter.messagereceived把該 i/o 事件傳遞到下一個過濾器;否則的話,則通過blocksession來阻止當前連接。使用 protocolcodecfilter protocolcodecfilter用來在字節(jié)流和消息對象之間互相轉換。當該過濾器接收到字節(jié)流的時候,需要首先判斷消息的邊界,

34、然后把表示一條消息的字節(jié)提取出來,通過一定的邏輯轉換成消息對象,再把消息對象往后傳遞,交給 i/o 處理器來執(zhí)行業(yè)務邏輯。這個過程稱為“解碼”。與“解碼”對應的是“編碼”過程。在“編碼”的時候,過濾器接收到的是消息對象,通過與“解碼”相反的邏輯,把消息對象轉換成字節(jié),并反向傳遞,交給 i/o 服務來執(zhí)行 i/o 操作。在“編碼”和“解碼”中的一個重要問題是如何在字節(jié)流中判斷消息的邊界。通常來說,有三種辦法解決這個問題:使用固定長度的消息。這種方式實現(xiàn)起來比較簡單,只需要每次讀取特定數(shù)量的字節(jié)即可。使用固定長度的消息頭來指明消息主體的長度。比如每個消息開始的 4 個字節(jié)的值表示了后面緊跟的消息主

35、體的長度。只需要首先讀取該長度,再讀取指定數(shù)量的字節(jié)即可。使用分隔符。 消息之間通過特定模式的分隔符來分隔。每次只要遇到該模式的字節(jié),就表示到了一個消息的末尾。具體到示例應用來說,客戶端和服務器之間的通信協(xié)議比較復雜,有不同種類的消息。每種消息的格式都不相同,同類消息的內容也不盡相同。因此,使用固定長度的消息頭來指明消息主體的長度就成了最好的選擇。示例應用中的每種消息主體由兩部分組成,第一部分是固定長度的消息類別名稱,第二部分是每種消息的主體內容。圖 4 中給出了示例應用中一條完整的消息的結構。圖 4. 示例應用中消息的結構abstracttetriscommand用來描述聯(lián)機游戲示例應用中的

36、消息。它是一個抽象類,是所有具體消息的基類。其具體實現(xiàn)如清單 5 所示。清單 5. 聯(lián)機游戲示例應用中的消息 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; pu

37、blic byte tobytes() throws exception byte body = bodytobytes(); int commandnamelength = constants.command_name_length; int len = commandnamelength + body.length; byte bytes = new bytelen; string name = stringutils.rightpad(getname(), commandnamelength, constants.command_name_pad_char); name = name.s

38、ubstring(0, commandnamelength); system.arraycopy(name.getbytes(), 0, bytes, 0, commandnamelength); system.arraycopy(body, 0, bytes, commandnamelength, body.length); return bytes; 如清單 5 所示,abstracttetriscommand中定義了 3 個抽象方法:getname、bodytobytes和bodyfrombytes,分別用來獲取消息的名稱、把消息的主體轉換成字節(jié)數(shù)組和從字節(jié)數(shù)組中構建消息。bodytob

39、ytes對應于前面提到的“編碼”過程,而bodyfrombytes對應于“解碼”過程。每種具體的消息都應該實現(xiàn)這 3 個方法。abstracttetriscommand中的方法tobytes封裝了把消息的主體轉換成字節(jié)數(shù)組的邏輯,在字節(jié)數(shù)組中,首先是長度固定為constants.command_name_length的消息類別名稱,緊接著是每種消息特定的主體內容,由bodytobytes方法來生成。在介紹完示例應用中的消息格式之后,下面將討論具體的“編碼”和“解碼”過程?!熬幋a”過程由編碼器來完成,編碼器需要實現(xiàn)org.apache.mina.filter.codec.protocolenco

40、der接口,一般來說繼承自org.apache.mina.filter.codec.protocolencoderadapter并覆寫所需的方法即可。清單 6 中給出了示例應用中消息編碼器commandencoder的實現(xiàn)。清單 6. 聯(lián)機游戲示例應用中消息編碼器 commandencoder public class commandencoder extends protocolencoderadapter public void encode(iosession session, object message, protocolencoderoutput out) throws excep

41、tion 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方法封裝了編碼的邏輯。由于abstracttetriscommand

42、的tobytes已經(jīng)完成了到字節(jié)數(shù)組的轉換,encode方法直接使用即可。首先寫入消息主體字節(jié)數(shù)組的長度,再是字節(jié)數(shù)組本身,就完成了編碼的過程。與編碼過程相比,解碼過程要相對復雜一些。具體的實現(xiàn)如清單 7 所示。清單 7. 聯(lián)機游戲示例應用中消息解碼器 commanddecoder public class commanddecoder extends cumulativeprotocoldecoder protected boolean dodecode(iosession session, iobuffer in, protocoldecoderoutput out) throws exc

43、eption if (in.prefixeddataavailable(4, constants.max_command_length) int length = in.getint(); byte bytes = new bytelength; in.get(bytes); int commandnamelength = constants.command_name_length; byte cmdnamebytes = new bytecommandnamelength; system.arraycopy(bytes, 0, cmdnamebytes, 0, commandnameleng

44、th); string cmdname = stringutils.trim(new string(cmdnamebytes); abstracttetriscommand command = tetriscommandfactory .newcommand(cmdname); if (command != null) byte cmdbodybytes = new bytelength - commandnamelength; system.arraycopy(bytes, commandnamelength, cmdbodybytes, 0, length - commandnamelen

45、gth); command.bodyfrombytes(cmdbodybytes); out.write(command); return true; else return false; 在清單 7 中可以看到,解碼器commanddecoder繼承自cumulativeprotocoldecoder。這是apache mina 提供的一個幫助類,它會自動緩存所有已經(jīng)接收到的數(shù)據(jù),直到編碼器認為可以開始進行編碼。這樣在實現(xiàn)自己的編碼器的時候,就只需要考慮如何判斷消息的邊界即可。如果一條消息的后續(xù)數(shù)據(jù)還沒有接收到,cumulativeprotocoldecoder會自動進行緩存。在之前提到過,

46、解碼過程的一個重要問題是判斷消息的邊界。對于固定長度的消息來說,只需要使用 apache mina 的iobuffer的remaining方法來判斷當前緩存中的字節(jié)數(shù)目,如果大于消息長度的話,就進行解碼;對于使用固定長度消息頭來指明消息主體的長度的情況,iobuffer提供了prefixeddataavailable方法來滿足這一需求。prefixeddataavailable會檢查當前緩存中是否有固定長度的消息頭,并且由此消息頭指定長度的消息主體是否已經(jīng)全部在緩存中。如果這兩個條件都滿足的話,說明一條完整的消息已經(jīng)接收到,可以進行解碼了。 解碼的過程本身并不復雜,首先讀取消息的類別名稱,然后

47、通過tetriscommandfactory.newcommand方法來生成一個該類消息的實例,接著通過該實例的bodyfrombytes方法就可以從字節(jié)數(shù)組中恢復消息的內容,得到一個完整的消息對象。每次成功解碼一個消息對象,需要調用protocoldecoderoutput的write把此消息對象往后傳遞。消息對象會通過過濾器鏈,最終達到 i/o 處理器,在iohandler.messagereceived中接收到此消息對象。如果當前緩存的數(shù)據(jù)不足以用來解碼一條消息的話,dodecode只需要返回false即可。接收到新的數(shù)據(jù)之后,dodecode會被再次調用。過濾器鏈過濾器只有在添加到過濾

48、器鏈中的時候才起作用。過濾器鏈是過濾器的容器。過濾器鏈與 i/o 會話是一一對應的關系。org.apache.mina.core.filterchain.iofilterchain是 apache mina 中過濾器鏈的接口,其中提供了一系列方法對其中包含的過濾器進行操作,包括查詢、添加、刪除和替換等。如表 5 所示。表 5. iofilterchain 接口的方法方法說明addfirst(string name, iofilter filter) 將指定名稱的過濾器添加到過濾器鏈的開頭。addlast(string name, iofilter filter) 將指定名稱的過濾器添加到過濾器

49、鏈的末尾。contains(string name) 判斷過濾器鏈中是否包含指定名稱的過濾器。get(string name) 從過濾器鏈中獲取指定名稱的過濾器。remove(string name) 從過濾器鏈中刪除指定名稱的過濾器。replace(string name, iofilter newfilter) 用過濾器newfilter替換掉過濾器鏈中名為name的過濾器。getsession() 獲取與過濾器鏈一一對應的 i/o 會話。在介紹完 i/o 過濾器和過濾器鏈之后,下面介紹 i/o 處理器。回頁首i/o 處理器i/o 事件通過過濾器鏈之后會到達 i/o 處理器。 i/o 處理

50、器中與 i/o 事件對應的方法會被調用。apache mina 中org.apache.mina.core.service.iohandler是 i/o 處理器要實現(xiàn)的接口,一般情況下,只需要繼承自org.apache.mina.core.service.iohandleradapter并覆寫所需方法即可。iohandler接口的方法如 表 6 所示。表 6. iohandler 接口的方法方法說明sessioncreated(iosession session) 當有新的連接建立的時候,該方法被調用。sessionopened(iosession session) 當有新的連接打開的時候,該

51、方法被調用。該方法在sessioncreated之后被調用。sessionclosed(iosession session) 當連接被關閉的時候,此方法被調用。sessionidle(iosession session, idlestatus status) 當連接變成閑置狀態(tài)的時候,此方法被調用。exceptioncaught(iosession session, throwable cause) 當 i/o 處理器的實現(xiàn)或是 apache mina 中有異常拋出的時候,此方法被調用。messagereceived(iosession session, object message) 當接收

52、到新的消息的時候,此方法被調用。messagesent(iosession session, object message) 當消息被成功發(fā)送出去的時候,此方法被調用。對于 表 6 中的方法,有幾個需要重點的說明一下。首先是sessioncreated和sessionopened的區(qū)別。sessioncreated方法是由 i/o 處理線程來調用的,而sessionopened是由其它線程來調用的。因此從性能方面考慮,不要在sessioncreated方法中執(zhí)行過多的操作。對于sessionidle,默認情況下,閑置時間設置是禁用的,也就是說sessionidle并不會被調用??梢酝ㄟ^iose

53、ssionconfig.setidletime(idlestatus, int)來進行設置。apache mina 中的基本概念已經(jīng)介紹完了,下面介紹狀態(tài)機的使用?;仨撌资褂脿顟B(tài)機在 i/o 處理器中實現(xiàn)業(yè)務邏輯的時候,對于簡單的情況,一般只需要在messagereceived方法中對傳入的消息進行處理。如果需要寫回數(shù)據(jù)到對等體的話,用iosession.write方法即可。在另外的一些情況下,客戶端和服務器端的通信協(xié)議比較復雜,客戶端其實是有狀態(tài)變遷的。這個時候可以用 apache mina 提供的狀態(tài)機實現(xiàn),可以使得 i/o 處理器的實現(xiàn)更加簡單。狀態(tài)機中兩個重要的元素是狀態(tài)以及狀態(tài)之間的

54、遷移。示例應用中客戶端的狀態(tài)以及遷移如圖 5 所示。圖 5. 聯(lián)機游戲示例應用中客戶端的狀態(tài)以及遷移客戶端初始化的時候,其狀態(tài)為“未連接”,表示客戶端還沒有在服務器上面注冊,此時還不能進行游戲;接著用戶需要輸入一個昵稱來注冊到服務器上面,完成之后狀態(tài)遷移到“閑置”。此時客戶端會接收到當前在線的所有其它用戶的列表。當前用戶可以邀請其它用戶和他一塊游戲,也可以接收來自其它用戶的邀請。邀請發(fā)送出去之后,客戶端的狀態(tài)遷移到“邀請已發(fā)送”。如果接受了其它用戶的邀請,客戶端的狀態(tài)遷移到“邀請已接收”。如果某個用戶的邀請被另外一個用戶接受的話,兩個客戶端的狀態(tài)都會遷移到“游戲中”。要實現(xiàn)這樣較為復雜的狀態(tài)機

55、的話,只需要在 i/o 處理器中以聲明式的方式定義狀態(tài)和遷移條件就可以了。首先需要聲明狀態(tài)機中狀態(tài),如清單 8 所示。清單 8. 聯(lián)機游戲示例應用中的狀態(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

56、 = invitationsent; state(root) public static final string invitation_accepted = invitationaccepted; state(root) public static final string playing = playing; 如清單 8 所示,上面定義了一共六個狀態(tài)。通過標注state就聲明了一個狀態(tài)。需要注意的是狀態(tài)之間是可以繼承的。如果狀態(tài)機接收到一個事件的時候,在當前狀態(tài)中找不到對應的遷移,就會在其父狀態(tài)上繼續(xù)查找。狀態(tài)的繼承在某些情況下是很有用的,比如希望為所有的狀態(tài)都增加同樣的遷移邏輯,就可以直

57、接把遷移條件添加在父狀態(tài)上面。一個典型的場景就是錯誤處理,一般來說,所有的狀態(tài)都需要錯誤處理,而錯誤處理的邏輯一般都是相同的。把發(fā)生錯誤時候的遷移放在父狀態(tài)中,可以簡潔的描述這一場景。定義了狀態(tài)之后,下面應該聲明狀態(tài)之間的遷移。如清單 9 所示。清單 9. 聯(lián)機游戲示例應用中的狀態(tài)遷移聲明iohandlertransition(on = message_received, in = not_connected, next = idle) public void login(tetrisservercontext context, iosession session, logincommand

58、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); iohan

59、dlertransition(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 中,使用標注iohand

60、lertransition聲明了一個狀態(tài)遷移。每個狀態(tài)遷移可以有四個屬性:on、in、next和weight,其中屬性in是必須的,其余是可選的。屬性on表示觸發(fā)此狀態(tài)遷移的事件名稱,如果省略該屬性的話,則默認為匹配所有事件的通配符。該屬性的值可以是表中給出的 i/o 處理器中能處理的七種事件類型。屬性in表示狀態(tài)遷移的起始狀態(tài)。屬性next表示狀態(tài)遷移的結束狀態(tài),如果省略該屬性的話,則默認為表示當前狀態(tài)的_self_。屬性weight用來指明狀態(tài)遷移的權重。一個狀態(tài)的所有遷移是按照其權重升序排列的。對于當前狀態(tài),如果有多個可能的遷移,排序靠前的遷移將會發(fā)生。代碼中的第一個標注聲明了如果當前狀

溫馨提示

  • 1. 本站所有資源如無特殊說明,都需要本地電腦安裝OFFICE2007和PDF閱讀器。圖紙軟件為CAD,CAXA,PROE,UG,SolidWorks等.壓縮文件請下載最新的WinRAR軟件解壓。
  • 2. 本站的文檔不包含任何第三方提供的附件圖紙等,如果需要附件,請聯(lián)系上傳者。文件的所有權益歸上傳用戶所有。
  • 3. 本站RAR壓縮包中若帶圖紙,網(wǎng)頁內容里面會有圖紙預覽,若沒有圖紙預覽就沒有圖紙。
  • 4. 未經(jīng)權益所有人同意不得將文件中的內容挪作商業(yè)或盈利用途。
  • 5. 人人文庫網(wǎng)僅提供信息存儲空間,僅對用戶上傳內容的表現(xiàn)方式做保護處理,對用戶上傳分享的文檔內容本身不做任何修改或編輯,并不能對任何下載內容負責。
  • 6. 下載文件中如有侵權或不適當內容,請與我們聯(lián)系,我們立即糾正。
  • 7. 本站不保證下載資源的準確性、安全性和完整性, 同時也不承擔用戶因使用這些下載資源對自己和他人造成任何形式的傷害或損失。

評論

0/150

提交評論