聯機遊戲示例說明
上一節中給出了一個簡單的基于 Apache MINA 的網絡應用的實作,可以用來熟悉基本的架構。而在實際開發中,網絡應用都是有一定複雜度的。下面會以一個比較複雜的聯機遊戲作為示例來詳細介紹 Apache MINA 的概念、API 和典型用法。
該聯機遊戲支援兩個人進行俄羅斯方塊的對戰。這個遊戲借鑒了 QQ 的“火拼俄羅斯”。使用者在啟動用戶端之後,需要輸入一個昵稱進行注冊。使用者可以在“遊戲大廳”中檢視目前已注冊的所有其它使用者。目前使用者可以選擇另外的一個使用者發送遊戲邀請。邀請被接受之後就可以開始進行對戰。在遊戲過程中,目前使用者可以看到對方的遊戲狀态,即方塊的情況。該遊戲的運作效果如 圖 3 所示。
圖 3. 聯機遊戲示例運作效果圖
下面開始以這個應用為例來具體介紹 Apache MINA 中的基本概念。先從 I/O 服務開始。
回頁首
I/O 服務
I/O 服務用來執行真正的 I/O 操作,以及管理 I/O 會話。根據所使用的資料傳輸方式的不同,有不同的 I/O 服務的實作。由于 I/O 服務執行的是輸入和輸出兩種操作,實際上有兩種具體的子類型。一種稱為“I/O 接受器(I/O acceptor)”,用來接受連接配接,一般用在伺服器的實作中;另外一種稱為“I/O 連接配接器(I/O connector)”,用來發起連接配接,一般用在用戶端的實作中。對應在 Apache MINA 中的實作,org.apache.mina.core.service.IoService是 I/O 服務的接口,而繼承自它的接口 org.apache.mina.core.service.IoAcceptor 和 org.apache.mina.core.service.IoConnector 則分别表示 I/O 接受器和 I/O 連接配接器。IoService 接口提供的重要方法如 表 1 所示。
表 1. IoService 中的重要方法
方法 說明
setHandler(IoHandler handler) 設定 I/O 處理器。該 I/O 處理器會負責處理該 I/O 服務所管理的所有 I/O 會話産生的 I/O 事件。
getFilterChain() 擷取 I/O 過濾器鍊,可以對 I/O 過濾器進行管理,包括添加和删除 I/O 過濾器。
getManagedSessions() 擷取該 I/O 服務所管理的 I/O 會話。
下面具體介紹 I/O 接受器和 I/O 連接配接器。
I/O 接受器
I/O 接受器用來接受連接配接,與對等體(用戶端)進行通訊,并發出相應的 I/O 事件交給 I/O 處理器來處理。使用 I/O 接受器的時候,隻需要調用 bind方法并指定要監聽的套接字位址。當不再接受連接配接的時候,調用 unbind停止監聽即可。關于 I/O 接受器的具體用法,可以參考 清單 2 中給出的電腦服務的實作。
I/O 連接配接器
I/O 連接配接器用來發起連接配接,與對等體(伺服器)進行通訊,并發出相應的 I/O 事件交給 I/O 處理器來處理。使用 I/O 連接配接器的時候,隻需要調用 connect方法連接配接指定的套接字位址。另外可以通過 setConnectTimeoutMillis設定連接配接逾時時間(毫秒數)。
清單 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(new TetrisCodecFactory()));
ConnectFuture connectFuture = connector.connect(new InetSocketAddress(host, port));
connectFuture.awaitUninterruptibly();
在 清單 3 中,首先建立一個 Java NIO 的套接字連接配接器 NioSocketConnector 的執行個體,接着設定逾時時間。再添加了 I/O 過濾器之後,通過 connect 方法連接配接到指定的位址和端口即可。
在介紹完 I/O 服務之後,下面介紹 I/O 會話。
回頁首
I/O 會話
I/O 會話表示一個活動的網絡連接配接,與所使用的傳輸方式無關。I/O 會話可以用來存儲使用者自定義的與應用相關的屬性。這些屬性通常用來儲存應用的狀态資訊,還可以用來在 I/O 過濾器和 I/O 處理器之間交換資料。I/O 會話在作用上類似于 Servlet 規範中的 HTTP 會話。
Apache MINA 中 I/O 會話實作的接口是 org.apache.mina.core.session.IoSession。該接口中比較重要的方法如 表 2 所示。
表 2. IoSession 中的重要方法
方法 說明
close(boolean immediately) 關閉目前連接配接。如果參數 immediately為 true的話,連接配接會等到隊列中所有的資料發送請求都完成之後才關閉;否則的話就立即關閉。
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發送到目前連接配接的對等體。該方法是異步的,當消息被真正發送到對等體的時候,IoHandler.messageSent(IoSession,Object)會被調用。如果需要的話,也可以等消息真正發送出去之後再繼續執行後續操作。
在介紹完 I/O 會話之後,下面介紹 I/O 過濾器。
回頁首
I/O 過濾器
從 I/O 服務發送過來的所有 I/O 事件和請求,在到達 I/O 處理器之前,會先由 I/O 過濾器鍊中的 I/O 過濾器進行處理。Apache MINA 中的過濾器與 Servlet 規範中的過濾器是類似的。過濾器可以在很多情況下使用,比如記錄日志、性能分析、通路控制、負載均衡和消息轉換等。過濾器非常适合滿足網絡應用中各種橫切的非功能性需求。在一個基于 Apache MINA 的網絡應用中,一般存在多個過濾器。這些過濾器互相串聯,形成鍊條,稱為過濾器鍊。每個過濾器依次對傳入的 I/O 事件進行處理。目前過濾器完成處理之後,由過濾器鍊中的下一個過濾器繼續處理。目前過濾器也可以不調用下一個過濾器,而提前結束,這樣 I/O 事件就不會繼續往後傳遞。比如負責使用者認證的過濾器,如果遇到未認證的對等體發出的 I/O 事件,則會直接關閉連接配接。這可以保證這些事件不會通過此過濾器到達 I/O 處理器。
Apache MINA 中 I/O 過濾器都實作 org.apache.mina.core.filterchain.IoFilter接口。一般來說,不需要完整實作 IOFilter接口,隻需要繼承 Apache MINA 提供的擴充卡 org.apache.mina.core.filterchain.IoFilterAdapter,并覆寫所需的事件過濾方法即可,其它方法的預設實作是不做任何處理,而直接把事件轉發到下一個過濾器。
IoFilter 接口詳細說明
IoFilter接口提供了 15 個方法。這 15 個方法大緻分成兩類,一類是與過濾器的生命周期相關的,另外一類是用來過濾 I/O 事件的。第一類方法如 表 3 所示。
表 3. IoFilter 中與過濾器的生命周期相關的方法
方法 說明
init() 當過濾器第一次被添加到過濾器鍊中的時候,此方法被調用。用來完成過濾器的初始化工作。
onPreAdd(IoFilterChain parent, String name, IoFilter.NextFilter nextFilter) 當過濾器即将被添加到過濾器鍊中的時候,此方法被調用。
onPostAdd(IoFilterChain parent, String name, IoFilter.NextFilter nextFilter) 當過濾器已經被添加到過濾器鍊中之後,此方法被調用。
onPreRemove(IoFilterChain parent, String name, IoFilter.NextFilter nextFilter) 當過濾器即将被從過濾器鍊中删除的時候,此方法被調用。
onPostRemove(IoFilterChain parent, String name, IoFilter.NextFilter nextFilter) 當過濾器已經被從過濾器鍊中删除的時候,此方法被調用。
destroy() 當過濾器不再需要的時候,它将被銷毀,此方法被調用。
在 表 3 中給出的方法中,參數 parent 表示包含此過濾器的過濾器鍊,參數 name 表示過濾器的名稱,參數 nextFilter 表示過濾器鍊中的下一個過濾器。
第二類方法如 表 4 所示。
表 4. IoFilter 中過濾 I/O 事件的方法
方法 說明
filterClose(IoFilter.NextFilter nextFilter, IoSession 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.NextFilter 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方法的調用。
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的 sessionOpened方法的調用。
對于 表 4 中給出的與 I/O 事件相關的方法,它們都有一個參數是 nextFilter,表示過濾器鍊中的下一個過濾器。如果目前過濾器完成處理之後,可以通過調用 nextFilter 中的方法,把 I/O 事件傳遞到下一個過濾器。如果目前過濾器不調用 nextFilter 中的方法的話,該 I/O 事件就不能繼續往後傳遞。另外一個共同的參數是 session,用來表示目前的 I/O 會話,可以用來發送消息給對等體。下面通過具體的執行個體來說明過濾器的實作。
BlacklistFilter
BlacklistFilter是 Apache MINA 自帶的一個過濾器實作,其功能是阻止來自特定位址的連接配接,即所謂的“黑名單”功能。BlacklistFilter繼承自 IoFilterAdapter,并覆寫了 IoHandler相關的方法。清單 4 中給出了部分實作。
清單 4. 阻止來自特定位址連接配接的 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);
}
在 清單 4 中 messageReceived 方法的實作中,首先通過 isBlocked 來判斷目前連接配接是否應該被阻止,如果不是的話,則通過 nextFilter.messageReceived 把該 I/O 事件傳遞到下一個過濾器;否則的話,則通過 blockSession 來阻止目前連接配接。