【mina指南】mina中的reactor模式(一)

来源:互联网 发布:单片机lcd显示屏汇编 编辑:程序博客网 时间:2024/04/29 07:37

【mina指南】mina中的reactor模式(一)

mina中的reactor模式实现参考了Doug Lea 在《Scalable IO in Java》中的reactor。
 



从上面来两个图可以看出:与传统的单个Reactor模式实现不同,mina中采用了Multiple Reactor的方式。NioSocketAcceptor和NioProcessor使用不同selector,能够更加充分的榨取服务器的性能。
acctptor主要负责
1. 绑定一个/多个端口,开始监听
2. 处理客户端的建链请求
3. 关闭一个/多个监听端口
processor主要负责
1. 接受客户端发送的数据,并转发给业务逻辑成处理
2. 发送数据到客户端

首先看一个最简单的mina服务端程序,
Java代码 复制代码
  1. SocketAcceptor acceptor = new NioSocketAcceptor();   
  2. // 设定一个事件处理器   
  3. acceptor.setHandler(new EchoProtocolHandler());   
  4. // 绑定一个监听端口   
  5. acceptor.bind(new InetSocketAddress(PORT));  

就这3句话一个服务端程序就ok了,让我们看看,mina在背后做了点什么。
1、NioSocketAcceptor的初始化
1.1 在NioSocketAcceptor的构造函数中,把NioProcessor也构造出来了
Java代码 复制代码
  1.   
  2. protected AbstractPollingIoAcceptor(IoSessionConfig sessionConfig,   
  3.           Class<? extends IoProcessor<T>> processorClass) {   
  4.       this(sessionConfig, nullnew SimpleIoProcessorPool<T>(processorClass), true);   
  5.   }  

1.2 SimpleIoProcessorPool是一个NioProcessor池,默认大小是cpu个数+1,这样能够充分利用多核的cpu
Java代码 复制代码
  1. private static final int DEFAULT_SIZE = Runtime.getRuntime().availableProcessors() + 1;   
  2.     public SimpleIoProcessorPool(Class<? extends IoProcessor<T>> processorType) {   
  3.         this(processorType, null, DEFAULT_SIZE);   
  4.     }  

1.3 调用init方法,准备selector
Java代码 复制代码
  1.  protected void init() throws Exception {   
  2.     selector = Selector.open();   
  3. }  


到此,生产线已经装配好,就等按下开关,就可以正常运行了。


2.绑定端口
2.1 在调用NioSocketAcceptor.bind()函数的时候最终调用AbstractPollingIoAcceptor.bind0,这是NioSocketAcceptor的关键所在
Java代码 复制代码
  1.     
  2. protected final Set<SocketAddress> bind0(   
  3.        List<? extends SocketAddress> localAddresses) throws Exception {   
  4.           
  5.        /*2.1.1registerQueue是一个待监听动作列表,每次要新绑定一个端口,增加一个register动作,在工作者线程中处理*/  
  6.        AcceptorOperationFuture request = new AcceptorOperationFuture(   
  7.                localAddresses);   
  8.        registerQueue.add(request);   
  9.   
  10.        /*2.1.2 创建并启动工作者线程*/  
  11.        startupWorker();   
  12.        wakeup();   
  13.        //堵塞直至监听成功   
  14.        request.awaitUninterruptibly();   
  15.           
  16.        //以下省略   
  17.      }  

2.1.1 通过实现生产者/消费者问题来处理绑定端口开始监听动作,其中生产者是bind动作,消费者在Work.run方法中,registQueue是消息队列

2.1.2 在线程池中启动工作者线程

2.1.3 堵塞直至绑定监听端口成功
Java代码 复制代码
  1.       
  2. public class DefaultIoFuture implements IoFuture {   
  3. public IoFuture awaitUninterruptibly() {   
  4.         synchronized (lock) {   
  5.             while (!ready) {//当ready为true时候,跳出循环   
  6.                 waiters++;   
  7.                 try {   
  8.                     lock.wait(DEAD_LOCK_CHECK_INTERVAL);   
  9.                 } catch (InterruptedException ie) {   
  10.              // Do nothing : this catch is just mandatory by contract   
  11.                 } finally {   
  12.                     waiters--;   
  13.                     if (!ready) {   
  14.                         checkDeadLock();   
  15.                     }   
  16.                 }   
  17.             }   
  18.         }   
  19.   
  20.         return this;   
  21.     }   
  22. }  

真正的绑定端口开始监听动作是在Woker线程中执行的
取消绑定操作与绑定操作类似,暂时先不描述。

3.AbstractPollingIoAcceptor的工作者线程是NioSocketAcceptor的核心所在,完成了以下三个主要功能:
1、处理绑定监听端口请求
2、处理取消监听端口绑定请求
3、处理socket连接请求

1,2主要是在系统初始化或者系统关闭的时候在registerQuerue/cancelQueue中增加一个消息,3是系统运行时NioSocketAcceptor处理socket建链请求的关键。1,2,3的主要内容在工作者线程Work.run方法中。

NioSocketAcceptor继承自父类AbstractPollingIoAcceptor的Worker.run
Java代码 复制代码
  1. private class Worker implements Runnable {   
  2.       public void run() {   
  3.           int nHandles = 0;   
  4.   
  5.           while (selectable) {   
  6.               try {   
  7.                   // Detect if we have some keys ready to be processed   
  8.                   boolean selected = select();   
  9.   
  10.                   // this actually sets the selector to OP_ACCEPT,   
  11.                   // and binds to the port in which this class will   
  12.                   // listen on   
  13.                   nHandles += registerHandles();   
  14.   
  15.                   if (selected) {   
  16.                       processHandles(selectedHandles());   
  17.                   }   
  18.   
  19.                   // check to see if any cancellation request has been made.   
  20.                   nHandles -= unregisterHandles();   
  21.   
  22.   
  23.   
  24.   
  25.                   if (nHandles == 0) {   
  26.                       synchronized (lock) {   
  27.                           if (registerQueue.isEmpty()   
  28.                                   && cancelQueue.isEmpty()) {   
  29.                               worker = null;   
  30.                               break;   
  31.                           }   
  32.                       }   
  33.                   }   
  34.               } catch (Throwable e) {   
  35.                   ExceptionMonitor.getInstance().exceptionCaught(e);   
  36.   
  37.                   try {   
  38.                       Thread.sleep(1000);   
  39.                   } catch (InterruptedException e1) {   
  40.                       ExceptionMonitor.getInstance().exceptionCaught(e1);   
  41.                   }   
  42.               }   
  43.           }   
  44.   
  45.           if (selectable && isDisposing()) {   
  46.               selectable = false;   
  47.               try {   
  48.                   if (createdProcessor) {   
  49.                       processor.dispose();   
  50.                   }   
  51.               } finally {   
  52.                   try {   
  53.                       synchronized (disposalLock) {   
  54.                           if (isDisposing()) {   
  55.                               destroy();   
  56.                           }   
  57.                       }   
  58.                   } catch (Exception e) {   
  59.                       ExceptionMonitor.getInstance().exceptionCaught(e);   
  60.                   } finally {   
  61.                       disposalFuture.setDone();   
  62.                   }   
  63.               }   
  64.           }   
  65.       }  


3.1 首先系统在一个无限循环中不停的允许,处理socket建链请求/端口监听/取消端口监听请求,selectable是一个标志位,初始化的时候置为true,要关闭时候置为false,则系统退出循环。

3.2 使用selector监听是否有连接请求操作。
Java代码 复制代码
  1. public void run() {   
  2.          int nHandles = 0;   
  3.   
  4.          while (selectable) {   
  5.              try {   
  6.                  // Detect if we have some keys ready to be processed   
  7.                  boolean selected = select();   
  8.                  //以下省略   
  9.              
  10.      }  

注意,虽然 boolean selected = select();是一个堵塞操作,但是run方法不会陷入死循环。因为即使没有新的连接请求到达,但是每次bind/unbind都会调用NioSocketAccepto.wakeup唤醒处于select状态的selctor。
Java代码 复制代码
  1. protected void wakeup() {   
  2.     selector.wakeup();   
  3. }  

而系统退出是,关闭的acceptor的dispose方法最终会调用unbind,所以退出时不会有问题。

3.2 从registerQueue中获取绑定请求消息,开始绑定某个端口,并开始监听,准备相关上下文信息
Java代码 复制代码
  1. public void run() {   
  2.           int nHandles = 0;   
  3.   
  4.           while (selectable) {   
  5.               try {   
  6.                   // Detect if we have some keys ready to be processed   
  7.                   boolean selected = select();   
  8.   
  9.                   // this actually sets the selector to OP_ACCEPT,   
  10.                   // and binds to the port in which this class will   
  11.                   // listen on   
  12.                   nHandles += registerHandles();   
  13.   
  14.                   //以下省略   
  15.       }  

对于registerHandleres真正完成了对端口的监听
Java代码 复制代码
  1. private int registerHandles() {   
  2.        for (;;) {   
  3.            AcceptorOperationFuture future = registerQueue.poll();   
  4.            if (future == null) {   
  5.                return 0;   
  6.            }   
  7.   
  8.            Map<SocketAddress, H> newHandles = new HashMap<SocketAddress, H>();   
  9.            List<SocketAddress> localAddresses = future.getLocalAddresses();   
  10.   
  11.            try {   
  12.                for (SocketAddress a : localAddresses) {   
  13.                    H handle = open(a);   
  14.                    newHandles.put(localAddress(handle), handle);   
  15.                }   
  16.   
  17.                boundHandles.putAll(newHandles);   
  18.   
  19.                // and notify.   
  20.                future.setDone();   
  21.                return newHandles.size();   
  22.            } catch (Exception e) {   
  23.                future.setException(e);   
  24.            } finally {   
  25.                // Roll back if failed to bind all addresses.   
  26.                if (future.getException() != null) {   
  27.                    for (H handle : newHandles.values()) {   
  28.                        try {   
  29.                            close(handle);   
  30.                        } catch (Exception e) {   
  31.                            ExceptionMonitor.getInstance().exceptionCaught(e);   
  32.                        }   
  33.                    }   
  34.                    wakeup();   
  35.                }   
  36.            }   
  37.        }   
  38.    }  

3.2.1 registerHandlers将registerQueue中的所有绑定监听消息取出来,在循环中处理,注意一个细节是,一个绑定监听消息AcceptorOperationFuture可能要绑定监听多个ip的同一个端口
3.2.2 registerHandlers的一个关键动作是
Java代码 复制代码
  1. H handle = open(a);  

参看NioSocketAcceptor.open方法的实现,发现这就是关键
Java代码 复制代码
  1. protected ServerSocketChannel open(SocketAddress localAddress)   
  2.         throws Exception {   
  3.     ServerSocketChannel c = ServerSocketChannel.open();   
  4.     boolean success = false;   
  5.     try {   
  6.         c.configureBlocking(false);   
  7.         // Configure the server socket,   
  8.         c.socket().setReuseAddress(isReuseAddress());   
  9.         // XXX: Do we need to provide this property? (I think we need to remove it.)   
  10.         c.socket().setReceiveBufferSize(   
  11.                 getSessionConfig().getReceiveBufferSize());   
  12.         // and bind.   
  13.         c.socket().bind(localAddress, getBacklog());   
  14.         c.register(selector, SelectionKey.OP_ACCEPT);   
  15.         success = true;   
  16.     } finally {   
  17.         if (!success) {   
  18.             close(c);   
  19.         }   
  20.     }   
  21.     return c;   
  22. }  

在这个方法中真正开始监听一个端口,并设置相关细节
1.采用非堵塞方式
2.可从用端口
3.socket读取的缓冲区大小
4.listen队列的长度
并且在selector中注册了对SelectionKey.OP_ACCEPT的关注。

3.2.3 通过  future.setDone();唤醒bind线程,告知绑定操作已经成功,否则bind线程还将继续堵塞。

3.3 如果发现有连接请求过来,则处理之
Java代码 复制代码
  1. private class Worker implements Runnable {   
  2.       public void run() {   
  3.           int nHandles = 0;   
  4.   
  5.           while (selectable) {   
  6.               try {   
  7.                   // Detect if we have some keys ready to be processed   
  8.                   boolean selected = select();   
  9.   
  10.                   // this actually sets the selector to OP_ACCEPT,   
  11.                   // and binds to the port in which this class will   
  12.                   // listen on   
  13.                   nHandles += registerHandles();   
  14.   
  15.                   if (selected) {   
  16.                       processHandles(selectedHandles());   
  17.                   }   
  18.   
  19.                //以下省略  

processHandles是负责完成新建一个连接,并将这个连接交给NioProcessor监控
Java代码 复制代码
  1. private void processHandles(Iterator<H> handles) throws Exception {   
  2.         while (handles.hasNext()) {   
  3.             H handle = handles.next();   
  4.             handles.remove();   
  5.   
  6.             T session = accept(processor, handle);   
  7.             if (session == null) {   
  8.                 break;   
  9.             }   
  10.   
  11.             finishSessionInitialization(session, nullnull);   
  12.   
  13.             // add the session to the SocketIoProcessor   
  14.             session.getProcessor().add(session);   
  15.         }   
  16.     }  

3.3.1 accept方法创建了一个NioSocketSession,
3.3.2 finishSessionInitialization对这个session进行初始化
3.3.3 将session交给NioProcessor管理,这里有两个地方需要注意
3.3.3.1 processor是一个SimpleIoProcessorPool,里面有个多个NioProcessor,SimpleIoProcessor将轮询取一个processor负责管理新建的NioSocketSession
3.3.3.2 NioProcessor.add方法只是将NioSocketSession放到一个newSessions的队列中,并启动NioProcessor的工作者线程。不会马上生效,要等NioProcessor的worker线程执行addNew的时候,才会真正开始管理新增的session,这个与Acceptor的bind类似
Java代码 复制代码
  1. public final void add(T session) {   
  2.     if (isDisposing()) {   
  3.         throw new IllegalStateException("Already disposed.");   
  4.     }   
  5.   
  6.     newSessions.add(session);   
  7.     startupWorker();   
  8. }  



至此NioSocketAcceptor的基本实现已经描述完毕,相信读者对也有一个初步的认识。

思考题:
1. 描述一下NioSocketAcceptor处理一个新连接请求的全过程;
2. 下面代码中,mina做了什么,是怎么关闭监听端口的。
Java代码 复制代码
  1. NioSocketAcceptor acceptor = new NioSocketAcceptor();   
  2. //省略   
  3. accetpro.dispose();   
原创粉丝点击