APR连接器的思路和bio,nio的整体架构也是类似的,可以看到下面的整体框图:
第一个区别是,对于从Acceptor线程中的socket解析这块,无论是nio还是bio都是在Acceptor线程内直接阻塞执行的,对于APR通道,搞出一个SocketWithOptions的线程,专门执行这个socket解析的工作,然后直接交给Poller线程进行poll;
其次,SSL交互和socket接收等等都是调用tomcat-native的JNI的代码来完成的。
下面通过源码分析,讲讲整个框图中比较核心的部分。
0.LifecycleListener
对于LifecycleListener来说,每一个安插在Tomcat的组件,只要实现了Lifecycle和LifecycleListener都会有生命周期事件,当组件在tomcat进行启动,停止都会出发Lifecycle事件,然后调用对应的LifecycleListener方法,我们以一个例子:
a.自定义监听类,代码打成任意名的jar包放到Tomcat/lib下
package com.test.listener;
import org.apache.catalina.*;
public class MyListener implements LifecycleListener{
public void lifecycleEvent(LifecycleEvent event) {
System.out.println("组件类型:"+event.getLifecycle());
System.out.println("生命周期阶段 : "+event.getType());
}
}
b.配置Tomcat/conf/server.xml,
加上一句 <Listener className="com.test.listener.MyListener" />
c.运行Tomcat,Console上得到System.out.print输出
组件类型:StandardServer[8005]
生命周期阶段 : before_init
组件类型:StandardServer[8005]
生命周期阶段 : after_init
组件类型:StandardServer[8005]
生命周期阶段 : before_start
组件类型:StandardServer[8005]
生命周期阶段 : configure_start
组件类型:StandardServer[8005]
生命周期阶段 : start
组件类型:StandardServer[8005]
生命周期阶段 : after_start
.....
上述的这些事件,就是Tomcat中各组件的触发的事件:
before_init ,after_init 之间会触发组件的 initinternal,会做一些加载资源,库等一些准备工作;
configure_start 是配置属性设置到组件中事件;
start,after_start之间会调用组件的starttinternal,进行组件的真正的加载工作;
这个是启动的时候的相关的事件,停止,销毁也有对应的事件和上述的过程是对应的;
我们可以从日志中就可以通过这些事件来查看,tomcat中运行的各个组件究竟是哪一些组件处于什么状态;
1.AprLifecycleListener
对于APR通道来将,需要在server.xml中配置一个LifecycleListener:
这也就意味着,在APR通道启动的时候,APR组件需要加载一些内容,主要的内容就是tomcat-native需要依赖的库。
对于Listener继承了LifecycleListener接口之后,要实现其lifecycleEvent方法,这些方法就是上一小节中的几个生命周期事件。
从代码中可以分析出来,当组件init之前,首先进行init初始化加载native的库,然后基于apr和openssl是否加载,来启动SSL的配置;
在组件销毁之后,卸载掉native库,以免占用操作系统的资源。
首先看看init方法:
对于init方法,通过Library类的来加载native的资源,传递APR的一些参数进去。
第二步是initialize方法,这个initialize主要是启动openssl:
调用的方式是通过tomcat-native的SSL类,这个SSL类方法都是native的,其通过c去直接启动opensslEngine;
除此之外,可以看到对于tomcat-native主页中提到的美国的FIPS安全认证,tomcat在这块也提供了支持。
2.Http11AprProtocol
Http11AprProtocol是APR通道的http协议实现的总控,这个类和NIO,BIO通道一样,持有Endpoint和Handler:
其次,对于Apr通道的各个属性,这个Http11AprProtocol 仅仅起到了代理的作用:
其最终的属性都会设置到APREndpoint中去,然后在通道启动的时候,将这些属性分发到逻辑中。
3.AprEndpoint
APREndpoint是APR通道的主要实现类,负责几个线程池启动,socket属性处理,按照惯例我们还是看看其bind(初始化),start(启动)两个过程。
bind方法中一般是启动ServerSocket绑定,而对于APR来讲,其socket直接就是操作系统的socket,走的是JNI的流程:
基于APR的实现逻辑,首先调用APR的memory pool,然后创建APR的serversocket池,最后再通过serversocket池创建serversocket;
上述的三个调用都是系统调用,也就是都是通过JNI对不同操作系统的socket生成。
其次,bind方法中同样对SSL启动也进行调用,这里调用最终会将APR通道配置的SSL参数都配置到openssl中:
SSLContext.make实际上是也是native方法,通过openssl调用其内部的SSLContext的生成,这里的sslcontext仅仅是一个返回值,标识此次调用是否成功;
其余的操作,都是将APR的SSL属性设置到openssl中去,也是采用这种JNI调用:
4.Acceptor/Poller/SocketProcessor
bind方法是做初始化,在start启动的时候,会将APREndpoint的几个线程池启动:
从上面的start代码可以看到5类线程。
首先来看一下Acceptor线程主要的作用接收socket请求,同样也是系统调用:
但是在APR通道中,因为socket设置属性需要调用系统调用,也就是JNI的代码,
因此为了防止这块的性能的损失,单独做出一个线程对于socket的options的处理,也就是SocketOptionsProcessor:
将socket包装成AprSocketWrapper,传入到SocketOptionsPorcessor中;
SocketOptionsPorcessor线程使用的是工作线程池,这个需要注意;
SocketOptionsPorcessor线程中,将socket属性设置完,加入到addList中,传递给Poller线程进行socket;
Poller线程主要维护的是socket的read和write,Acceptor线程当socket进来以后,后续的读写都委托给poller来做了;
Poller线程中还有关于comet,socket的进一步包装处理,也有关于pollertimeout的超时控制,将这些处理完,其余的都交给SocketPorcessor;
其余的流程就是和其它通道比较类似了,这里就不再缀余了。
总结:
可以总结一下:APR通道无论是socket创建,还是SSL引擎启动等等这些都是采用JNI的代码调用底层的系统调用来完成的,因为调用系统调用次数,所以对于整个
APR调用链条中,系统调用比较多的部分,多采用一个线程来异步的做这些事,这样可以提升整体的效率。