原文地址: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. if (writeTimeout > 0 && (keycount == 0))
  43. timedout = (System.currentTimeMillis() - time) >= writeTimeout;
  44. } //while
  45. if (timedout)
  46. throw new SocketTimeoutException();
  47. } finally {
  48. poller.remove(att,SelectionKey.OP_WRITE);
  49. if (timedout && key != null) {
  50. poller.cancelKey(socket, key);
  51. }
  52. }
  53. return written;
  54. }

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

使用这个辅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对象时的性能消耗。

转载于:https://www.cnblogs.com/davidwang456/articles/11452705.html

tomcat架构分析 (connector NIO 实现)【转】相关推荐

  1. tomcat架构分析(connector BIO 实现)【转】

    原文地址:https://www.iteye.com/blog/gearever-1841586 在tomcat架构分析(概览)中已经介绍过,connector组件是service容器中的一部分.它主 ...

  2. tomcat架构分析 (Session管理)【转】

    原文地址:https://www.iteye.com/blog/gearever-1546423 Session管理是JavaEE容器比较重要的一部分,在app中也经常会用到.在开发app时,我们只是 ...

  3. tomcat架构分析(valve源码导读)【转】

    原文地址:https://www.iteye.com/blog/gearever-1540028 源码面前,了无秘密                               ----侯捷  在to ...

  4. tomcat架构分析(概览)【转】

    原文地址: https://www.iteye.com/blog/gearever-1532822 Tomcat是目前应用比较多的servlet容器.关于tomcat本身的特点及介绍,网上已经有很多描 ...

  5. tomcat架构分析(容器类)【转】

    原文地址:https://www.iteye.com/blog/gearever-1533678 Tomcat提供了engine,host,context及wrapper四种容器.在总体结构中已经阐述 ...

  6. tomcat架构分析(容器类)

    Tomcat提供了engine,host,context及wrapper四种容器.在总体结构中已经阐述了他们之间的包含关系.这四种容器继承了一个容器基类,因此可以定制化.当然,tomcat也提供了标准 ...

  7. tomcat架构分析(valve机制)

    出处:http://gearever.iteye.com 关于tomcat的内部逻辑单元的存储空间已经在相关容器类的blog里阐述了.在每个容器对象里面都有一个pipeline及valve模块.它们是 ...

  8. tomcat架构分析(valve机制)【转】

    原文地址:https://www.iteye.com/blog/gearever-1536022 关于tomcat的内部逻辑单元的存储空间已经在相关容器类的blog里阐述了.在每个容器对象里面都有一个 ...

  9. 应用服务器——tomcat架构分析

    先mark,后续补充 https://blog.csdn.net/qq_38245537/article/details/79009448

最新文章

  1. linux介绍及目录结构(一)
  2. 腾讯朱华:数据中心下一个风向的探索
  3. 20种PLC元件编号和Modbus编号地址对应表
  4. 突发!Python再次卫冕,Java和C下降,你怎么看?
  5. [JS调用]automation服务器不能创建对象
  6. NewSQL登堂入室 数据库厂商掘金行业大数据
  7. clion使用之如何在编译运行多个程序(以cpp为例)
  8. 关于NB-IoT的十大问题和答案
  9. 新浪微博开放平台API访问频率限制解决方法
  10. android qq聊天图片无法显示,QQ聊天时无法打开图片
  11. 【操作系统】DOS界面与常用操作命令
  12. 基础30讲 第九讲 一元函数积分学的几何应用
  13. 三角形面积的两种计算方法
  14. C语言课程设计——学生成绩管理系统(详细报告)
  15. 高阶低通滤波算法_高/低算法
  16. 东师理想云平台异步任务处理系统V2.0重构思路
  17. java中的命令执行汇总
  18. Ice自学第一步——Windows下安装Ice和设置Ice的环境变量
  19. vector 删除元素的几种方法
  20. 金融分析与风险管理——投资组合的有效前沿及资本市场线

热门文章

  1. 服务器拒绝接收office文件,Ghost Win7系统下Outlook设置拒绝接收垃圾文件的方法
  2. Java中的输入输出流
  3. Linux文件IO深入剖析
  4. 怎么测并发 PHP,PHP接口并发测试的方法(推荐)
  5. 通达oa oracle数据库,通达OA 2016系统连接ORACLE 11g数据库(图文)
  6. PHP不及格标红,php语言编写switch判断成绩代码。分别输出优秀、良好、中等、及格和不及格。...
  7. 全国计算机三级哪个容易一点,给考三级网络的朋友们一点儿真诚的建议
  8. 使用隐式Intent打开系统浏览器的百度网页
  9. mybatis mysql自动连接数据库_如何用mybatis链接数据库
  10. python flask 路由_python框架之Flask(2)-路由和视图Session