Tomcat主要由两大核心组件,一个是connector,一个是container。connector负责的是底层的网络通信的实现,而container负责的是上层servlet业务的实现。一个应用服务器的性能很大程度上取决于网络通信模块的实现,因此connector对于tomcat而言是重中之重。源码看到Connector的时候,确实看的比较费劲,里面错综复杂的类结构和继承关系,确实头昏脑涨,下面我尝试用清晰一点的脉络去理解Connector。
Connector配置
在{源码根目录}/conf路径下的server.xml文件中,service内部可以看到Connector的配置:
尤其是HTTP/1.1协议的配置,后面在Connector初始化的过程中,我们可以看到它的作用(下面的讨论都是基于HTTP/1.1之上,不再说明)。
主要类和接口
下面类会比较常见,而且关系比较错综复杂,提前心中有数比较好,如下:
- Http11Protocol
1、继承关系:Http11Protocol - AbstractProtocol - ProtocolHandler。
2、Http11Protocol是ProtocolHandler的的最终实现,并且Connector对于请求的真正的处理是由Http11Protocol这个boss搞定的,这个boss负责有三个重要的实例,分别是JIoEndpoint、Http11ConnectionHandler和CoyoteAdapter,他们各司其职。如图: - JIoEndpoint
1、继承关系:JIoEndpoint - AbstractEndpoint。
2、在Http11Protocol的构造函数中被初始化。
3、完成端口的绑定,对请求进行监听,并将获取的socket交给Http11ConnectionHandler来process。 - Http11ConnectionHandler
1、继承关系:Http11ConnectionHandler - AbstractConnectionHandler - Handler。
2、在Http11Protocol的构造函数中被初始化,并被设置为JIoEndpoint的cHandler。
3、作为Http11Protocol的内部类,处理socket任务。 - CoyoteAdapter
1、继承关系:CoyoteAdapter - Adapter
2、在Connector的init方法中被初始化,并被设置为protocolHandler的adapter。
3、作为Connector和Container之间的桥梁,可以理解为有两个作用,一个是将Connector的请求交给Container,同时隔离了两边请求;二个是可以作为拓展。
【注】:JIoEndpoint和Http11ConnectionHandler是在Http11Protocol的构造函数中被初始化,代码如下:
JIoEndpoint设置了Handler为Http11ConnectionHandler,至于后面怎么用,会继续分析。下面分析Connector是如何初始化和启动的。
Connector初始化
Connector的构造函数
构造函数里面主要是搞定Http11Protocol这个超级Handler的实例化,Connector的通信的实际任务就是由该对象一手策划完成,代码如下:
Connector的initInternal方法
Connector的initInternal()初始化方法被调用主要是设置当前Connector的适配器并且完成Http11Protocol的初始化,如下:
【注】:这里先对CoyoteAdapter做一个简单的解释,Connector监听到底层的请求之后,
会中间经过一系列准备工作之后,创建org.apache.coyote.Request和org.apache.coyote.Response(注意Request和Response所属的范围),交给CoyoteAdapter的service(org.apache.coyote.Request req,org.apache.coyote.Response res)方法,在这个方法内部会进行适配,也可以理解为转换,变成org.apache.catalina.connector.Request和org.apache.catalina.connector.Response,然后交给Container处理,service方法的代码可以简单了解如下(部分省略):
JIoEndpoint的init方法
protocolHandler.init()方法会调用Http11Protocol祖父类(AbstractProtocol)的init方法,然后接着调用JIoEndpoint抽象父类(AbstractEndpoint)的init,最终是由IoEndpoint的band方法完成端口的绑定。我们可以看到AbstractEndpoint的init()方法,如下:
如果在eclipse中打开bind()方法会看到三个不同类的实现,毫无疑问应该是第二个实现JIoEndpoint,如下图:
在这个过程中我们可以知道设计者的心思,设计者在AbstractProtocol和AbstractEndpoint中分别将各种共用的关键的方法和变量进行了定义,这样体现了抽象类的作用。
在JIoEndpoint中bind方法代码实现如下:
Connector启动
Connector的startInternal方法
这个过程跟Connector的初始化过程前半部分相似,代码实现如下:
毫无疑问是调用protocolHandler的start方法,由Http11Protocol的祖父类AbstractProtocol的start方法,代码如下:
父类抽象的实现了start方法,接下来调用endpoint.start(),目前为止与上文中的endpoint.init()方法如出一辙,如下AbstractEndpoint中的start方法:
JIoEndpoint中的startInternal方法
同样startInternal()也有三个实现,进入JIoEndpoint中,则有:
JIoEndpoint的startAcceptorThreads方法
中间的startAcceptorThreads()就是开启接受请求线程咯,我们进去一睹庐山真面看看,进去之后会进入AbstractEndpoint中,又来到这个类了,代码如下:
new Acceptor[count]中的Acceptor其实就是AbstractEndpoint的一个抽象的内部类,看看它大概是什么样的吧:
一看就是个线程对不对,但是只是定义了一些变量,没有定义run() 方法,所以我们要看createAcceptor()中是如何实现的,进入由JIoEndpoint中,如下:
尼玛搞了半天,以为快水落石出了,结果你只是又扔出来一个Acceptor,好吧看看那么这个有何不同,进去看看:
从processSocket(socket)中进去看到socket被包装了起来,交给了SocketProcessor,该类是JIoEndpoint的内部类,作为专门处理socket的线程。
Http11ConnectionHandler的process方法
接下来就可以看到真正处理socket的东西了,最前面前面我们提到过,JIoEndpoint和Http11ConnectionHandler是在Http11Protocol的构造函数中被初始化,JIoEndpoint同时设置了Http11ConnectionHandler作为Handler。所以,回头来看,我们知道JIoEndpoint在接收到socket请求之后,实际是经过一定的准备工作,然后将socket交给了Http11ConnectionHandler这个去处理了。代码如下:
由源码可知,通过Http11ConnectionHandler的父类AbstractConnectionHandler定义的process方法(设计者可能是为了方便拓展),然后又用Http11Processor的父类的AbstractHttp11Processor的process方法,才调用了埋伏已久的adapter.service(req,resp)方法,等这一刻等的有点久了。代码如下:
至此,Connector的初始化和启动分析的差不多了,并将这个过程中创建的Request和Response交给了adapter这个桥梁,至于service方法内部做了哪些工作,在前面有分析,如果看到这儿,再回头看看文章开始的那几个类,大概整个流程差不多了然于胸了。
参考资料
tomcat源码分析-Connector初始化与启动
tomcat6中的请求流程 - CSDN.NET
tomcat connector学习笔记
连接器与容器的桥梁——CoyoteAdapter
-EOF-