tomcat服务器解析(五)-- Poller

在前面的分析中介绍过,Acceptor的作用是控制与tomcat建立连接的数量,但Acceptor只负责建立连接。socket内容的读写是通过Poller来实现的。
 
Poller使用java nio来实现连接的管理。

关于nio,主要需要明确三个概念:Channel、Selector和SelectionKey.
在这里的使用上,它们之间的关系可以简单这样理解,Channel必须注册到Selector上才能用于接收socket数据,在Selector上有数据到达的Channel可以用SelectionKey来表示

[注册]
Poller使用nio来进行socket数据的读写,一个通过Poller的register方法,注册到Poller上。
对Poller的注册,先进入Poller内部维护的一个事件队列上。Poller线程在执行过程中会去检查队列,将channel注册到selector上。为了保证在多线程同时访问时数据的一致性,这个队              
     列是一个SynchronizedQueue,使用synchronized来保证对队列中数据的一致性。
注册的时候,每个channel会有KeyAttachment对象,用来进行channel上的多线程并发执行时的控制。

ServerSocketChannel建立连接是在Acceptor上。

[队列]
队列定义如下  private  final  SynchronizedQueue<PollerEvent> events =  new  SynchronizedQueue<>();

队列中的每个元素是PollerEvent对象,它携带完成的处理channel相关的信息。每个事件在处理过程中,会根据事件的状态,来实现Channel到Selector上的注册。队列处理完成后,每个注册到Poller上的channel就完成了到Selector上的注册。

[socket数据读取]
Poller线程的run方法的主题部分使用while(true)的无限循环来执行,当所属的Endpoint正常运行的时候,在每次执行过程中,处理其事件队列,调用selector来读取数据,然后处理读取到的数据。
在run方法中,会调用 events方法来处理事件队列
调用selector.selectNow或selector.select(xxx)来获取有数据到达的channel

[Poller缓存]
在Poller中使用的缓存是来其所属的Endpoint的缓存。keyCache和eventCache

eventCache是PollerEvent事件的缓存,在Poller上注册的时候,从eventCache中取出PollerEvent对象,重置这个对象,然后再放入Poller的事件队列中。Poller在处理队列的过程中,每从队列中取出一个要处理的PollerEvent事件,处理完之后,把这个PollerEvent对象放回缓存中。   ---- 避免频繁地创建PollerEvent对象和GC回收。


keyCache是对应的socket信息的缓存,在Poller上注册的时候,从keyCache中取出KeyAttachment对象,重置这个对象,作用附件用于channel到selector上的注册。在Processor处理完数据之后,将这个KeyAttachment对象放回keyCache中。  ----- 避免频繁地创建KeyAttachment对象和GC回收。


[多线程并发控制]
events队列,为SychronizedQueue<PollerEvent>,SychronizedQueue提供的offer、poll、size和clear方法都使用了sychronized关键字进行修饰,用来保证同一时刻只有一个线程能对队列进行读写。

系统中是同时有多个Poller线程在运行的,每个Polle线程有各自的events队列。但每个Poller线程可能同时被多个Acceptor线程调用进行注册。


[属性说明]

Poller的属性如下


        private  Selector selector;
         private   final  SynchronizedQueue<PollerEvent> events =
                 new  SynchronizedQueue<>();

         private   volatile  boolean  close =  false ;
         private   long  nextExpiration = 0;  //optimize expiration handling

         private  AtomicLong wakeupCounter =  new  AtomicLong(0);

         private   volatile  int  keyCount = 0;

selector,java nio必备组成部分
events 当前Poller的事件队列,主要是channel注册事件
close 当前Poller是否可用的状态开关
nextExpiration 当前连接到此Poller上的socket超时的时限点。Poller线程在其run方法的每遍执行过程中,会调用timeout方法来检查当前连接的socket,是否达到了超时的时限,如果达到了超时的时限,则告诉客户端连接超时。每次执行完timeout方法后,会重新设置nextExpiration的值
wakeupCounter的作用:1、告诉Poller当前有多少个新连接,这样当Poller进行selector的操作时,可以选择是否需要阻塞来等待读写请求到达。2、标识Poller在进行select选择时,是否有连接到达。如果有,就让当前的阻塞调用立即返回
这个地方比较隐晦,结合代码来进行解释

     channel注册到Poller时执行的部分代码
       private   void  addEvent(PollerEvent event) {
            events.offer(event);
             if  ( wakeupCounter.incrementAndGet() == 0 ) selector.wakeup();
        }

     Poller的run方法部分代码

                if  (wakeupCounter.getAndSet(-1) > 0) {
               //if we are here, means we have other stuff to do
                     //do a non blocking select
                    keyCount = selector.selectNow();
                }  else  {
                    keyCount = selector.select(selectorTimeout);
                }
                wakeupCounter.set(0);

考虑下面的两个场景:
作用一,帮助Poller选择select方法
在run执行时,当前已经有了5个channel注册到Poller上,所以 wakeupCounter.getAndSet(-1) > 0 条件满足,Poller调用selector的非阻塞模式的select方法被调用
作用二,让当前阻塞的select方法立即返回
在run执行时,如果当前没有channel注册到Poller上, wakeupCounter.getAndSet(-1) > 0  条件不满足,但wakeupCounter的值已经被设为-1了。Poller调用阻塞的select方法。在这期间,如果有新的channel注册进来,则   wakeupCounter.incrementAndGet() == 0条件满足,select.wakeup方法被调用,让 selector.select(selectorTimeout)方法立即返回。
keyCount 注册到Poller的channel中,I/O状态已经OK的的个数

你可能感兴趣的:(java,tomcat)