最近由於項目本身的需要,正在進行Mina框架的學習,並且將其整合到正在開發的系統中。下面將會根據實際的工作情況分享一些心得感受。
一、 項目需求:
我們正在開發的系統,現在主要分爲兩個部分,正兩個部分之間需要使用TCP Socket進 行網絡通訊。具體開發的難點是發送消息的部分。由於需要考慮到每次創建連接時造成的系統開銷,所以使用的連接方式必須是長連接,就是保存連接,不能斷開。 而且在連接的另一端發生當機的情況下能夠及時回覆,不會就此丟掉和這一端的通訊。在連接發送消息後,能夠判定另一端無法及時收到消息的情況,並且做出正確 處理。
綜上所述,能夠整理出如下三條需求:
l 兩端的連接通訊必須是長連接,不能每次重新建立。
l 在連接斷開的情況下能夠及時處理,並能有效恢復。
l 發送數據需要有超時機制。
我們這一階段的Mina框架的使用便是圍繞着這三條需求展開的。
二、 Mina框架相關知識簡介:
在正式開始Mina框架的實際應用前,先簡單介紹一些Mina的基本知識,以便於下面的實用場景分析。中間會穿插架構圖和示例代碼。
在介紹架構之前先認識幾個接口:
IoAccepter 相當於網絡應用程序中的服務器端
IoConnector 相當於客戶端
IoSession 當前客戶端到服務器端的一個連接實例
IoHandler 業務處理邏輯
IoFilter 過濾器用於懸接通訊層接口與業務層接口
然後可以看一下Mina的架構圖,如圖2-1Mina框架圖所示。
在圖中的模塊鏈中,IoService便是應用程序的入口,相當於基本接口中的IoAccepter,IoAccepter便是IoService的一個擴展接口。IoService接口可以用來添加多個IoFilter,這些IoFilter符合責任鏈模式並由IoProcessor線程負責調用。而IoAccepter在ioService接口的基礎上還提供綁定某個通訊端口以及取消綁定的接口。在日常應用中,我們可以這樣使用IoAccepter:
IoAcceptor acceptor = new SocketAcceptor(); |
相當於我們使用了 Socket 通訊方式作爲服務的接入,當前版本的 Mina 還提供了除SocketAccepter外的基於數據報文通訊的DatagramAccepter以及基於管道通訊的VmPipeAccepter。另外還包括串口通訊接入方式,目前基於串口通訊的接入方式已經在最新測試版的MINA中提供。我們也可以自行實現IoService接口來使用自己的通訊方式。
而在上圖中最右端也就是IoHandler,這便是業務處理模塊。我們的項目大部分的工作也就是在這個接口的實現類中完成。在業務處理類中不需要去關心實際的通訊細節,只管處理客戶端傳輸過來的信息即可。編寫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) |
前面我們提到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框架的實現主要是圍繞着IoHandler和IoSession進行展開。根據上面的講解,在實際使用中,可以用下面的代碼創建一個簡單的用戶發送消息的客戶端。
SocketConnector connector = new SocketConnector();
IoFilter filter = new ProtocolCodecFilter(new TextLineCodecFactory());
connector.getFilterChain().addLast("audit", filter);
SocketAddress address = new InetSocketAddress(ip, port);
ConnectFuture future = connector.connect(address, new ClientHandler());
future.join();
if (!future.isConnected()) {
logger.error("不能建立網絡連接。" + address);
return null;
}
session = future.getSession();
這樣便可以使用session進行消息發送,方法是使用write方法,創建一個WriteFuture就可以將信息發送出去了。下面將結合我們的三個網絡通訊的需求,在實際項目中分析Mina框架的使用。
三、 實用場景分析:
首先是長連接的問題,爲了避免每次重複創建連接,就要對連接進行管理。每一個連接在Mina中的就是通過session進行體現的,換言之,就是將session管理起來。所以,我們索性就把網絡通訊的部分,與項目的其他部分進行隔離,實現一個網絡通訊層,在其中統一管理session。然後在通訊層中實現一個靜態Map,Key是IP+Port,Value就是對應的session。然後在每次需要進行連接的時候,從這個Map中通過IP和Port獲取對應的session。然後判斷這個session是否被創建,或者是session是否被關閉。如果這個session有效,便直接進行使用。如果session無效,再重新創建這個session,然後放到Session Map中。如圖3-1通訊層結構類圖所示。
然後是連接斷開和恢復的處理,在我們實現的Client Handler中,有一個可以被覆蓋的方法,void exceptionCaught(IoSession session, Throwable cause)。這個方法在服務器端非正常當機的情況下可以捕獲到異常,而且服務器端在線上環境下是不會進行主動連接斷開的。所以異常情況便可以包括現有的連接斷開情況。如果有這樣的情況發生,就將這個session關閉到,然後再需要重新獲取這個session的時候,便會判定這個session已經斷開,這時會重新創建一個新的session,將Session Map中的元素覆蓋掉。
最後是發送信息超時,這個是在每個session的寫操作的時候處理,每個寫操作在Mina框架中都是一個異步操作,本身程序是不會等待整個操作的結束的,因爲這是根據性能上的考慮。但是如果我們需要知道發送消息是否超時,便可以在前期的簡單實現過程中使用這種方式。首先爲session設定一個寫操作的超時時間,我們設置5秒鐘,然後在每個寫操作之後都使用join方法,等待異步操作結束。最後便可以判斷寫操作是否成功進行,這樣就可以處理髮送消息超時的問題。可以用下面的代碼表示。
public void sessionCreated(IoSession session) throws Exception {
super.sessionCreated(session);
session.setWriteTimeout(5);
if (session.getTransportType() == TransportType.SOCKET) {
((SocketSessionConfig) session.getConfig()).setKeepAlive(true);
}
}
這個是在session被創建的時候,同時還有一項設置是,將Session的連接模式設置成長連接。這樣連接就不會有超時中斷的現象。
public static boolean setMessage(String ip, int port, String message) {
IoSession session = getSession(ip, port);
if (session != null) {
WriteFuture wf = session.write(message);
wf.join();
if (wf.isWritten()) {
return true;
}
}
return false;
}
這個是發送消息的部分,在發送消息後,判斷消息是否成功發送,這樣就可以做到處理髮送消息超時的問題。
四、 後期優化:
在項目中,現在的Mina框架實現形式還是有許多需要改進,並優化的部分。比如說超時的處理和多線程的問題。
在發送超時的問題上,如果每次發送消息後,都進行異步操作的等待,那麼在數據量十分龐大的情況下便會產生效率問題。根據Mina框架中存在的Future模式,可以使用listener來處理是否發送消息超時。可以在Session中添加專門用來處理消息發送超時的listener,然後在需要發送的消息上標註是否超時,如果超時進行重發,或者是其他操作。這樣可以大大加快發送消息的速度,但是對於程序的複雜性,便會有很大的提升。由於現在正處在項目的初期是現階段,可以不需要考慮這種複雜的模式。但是可以最爲後期優化的內容。
至於多線程的問題,由於Mina框架本身就擁有線程池的功能,所以它是可以做到多線程的消息發送的。可是這需要消息緩衝區的配合,而且會造成現有系統整合上的衝突,所以,也不在目前情況的考慮範圍內。但是,後期可以考慮實現這方面的功能。
PS: mina是日語,意思是大家。作者使用這個名字,可能是希望大家都來使用這個框架,並且通過這個網絡框架,將大家聯繫在一起。