精华内容
下载资源
问答
  • Java NIOTomcat 原理理解

    千次阅读 2015-12-11 11:47:17
    tomcat的运行模式有3种.修改他们的运行模式.3种模式的运行是否成功,可以看他的启动控制台,或者启动日志.... 1)bio 默认的模式,性能非常低下,没有经过任何优化处理和支持. ...2)nio 利用java的异步io护理技术,no

    源链接:http://m.oschina.net/blog/163549

    tomcat的运行模式有3种.修改他们的运行模式.3种模式的运行是否成功,可以看他的启动控制台,或者启动日志.或者登录他们的默认页面http://localhost:8080/查看其中的服务器状态。

    1)bio

    默认的模式,性能非常低下,没有经过任何优化处理和支持.

    2)nio

    利用java的异步io护理技术,no blocking IO技术.

    想运行在该模式下,直接修改server.xml里的Connector节点,修改protocol为

    <Connector port="80" protocol="org.apache.coyote.http11.Http11NioProtocol" 
    	connectionTimeout="20000" 
    	URIEncoding="UTF-8" 
    	useBodyEncodingForURI="true" 
    	enableLookups="false" 
    	redirectPort="8443" />

    启动后,就可以生效。

    3)apr

    安装起来最困难,但是从操作系统级别来解决异步的IO问题,大幅度的提高性能.

    必须要安装apr和native,直接启动就支持apr。下面的修改纯属多余,仅供大家扩充知识,但仍然需要安装apr和native

    如nio修改模式,修改protocol为org.apache.coyote.http11.Http11AprProtocol

     

    Tomcat 6.X实现了JCP的Servlet 2.5和JSP2.1的规范,并且包括其它很多有用的功能,使它成为开发
    和部署web应用和web服务的坚实平台。
           NIO (No-blocking I/O)从JDK 1.4起,NIO API作为一个基于缓冲区,并能提供非阻塞I/O操作的API
    被引入。 


           作为开源web服务器的java实现,tomcat几乎就是web开发者开发、测试的首选,有很多其他商业服务
    器的开发者也会优先选择tomcat作为开发时候使用,而在部署的时候,把应用发布在商业服务器上。也有
    许多商业应用部署在tomcat上,tomcat承载着其核心的应用。但是很多开发者很迷惑,为什么在自己的应
    用里使用tomcat作为平台的时候,而并发用户超过一定数量,服务器就变的非常繁忙,而且很快就出现了
    connection refuse的错误。但是很多商业应用部署在tomcat上运行却安然无恙。

          其中有个很大的原因就是,配置良好的tomcat都会使用APR(Apache Portable Runtime),APR是
    Apache HTTP Server2.x的核心,它是高度可移植的本地库,它使用高性能的UXIN I/O操作,低性能的
    java io操作,但是APR对很多Java开发者而言可能稍稍有点难度,在很多OS平台上,你可能需要重新编
    译APR。但是从Tomcat6.0以后, Java开发者很容易就可以是用NIO的技术来提升tomcat的并发处理能力。
    但是为什么NIO可以提升tomcat的并发处理能力呢,我们先来看一下java 传统io与 java NIO的差别。
         
    Java 传统的IO操作都是阻塞式的(blocking I/O), 如果有socket的编程基础,你会接触过堵塞socket和
    非堵塞socket,堵塞socket就是在accept、read、write等IO操作的的时候,如果没有可用符合条件的资
    源,不马上返回,一直等待直到有资源为止。而非堵塞socket则是在执行select的时候,当没有资源的时
    候堵塞,当有符合资源的时候,返回一个信号,然后程序就可以执行accept、read、write等操作,一般来
    说,如果使用堵塞socket,通常我们通常开一个线程accept socket,当读完这次socket请求的时候,开一
    个单独的线程处理这个socket请求;如果使用非堵塞socket,通常是只有一个线程,一开始是select状,
    当有信号的时候可以通过 可以通过多路复用(Multiplexing)技术传递给一个指定的线程池来处理请求,然
    后原来的线程继续select状态。 最简单的多路复用技术可以通过java管道(Pipe)来实现。换句话说,如果
    客户端的并发请求很大的时候,我们可以使用少于客户端并发请求的线程数来处理这些请求,而这些来不
    及立即处理的请求会被阻塞在java管道或者队列里面,等待线程池的处理。请求 听起来很复杂,在这个架
    构当道的java 世界里,现在已经有很多优秀的NIO的架构方便开发者使用,比如Grizzly,Apache Mina等
    等,如果你对如何编写高性能的网络服务器有兴趣,你可以研读这些源代码。

          简单说一下,在web服务器上阻塞IO(BIO)与NIO一个比较重要的不同是,我们使用BIO的时候往往会
    为每一个web请求引入多线程,每个web请求一个单独的线程,所以并发量一旦上去了,线程数就上去
    了,CPU就忙着线程切换,所以BIO不合适高吞吐量、高可伸缩的web服务器;而NIO则是使用单线程(单
    个CPU)或者只使用少量的多线程(多CPU)来接受Socket,而由线程池来处理堵塞在pipe或者队列里的请
    求.这样的话,只要OS可以接受TCP的连接,web服务器就可以处理该请求。大大提高了web服务器的可
    伸缩性。

        我们来看一下配置,你只需要在server.xml里把 HTTP Connector做如下更改,

        <Connector port="8080" protocol="HTTP/1.1" 
                   connectionTimeout="20000" 
                   redirectPort="8443" />
        改为
        <Connector port="8080" protocol="org.apache.coyote.http11.Http11NioProtocol" 
                   connectionTimeout="20000" 
                   redirectPort="8443" />

    然后启动服务器,你会看到org.apache.coyote.http11.Http11NioProtocol start的信息,表示NIO已经启动。其他的配置请参考官方配置文档。

    Enjoy it.

    最后贴上官方文档上对tomcat的三种Connector的方式做一个简单比较,
        

    Java Blocking Connector       Java Nio Blocking Connector       APR Connector
    
    Classname         Http11Protocol                  Http11NioProtocol         Http11AprProtocol
    
    Tomcat Version   3.x 4.x 5.x 6.x                       6.x                     5.5.x 6.x
    
    Support Polling         NO                             YES                        YES
    
    Polling Size           N/A                   Unlimited - Restricted by mem        Unlimited
    
    Read HTTP Request     Blocking                     Blocking                       Blocking
    
    Read HTTP Body        Blocking                     Blocking                       Blocking
    
    Write HTTP Response   Blocking                     Blocking                       Blocking
    
    SSL Support           Java SSL                     Java SSL                       OpenSSL
    
    SSL Handshake         Blocking                     Non blocking                   Blocking
    
    Max Connections       maxThreads                   See polling size               See polling size
     
     
    如果读者有socket的编程基础,应该会接触过堵塞socket和非堵塞socket,堵塞socket就是在accept、read、write等IO操作的的时候,如果没有可用符合条件的资源,不马上返回,一直等待直到有资源为止。而非堵塞socket则是在执行select的时候,当没有资源的时候堵塞,当有符合资源的时候,返回一个信号,然后程序就可以执行accept、read、write等操作,这个时候,这些操作是马上完成,并且马上返回。而windows的winsock则有所不同,可以绑定到一个EventHandle里,也可以绑定到一个HWND里,当有资源到达时,发出事件,这时执行的io操作也是马上完成、马上返回的。一般来说,如果使用堵塞socket,通常我们时开一个线程accept socket,当有socket链接的时候,开一个单独的线程处理这个socket;如果使用非堵塞socket,通常是只有一个线程,一开始是select状态,当有信号的时候马上处理,然后继续select状态。 
       
      按照大多数人的说法,堵塞socket比非堵塞socket的性能要好。不过也有小部分人并不是这样认为的,例如Indy项目(Delphi一个比较出色的网络包),它就是使用多线程+堵塞socket模式的。另外,堵塞socket比非堵塞socket容易理解,符合一般人的思维,编程相对比较容易。 
       
      nio其实也是类似上面的情况。在JDK1.4,sun公司大范围提升Java的性能,其中NIO就是其中一项。Java的IO操作集中在java.io这个包中,是基于流的阻塞API(即BIO,Block IO)。对于大多数应用来说,这样的API使用很方便,然而,一些对性能要求较高的应用,尤其是服务端应用,往往需要一个更为有效的方式来处理IO。从JDK 1.4起,NIO API作为一个基于缓冲区,并能提供非阻塞O操作的API(即NIO,non-blocking IO)被引入。 
       
      BIO与NIO一个比较重要的不同,是我们使用BIO的时候往往会引入多线程,每个连接一个单独的线程;而NIO则是使用单线程或者只使用少量的多线程,每个连接共用一个线程。  
       
      这个时候,问题就出来了:我们非常多的java应用是使用ThreadLocal的,例如JSF的FaceContext、Hibernate的session管理、Struts2的Context的管理等等,几乎所有框架都或多或少地应用ThreadLocal。如果存在冲突,那岂不惊天动地? 
       
      后来终于在Tomcat6的文档(http://tomcat.apache.org/tomcat-6.0-doc/aio.html)找到答案。根据上面说明,应该Tomcat6应用nio只是用在处理发送、接收信息的时候用到,也就是说,tomcat6还是传统的多线程Servlet,我画了下面两个图来列出区别:      tomcat5:客户端连接到达 -> 传统的SeverSocket.accept接收连接 -> 从线程池取出一个线程 -> 在该线程读取文本并且解析HTTP协议 -> 在该线程生成ServletRequest、ServletResponse,取出请求的Servlet -> 在该线程执行这个Servlet -> 在该线程把ServletResponse的内容发送到客户端连接 -> 关闭连接。      我以前理解的使用nio后的tomcat6:客户端连接到达 -> nio接收连接 -> nio使用轮询方式读取文本并且解析HTTP协议(单线程) -> 生成ServletRequest、ServletResponse,取出请求的Servlet -> 直接在本线程执行这个Servlet -> 把ServletResponse的内容发送到客户端连接 -> 关闭连接。      实际的tomcat6:客户端连接到达 -> nio接收连接 -> nio使用轮询方式读取文本并且解析HTTP协议(单线程) -> 生成ServletRequest、ServletResponse,取出请求的Servlet -> 从线程池取出线程,并在该线程执行这个Servlet -> 把ServletResponse的内容发送到客户端连接 -> 关闭连接。           从上图可以看出,BIO与NIO的不同,也导致进入客户端处理线程的时刻有所不同:tomcat5在接受连接后马上进入客户端线程,在客户端线程里解析HTTP协议,而tomcat6则是解析完HTTP协议后才进入多线程,另外,tomcat6也比5早脱离客户端线程的环境。      实际的tomcat6与我之前猜想的差别主要集中在如何处理servlet的问题上。实际上即使抛开ThreadLocal的问题,我之前理解tomcat6只使用一个线程处理的想法其实是行不同的。大家都有经验:servlet是基于BIO的,执行期间会存在堵塞的,例如读取文件、数据库操作等等。tomcat6使用了nio,但不可能要求servlet里面要使用nio,而一旦存在堵塞,效率自然会锐降。          所以,最终的结论当然是tomcat6的servlet里面,ThreadLocal照样可以使用,不存在冲突。 
    展开全文
  • tomcat NIO

    2019-04-19 11:13:09
    TomcatNIO是基于I/O复用来实现的。对这点一定要清楚,不然我们的讨论就不在一个逻辑线上。I/O模型一共有阻塞式I/O,非阻塞式I/O,I/O复用(select/poll/epoll),信号驱动式I/O和异步I/O。首先看下IO的五种模型。...

           Tomcat的NIO是基于I/O复用来实现的。对这点一定要清楚,不然我们的讨论就不在一个逻辑线上。I/O模型一共有阻塞式I/O,非阻塞式I/O,I/O复用(select/poll/epoll),信号驱动式I/O和异步I/O。首先看下IO的五种模型。下面是摘录前辈的文章 ,分析的不错,仅做收藏

          

    1.1 五种I/O模型  

    1)阻塞I/O

    2)非阻塞I/O

    3)I/O复用

    4)事件(信号)驱动I/O

    5)异步I/O

     

    1.2 为什么要发起系统调用? 

    因为进程想要获取磁盘中的数据,而能和磁盘打交道的只能是内核, 进程通知内核,说要磁盘中的数据

    此过程就是系统调用 

     

    1.3 一次I/O完成的步骤

    当进程发起系统调用时候,这个系统调用就进入内核模式, 然后开始I/O操作

    I/O操作分为俩个步骤: 

             1) 磁盘把数据装载进内核的内存空间

             2) 内核的内存空间的数据copy到用户的内存空间中(此过程才是真正I/O发生的地方) 

    注意: io调用大多数都是阻塞的

     过程分析

           整个过程:此进程需要对磁盘中的数据进行操作,则会向内核发起一个系统调用,然后此进程,将会被切换出去,

    此进程会被挂起或者进入睡眠状态,也叫不可中 断的睡眠,因为数据还没有得到,只有等到系统调用的结果完成后,

    则进程会被唤醒,继续接下来的操作,从系统调用的开始到系统调用结束经过的步骤:

    ①进程向内核发起一个系统调用,

    ②内核接收到系统调用,知道是对文件的请求,于是告诉磁盘,把文件读取出来

    ③磁盘接收到来着内核的命令后,把文件载入到内核的内存空间里面

    ④内核的内存空间接收到数据之后,把数据copy到用户进程的内存空间(此过程是I/O发生的地方)

    ⑤进程内存空间得到数据后,给内核发送通知

    ⑥内核把接收到的通知回复给进程,此过程为唤醒进程,然后进程得到数据,进行下一步操作

    2.1 阻塞

        是指调用结果返回之前,当前线程会被挂起(线程进入睡眠状态) 函数只有在得到结果之后,才会返回,才能继续执行

    阻塞I/O系统怎么通知进程? 

    I/O 完成后, 系统直接通知进程, 则进程被唤醒   

    第一阶段是指磁盘把数据装载到内核的内存中空间中

    第二阶段是指内核的内存空间的数据copy到用户的内存空间 (这个才是真实I/O操作)

     

    2.2 非阻塞

      非阻塞:进程发起I/O调用,I/O自己知道需过一段时间完成,就立即通知进程进行别的操作,则为非阻塞I/O

    非阻塞I/O,系统怎么通知进程?

    每隔一段时间,问内核数据是否准备完成,系统完成后,则进程获取数据,继续执行(此过程也称盲等待)

    缺点: 无法处理多个I/O,比如用户打开文件,ctrl+C想终止这个操作,是无法停掉的

     

                第一阶段是指磁盘把数据装载到内核的内存中空间中

     

     

                第二阶段是指内核的内存空间的数据copy到用户的内存空间 (这个才是真实I/O操作)

     

    2.3 I/O多路复用 select

    为什么要用I/O多路复用

      某个进程阻塞多个io上 ,一个进程即要等待从键盘输入信息, 另一个准备从硬盘装入信息

    比如通过read这样的命令, 调用了来个io操作,一个io完成了,一个io没有完成, 阻塞着键盘io,磁盘io完成了 ,

    这个进程也是不能响应, 因为键盘io还没有完成,还在阻塞着 , 这个进程还在睡眠状态 ,这个时候怎么办 ?

    由此需要I/O多路复用。

    执行过程

           以后进程在调用io的时候, 不是直接调用io的功能,在系统内核中, 新增了一个系统调用, 帮助进程监控多个io,

    一旦一个进程需要系统调用的时候, 向内核的一个特殊的系统调用,发起申请时,这个进程会被阻塞在这个复用器的调用上,

    所以复用这个功能会监控这些io操作,任何一个io完成了,它都会告诉进程,其中某个io完成,如果进程依赖某个io操作,

    那么这个时候,进程就可以继续后面的操作. 能够帮组进程监控这些io的工具叫做io复用器

    Linux中  I/O 复用器

    select: 就是一种实现,进程需要调用的时候,把请求发送给select ,可以发起多个,但是最多只能支持1024个,先天性的限制  

    poll: 没有限制,但是多余1024个性能会下降

    所以早期的apache 本身prefork mpm模型,主进程在接受多个用户请求的时候,在线请求数超过1024个,就不工作了.

    那么io复用会比前俩种好吗?

      本来进程和系统内核直接沟通的 ,在中间加一个i/o复用select, 如果是传话,找人传话,那么这个传话最后会是什么样的呢?

    虽然解决了多个系统调用的问题,多路io复用本身的后半段依然是阻塞的,阻塞在select 上, 而不是阻塞在系统调用上,

    但是他第二段仍然是阻塞的,由于要扫描所有多个io操作, 多了一个处理机制,性能未必上升, 性能上也许不会有太大的改观

     

            
                        第一阶段是指磁盘把数据装载到内核的内存中空间中

                                            第二阶段是指内核的内存空间的数据copy到用户的内存空间 (这个才是真实I/O操作) 

    2.4 事件驱动 

    进程发起调用,通过回调函数, 内核会记住是那个进程申请的,一旦第一段完成了,就可以向这个进程发起通知,

    这样第一段就是非阻塞的,进程不需要盲等了, 但是第二段依然是阻塞的

     

    事件驱动机制(event-driven)

    正是由于事件驱动机制 ,才能同时相应多个请求的

    比如: 一个web服务器. 一个进程响应多个用户请求

    缺陷: 第二段仍然是阻塞的

    俩种机制

    如果一个事件通知一个进程,进程正在忙, 进程没有听见, 这个怎么办?

    水平触发机制: 内核通知进程来读取数据,进程没来读取数据,内核需要一次一次的通知进程

    边缘触发机制: 内核只通知一次让进程来取数据,进程在超时时间内,随时可以来取数据, 把这个事件信息状态发给进程,好比发个短息给进程,

    nginx

         nginx默认采用了边缘触发驱动机制 

     

        

     

    第一阶段是指磁盘把数据装载到内核的内存中空间中

    第二阶段是指内核的内存空间的数据copy到用户的内存空间 (这个才是真实I/O操作)

     

     

    2.5 异步AIO

    无论第一第二段, 不再向系统调用提出任何反馈, 只有数据完全复制到服务进程内存中后, 才向服务进程返回ok的信息,其它时间,

    进程可以随意做自己的事情,直到内核通知ok信息

    注意: 只在文件中可以实现AIO, 网络异步IO 不可能实现

    nginx:

    nginxfile IO 文件异步请求的
    一个进程响应N个请求
    静态文件界别: 支持sendfile   
    避免浪费复制时间:  mmap 支持内存映射,内核内存复制到进程内存这个过程, 不需要复制了, 直接映射到进程内存中
    支持边缘触发
    支持异步io
    解决了c10k的问题
    c10k : 有一万个同时的并发连接
    c100k: 你懂得

     

     

    第一阶段是指磁盘把数据装载到内核的内存中空间中

    第二阶段是指内核的内存空间的数据copy到用户的内存空间 (这个才是真实I/O操作)

     

    前四种I/O模型属于同步操作,最后一个AIO则属于异步操作 

    2.6 五种模型比较

     

    同步阻塞

    俩段都是阻塞的,所有数据准备完成后,才响应

    同步非阻塞

    磁盘从磁盘复制到内核内存中的时候, 不停询问内核数据是否准备完成. 盲等

    性能有可能更差 ,看上去他可以做别的事情了, 但是其实他在不停的循环. 

    但还是有一定的灵活性的

    缺点: 无法处理多个I/O,比如用户打开文件,ctrl+C想终止这个操作,是无法停掉的

    同步IO

    如果第二段是阻塞的 ,代表是同步的

    第一种,第二种,io复用,事件驱动,都是同步的.  

    异步IO

    内核后台自己处理 ,把大量时间拿来处理用户请求。

    下面看下tomcat NIO的请求流程。

     

         这里先来说下用户态和内核态,直白来讲,如果线程执行的是用户代码,当前线程处在用户态,如果线程执行的是内核里面的代码,当前线程处在内核态。更深层来讲,操作系统为代码所处的特权级别分了4个级别。不过现代操作系统只用到了0和3两个级别。0和3的切换就是用户态和内核态的切换。更详细的可参照《深入理解计算机操作系统》。I/O复用模型,是同步非阻塞,这里的非阻塞是指I/O读写,对应的是recvfrom操作,因为数据报文已经准备好,无需阻塞。说它是同步,是因为,这个执行是在一个线程里面执行的。有时候,还会说它又是阻塞的,实际上是指阻塞在select上面,必须等到读就绪、写就绪等网络事件。有时候我们又说I/O复用是多路复用,这里的多路是指N个连接,每一个连接对应一个channel,或者说多路就是多个channel。复用,是指多个连接复用了一个线程或者少量线程(在Tomcat中是Math.min(2,Runtime.getRuntime().availableProcessors()))。

     

    上面提到的网络事件有连接就绪,接收就绪,读就绪,写就绪四个网络事件。I/O复用主要是通过Selector复用器来实现的,可以结合下面这个图理解上面的叙述。

    Selector图解.png

    二、TOMCAT对IO模型的支持

    tomcat支持IO类型图.png

    tomcat从6以后开始支持NIO模型,实现是基于JDK的java.nio包。这里可以看到对read body 和response body是Blocking的。关于这点在第6.3节源代码阅读有重点介绍。

    三、TOMCAT中NIO的配置与使用

    在Connector节点配置protocol="org.apache.coyote.http11.Http11NioProtocol",Http11NioProtocol协议下默认最大连接数是10000,也可以重新修改maxConnections的值,同时我们可以设置最大线程数maxThreads,这里设置的最大线程数就是Excutor的线程池的大小。在BIO模式下实际上是没有maxConnections,即使配置也不会生效,BIO模式下的maxConnections是保持跟maxThreads大小一致,因为它是一请求一线程模式。

    四、NioEndpoint组件关系图解读

    tomcatnio组成.png

    我们要理解tomcat的nio最主要就是对NioEndpoint的理解。它一共包含LimitLatch、Acceptor、Poller、SocketProcessor、Excutor5个部分。LimitLatch是连接控制器,它负责维护连接数的计算,nio模式下默认是10000,达到这个阈值后,就会拒绝连接请求。Acceptor负责接收连接,默认是1个线程来执行,将请求的事件注册到事件列表。有Poller来负责轮询,Poller线程数量是cpu的核数Math.min(2,Runtime.getRuntime().availableProcessors())。由Poller将就绪的事件生成SocketProcessor同时交给Excutor去执行。Excutor线程池的大小就是我们在Connector节点配置的maxThreads的值。在Excutor的线程中,会完成从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。详细源代码可以参照 第6.3节。

    五、NioEndpoint执行序列图

    tomcatnio序列图.png

    在下一小节NioEndpoint源码解读中我们将对步骤1-步骤11依次找到对应的代码来说明。

    六、NioEndpoint源码解读

    6.1、初始化

    无论是BIO还是NIO,开始都会初始化连接限制,不可能无限增大,NIO模式下默认是10000。

    public void startInternal() throws Exception {
    
            if (!running) {
                //省略代码...
                initializeConnectionLatch();
                //省略代码...
            }
        }
    protected LimitLatch initializeConnectionLatch() {
            if (maxConnections==-1) return null;
            if (connectionLimitLatch==null) {
                connectionLimitLatch = new LimitLatch(getMaxConnections());
            }
            return connectionLimitLatch;
        }
    

    6.2、步骤解读

    下面我们着重叙述跟NIO相关的流程,共分为11个步骤,分别对应上面序列图中的步骤。
    步骤1:绑定IP地址及端口,将ServerSocketChannel设置为阻塞。
    这里为什么要设置成阻塞呢,我们一直都在说非阻塞。Tomcat的设计初衷主要是为了操作方便。这样这里就跟BIO模式下一样了。只不过在BIO下这里返回的是Socket,NIO下这里返回的是SocketChannel。

    public void bind() throws Exception {
    
            //省略代码...
            serverSock.socket().bind(addr,getBacklog());
            serverSock.configureBlocking(true); 
            //省略代码...
            selectorPool.open();
        }
    

    步骤2:启动接收线程

    public void startInternal() throws Exception {
    
            if (!running) {
                //省略代码...
                startAcceptorThreads();
            }
        }
    
    //这个方法实际是在它的超类AbstractEndpoint里面    
    protected final void startAcceptorThreads() {
            int count = getAcceptorThreadCount();
            acceptors = new Acceptor[count];
    
            for (int i = 0; i < count; i++) {
                acceptors[i] = createAcceptor();
                Thread t = new Thread(acceptors[i], getName() + "-Acceptor-" + i);
                t.setPriority(getAcceptorThreadPriority());
                t.setDaemon(getDaemon());
                t.start();
            }
        }   
    

    步骤3:ServerSocketChannel.accept()接收新连接

    protected class Acceptor extends AbstractEndpoint.Acceptor {
    
            @Override
            public void run() {
                while (running) {
                    
                    try {
                        //省略代码...
                        SocketChannel socket = null;
                        try {                        
                            socket = serverSock.accept();//接收新连接
                        } catch (IOException ioe) {
                            //省略代码...
                            throw ioe;
                        }
                        //省略代码...
                        if (running && !paused) {
                            if (!setSocketOptions(socket)) {
                                //省略代码...
                            }
                        } else {
                            //省略代码...
                        }
                    } catch (SocketTimeoutException sx) {
                        
                    } catch (IOException x) {
                        //省略代码...
                    } catch (OutOfMemoryError oom) {
                        //省略代码...
                    } catch (Throwable t) {
                        //省略代码...
                    }
                }
               
            }
        }
    

    步骤4:将接收到的链接通道设置为非阻塞
    步骤5:构造NioChannel对象
    步骤6:register注册到轮询线程

    protected boolean setSocketOptions(SocketChannel socket) {
            
            try {
                
                socket.configureBlocking(false);//将连接通道设置为非阻塞
                Socket sock = socket.socket();
                socketProperties.setProperties(sock);
    
                NioChannel channel = nioChannels.poll();//构造NioChannel对象
                //省略代码...
                getPoller0().register(channel);//register注册到轮询线程
            } catch (Throwable t) {
               //省略代码...
            }
            //省略代码...
        }
    

    步骤7:构造PollerEvent,并添加到事件队列

    protected ConcurrentLinkedQueue<Runnable> events = new ConcurrentLinkedQueue<Runnable>();
    public void register(final NioChannel socket)
            {
                //省略代码...
                PollerEvent r = eventCache.poll();
                //省略代码...
                addEvent(r);
            }
    

    步骤8:启动轮询线程

    public void startInternal() throws Exception {
    
            if (!running) {
                //省略代码...
                // Start poller threads
                pollers = new Poller[getPollerThreadCount()];
                for (int i=0; i<pollers.length; i++) {
                    pollers[i] = new Poller();
                    Thread pollerThread = new Thread(pollers[i], getName() + "-ClientPoller-"+i);
                    pollerThread.setPriority(threadPriority);
                    pollerThread.setDaemon(true);
                    pollerThread.start();
                }
                //省略代码...
            }
        }
    

    步骤9:取出队列中新增的PollerEvent并注册到Selector

    public static class PollerEvent implements Runnable {
    
            //省略代码...
    
            @Override
            public void run() {
                if ( interestOps == OP_REGISTER ) {
                    try {
                        socket.getIOChannel().register(socket.getPoller().getSelector(), SelectionKey.OP_READ, key);
                    } catch (Exception x) {
                        log.error("", x);
                    }
                } else {
                    //省略代码...
                }//end if
            }//run
            //省略代码...
        }
    

    步骤10:Selector.select()

    public void run() {
                // Loop until destroy() is called
                while (true) {
                    try {
                        //省略代码...
                        try {
                            if ( !close ) {
                                if (wakeupCounter.getAndSet(-1) > 0) {
                                    keyCount = selector.selectNow();
                                } else {
                                    keyCount = selector.select(selectorTimeout);
                                }
                                //省略代码...
                            }
                            //省略代码...
                        } catch ( NullPointerException x ) {
                            //省略代码...
                        } catch ( CancelledKeyException x ) {
                            //省略代码...
                        } catch (Throwable x) {
                            //省略代码...
                        }
                        //省略代码...
    
                        Iterator<SelectionKey> iterator =
                            keyCount > 0 ? selector.selectedKeys().iterator() : null;
                        
                        while (iterator != null && iterator.hasNext()) {
                            SelectionKey sk = iterator.next();
                            KeyAttachment attachment = (KeyAttachment)sk.attachment();
                            
                            if (attachment == null) {
                                iterator.remove();
                            } else {
                                attachment.access();
                                iterator.remove();
                                processKey(sk, attachment);//此方法跟下去就是把SocketProcessor交给Excutor去执行
                            }
                        }//while
    
                        //省略代码...
                    } catch (OutOfMemoryError oom) {
                        //省略代码...
                    }
                }//while
                //省略代码...
            }
    

    步骤11:根据选择的SelectionKey构造SocketProcessor提交到请求处理线程

    public boolean processSocket(NioChannel socket, SocketStatus status, boolean dispatch) {
            try {
                //省略代码...
                SocketProcessor sc = processorCache.poll();
                if ( sc == null ) sc = new SocketProcessor(socket,status);
                else sc.reset(socket,status);
                if ( dispatch && getExecutor()!=null ) getExecutor().execute(sc);
                else sc.run();
            } catch (RejectedExecutionException rx) {
                //省略代码...
            } catch (Throwable t) {
                //省略代码...
            }
            //省略代码...
        }
    

    6.3、NioBlockingSelector和BlockPoller介绍

    上面的序列图有个地方我没有描述,就是NioSelectorPool这个内部类,是因为在整体理解tomcat的nio上面在序列图里面不包括它更好理解。在有了上面的基础后,我们在来说下NioSelectorPool这个类,对更深层了解Tomcat的NIO一定要知道它的作用。NioEndpoint对象中维护了一个NioSelecPool对象,这个NioSelectorPool中又维护了一个BlockPoller线程,这个线程就是基于辅Selector进行NIO的逻辑。以执行servlet后,得到response,往socket中写数据为例,最终写的过程调用NioBlockingSelector的write方法。代码如下:

    public int write(ByteBuffer buf, NioChannel socket, long writeTimeout,MutableInteger lastWrite) throws IOException {  
            SelectionKey key = socket.getIOChannel().keyFor(socket.getPoller().getSelector());  
            if ( key == null ) throw new IOException("Key no longer registered");  
            KeyAttachment att = (KeyAttachment) key.attachment();  
            int written = 0;  
            boolean timedout = false;  
            int keycount = 1; //assume we can write  
            long time = System.currentTimeMillis(); //start the timeout timer  
            try {  
                while ( (!timedout) && buf.hasRemaining()) {  
                    if (keycount > 0) { //only write if we were registered for a write  
                        //直接往socket中写数据  
                        int cnt = socket.write(buf); //write the data  
                        lastWrite.set(cnt);  
                        if (cnt == -1)  
                            throw new EOFException();  
                        written += cnt;  
                        //写数据成功,直接进入下一次循环,继续写  
                        if (cnt > 0) {  
                            time = System.currentTimeMillis(); //reset our timeout timer  
                            continue; //we successfully wrote, try again without a selector  
                        }  
                    }  
                    //如果写数据返回值cnt等于0,通常是网络不稳定造成的写数据失败  
                    try {  
                        //开始一个倒数计数器   
                        if ( att.getWriteLatch()==null || att.getWriteLatch().getCount()==0) att.startWriteLatch(1);  
                        //将socket注册到辅Selector,这里poller就是BlockSelector线程  
                        poller.add(att,SelectionKey.OP_WRITE);  
                        //阻塞,直至超时时间唤醒,或者在还没有达到超时时间,在BlockSelector中唤醒  
                        att.awaitWriteLatch(writeTimeout,TimeUnit.MILLISECONDS);  
                    }catch (InterruptedException ignore) {  
                        Thread.interrupted();  
                    }  
                    if ( att.getWriteLatch()!=null && att.getWriteLatch().getCount()> 0) {  
                        keycount = 0;  
                    }else {  
                        //还没超时就唤醒,说明网络状态恢复,继续下一次循环,完成写socket  
                        keycount = 1;  
                        att.resetWriteLatch();  
                    }  
      
                    if (writeTimeout > 0 && (keycount == 0))  
                        timedout = (System.currentTimeMillis() - time) >= writeTimeout;  
                } //while  
                if (timedout)   
                    throw new SocketTimeoutException();  
            } finally {  
                poller.remove(att,SelectionKey.OP_WRITE);  
                if (timedout && key != null) {  
                    poller.cancelKey(socket, key);  
                }  
            }  
            return written;  
        }
    

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

    public void run() {  
                while (run) {  
                    try {  
                        ......  
      
                        Iterator iterator = keyCount > 0 ? selector.selectedKeys().iterator() : null;  
                        while (run && iterator != null && iterator.hasNext()) {  
                            SelectionKey sk = (SelectionKey) iterator.next();  
                            KeyAttachment attachment = (KeyAttachment)sk.attachment();  
                            try {  
                                attachment.access();  
                                iterator.remove(); ;  
                                sk.interestOps(sk.interestOps() & (~sk.readyOps()));  
                                if ( sk.isReadable() ) {  
                                    countDown(attachment.getReadLatch());  
                                }  
                                //发现socket可写状态恢复,将倒数计数器置位,通知Worker线程继续  
                                if (sk.isWritable()) {  
                                    countDown(attachment.getWriteLatch());  
                                }  
                            }catch (CancelledKeyException ckx) {  
                                if (sk!=null) sk.cancel();  
                                countDown(attachment.getReadLatch());  
                                countDown(attachment.getWriteLatch());  
                            }  
                        }//while  
                    }catch ( Throwable t ) {  
                        log.error("",t);  
                    }  
                }  
                events.clear();  
                try {  
                    selector.selectNow();//cancel all remaining keys  
                }catch( Exception ignore ) {  
                    if (log.isDebugEnabled())log.debug("",ignore);  
                }  
            } 
    
    

    使用这个辅Selector主要是减少线程间的切换,同时还可减轻主Selector的负担。

    七、关于性能

    下面这份报告是我们压测的一个结果,跟想象的是不是不太一样?几乎没有差别,实际上NIO优化的是I/O的读写,如果瓶颈不在这里的话,比如传输字节数很小的情况下,BIO和NIO实际上是没有差别的。NIO的优势更在于用少量的线程hold住大量的连接。还有一点,我们在压测的过程中,遇到在NIO模式下刚开始的一小段时间内容,会有错误,这是因为一般的压测工具是基于一种长连接,也就是说比如模拟1000并发,那么同时建立1000个连接,下一时刻再发送请求就是基于先前的这1000个连接来发送,还有TOMCAT的NIO处理是有POLLER线程来接管的,它的线程数一般等于CPU的核数,如果一瞬间有大量并发过来,POLLER也会顿时处理不过来。

    压测1.jpeg

    压测2.jpeg

    八、总结

    NIO只是优化了网络IO的读写,如果系统的瓶颈不在这里,比如每次读取的字节说都是500b,那么BIO和NIO在性能上没有区别。NIO模式是最大化压榨CPU,把时间片都更好利用起来。对于操作系统来说,线程之间上下文切换的开销很大,而且每个线程都要占用系统的一些资源如内存,有关线程资源可参照这篇文章《一台java服务器可以跑多少个线程》。因此,使用的线程越少越好。而I/O复用模型正是利用少量的线程来管理大量的连接。在对于维护大量长连接的应用里面更适合用基于I/O复用模型NIO,比如web qq这样的应用。所以我们要清楚系统的瓶颈是I/O还是CPU的计算



    转载:https://www.jianshu.com/p/76ff17bc6dea

    展开全文
  • Tomcat架构解析之NIO和NIO2

    千次阅读 2019-08-02 15:38:22
    为了提高I/O性能,JDK与1.4版本引入NIo,他弥补了原来BIO方式的不足,在标准的Java代码中提供了高速、面向块的I/O。通过定义包含数据的类以及块的形式处理数据,NIO可以再不编写表弟代码的气哭下利用底层优化,这是...

    一、前言

        传统的BIO方式是基于流行进行读写的,而且是阻塞的,整体性能比较差。为了提高I/O性能,JDK与1.4版本引入NIo,他弥补了原来BIO方式的不足,在标准的Java代码中提供了高速、面向块的I/O。通过定义包含数据的类以及块的形式处理数据,NIO可以再不编写表弟代码的气哭下利用底层优化,这是BIO无法做到的。

    二、NIO

        与BIO相比,NIO有如下几个新的概念:
        1.通道
        通道(Channel)是对BIO中流的模拟,到任何目的地(或者来自任何地方)的所有数据都必须通过一个通道对象。

        通道与流的不同之处在于通道是双向的。流只是在一个方向上移动(一个流要么用于读,要么用于写),而通道可以用于读、写或者同事用于读写。因为通道是双向的,所以他可以比流更好的反应底层操作系统的真实情况(特别是在UNIX模型中底层操作系统通道同样是双向的情况下)。

        2.缓冲区
        尽管通道用于读写数据,但是我们却并吧直接操作通道进行读写,而是通过缓冲区(Buffer)完成。缓冲区实质上是一个容器对象。发送给通道的所有对象都必须先放到缓冲区中,同样从通道中读取的任何数据都要先读到缓冲区中。

        缓冲区体现了NIO与BIO的一个重要区别。在BIO中,读写可以直接操作流对象。简单讲,缓冲区通常是一个字节数组,也可以使用其他类型的数组。但是缓冲区不仅仅是一个数组,他提供了对数据的结构化访问,而且还可以跟踪系统的读/写进程。

        3.选择器
        Java NIO提供了选择器组件(Selector)用于同时检测多个通道的事件以实现异步I/O。我们将感兴趣的事件注册到Selector上,当事件发生时可以通过Selector获得事件发生的通道,并进行相关的操作。

        异步I/O的一个优势在于,他允许你同时根据大量的输入、输出执行I/O操作。同步I/O一般要借助于轮询,或者创建许许多多的线程以处理大量的链接。使用异步I/O,你可以监听任意数量的通道事件,不必轮询,也不必启动额外的线程。

        由于Selector.select()方法是阻塞的,因此Tomcat采用轮询的方式进行处理,轮询线程称为Poller。每个Poller维护了一个Selector实例以及一个PollerEvent事件队列。每当接收到新的链接时,会将获得的SocketChannel对象封装为org.apache.tomcat.util.net.NioChannel,并且将其注册到Poller(创建一个PollerEvent实例,添加到事件队列)。

        Poller运行时,首先将新添加到队列中的PollerEvent取出,并将SocketChannel的读事件(OP_READ)注册到Poller持有的Selector上,然后执行Selector.select。当捕获到读事件时,构造SocketProcessor,并提交到线程池进行请求处理。

        为了提升对象的利用率,NioEndpoint分别为NioChannel和PollerEvent对象创建了缓存队列。当需要NioChannel和PollerEvent对象时,会检测缓存队列中是否存在可用对象,如果存在则从队列中取出对象并且重置,如果不存在则新建。

        NioEdnpoint的处理过程如下图所示:
    在这里插入图片描述

    • NioEndpoint中ServerSocketChannel是阻塞的。因此,仍采用多线程并发接收客户端链接。
    • NioEndpoint根据pollerThreadCount配置的数量创建Poller线程。与Acceptor相同,Poller线程也是单独启动,不会占用请求处理的线程池。默认Poller线程个数与JVM可以使用的处理器个数相关,上限为2。
    • Acceptor接收到新的链接后,将获得的SocketChannel置为非阻塞,构造NioChannel对象,按照轮转法(Round-Robin)获取Poller实例,并且将NioChannel注册到PollerEvent事件队列。
    • Poller负责为SocketChannel注册读事件。接收到读事件后,由SocketProcessor完成客户端请求处理。

        Poller在将SocketProcessor添加到请求处理线程池之前,会将接收到读事件的SocketChannel从Poller维护的Selector上取消注册,避免当前Socket多线程同时处理。而读写过程中的事件处理则是由NioSelectorPool完成的。事件变化如下图所示:
    在这里插入图片描述
        NioSelectorPool提供了一个Selector池,用于获取有效的Selector供SocketChannel读写使用。他由NioEndpoint维护,可以通过系统属性org.apache.tomcat.util.net.NioSelcetorShard配置是否在SocketChannel之间共享Selector,如果为true则所有SocketChannel均共享一个Selector实例,否则每一个SocketChannel使用不同的Selector,NioSelectorPool池维护的Selector实例数上限由属性maxSelectors确定。

        NIOSelectorPool读信息分为阻塞和非阻塞两种方式:

    • 在阻塞模式下,如果第一次读取不到数据,则会在NioSelectorPool提供的Selector对象上注册OP_READ事件,并循环调用Selector.select,超时等待OP_READ事件。如果OP_READ事件就绪,则进行数据读取。
    • 在非阻塞模式下,如果读不到数据,则直接返回。

        同样,在NioEndpoint中写详细也分为阻塞和非阻塞两种方式:

    • 在阻塞模式下,如果第一次写数据没有成功,则会在NioSelectorPool提供的Selector对象上注册OP_WRITE事件,并循环调用Selector.select()方法,超时等待OP_WRITE事件。如果OP_WRITE事件就绪,则会进行写数据操作。
    • 在非阻塞模式下,写数据之前不会监听OP_WRITE事件。如果没有成功,则直接返回。

        综上可知,Tomcat在阻塞方式下读/写时并没有监听OP_READ/OP)WRITE事件,而是当第一次操作没有成功时再进行注册。这实际上是一种乐观设计,即假设网络大多数情况下是正常的。第一次操作不成功,则表明网络存在异常,此时再对事件进行监听。

    三、NIO2

        NIO2是JDK7新增的文件及网络I/O特性,他继承自NIO,同时添加了众多特性及功能改进,其中最重要的即是对异步I/O(AIO)的支持。

        1.通道
        在AIO中,通道必须实现接口java.nio.channels.AsynchronousChannel。JDK7提供了3个通道实现类:java.nio.channels.AsynchronousFileChannel用于文件I/O,Java.nio.channels.AsyschronousServerSocketChannel和java.nio.channels.AsyschronousSocketChannel用于网络I/O。

        2.缓冲区
        AIO仍通过操作缓冲区完成数据的读写操作,此处不再描述。

        3.Future和CompletionHandler
        AIO操作存在两种操作方式:Future和CompletionHandler。

        首先,AIO使用了Java并发包的API,无论接收Socket请求还是读写操作,均可以返回一个java.util.concurrent.Future对象来表示I/O处于等待状态。通过Future的方法,我们可以检测操作是否完成(isDone)、等待完成并取得操作结果(get)等。当接收请求(accept)结束时,Future.get返回值为AsynchronousSocketChannel;读写操作时(read/write),Future.get返回值为读写操作结果。

        除了Future外,接收请求以及读写操作还支持指定一个java.nio.channels.CompletionHandler<V,A>接口(此时不再返回Future对象),当I/O操作完成时,会调用接口的completed()方法,当操作失败时,则会调用failed()方法。

        比较两种操作方式,Future方式需要我们自己检测I/O操作状态或者直接通过Future.get()方法等待I/O操作结束,而CompletionHandler方式则由JDK检测I/O状态,我们需要实现每种操作状态的处理即可。在实际应用中,我们可以只采用Future方式或者CompletionHandler方式,也可以两者混合使用。

        4.异步通道组
        AIO新引入了异步通道组(Asynchronous Channel Group)的概念,每个异步通道均属于一个指定的异步通道组,同一个通道组内的通道共享一个线程池。线程池内的线程接收指令来执行I/O事件并将结果分发到CompletionHandler。异步通道组包括线程池以及所有通道工作线程共享的资源。通道生命周期受所属通道组影响,当通道组关闭后,通道也随着关闭。

        在实际开发中,除了可以手动创建异步通道组外,JVM还维护了一个系统分为的通道组实例,作为默认通道组。如果创建通道时为指定通道组或者指定的通道组为空,那么将会使用默认通道组。

        默认通道组通过两个系统属性进行配置。首先是java.nio.channels.DefaultThreadPool.threadFactory,该属性值为具体的java.util.concurrent.ThreadFactory类,由系统类加载器加载并且实例化,用于创建默认通道组线程池的线程。其次为java.nio.channels.DefaultThreadPool.initialSize,用于指定线程池的初始化大小。

        如果默认 通道组不能满足需要,我们还可以通过AsynchronousChannelGroup的下列3个方法来创建自定义的通道组:

    • withFixedThreadPool用于创建固定大小的线程池,固定大小的线程池适合简单的场景:一个线程等待I/O事件、完成I/O事件、执行CompletionHandler(内核将事件直接分发到这些线程)。当CompletionHandler正常终止,线程将返回线程池并且等待下一个事件。但是如果CompletionHandler未能及时完成,他将会阻塞处理线程。如果所有线程均在CompletionHandler内部阻塞,整个应用将会被阻塞。此时所有新事件均会排队等待,知道有一个线程变为有效。最糟糕的场景是没有线程被释放,内核将不再执行任何操作。这个问题避免的方法是在CompletionHandler内部不采用阻塞或者长时间的操作,也可以使用一个缓存线程池或者超时时间来避免这个问题。
    • withCachedThreadPool用于创建缓存线程池。异步通道组提交时间到线程池,线程池知识简单地执行CompletionHandler的方法。此时大家会有疑问,如果线程只是简单地执行CompletionHandler的方法,那么是谁执行具体的I/O操作?答案是隐藏线程池。这是一组独立的线程用于等待I/O事件。更准确的讲,内核I/O操作由一个或者多个不可见的内部线程处理并且将事件分发到缓存线程池,缓存线程池依次执行CompletionHandler。隐藏线程池非常重要,因为他显著降低了应用程序阻塞的可能性(解决了固定大小线程池的问题),确保内核能够完成I/O操作。但是他仍存在一个问题,由于缓存线程池需要无边界的队列,这将使队列无限制的增长并最终导致outOfMemoryError。因此仍需要注意避免CompletionHanler中的阻塞以及长时间的操作。

        Tomcat中AIO的使用可以创建Nio2Endpoint。与BIO、NIO类似,Tomcat仍使用Acceptor线程池的方式接收客户端请求。在Acceptor中,采用Fy=uture方式进行请求接收。此外,Tomcat分别采用Future方式实现阻塞读写,采用CompletionHandler方式实现非阻塞读写。

    • Nio2Endpoint创建异步通道时,指定了自定义异步通道组,并且使用的是请求处理线程池。
    • Nio2Endpoint中接收请求仍采用多线程处理,以Future的方式阻塞调用。
    • 当接收到请求后,构造Nio2SocketWrapper以及SocketProcessor并且提交到请求处理线程池,最终由Http11Nio2Processor(HTTP协议)完成请求处理。
    • Nio2Endpoint通过Nio2Channel封装了AsynchronousSocketChannel和读写ByteBugger,并提供了Nio2Channel缓存以实现ByteBuffer的重复利用。当接收到客户端请求后,Nio2Endpoint先从缓存中查找可用的Nio2Channel。如果存在,则使用当前的AsynchronousSocketChannel进行重置,否则创建新的Nio2Channel实例。
    • Nio2Endpoint只有在读取请求头时采用非阻塞方式,即CompletionHandler。在读取请求体和写响应均采用阻塞方式,即为Futrue。

        Enio2Endpoint的处理过程如下图所示:
    在这里插入图片描述

    展开全文
  • tomcat使用nio2连接器

    千次阅读 2018-08-04 22:24:00
    nio2连接器需要tomcat8.0以上才有 修改conf目录下的server.xml <!-- HTTP1.1连接器 tmocat8以上默认使用NIO,7及以下默认BIO --> <Connector port="8080" protocol="HTTP/1.1" ...

    nio2连接器需要tomcat8.0以上才有
    修改conf目录下的server.xml

    <!-- HTTP1.1连接器 tmocat8以上默认使用NIO,7及以下默认BIO -->
    <Connector port="8080" protocol="HTTP/1.1"
                   connectionTimeout="20000"
                   redirectPort="8443" />
    <!-- 修改protocol属性使用NIO2 -->
    <Connector port="8080" protocol="org.apache.coyote.http11.Http11Nio2Protocol"
                   connectionTimeout="20000"
                   redirectPort="8443" />
    
    <!-- AJP1.3连接器 tmocat8以上默认使用NIO,7及以下默认BIO -->
    <Connector port="8009" protocol="AJP/1.3" redirectPort="8443" />
    <!-- 修改protocol属性使用NIO2 -->
    <Connector port="8009" protocol="org.apache.coyote.ajp.AjpNio2Protocol" redirectPort="8443" />

    若是嵌入式使用

    //NIO2 HTTP 1.1
    Connector httpConnector = new Connector("org.apache.coyote.http11.Http11Nio2Protocol");
    httpConnector.setPort(port);
    httpConnector.setURIEncoding(URIEncoding);
    httpConnector.setMaxPostSize(maxPostSize);
    httpConnector.setAttribute("maxThreads", maxThreads);
    httpConnector.setAttribute("acceptCount", acceptCount);
    httpConnector.setAttribute("disableUploadTimeout", true);
    httpConnector.setAttribute("enableLookups", false);
    tomcat.getService().addConnector(httpConnector);
    
    //NIO2 AJP 1.3
    Connector ajpConnector = new Connector("org.apache.coyote.ajp.AjpNio2Protocol");
    ajpConnector.setPort(8009);
    tomcat.getService().addConnector(ajpConnector);

    修改后正常启动应该能看到如下的终端输出/日志

    八月 04, 2018 10:25:01 下午 org.apache.coyote.AbstractProtocol start
    信息: Starting ProtocolHandler ["http-nio2-8080"]
    八月 04, 2018 10:25:01 下午 org.apache.coyote.AbstractProtocol start
    信息: Starting ProtocolHandler ["ajp-nio2-8009"]

     

    转载于:https://my.oschina.net/wearecat/blog/1922663

    展开全文
  • Tomcat nio

    2017-09-23 16:52:57
    tomcat的运行模式有3种.修改他们的运行模式.3种模式的运行是否成功,可以看他的启动控制台,或者启动日志.... ...2)nio 利用java的异步io护理技术,no blocking IO技术. 想运行在该模式下,直接
  • Tomcat NIO

    千次阅读 2013-04-08 08:57:59
    Tomcat从6.0版本开始支持NIO, 主体部分代码集中在NioEndpoint这个类当中,Tomcat NIO采用经典的反应堆模式设计,其中核心的组件包括: 1) NioEndpoint (组装器) 2) Acceptor (独立子线程) 3) Poller (独立子...
  • 图2:对比NIO和NIO2 分析BIO NIO NIO2的Read Request Headers BIO的Read Request Headers是阻塞的,是因为其没有poller这个I/O多路复用机制,而是直接拿线程池的线程去获取请求行,所以其是阻塞的,因为此时请求...
  • ((TomcatServletWebServerFactory) factory).setProtocol("org.apache.coyote.http11.Http11Nio2Protocol"); ((TomcatServletWebServerFactory) factory).addConnectorCustomizers(new TomcatConnectorCustomizer...
  • 概述注: NIO2(AIO) 即异步IONIO2 简介AIO和NIO2其实是一回事,如同孙行者、者行孙其实都是孙猴子,只是名称不同本质都一样那么如何理解这个概念呢?举个例子假设一个妹子有许多的舔狗(SpareTire),如果妹子想要完成...
  • Tomcat NIO(2)-epoll多路复用
  • Tomcat配置NIO

    2019-09-29 22:31:46
    tomcat的运行模式有3种.修改他们的运行模式.3种模式的运行是否成功,可以看他的启动控制台,或者启动日志....2)nio 利用java的异步io护理技术,no blocking IO技术. 想运行在该模式下,直接修改server...
  • Java 提供了 BIO、NIONIO.2 这些 API 来实现这些 I/O 模型。BIO 是我们最熟悉的同步阻塞,NIO 是同步非阻塞,那 NIO.2 又是什么呢?NIO 已经足够好了,为什么还要 NIO.2 呢? NIONIO.2 最大的区别是,一个...
  • 那么今天就来说说NIO2Endpoint,它跟NIOEndpoint的区别就是它实现了IO异步非阻塞。 首先先来说说什么是IO多路复用以及异步非阻塞。 IO多路复用 线程读取请求的数据分为两步。第一步就是通过select询问内核数据是否...
  • Tomcat从5.5版本开始,支持以下四种Connector的配置分别为: Java代码  1."8081" protocol="org.apache.coyote.http11.Http11NioProtocol" connectionTimeout="20000" redirectPort="8443"/>  ...
  • tomcat NIO connector fails

    2021-01-08 13:14:13
    <div><p>Change tomcat to use NIO connector instead of BIO by editing: $ vim osv/apps/tomcat/upstream/apache-tomcat-7.0.42/conf/server.xml <p></p> $ make image=tomcat $ sudo make scripts/run.py -nv...
  • 概述注: NIO2(AIO) 即异步IONIO2 简介AIO和NIO2其实是一回事,如同孙行者、者行孙其实都是孙猴子,只是名称不同本质都一样那么如何理解这个概念呢?举个例子假设一个妹子有许多的舔狗(SpareTire),如果妹子想要完成...
  • 基于官方文档,我们尝试在tomcat源码中寻找NIO/NIO2相关的类信息: 经过一番搜寻可以很明显的看到NIO相关的信息。channel,selector,pool经验告诉我们这些都不可能是业务处理的核心逻辑,剩下的就只有这个end
  • Tomcat 的 Connector 有三种运行模式 bio、nio、apr ,先了解一下这三种的区别。1、 bio(blocking I/O),顾名思义即阻塞式 I/O 操作,表示 Tomcat 使用的是传统的 Java I/O 操作(即java.io包及其子包)。...2nio...
  • protocol="org.apache.coyote.http11.Http11Nio2Protocol" connectionTimeout="60000" maxConnections="10000" redirectPort="8443" enableLookups="false" acceptCount="100" ...
  • 2nio nio(new I/O),是Java SE 1.4及后续版本提供的一种新的I/O操作方式(即java.nio包及其子包)。Java nio是一个基于缓冲区、并能提供非阻塞I/O操作的Java API,因此nio也被看成是non-blocking I/O的缩写。它拥有...
  • tomcat bio nio apr

    2017-01-09 17:44:09
    tomcat的运行模式有3种.修改他们的运行模式.3种模式的运行是否成功,可以看他的启动控制台,或者启动日志.... 1)bio ...2)nio 利用Java的异步io护理技术,no blocking IO技术. 想运行在该模式下,直接
  • 浅析tomcat nio 配置

    2015-12-17 12:38:29
     tomcat的运行模式有3种.修改他们的运行模式.3种模式的运行是否成功,可以看他的启动控制台,或者启动日志....2)nio 利用java的异步io护理技术,no blocking IO技术. 想运行在该模式下,直接
  • 1.0.x branch</li><li>cd samples/native-socketio-chat</li><li>Add src/main/webapp/META-INF/context.xml</li><li>mvn clean install</li><li>Configure tomcat with nio Connector</li><li>Deploy war file to ...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 1,099
精华内容 439
关键字:

nio2tomcat