MINA 基本類的描述在介紹架構(gòu)之前先認識幾個接口:
IoAccepter 相當于網(wǎng)絡應用程序中的服務器端
IoConnector 相當于客戶端
IoSession 當前客戶端到服務器端的一個連接實例。此會話將一直保持連接,除非網(wǎng)絡斷開或用戶主動斷開連接(session.close())
IoHandler 業(yè)務處理邏輯
IoFilter 過濾器用于懸接通訊層接口與業(yè)務層接口


MINA 的基礎架構(gòu)下圖是 MINA 的架構(gòu)圖,



        圖中的模塊鏈中,IoService 便是應用程序的入口,相當于我們前面代碼中的 IoAccepter,IoAccepter 便是 IoService 的一個擴展接口。IoService 接口可以用來添加多個 IoFilter,這些 IoFilter 符合責任鏈模式并由 IoProcessor 線程負責調(diào)用。而 IoAccepter 在 ioService 接口的基礎上還提供綁定某個通訊端口以及取消綁定的接口。在上面的例子中,我們是這樣使用 IoAccepter 的

IoAcceptor acceptor = new SocketAcceptor();


相當于我們使用了 Socket 通訊方式作為服務的接入,當前版本的 MINA 還提供了除 SocketAccepter 外的基于數(shù)據(jù)報文通訊的 DatagramAccepter 以及基于管道通訊的 VmPipeAccepter。另外還包括串口通訊接入方式,目前基于串口通訊的接入方式已經(jīng)在最新測試版的 MINA 中提供。你也可以自行實現(xiàn) IoService 接口來使用自己的通訊方式。

而在上圖中最右端也就是 IoHandler,這便是業(yè)務處理模塊。相當于前面例子中的 HelloHandler 類。在業(yè)務處理類中不需要去關心實際的通訊細節(jié),只管處理客戶端傳輸過來的信息即可。編寫 Handler 類就是使用 MINA 開發(fā)網(wǎng)絡應用程序的重心所在,相當于 MINA 已經(jīng)幫你處理了所有的通訊方面的細節(jié)問題。為了簡化 Handler 類,MINA 提供了 IoHandlerAdapter 類,此類僅僅是實現(xiàn)了 IoHandler 接口,但并不做任何處理。


一個 IoHandler 接口中具有如下一些方法(摘自 MINA 的 API 文檔):

//當接口中其他方法拋出異常未被捕獲時觸發(fā)此方法
void exceptionCaught(IoSession session, Throwable cause)

//當接收到客戶端的請求信息后觸發(fā)此方法.
void messageReceived(IoSession session, Object message)

//當信息已經(jīng)傳送給客戶端后觸發(fā)此方法.
void messageSent(IoSession session, Object message)

//當連接被關閉時觸發(fā),例如客戶端程序意外退出等等.
void sessionClosed(IoSession session)

//當一個新客戶端連接后觸發(fā)此方法.
void sessionCreated(IoSession session)

//當連接空閑時觸發(fā)此方法.
void sessionIdle(IoSession session, IdleStatus status)

//當連接后打開時觸發(fā)此方法,一般此方法與 sessionCreated 會被同時觸發(fā)
void sessionOpened(IoSession session)



前面我們提到 IoService 是負責底層通訊接入,而 IoHandler 是負責業(yè)務處理的。那么 MINA 架構(gòu)圖中的 IoFilter 作何用途呢?
答案是你想作何用途都可以。但是有一個用途卻是必須的,那就是作為 IoService 和 IoHandler 之間的橋梁。IoHandler 接口中最重要的一個方法是 messageReceived,這個方法的第二個參數(shù)是一個 Object 型的消息,總所周知,Object 是所有 Java 對象的基礎,那到底誰來決定這個消息到底是什么類型呢?答案也就在這個 IoFilter 中。添加了一個 IoFilter 是 new ProtocolCodecFilter(new TextLineCodecFactory()),這個過濾器的作用是將來自客戶端輸入的信息轉(zhuǎn)換成一行行的文本后傳遞給 IoHandler,因此我們可以在 messageReceived 中直接將 msg 對象強制轉(zhuǎn)換成 String 對象。

而 如果我們不提供任何過濾器的話,那么在 messageReceived 方法中的第二個參數(shù)類型就是一個 byte 的緩沖區(qū),對應的類是 org.apache.mina.common.ByteBuffer。雖然你也可以將解析客戶端信息放在 IoHandler 中來做,但這并不是推薦的做法,使原來清晰的模型又模糊起來,變得 IoHandler 不只是業(yè)務處理,還得充當協(xié)議解析的任務。


MINA自身帶有一些常用的過濾器,例如LoggingFilter(日志記錄)、BlackListFilter(黑名單過濾)、CompressionFilter(壓縮)、SSLFilter(SSL加密)等。
public class MinaTimeServer {

    
private static final int PORT = 9123;

    
public static void main(String[] args) throws IOException {
 
        
//Mina包裝了J2SE中的ByteBuffer類,用法也一樣
        ByteBuffer.setUseDirectBuffers(false);

    
//設置緩存策略
        ByteBuffer.setAllocator(new SimpleByteBufferAllocator());

       
//創(chuàng)建一個SocketAcceptor,用于在服務器端監(jiān)聽客戶端的連接.
        IoAcceptor acceptor = new SocketAcceptor();

        
//創(chuàng)建一個與SocketAcceptor相關聯(lián)的配置對象.
        SocketAcceptorConfig cfg = new SocketAcceptorConfig();
        cfg.getSessionConfig().setReuseAddress( 
true );
        cfg.getFilterChain().addLast( 
"logger"new LoggingFilter() );
        cfg.getFilterChain().addLast( 
"codec"new ProtocolCodecFilter( new TextLineCodecFactory( Charset.forName( "UTF-8" ))));

        
//綁定端口,處理對象和配置對象.
        acceptor.bind( new InetSocketAddress(PORT), new TimeServerHandler(), cfg);
        System.out.println(
"MINA Time server started.");
    }

}


下面我們編寫一個處理連接的類 TimeServerHandler.java
public class TimeServerHandler extends IoHandlerAdapter 
       
//出現(xiàn)異常的時候調(diào)用.
      public void exceptionCaught(IoSession session, Throwable t) throws Exception {
                t.printStackTrace();
                session.close();
        }

      
//接收客戶端新的消息的時候調(diào)用.
        public void messageReceived(IoSession session, Object msg) throws Exception 
                String str 
= msg.toString();
                
if( str.trim().equalsIgnoreCase("quit") ) {
                        session.close();
                        
return;
                }


                Date date 
= new Date();
                session.write( date.toString() );
                System.out.println(
"Message written");
        }

       
//當一個客戶端連接到服務器的時候被調(diào)用.
        public void sessionCreated(IoSession session) throws Exception {  
                System.out.println(
"Session created");

                
if( session.getTransportType() == TransportType.SOCKET )
                        ((SocketSessionConfig) session.getConfig() ).setReceiveBufferSize( 
2048 );

        session.setIdleTime( IdleStatus.BOTH_IDLE, 
10 );
        }

}

輸入命令 telnet 127.0.0.1 9123,按回車將顯示服務器的時間信息.
輸入 quit 將斷開連接.

下面看一個示例:
具體說明:

import java.util.logging.Level;

import org.apache.mina.common.ByteBuffer;
import org.apache.mina.common.IdleStatus;
import org.apache.mina.common.SessionConfig;
import org.apache.mina.io.IoHandlerAdapter;
import org.apache.mina.io.IoSession;
import org.apache.mina.io.socket.SocketSessionConfig;
import org.apache.mina.util.SessionLog;

public class AppProtocolHandler extends IoHandlerAdapter {

private boolean isLogin;

   
public AppProtocolHandler() {

}


public void dataRead(IoSession session, ByteBuffer buffer) throws Exception {

//當有數(shù)據(jù)讀入時此方法被調(diào)用,數(shù)據(jù)封裝在ByteBuffer中
   if(!isLogin){
    
//如果用戶沒有登陸過則檢查用戶的合法性
    
// TODO 檢查用戶的合法性,如果不合法者放回錯誤代碼給客戶端或直接關閉連接\n
    
//如果合法則置isLogin=true
    session.close();
   }
else{
    
// TODO 如果用戶已經(jīng)登陸成功則讀取消息并返回應答給客戶端
   }

}



public void dataWritten(IoSession session, Object mark) throws Exception {

//當數(shù)據(jù)被寫入通道時此方法被調(diào)用
   SessionLog.log(Level.INFO,session,mark.toString());//必要時打印所寫入的內(nèi)容,mark的內(nèi)容就是session.write(session,mark)中的第二個參數(shù)
}


public void exceptionCaught(IoSession session, Throwable arg1)
    
throws Exception {

// 當出現(xiàn)網(wǎng)絡異常時此方法被調(diào)用,這里要注意如果客戶端要保持與服務器端的連接時不要在這里馬上重新連接不然會拋出CancelKeyException運 行期異常直接導致程序死掉(特別是與服務器端有超過兩個連接時一定會發(fā)生并且此異常無法捕獲),建議的方法是啟動一個單獨的線程來完成與服務器端的重新連 接
   session.close();
  
}


public void sessionClosed(IoSession session) throws Exception {

//當網(wǎng)絡連接被關閉是此方法被調(diào)用
   SessionLog.log(Level.INFO,session,"Close a Session");//必要時打印出信息
}


public void sessionCreated(IoSession session) throws Exception {

//當網(wǎng)絡連接被創(chuàng)建時此方法被調(diào)用(這個肯定在sessionOpened(IoSession session)方法被調(diào)用),這里可以對Socket設置一些網(wǎng)絡參數(shù)
   SessionConfig cfg = session.getConfig();
   
if (cfg instanceof SocketSessionConfig) {
    ((SocketSessionConfig) cfg).setSessionReceiveBufferSize(
2048);
    ((SocketSessionConfig) cfg).setKeepAlive(
true);
    ((SocketSessionConfig) cfg).setSoLinger(
true0);
    ((SocketSessionConfig) cfg).setTcpNoDelay(
true);
    ((SocketSessionConfig) cfg).setWriteTimeout(
1000 * 5);
   }

}


public void sessionIdle(IoSession arg0, IdleStatus arg1) throws Exception {
   
// 當網(wǎng)絡通道空閑時此方法被調(diào)用,在這里可以判斷是讀空閑、寫空閑還是兩個都空閑,以便做出正確的處理

一般的網(wǎng)絡通訊程序都要與服務器端保持長連接,所以這里可以發(fā)一下網(wǎng)絡測試數(shù)據(jù)以保持與服務器端的連接
}


public void sessionOpened(IoSession session) throws Exception {

//當網(wǎng)絡連接被打開時此方法被調(diào)用,這里可以對session設置一些參數(shù)或者添加一些IoFilter的實現(xiàn),也可以對客戶端做一些認證之類的工作
   session.getConfig().setIdleTime(IdleStatus.BOTH_IDLE, 60);
}


}