NIO框架之MINA源代码解析(一):背景

MINA的底层还是利用了jdk提供了nio功能,mina仅仅是对nio进行封装。包含MINA用的线程池都是jdk直接提供的。

MINA的server端主要有accept、processor、session三部分组成的。当中accept主要负责在指定的port监听。若有新连接则建立一个新的session。processor则负责处理session相应的发送数据和接收数据并调用上层处理;session则缓存当前连接数据

MINA採用了线程懒启动的技术,即最少启动线程。在MINA server启动的时候,仅仅有一个线程-accept,而且accept线程仅仅有一个,在指定的port进行监听(能够同一时候监听多个port,mina能够绑定多port)。

1、acceptor

先看下acceptor的主要类图吧。

watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvY2hhb2ZhbndlaQ==/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/Center" alt="" />

mina server的启动入口是在NioSocketAcceptor.bind(InetSocketAddress)或者NioSocketAcceptor.bind(SocketAddress...)方法, acceptor.bind(new InetSocketAddress(1234));  
然后会调用AbstractPollingIoAcceptor.bindInternal(List<? extends SocketAddress>)方法,在bindInternal方法里面会调用startupAcceptor()方法提交一个accept线程到线程池里面(仅仅提交一次),并初始化acceptor端的Selector。就这样一个acceptor线程启动了。

acceptor端业务相对简单,相当于在当前Selector里面监听acceptor事件,处理新连接并新建一个session放到相应的processor里面。

acceptor 代码。非常easy。

  1. private class Acceptor implements Runnable {
  2. public void run() {
  3. assert (acceptorRef.get() == this);
  4.  
  5. int nHandles = 0;
  6.  
  7. // Release the lock
  8. lock.release();
  9.  
  10. while (selectable) {
  11. try {
  12. // Detect if we have some keys ready to be processed
  13. // The select() will be woke up if some new connection
  14. // have occurred, or if the selector has been explicitly
  15. // woke up
  16. int selected = select();
  17.  
  18. // this actually sets the selector to OP_ACCEPT,
  19. // and binds to the port on which this class will
  20. // listen on 在通道里面注冊连接事件
  21. nHandles += registerHandles();
  22.  
  23. // Now, if the number of registred handles is 0, we can
  24. // quit the loop: we don't have any socket listening
  25. // for incoming connection.
  26. if (nHandles == 0) {
  27. acceptorRef.set(null);
  28.  
  29. if (registerQueue.isEmpty() && cancelQueue.isEmpty()) {
  30. assert (acceptorRef.get() != this);
  31. break;
  32. }
  33.  
  34. if (!acceptorRef.compareAndSet(null, this)) {
  35. assert (acceptorRef.get() != this);
  36. break;
  37. }
  38.  
  39. assert (acceptorRef.get() == this);
  40. }
  41.  
  42. if (selected > 0) {
  43. // We have some connection request, let's process
  44. // them here.处理连接
  45. processHandles(selectedHandles());
  46. }
  47.  
  48. // check to see if any cancellation request has been made.
  49. nHandles -= unregisterHandles();
  50. } catch (ClosedSelectorException cse) {
  51. // If the selector has been closed, we can exit the loop
  52. break;
  53. } catch (Throwable e) {
  54. ExceptionMonitor.getInstance().exceptionCaught(e);
  55.  
  56. try {
  57. Thread.sleep(1000);
  58. } catch (InterruptedException e1) {
  59. ExceptionMonitor.getInstance().exceptionCaught(e1);
  60. }
  61. }
  62. }
  63.  
  64. // Cleanup all the processors, and shutdown the acceptor.
  65. if (selectable && isDisposing()) {
  66. selectable = false;
  67. try {
  68. if (createdProcessor) {
  69. processor.dispose();
  70. }
  71. } finally {
  72. try {
  73. synchronized (disposalLock) {
  74. if (isDisposing()) {
  75. destroy();
  76. }
  77. }
  78. } catch (Exception e) {
  79. ExceptionMonitor.getInstance().exceptionCaught(e);
  80. } finally {
  81. disposalFuture.setDone();
  82. }
  83. }
  84. }
  85. }

2、processor

processor顾名思义,就是进行IO处理,处理当前session的数据读写,并进行业务处理。

在mina server初始化的时候,会初始化一个processor池,通过NioSocketAcceptor的构造器传入池的大小,默认是当前处理器的个数+1。

processor池里面有一个jdk提供的 线程池 - Executors.newCachedThreadPool()。各个processor线程会引用此线程池,即每一个processor线程都在这个线程池里面运行。

在mina server实际处理时,每一个processor相当于一个线程,轮流处理当前的session队列里面的数据(每一个processor里面的session相当于顺序处理,共享一个线程)。

每一个processor有一个Selector对象

processor类图

processor端的处理逻辑相对有点复杂。看以下的流程图。

1、把新加入进来的session注冊到当前processor的Selector里面的read事件,并初始化session;
2、推断当前Selector是否有读写事件。
3、若第2步有读事件时。进入步骤4,若没有的话,直接到第6步。
4、处理当前读事件,并把处理后的数据放入到flush队列里面。
5、把第4步运行的结果flush到client;
6、处理session。比方session idle时间等。

7、又一次运行第1步,循环运行。

processor端代码。

  1. private class Processor implements Runnable {
  2. public void run() {
  3. assert (processorRef.get() == this);
  4.  
  5. int nSessions = 0;
  6. lastIdleCheckTime = System.currentTimeMillis();
  7.  
  8. for (;;) {
  9. try {
  10. // This select has a timeout so that we can manage
  11. // idle session when we get out of the select every
  12. // second. (note : this is a hack to avoid creating
  13. // a dedicated thread).
  14. long t0 = System.currentTimeMillis();
  15. int selected = select(SELECT_TIMEOUT);
  16. long t1 = System.currentTimeMillis();
  17. long delta = (t1 - t0);
  18.  
  19. if ((selected == 0) && !wakeupCalled.get() && (delta < 100)) {
  20. // Last chance : the select() may have been
  21. // interrupted because we have had an closed channel.
  22. if (isBrokenConnection()) {
  23. LOG.warn("Broken connection");
  24.  
  25. // we can reselect immediately
  26. // set back the flag to false
  27. wakeupCalled.getAndSet(false);
  28.  
  29. continue;
  30. } else {
  31. LOG.warn("Create a new selector. Selected is 0, delta = " + (t1 - t0));
  32. // Ok, we are hit by the nasty epoll
  33. // spinning.
  34. // Basically, there is a race condition
  35. // which causes a closing file descriptor not to be
  36. // considered as available as a selected channel, but
  37. // it stopped the select. The next time we will
  38. // call select(), it will exit immediately for the same
  39. // reason, and do so forever, consuming 100%
  40. // CPU.
  41. // We have to destroy the selector, and
  42. // register all the socket on a new one.
  43. registerNewSelector();
  44. }
  45.  
  46. // Set back the flag to false
  47. wakeupCalled.getAndSet(false);
  48.  
  49. // and continue the loop
  50. continue;
  51. }
  52.  
  53. // Manage newly created session first 处理新加入进来的session,并注冊到当前processor的Selector读事件
  54. nSessions += handleNewSessions();
  55.  
  56. updateTrafficMask();
  57.  
  58. // Now, if we have had some incoming or outgoing events,
  59. // deal with them
  60. if (selected > 0) {
  61. //LOG.debug("Processing ..."); // This log hurts one of the MDCFilter test...处理读事件。并把结果放入flush队列里面
  62. process();
  63. }
  64.  
  65. // Write the pending requests 把flush队列里面的session的处理完的数据发送给client
  66. long currentTime = System.currentTimeMillis();
  67. flush(currentTime);
  68.  
  69. // And manage removed sessions
  70. nSessions -= removeSessions();
  71.  
  72. // Last, not least, send Idle events to the idle sessions
  73. notifyIdleSessions(currentTime);
  74.  
  75. // Get a chance to exit the infinite loop if there are no
  76. // more sessions on this Processor
  77. if (nSessions == 0) {
  78. processorRef.set(null);
  79.  
  80. if (newSessions.isEmpty() && isSelectorEmpty()) {
  81. // newSessions.add() precedes startupProcessor
  82. assert (processorRef.get() != this);
  83. break;
  84. }
  85.  
  86. assert (processorRef.get() != this);
  87.  
  88. if (!processorRef.compareAndSet(null, this)) {
  89. // startupProcessor won race, so must exit processor
  90. assert (processorRef.get() != this);
  91. break;
  92. }
  93.  
  94. assert (processorRef.get() == this);
  95. }
  96.  
  97. // Disconnect all sessions immediately if disposal has been
  98. // requested so that we exit this loop eventually.
  99. if (isDisposing()) {
  100. for (Iterator<S> i = allSessions(); i.hasNext();) {
  101. scheduleRemove(i.next());
  102. }
  103.  
  104. wakeup();
  105. }
  106. } catch (ClosedSelectorException cse) {
  107. // If the selector has been closed, we can exit the loop
  108. break;
  109. } catch (Throwable t) {
  110. ExceptionMonitor.getInstance().exceptionCaught(t);
  111.  
  112. try {
  113. Thread.sleep(1000);
  114. } catch (InterruptedException e1) {
  115. ExceptionMonitor.getInstance().exceptionCaught(e1);
  116. }
  117. }
  118. }
  119.  
  120. try {
  121. synchronized (disposalLock) {
  122. if (disposing) {
  123. doDispose();
  124. }
  125. }
  126. } catch (Throwable t) {
  127. ExceptionMonitor.getInstance().exceptionCaught(t);
  128. } finally {
  129. disposalFuture.setValue(true);
  130. }
  131. }
  132. }

3、session

session做为一个连接的详细对象,缓存当前连接用户的一些信息。

session类图

watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvY2hhb2ZhbndlaQ==/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/Center" alt="" />

session对象是绑定在SelectableChannel的一个attach。

  1. class NioProcessor
  2. @Override
  3. protected void init(NioSession session) throws Exception {
  4. SelectableChannel ch = (SelectableChannel) session.getChannel();
  5. ch.configureBlocking(false);
  6. session.setSelectionKey(ch.register(selector, SelectionKey.OP_READ, session));//chanel 注冊读事件,并把session当做一个attach辅导SelectableChannel里面。
  7. }

NIO框架之MINA源代码解析(二):mina核心引擎的更多相关文章

  1. NIO框架之MINA源代码解析(一):背景

     "你们的agent占了好多系统的port.把我们的非常多业务系统都给整死了,给我们造成了非常大的损失.要求你们的相关领导下周过来道歉"   --   来自我们的一个客户.   ...

  2. (一)Mina源代码解析之总体架构

    Apache Mina Server 是一个网络通信应用框架.也就是说,它主要是对基于TCP/IP.UDP/IP协议栈的通信框架(当然,也能够提供JAVA 对象的序列化服务.虚拟机管道通信服务等).M ...

  3. Volley 框架解析(二)--RequestQueue核心解读

    主要围绕RequestQueue进行解读,它的两个请求队列CacheQueue.NetworkQueue是如何调用的,第一条请求的执行过程及如何处理重复请求?对RequestQueue及相关的类进行详 ...

  4. 【原创】NIO框架入门(一):服务端基于Netty4的UDP双向通信Demo演示

    申明:本文由作者基于日常实践整理,希望对初次接触MINA.Netty的人有所启发.如需与作者交流,见文签名,互相学习. 学习交流 更多学习资料:点此进入 推荐 移动端即时通讯交流: 215891622 ...

  5. Spring源代码解析

    Spring源代码解析(一):IOC容器:http://www.iteye.com/topic/86339 Spring源代码解析(二):IoC容器在Web容器中的启动:http://www.itey ...

  6. Spring源代码解析(收藏)

    Spring源代码解析(收藏)   Spring源代码解析(一):IOC容器:http://www.iteye.com/topic/86339 Spring源代码解析(二):IoC容器在Web容器中的 ...

  7. NIO框架之MINA源码解析(五):NIO超级陷阱和使用同步IO与MINA通信

    1.NIO超级陷阱 之所以说NIO超级陷阱,就是因为我在本系列开头的那句话,因为使用缺陷导致客户业务系统瘫痪.当然,我对这个问题进行了很深的追踪,包括对MINA源码的深入了解,但其实之所以会出现这个问 ...

  8. NIO框架Mina学习

    前言: 找了篇文章看了看,nio框架数Mina用的最多! 代码: 服务端: package com.mina; import java.net.InetSocketAddress; import ja ...

  9. 【原创】NIO框架入门(二):服务端基于MINA2的UDP双向通信Demo演示

    前言 NIO框架的流行,使得开发大并发.高性能的互联网服务端成为可能.这其中最流行的无非就是MINA和Netty了,MINA目前的主要版本是MINA2.而Netty的主要版本是Netty3和Netty ...

随机推荐

  1. SAS进阶《深入分析SAS》之数据汇总和展现

    SAS进阶<深入分析SAS>之数据汇总和展现 1. 通过Print过程制作报表 proc print <data=数据集>; run; 选项: obs=修改观测序号列标签 no ...

  2. Python之数据聚合与分组运算

    Python之数据聚合与分组运算 1. 关系型数据库方便对数据进行连接.过滤.转换和聚合. 2. Hadley Wickham创建了用于表示分组运算术语"split-apply-combin ...

  3. Android Studio Activity Intent 闪退崩溃 Toolbar

    今天写登录注册页面,点击登录页面的“注册”按钮后软件突然崩溃,直接闪退,因为是新手,只能去网上搜.虽然网上解决方法众多,但也没找到可行的.想起来可以看Logcat,马上重新运行应用,查看崩溃时的日志, ...

  4. Mac上简单常用Terminal命令

    方案1 SSH是一个非常伟大的工具,如果你要在互联网上远程连接到服务器,那么SSH无疑是最佳的候选.SSH是加密的,OpenSSH加密所有通信(包括密码),有效消除了窃听,连接劫持和其它攻击.本文将为 ...

  5. vba txt读写的几种方式

    四种方式写txt 1.这种写出来的是ANSI格式的txt Dim TextExportFile As String TextExportFile = ThisWorkbook.Path & & ...

  6. 安全,轻松的Axios与Nuxt.js集成

    modules: [ // Doc: https://github.com/nuxt-community/axios-module#usage '@nuxtjs/axios' ], /* ** Axi ...

  7. java_IO_装饰器

    装饰器模式 装饰器模式(Decorator Pattern)允许向一个现有的对象添加新的功能,同时又不改变其结构.这种类型的设计模式属于结构型模式,它是作为现有的类的一个包装. 这种模式创建了一个装饰 ...

  8. 04C语言输入输出

    C语言输入输出 输入字符getchar() #include <stdio.h> int main(){ putchar(getchar()); putchar(getchar()); ; ...

  9. 03Oracle Database 物理结构,逻辑结构

    Oracle Database 物理结构,逻辑结构 Oracle Database 物理结构 数据文件 Oracle数据库有一个或多个物理的数据文件.数据库的数据文件包含全部数据库数据.逻辑数据物理地 ...

  10. java mysql prepareStatement模糊查询like使用注意

    今天在使用mysql 的like语句是,发现prepareStatement的like语句和一般的=写法有一样. 当要使用prepareStatement的like查询时,按照一般写法,都会写成: S ...