Mina框架與源碼的簡單理解
一.Mina架構圖:
其框架實現如下圖所示:(圖來自於網上:)
二.Mina 一個請求的主要實現流程:
服務器啓動時,構造NioSocketAcceptor,服務器同時也會構造NioProcessor。
client請求->NioSocketAcceptor建立連接,在bind監聽端口後,調用startupAcceptor()方法->接收線程Acceptor啓動->processHandles()方法調用->初始化session,並把session加入到NioProcessor待處理session隊列中。
當始化session時,NioProcessor已經在通道中註冊可讀事件,同時add(T session)方法調用後->啓動startupProcessor()方法->處理線程Processor啓動->然後讀事件讀取數據時會在通道中執行過濾器連。
如果有相應的業務處理,則調用相應的IoHandler(),然後組織返回給client的數據,經過數據經過濾連,數據發出。
一般我們自己寫nio服務器端時,只有一個selector,但mina框架使用了兩個選擇器selector,其中一個用來建立連接,其核心爲NioSocketAcceptor
另一個負責讀寫事件的,其核心爲NioProcessor
三.NioSocketAcceptor相關說明
1.NioSocketAcceptor的父類爲AbstractPollingIoAcceptor,很多實現是在父類中實現的。在構造此類的時候,同時也構造了NioProcessor類,代碼如下:
protected AbstractPollingIoAcceptor(IoSessionConfig sessionConfig,
Class<? extends IoProcessor<T>> processorClass) {
this(sessionConfig, null, new SimpleIoProcessorPool<T>(processorClass),
true);
}
並且NioProcessor的個數爲cpu+1,
private static final int DEFAULT_SIZE = Runtime.getRuntime()
.availableProcessors() + 1;
2.init方法,打開selector通道;aceeptor()方法處理接收連接,把SocektChannel綁定到NioSession中;open()方法是打開serversocketchannel,然後配製socket的一些基本屬性,並註冊此事件是可連接的事件。
socket.bind(localAddress, getBacklog());
// Register the channel within the selector for ACCEPT event
channel.register(selector, SelectionKey.OP_ACCEPT);
3.父類中有內部線程類Acceptor,主要負責輪詢處理註冊過連接事件的請求建立起連接。此線程類是在startupAcceptor()方法中調用,代碼如下:
private void startupAcceptor() {
// If the acceptor is not ready, clear the queues
// TODO : they should already be clean : do we have to do that ?
if (!selectable) {
registerQueue.clear();
cancelQueue.clear();
}
// start the acceptor if not already started
synchronized (lock) {
if (acceptor == null) {
acceptor = new Acceptor();
executeWorker(acceptor);
}
}
}
四.NioProcessor相關說明:
1.NioProcessor 在接收連接之後,另開了一個selector,用來處理讀寫事件。其大部分功能,在父類AbstractPollingIoProcessor中實現
2.Mina中用NioSession來封裝了channel對應的讀寫操作等功能.
3.在NioProcessor類中init方法爲:
protected void init(NioSession session) throws Exception {
SelectableChannel ch = (SelectableChannel) session.getChannel();
ch.configureBlocking(false);
session.setSelectionKey(ch.register(selector, SelectionKey.OP_READ, session));
}
很明顯註冊了讀事件,並與相應的NioSession對應起來。
4.在NioProcessor類中表面上看代碼好像沒有註冊過寫事件,而且實際應用中,讀寫事件是來回切換的。但是實際上NioProcessor用另外的方法實現了:
setInterestedInRead()
setInterestedInWrite()
這兩個方法通過key.interestOps(newInterestOps)來實現事件切換,從而達到了註冊讀寫事件的作用。
要明白讀寫註冊事件,首先得明白SocketChannel註冊事件的原理,一個通道每次註冊時只對應一個事件,而每次註冊不同事件時,只是對此通道的事件進行了切換而已。所以通過key.interestOps(ops)方法是實現了最原始的操作,即切換通道事件。
5.NioSession中read與write方法同Buffer中一樣。
6.IoSessionIterator<NioSession> an encapsulating iterator 。是NioProcess一個內部類
實現了iterator<NioSession> 接口的類,主要是重寫了其方法public NioSession next(),目的是把迭代key時,把NioSession取出。
public NioSession next() {
SelectionKey key = iterator.next();
NioSession nioSession = (NioSession) key.attachment();
return nioSession;
}
五.NioProcessor 父類AbstractPollingIoProcessor的相關實現:
1.Processor,爲父類的內部線程類,在startProcessor()方法調用,通過executor啓動此線程。一般請求在NioSocketAcceptor上建立連接後,然後啓動NioProcessor中的工作線程Processor。一般也不是馬上企動的,需要在調用addNew方法後,有新的請求後纔會調用。
2.此類中有四個ConcurrentLinkedQueue分別用來存儲創建,刪除,操作中的session。其中值的說明的是flushingSessions是用來寫session的
3.executor爲NioProcessor工作線程,默認實現爲:Executors.newCachedThreadPool()來定義。
4.startupProcessor()方法,開始工作線程,此處把processor內部線程類放入線程池中運行。如果select()出現阻塞,才馬上喚醒,在processor線程每次select時間爲一秒
private void startupProcessor() {
synchronized (lock) {
if (processor == null) {
processor = new Processor();
executor.execute(new NamePreservingRunnable(processor,
threadName));
}
}
// Just stop the select() and start it again, so that the processor
// can be activated immediately.
wakeup();
}
5.handNewSessions(T session) 從新session隊列中取出所有待處理的session.然後通過addNow(T Session)給session init(),buildfilterchain過濾這些等。最後返回處理了session的數量。另外也有對應的removeSession()方法
6.read(T session)是讀取session裏面的數據。如果讀取數據的容易超過初始時設定的buffer大小時buffer會自動增加。寫數據是通過writeBuffer()方法實現,往外寫數據buffer大小也自動增加。
在此處要說明的,在此調用的read與writebuffer方法並不是最上層的方法,而是通過firechain連,調用fireMessageReceived(buf),以及fireMessageSent(session,buf)來讀寫。
請求到達時實例IoProcessor在會話通道中會執行過濾器鏈。
另外,如果此請求還有邏輯處理的話,接着也會執行相應的IoHandler。
六.Mina中IoProcessor處理方式
Mina 默認的線程工作方式爲一個IoProcessor 負責執行一個會話上的所有過濾器、IoHandler,也就是對於IO 讀寫操作來說,是單線程工作方式(就是按照順序逐個執行)。假如你想讓某個事件方法(譬如:sessionIdle()、sessionOpened()等)在單獨的線程中運行(也就是非IoProcessor 所在的線程),那麼這裏就需要用到一個ExecutorFilter 的過濾器。
增加一個ExecutorFilter代碼如下:
acceptor.getFilterChain().addLast("exceutor", new ExecutorFilter()); 對於ExecutorFilter默認線程實現方式也可以修改。