Apache MINA NIO模型

From: http://2006zhouxinfeng.blog.163.com/blog/static/5024021620115155106736/

 

前一段在網上看到了“淘寶伯巖”([email protected])的一份關於Java NIO 網絡編程的講義《NIO trick and trap——編寫高性能Java NIO網絡框架》。 其中裏面提到了Java NIO在網絡編程方面的應用和編程模型,同時也提到了Apache的一個開源網絡框架MINA。 正好自己對於NIO對網絡編程的應用也不是太熟悉,於是就簡單瞭解了下MINA。本文並不是針對於MINA這個框架如何使用(相關內容可以參見相關文 檔),而是整個MINA框架對於NIO的應用。

1、整體架構

首先我們來看一下MINA Server端的整體架構(圖片源自MINA官方文檔)。 其中的IoService用來負責實際的網絡連接,監聽用戶請求等功能。 對於每一個新的連接,MINA都會創建一個與之對應的Session。 MINA收到或者要發送的數據會經過一系列的Filter。過濾器可以用來對消息進行過濾和規範化。 最後MINA會調用用戶實現的一個IoHandler,來處理經過過濾器過濾的消息。 可以看出,對於框架的使用者來說,網絡的具體編程是對其透明的,用戶只需要實現相應的Handler,並且設置好Filter即可。

2、IoService細節

2.1、繼承關係

首先我們來看一下核心幾個類的繼承關係。

其中IoService爲整個框架的核心接口,其代表了網絡服務。

IoAcceptorIoService一個子接口,用來代表對用戶請求的接受和監聽。

SocketAcceptorIoAcceptor的一個子接口,用來代表基於Socket的網絡請求。

AbstractIoServiceAbstractIoAcceptor分別是對IoServiceIoAcceptor的一個默認抽象實現。

AbstractPoolingIoAcceptor則是引入了池的概念,建立了處理線程池等。

最後NIOSocketAcceptorAbstractPoolingIoAcceptor的NIO的一個實現。

2.2、整體流程

  • 創建一個NIOSocketAcceptor對象。
  • 調用IoAcceptorbind方法,創建SocketChannel,並綁定到之前的Selector上。
  • 創建一個新的線程,用來監聽用戶的連接。如果收到用戶的連接請求,則爲其創建一個Session,並把Session加入到一個Processor中等待處理。
  • 系統中有若干個Processor,每個有自己的線程。在該線程中,也存在一個Selector,用來監聽所有該Processor上的Session。如果某個Session有數據可以讀取或者寫入,則將數據傳遞給一系列的Filter,並最終調用相應的Handler進行處理。

2.3、具體代碼分析

爲了簡明起見,下面的代碼只是整個代碼的截取,目的是爲了更好的說明問題。完整的代碼請詳見項目源文件。

2.3.1、構造

我們首先來看一下NioSocketAcceptor的構造函數。

  1. public NioSocketAcceptor() {

  2.     super(new DefaultSocketSessionConfig(), NioProcessor.class);

  3.     ((DefaultSocketSessionConfig) getSessionConfig()).init(this);

  4. }

該構造函數傳給了父類構造函數兩個參數。首先是一個DefaultSocketSessionConfig的實例,用來表示一個配置對象。之後是NioProcessor.class,用來指明處理對象的類型。

接下來我們來看一下 NioSocketAcceptor的父類 AbstractPollingIoAcceptor<NioSession, ServerSocketChannel>

  1. protected AbstractPollingIoAcceptor(IoSessionConfig sessionConfig, Class<? extends IoProcessor<NioSession>> processorClass) {

  2.     // 根據之前子類傳遞過來的processorClass,新建一個SimpleIoProcessorPool對象

  3.     // 並調用另一個構造函數

  4.     this(sessionConfig, null, new SimpleIoProcessorPool<NioSession>(processorClass), true);

  5. }

  6. private AbstractPollingIoAcceptor(IoSessionConfig sessionConfig, Executor executor, IoProcessor<NioSession> processor, boolean createdProcessor) {

  7.     // 調用父類構造函數

  8.     super(sessionConfig, executor);

  9.     // 調用初始化函數

  10.     init();

  11. }

其中的init函數的實現在 NioSocketAcceptor中,代碼如下:

  1. @Override

  2. protected void init() throws Exception {

  3.     // 創建一個Selector對象

  4.     selector = Selector.open();

  5. }

其中的SimpleIoProcessorPool<NioSession>的構造函數如下:

  1. public SimpleIoProcessorPool(Class<? extends IoProcessor<NioSession>> processorType) {

  2.     // 用默認參數調用另一構造函數

  3.     this(processorType, null, DEFAULT_SIZE);

  4. }

  5. public SimpleIoProcessorPool(Class<? extends IoProcessor<NioSession>> processorType, Executor executor, int size) {

  6.     // 若executor爲null則新建,不然則使用傳入的對象。

  7.     createdExecutor = (executor == null);

  8.     if (createdExecutor) {

  9.         this.executor = Executors.newCachedThreadPool();

  10.     } else {

  11.         this.executor = executor;

  12.     }

  13.     // 新建一個IoProcessor池

  14.     pool = new IoProcessor[size];

  15.     // 根據傳入的processorType,利用Java的反射機制來創建對象,填填滿對象池

  16.     Constructor<? extends IoProcessor<S>> processorConstructor  = processorType.getConstructor(ExecutorService.class);

  17.     for(int i = 0; i < size; i++) {

  18.         pool[i] = processorConstructor.newInstance(this.executor);

  19.     }

  20. }

從以上代碼可以看出, SimpleIoProcessorPool<NioSession>根據傳入的IoProcessor的類型(這裏爲NioProcessor),創建了一個IoProcessor池,和一個Executor線程池,用來進行網絡IO的讀寫請求。

那我們就來看一下 NioProcessor的具體創建過程

  1. public NioProcessor(Executor executor) {

  2.     super(executor);

  3.     this.selector = Selector.open();

  4. }

可以看出,在NioProcessor創建的過程中,還創建了一個Selector對象,用來之後監聽網絡IO的讀寫請求。並把傳進來的Executor對象傳遞給父類進行保存。

之後我們再來看一下  AbstractPollingIoAcceptor<NioSession, ServerSocketChannel>的父類 AbstractIoAcceptor

  1. protected AbstractIoAcceptor(IoSessionConfig sessionConfig, Executor executor) {

  2.     super(sessionConfig, executor);

  3. }

可以看出該構造函數沒有進行額外的工作,而是直接調用父類。那我們就來看一下他的父類 AbstractIoService

  1. protected AbstractIoService(IoSessionConfig sessionConfig, Executor executor) {

  2.     // 保存配置對象

  3.     this.sessionConfig = sessionConfig;

  4.     // 若爲提供Executor對象,則新建一個。

  5.     if (executor == null) {

  6.         this.executor = Executors.newCachedThreadPool();

  7.         createdExecutor = true;

  8.     } else {

  9.     this.executor = executor;

  10.     createdExecutor = false;

  11.     }

  12. }

至此, NIOSocketAcceptor對象的創建也就到此結束。在整個創建過程中,我們創建了一個 DefaultSocketSessionConfig實例,用來表示一些配置選項。創建了一個Selector對象, 和一個Executor線程池,用來負責監聽用戶的請求(詳見後文)。創建了一個 SimpleIoProcessorPool<NioSession>對象,其中包括一組 NioProcessor對象和一個Executor線程池對象,每個NioProcessor對象中還包括一個Selector對象,用來負責網絡IO的讀寫處理(詳見後文)。

2.3.2、bind

我們接着來看一下IoAcceptor中聲明的bind方法。其現實在AbstractIoAcceptor中。代碼如下:

  1. public final void bind(Iterable<? extends SocketAddress> localAddresses) throws IOException {

  2.     // 檢查地址的類型,並加入到localAddressCopy中

  3.     List<SocketAddress> localAddressesCopy = new ArrayList<SocketAddress>();

  4.     for (SocketAddress a : localAddresses) {

  5.         checkAddressType(a);

  6.         localAddressesCopy.add(a);

  7.     }

  8.     // 調用bindInternal函數,返回成功綁定的地址。

  9.     Set<SocketAddress> addresses = bindInternal(localAddressesCopy);

  10.     synchronized (boundAddresses) {

  11.             boundAddresses.addAll(addresses);

  12.     }

  13. }

下面我們來看一下bindInternal的實現,該函數的實現在AbstractPollingIoAcceptor<NioSession, SocketChannel>中。

  1. @Override

  2. protected final Set<SocketAddress> bindInternal(List<? extends SocketAddress> localAddresses) throws Exception {

  3.     // 創建一個AcceptorOperationFuture對象,該對象的執行結果可以異步的通知

  4.     AcceptorOperationFuture request = new AcceptorOperationFuture(localAddresses);

  5.     // 將上文創建的對象加入隊列中,等待其他線程處理

  6.     registerQueue.add(request);

  7.     // 啓動Acceptor,進行地址的綁定

  8.      startupAcceptor();

  9.     // 喚起之前阻塞的Selector對象(詳見下文)

  10.     wakeup();

  11.     // 等待綁定完成

  12.     request.awaitUninterruptibly();

  13.     // 返回結果

  14.     Set<SocketAddress> newLocalAddresses = new HashSet<SocketAddress>();

  15.     for (H handle : boundHandles.values()) {

  16.         newLocalAddresses.add(localAddress(handle));

  17.     }

  18.     return newLocalAddresses;

  19. }

可以看出,在該函數中,首先創建了一個 AcceptorOperationFuture對象,並把其加入一個隊列中,等待其他線程處理。該對象類似Java中自帶的Future<?>對象,代表一個異步執行任務的結果。 自後調用了startupAcceptor來進行連接的監聽和綁定。下面我們就來看一下startupAcceptor函數的具體實現。

  1. private void startupAcceptor() {

  2.     // 如果acceptorRef沒個賦值,則新建一個Acceptor對象進行賦值,並執行該acceptor。

  3.     Acceptor acceptor = acceptorRef.get();

  4.     if (acceptor == null) {

  5.     acceptor = new Acceptor();

  6.     if (acceptorRef.compareAndSet(null, acceptor)) {

  7.         executeWorker(acceptor);

  8.     }

  9. }

從代碼可以看出,該函數進行了一個原子操作。如果acceptorRef沒被賦值,則新建對象進行賦值,並調用executeWroker函數。其中Acceptor實現了Runnable接口,而executorWorker函數的主要功能就是把該acceptor交給之前創建的Executor來執行。下面我們來看一下Acceptorrun函數,看一下它的具體執行過程。

  1. public void run() {

  2.     int nHandles = 0;

  3.     while (selectable) {

  4.         // 等待用戶連接請求

  5.         int selected = select();

  6.         // 註冊新的地址綁定

  7.         nHandles += registerHandles();

  8.         // 沒有綁定的地址,可以退出

  9.         if (nHandles == 0) {

  10.             acceptorRef.set(null);

  11.             if (registerQueue.isEmpty() && cancelQueue.isEmpty()) {

  12.                 break;

  13.            }

  14.            if (!acceptorRef.compareAndSet(null, this)) {

  15.                break;

  16.            }

  17.         }

  18.         // 處理用戶連接請求

  19.         if (selected > 0) {

  20.              processHandles(selectedHandles());

  21.         }

  22.        // 檢查是否有unbind請求.

  23.         nHandles -= unregisterHandles();

  24.     }

  25. }

其中的select()函數的實現在 NIOSocketAcceptor類中:

  1. @Override

  2. protected int select() throws Exception {

  3.     return selector.select();

  4. }

可以看出,select函數在之前創建的Selector上調用select函數。等待綁定在該Selector上的Channel狀態就緒。由於在第一次調用時,該Selector上並沒有綁定任何Channel,所以該函數會永遠阻塞住。這也就是爲什麼在之前的 bindInternal中,在調用 startAcceptor後,需要馬上調用wakeup函數。該函數的實現在 NIOSocketAcceptor類中:

  1. @Override

  2. protected void wakeup() {

  3.     selector.wakeup();

  4. }

該函數會調用Selectorwakeup函數,用來喚醒阻塞的select函數。

接下來我們來看一下registerHandlers函數,該函數的作用是用來檢查是否有地址綁定的請求。並進行綁定。(unRegisterHandles與之相似,過程相反,這裏略去不提。)

  1. private int registerHandles() {

  2.     for (;;) {

  3.         // 從隊列中獲得綁定任務

  4.         AcceptorOperationFuture future = registerQueue.poll();

  5.         // 隊列爲空,直接退出,返回0

  6.         if (future == null) {

  7.            return 0;

  8.         }

  9.         Map<SocketAddress, ServerSocketChannel> newHandles = new ConcurrentHashMap<SocketAddress, ServerSocketChannel>();

  10.         // 獲得需要綁定的地址

  11.         List<SocketAddress> localAddresses = future.getLocalAddresses();

  12.         // 一次綁定

  13.         for (SocketAddress a : localAddresses) {

  14.             // 爲每個地址創建ServerSocketChannel對象

  15.             ServerSocketChannel handle = open(a);

  16.             newHandles.put(localAddress(handle), handle);

  17.         }

  18.         // 更新已綁定的連接

  19.         boundHandles.putAll(newHandles);

  20.         // 通知 AcceptorOperationFuture任務已經完成

  21.         // 這樣之前調用awaitUninterruptibly()阻塞的線程將繼續執行。

  22.         future.setDone();

  23.         // 返回已綁定的個數

  24.         return newHandles.size();

  25.     }

  26. }

該函數的作用是檢查是否有綁定的請求,然後爲每個地址建立一個連接,並綁定。其中創建連接調用的是open函數。該函數的實現在 NIOSocketAcceptor類中:

  1. @Override

  2. protected ServerSocketChannel open(SocketAddress localAddress) throws Exception {

  3.       // 創建一個新的ServerSocketChannel

  4.      ServerSocketChannel channel = ServerSocketChannel.open();

  5.     // 設置爲非阻塞

  6.     channel.configureBlocking(false);

  7.     // 綁定地址

  8.     ServerSocket socket = channel.socket();

  9.     socket.bind(localAddress, getBacklog());

  10.     // 在Selector對象中註冊該Channel。

  11.     channel.register(selector, SelectionKey.OP_ACCEPT);

  12.     return channel;

  13. }

我們現在再把注意力移回之前的Acceptorrun函數。如果之前的select函數是正常返回,而不是被wakeup,那麼說明有用戶的連接請求。接下來就會執行processHandles函數。其實現如下:

  1. private void processHandles(Iterator<ServerSocketChannel> handles) throws Exception {

  2.    while (handles.hasNext()) {

  3.         ServerSocketChannel handle = handles.next();

  4.         handles.remove();

  5.         // 接受用戶的請求,並創Session。

  6.         NioSession session = accept(processor, handle);

  7.         // 初始化Session

  8.        initSession(session, null, null);

  9.        // 把session添加到一個Processor的處理隊列中,等待IO讀寫處理

  10.        session.getProcessor().add(session);

  11.     }

  12. }

下面我們來看一下accept函數。該函數接收兩個參數,一個是有連接請求的ServerSocketChannel,一個是之前創建的SimpleIoProcessorPool<NioSession>對象。

該函數的實現在NIOSocketAcceptor類中:

  1. @Override
  2. protected NioSession accept(IoProcessor<NioSession> processor, ServerSocketChannel handle) throws Exception {
  3.     // 獲得用戶連接
  4.     SocketChannel ch = handle.accept();
  5.     // 創建Session
  6.     return new NioSocketSession(this, processor, ch);
  7. }

之後我們來看一下Session在Processor上的註冊過程。及SimpleIoProcessorPool<NioSession>的add函數:

  1. public final void add(S session) {

  2.     // 從Processor池中獲得一個Processor(NioProcessor),並註冊session.

  3.     getProcessor(session).add(session);

  4. }

  5. private IoProcessor<S> getProcessor(S session) {

  6.     // 如果該session已經註冊一個Processor,則返回該Processor

  7.     // 如果不存在,則從池中隨機選擇一個,並綁定到該session上

  8.     IoProcessor<S> processor = (IoProcessor<S>) session.getAttribute(PROCESSOR);

  9.     if(processor == null) {

  10.         processor = pool[Math.abs((int) session.getId()) % pool.length];

  11.         session.setAttributeIfAbsent(PROCESSOR, processor);

  12.     }

  13.      return processor;

  14. }

接下來我們來看一下NioProcessor中的add函數。整個過程和之前監聽用戶連接請求的過程相似。故我們只關注其中不同的地方

  1. public final void add(S session)  {

  2.     newSessions.add(session); // 加入請求隊列

  3.     startupProcessor();

  4. }


  5. private void startupProcessor() {

  6.     Processor processor = processorRef.get();

  7.     if (processor == null) {

  8.         processor = new Processor();

  9.         if (processorRef.compareAndSet(null, processor)) {

  10.             executor.execute(new NamePreservingRunnable(processor, threadName));

  11.         }

  12.      }

  13.      wakeup();

  14. }


  15. public void run() {

  16.     int nSessions = 0;

  17.     for (;;) {

  18.         int selected = select(SELECT_TIMEOUT);

  19.         // 處理新的session的註冊

  20.         nSessions += handleNewSessions();

  21.         if (selected > 0) {

  22.             process();

  23.         }

  24.         // 刷新排隊的寫請求

  25.         flush(currentTime);

  26.         nSessions -= removeSessions();

  27.         if (nSessions == 0) {

  28.             processorRef.set(null);

  29.             if (newSessions.isEmpty() && isSelectorEmpty()) {

  30.                 break;

  31.             }

  32.             if (!processorRef.compareAndSet(null, this)) {

  33.                 break;

  34.             }

  35.        }

  36. }

接下來我們來看一下其中的 handleNewSessions函數

  1. private int handleNewSessions() {

  2.     int addedSessions = 0;

  3.     for (S session = newSessions.poll(); session != null; session = newSessions.poll()) {

  4.         if (addNow(session)) {

  5.             addedSessions++;

  6.         }

  7.     }

  8.     return addedSessions;

  9. }

  10. private boolean addNow(S session) {

  11.     boolean registered = false;

  12.     // 註冊session

  13.     init(session);

  14.     registered = true;

  15.     // 構建FilterChain

  16.     IoFilterChainBuilder chainBuilder = session.getService().getFilterChainBuilder();

  17.     chainBuilder.buildFilterChain(session.getFilterChain());

  18.     return registered;

  19. }

  20. @Override

  21. protected void init(NioSession session) throws Exception {

  22.     // 把Session對應的SocketChannel註冊在該session對應的processor的Selector。

  23.     SelectableChannel ch = (SelectableChannel) session.getChannel();

  24.     ch.configureBlocking(false);

  25.     session.setSelectionKey(ch.register(selector, SelectionKey.OP_READ,session));

  26. }

接下來我們再來看一下process函數。該函數負責數據的讀寫,並把數據傳遞給FilterChain,並最終調用用戶的IoHandler
  1. private void process() throws Exception {

  2.     for (Iterator<S> i = selectedSessions(); i.hasNext();) {

  3.         S session = i.next();

  4.         process(session);

  5.         i.remove();

  6.     }

  7. }

  8. private void process(S session) {

  9.     // 讀請求

  10.     if (isReadable(session) && !session.isReadSuspended()) {

  11.         read(session);

  12.     }

  13.     // 寫請求

  14.     if (isWritable(session) && !session.isWriteSuspended()) {

  15.         if (session.setScheduledForFlush(true)) {

  16.            // 加入寫刷新隊列

  17.            flushingSessions.add(session);

  18.         }

  19.     }

  20. }


  21. private void read(S session) {

  22. // 從SocketChannel中讀數據

  23. // 這裏略去

  24. // 如果讀到數據

  25. if (readBytes > 0) {

  26. // 把數據傳給FilterChain

  27. IoFilterChain filterChain = session.getFilterChain();

  28. filterChain.fireMessageReceived(buf);

  29. }

  30. }

最後是flush函數,用來刷新寫請求的數據。這裏暫時略去。
因爲我已經寫不動了,累死了。哪天在另開文章吧。
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章