原文地址:https://www.iteye.com/blog/gearever-1844203

ller线程中维护的这个Selector标为主Selector。 
Poller是NIO实现的主要线程。首先作为events queue的消费者,从queue中取出PollerEvent对象,然后将此对象中的channel以OP_READ事件注册到主Selector中,然后主Selector执行select操作,遍历出可以读数据的socket,并从Worker线程池中拿到可用的Worker线程,然后将socket传递给Worker。整个过程是典型的NIO实现。 

Worker 
Worker线程拿到Poller传过来的socket后,将socket封装在SocketProcessor对象中。然后从Http11ConnectionHandler中取出Http11NioProcessor对象,从Http11NioProcessor中调用CoyoteAdapter的逻辑,跟BIO实现一样。在Worker线程中,会完成从socket中读取http request,解析成HttpServletRequest对象,分派到相应的servlet并完成逻辑,然后将response通过socket发回client。在从socket中读数据和往socket中写数据的过程,并没有像典型的非阻塞的NIO的那样,注册OP_READ或OP_WRITE事件到主Selector,而是直接通过socket完成读写,这时是阻塞完成的,但是在timeout控制上,使用了NIO的Selector机制,但是这个Selector并不是Poller线程维护的主Selector,而是BlockPoller线程中维护的Selector,称之为辅Selector。 

NioSelectorPool 
NioEndpoint对象中维护了一个NioSelecPool对象,这个NioSelectorPool中又维护了一个BlockPoller线程,这个线程就是基于辅Selector进行NIO的逻辑。以执行servlet后,得到response,往socket中写数据为例,最终写的过程调用NioBlockingSelector的write方法。 

Java代码  收藏代码
  1. public int write(ByteBuffer buf, NioChannel socket, long writeTimeout,MutableInteger lastWrite) throws IOException {  
  2.         SelectionKey key = socket.getIOChannel().keyFor(socket.getPoller().getSelector());  
  3.         if ( key == null ) throw new IOException("Key no longer registered");  
  4.         KeyAttachment att = (KeyAttachment) key.attachment();  
  5.         int written = 0;  
  6.         boolean timedout = false;  
  7.         int keycount = 1; //assume we can write  
  8.         long time = System.currentTimeMillis(); //start the timeout timer  
  9.         try {  
  10.             while ( (!timedout) && buf.hasRemaining()) {  
  11.                 if (keycount > 0) { //only write if we were registered for a write  
  12.                     //直接往socket中写数据  
  13.                     int cnt = socket.write(buf); //write the data  
  14.                     lastWrite.set(cnt);  
  15.                     if (cnt == -1)  
  16.                         throw new EOFException();  
  17.                     written += cnt;  
  18.                     //写数据成功,直接进入下一次循环,继续写  
  19.                     if (cnt > 0) {  
  20.                         time = System.currentTimeMillis(); //reset our timeout timer  
  21.                         continue; //we successfully wrote, try again without a selector  
  22.                     }  
  23.                 }  
  24.                 //如果写数据返回值cnt等于0,通常是网络不稳定造成的写数据失败  
  25.                 try {  
  26.                     //开始一个倒数计数器   
  27.                     if ( att.getWriteLatch()==null || att.getWriteLatch().getCount()==0) att.startWriteLatch(1);  
  28.                     //将socket注册到辅Selector,这里poller就是BlockSelector线程  
  29.                     poller.add(att,SelectionKey.OP_WRITE);  
  30.                     //阻塞,直至超时时间唤醒,或者在还没有达到超时时间,在BlockSelector中唤醒  
  31.                     att.awaitWriteLatch(writeTimeout,TimeUnit.MILLISECONDS);  
  32.                 }catch (InterruptedException ignore) {  
  33.                     Thread.interrupted();  
  34.                 }  
  35.                 if ( att.getWriteLatch()!=null && att.getWriteLatch().getCount()> 0) {  
  36.                     keycount = 0;  
  37.                 }else {  
  38.                     //还没超时就唤醒,说明网络状态恢复,继续下一次循环,完成写socket  
  39.                     keycount = 1;  
  40.                     att.resetWriteLatch();  
  41.                 }  
  42.   
  43.                 if (writeTimeout > 0 && (keycount == 0))  
  44.                     timedout = (System.currentTimeMillis() - time) >= writeTimeout;  
  45.             } //while  
  46.             if (timedout)   
  47.                 throw new SocketTimeoutException();  
  48.         } finally {  
  49.             poller.remove(att,SelectionKey.OP_WRITE);  
  50.             if (timedout && key != null) {  
  51.                 poller.cancelKey(socket, key);  
  52.             }  
  53.         }  
  54.         return written;  
  55.     }  


也就是说当socket.write()返回0时,说明网络状态不稳定,这时将socket注册OP_WRITE事件到辅Selector,由BlockPoller线程不断轮询这个辅Selector,直到发现这个socket的写状态恢复了,通过那个倒数计数器,通知Worker线程继续写socket动作。看一下BlockSelector线程的逻辑; 

Java代码  收藏代码
  1. public void run() {  
  2.             while (run) {  
  3.                 try {  
  4.                     ......  
  5.   
  6.                     Iterator iterator = keyCount > 0 ? selector.selectedKeys().iterator() : null;  
  7.                     while (run && iterator != null && iterator.hasNext()) {  
  8.                         SelectionKey sk = (SelectionKey) iterator.next();  
  9.                         KeyAttachment attachment = (KeyAttachment)sk.attachment();  
  10.                         try {  
  11.                             attachment.access();  
  12.                             iterator.remove(); ;  
  13.                             sk.interestOps(sk.interestOps() & (~sk.readyOps()));  
  14.                             if ( sk.isReadable() ) {  
  15.                                 countDown(attachment.getReadLatch());  
  16.                             }  
  17.                             //发现socket可写状态恢复,将倒数计数器置位,通知Worker线程继续  
  18.                             if (sk.isWritable()) {  
  19.                                 countDown(attachment.getWriteLatch());  
  20.                             }  
  21.                         }catch (CancelledKeyException ckx) {  
  22.                             if (sk!=null) sk.cancel();  
  23.                             countDown(attachment.getReadLatch());  
  24.                             countDown(attachment.getWriteLatch());  
  25.                         }  
  26.                     }//while  
  27.                 }catch ( Throwable t ) {  
  28.                     log.error("",t);  
  29.                 }  
  30.             }  
  31.             events.clear();  
  32.             try {  
  33.                 selector.selectNow();//cancel all remaining keys  
  34.             }catch( Exception ignore ) {  
  35.                 if (log.isDebugEnabled())log.debug("",ignore);  
  36.             }  
  37.         }  


使用这个辅Selector主要是减少线程间的切换,同时还可减轻主Selector的负担。以上描述了NIO connector工作的主要逻辑,可以看到在设计上还是比较精巧的。NIO connector还有一块就是Comet,有时间再说吧。需要注意的是,上面从Acceptor开始,有很多对象的封装,NioChannel及其KeyAttachment,PollerEvent和SocketProcessor对象,这些不是每次都重新生成一个新的,都是NioEndpoint分别维护了它们的对象池; 

Java代码  收藏代码
  1. ConcurrentLinkedQueue<SocketProcessor> processorCache = new ConcurrentLinkedQueue<SocketProcessor>()  
  2. ConcurrentLinkedQueue<KeyAttachment> keyCache = new ConcurrentLinkedQueue<KeyAttachment>()  
  3. ConcurrentLinkedQueue<PollerEvent> eventCache = new ConcurrentLinkedQueue<PollerEvent>()  
  4. ConcurrentLinkedQueue<NioChannel> nioChannels = new ConcurrentLinkedQueue<NioChannel>()  


当需要这些对象时,分别从它们的对象池获取,当用完后返回给相应的对象池,这样可以减少因为创建及GC对象时的性能消耗。

posted on 2019-09-03 14:15  一天不进步,就是退步  阅读(153)  评论(0编辑  收藏  举报