Tomcat源码之Connector

很 多开源应用服务器都是集成tomcat作为web container的,而且对于tomcat的servlet container这部分代码很少改动。这样,这些应用服务器的性能基本上就取决于Tomcat处理HTTP请求的connector模块的性能。本文首 先从应用层次分析了tomcat所有的connector种类及用法,接着从架构上分析了connector模块在整个tomcat中所处的位置,最后对 connector做了详细的源代码分析。并且我们以Http11NioProtocol为例详细说明了tomcat是如何通过实现 ProtocolHandler接口而构建connector的。

 一、Connector介绍

1. Connector

在Tomcat架构中,Connector主要负责处理与客户端的通信。Connector的实例用于监听端口,接受来自客户端的请求并将请求转交给Engine处理。同时将来自Engine的答复返回给客户端。

2. Connector的种类

Tomcat源码中与connector相关的类位于org.apache.coyote包中,Connector分为以下几类:

Http Connector, 基于HTTP协议,负责建立HTTP连接。它又分为BIO Http Connector与NIO Http Connector两种,后者提供非阻塞IO与长连接Comet支持。默认情况下,Tomcat使用的就是这个Connector。

AJP Connector, 基于AJP协议,AJP是专门设计用来为tomcat与http服务器之间通信专门定制的协议,能提供较高的通信速度和效率。如与Apache服务器集成时,采用这个协议。

APR HTTP Connector, 用C实现,通过JNI调用的。主要提升对静态资源(如HTML、图片、CSS、JS等)的访问性能。现在这个库已独立出来可用在任何项目中。Tomcat在配置APR之后性能非常强劲。

具体地,Tomcat7中实现了以下几种Connector:

org.apache.coyote.http11.Http11Protocol : 支持HTTP/1.1 协议的连接器。

org.apache.coyote.http11.Http11NioProtocol : 支持HTTP/1.1 协议+New IO的连接器。

org.apache.coyote.http11.Http11AprProtocol : 使用APR(Apache portable runtime)技术的连接器,利用Native代码与本地服务器(如linux)来提高性能。

(以上三种Connector实现都是直接处理来自客户端Http请求,加上NIO或者APR)

org.apache.coyote.ajp.AjpProtocol:使用AJP协议的连接器,实现与web server(如Apache httpd)之间的通信

org.apache.coyote.ajp.AjpNioProtocol:SJP协议+ New IO

org.apache.coyote.ajp.AjpAprProtocol:AJP + APR

(这三种实现方法则是与web server打交道,同样加上NIO和APR)

当然,我们可以通过实现ProtocolHandler接口来定义自己的Connector。详细的实现过程请看文章后文。

3. Connector的配置

对Connector的配置位于conf/server.xml文件中,内嵌在Service元素中,可以有多个Connector元素。

(1) BIO HTTP/1.1 Connector配置

一个典型的配置如下:  

<Connector  port=”8080”  protocol=”HTTP/1.1”  maxThreads=”150”  conn ectionTimeout=”20000”   redirectPort=”8443” />
其它一些重要属性如下:
acceptCount : 接受连接request的最大连接数目,默认值是10
address : 绑定IP地址,如果不绑定,默认将绑定任何IP地址
allowTrace : 如果是true,将允许TRACE HTTP方法
compressibleMimeTypes : 各个mimeType, 以逗号分隔,如text/html,text/xml
compression : 如果带宽有限的话,可以用GZIP压缩
connectionTimeout : 超时时间,默认为60000ms (60s)
maxKeepAliveRequest : 默认值是100
maxThreads : 处理请求的Connector的线程数目,默认值为200

如果是SSL配置,如下:

<Connector port="8181" protocol="HTTP/1.1" SSLEnabled="true" 
    maxThreads="150" scheme="https" secure="true" 
    clientAuth="false" sslProtocol = "TLS" 
    address="0.0.0.0" 
    keystoreFile="E:/java/jonas-full-5.1.0-RC3/conf/keystore.jks" 
    keystorePass="changeit" />

其中,keystoreFile为证书位置,keystorePass为证书密码

(2) NIO HTTP/1.1 Connector配置

<Connector port=”8080” protocol=”org.apache.coyote.http11.Http11NioProtocol” maxThreads=”150” connectionTimeout=”20000” redirectPort=”8443”/>

(3) Native APR Connector配置

  • ARP是用C/C++写的,对静态资源(HTML,图片等)进行了优化。所以要下载本地库tcnative-1.dll与openssl.exe,将其放在%tomcat%\bin目录下。

下载地址是:http://tomcat.heanet.ie/native/1.1.10/binaries/win32/

  • 在server.xml中要配置一个Listener,如下图。这个配置tomcat是默认配好的。
    <!--APR library loader. Documentation at /docs/apr.html --> 
    <Listener className="org.apache.catalina.core.AprLifecycleListener" SSLEngine="on" />
  • 配置使用APR connector
    <Connector port=”8080” protocol=”org.apache.coyote.http11.Http11AprProtocol” 
    maxThreads=”150” connectionTimeout=”20000” redirectPort=”8443”

如果配置成功,启动tomcat,会看到如下信息:
org.apache.coyote.http11.Http11AprProtocol init
(4)  AJP Connector配置:
<Connector port="8009" protocol="AJP/1.3" redirectPort="8443" />

二、Connector在Tomcat中所处的位置

1. Tomcat架构

Tomcat源码之Connector_第1张图片

>>>Server(服 务器)是Tomcat构成的顶级构成元素,所有一切均包含在Serv    erz中,Server的实现类StandardServer可以包含一个到多个Services;>>>>Service的 实现类为StandardService调用了容器(Container)     接口,其实是调用了Servlet Engine(引擎),而且StandardService类中也   指明了该Service归属的Server;

  • >>>Container, 引擎(Engine)、主机(Host)  上下文(Context)和Wraper均继承自Container接口,所以它们都是容器       但是,它们是有父子关系的,在主机(Host)、上下文(Context)和引擎(En      gine)这三类容器中,引擎是顶级容器,直接包含是主机容器,而主机容       器又包含上下文容器,所以引擎、主机和上下文从大小上来说又构成父子     关系,虽然它们都继承自Container接口。
  • >>>连接器(Connector)将Service和Container连接起来,首先它需要注册到一个Service,它的作用就是把来自客户端的请求转发到Container(容器),这就是它为什么称作连接器的原因。

故我们从功能的角度将Tomcat源代码分成5个子模块,它们分别是:

Jsper子模块:这个子模块负责jsp页面的解析、jsp属性的验证,同时也负责将jsp页面动态转换为java代码并编译成class文件。在Tomcat源代码中,凡是属于org.apache.jasper包及其子包中的源代码都属于这个子模块;

Servlet 和Jsp规范的实现模块:这个子模块的源代码属于javax.servlet包及其子包,如我们非常熟悉的javax.servlet.Servlet接 口、javax.servet.http.HttpServlet类及javax.servlet.jsp.HttpJspPage就位于这个子模块中;

Catalina 子模块:这个子模块包含了所有以org.apache.catalina开头的java源代码。该子模块的任务是规范了Tomcat的总体架构,定义了 Server、Service、Host、Connector、Context、Session及Cluster等关键组件及这些组件的实现,这个子模块 大量运用了Composite设计模式。同时也规范了Catalina的启动及停止等事件的执行流程。从代码阅读的角度看,这个子模块应该是我们阅读和学 习的重点。

Connector 子模块:如果说上面三个子模块实现了Tomcat应用服务器的话,那么这个子模块就是Web服务器的实现。所谓连接器(Connector)就是一个连接 客户和应用服务器的桥梁,它接收用户的请求,并把用户请求包装成标准的Http请求(包含协议名称,请求头Head,请求方法是Get还是Post等 等)。同时,这个子模块还按照标准的Http协议,负责给客户端发送响应页面,比如在请求页面未发现时,connector就会给客户端浏览器发送标准的 Http 404错误响应页面。

Resource子模块:这个子模块包含一些资源文件,如Server.xml及Web.xml配置文件。严格说来,这个子模块不包含java源代码,但是它还是Tomcat编译运行所必需的。

2.Tomcat运行流程

假设来自客户的请求为:http://localhost:8080/test/index.jsp.请求被发送到本机端口8080,被在那里侦听的Coyote HTTP/1.1 Connector获得,然后
  • Connector把该请求交给它所在的Service的Engine来处理,并等待Engine的回应
  • Engine获得请求localhost:8080/test/index.jsp,匹配它所有虚拟主机Host
  • Engine匹配到名为localhost的Host(即使匹配不到也把请求交给该Host处理,因为该Host被定义为该Engine的默认主机)
  • localhost Host获得请求/test/index.jsp,匹配它所拥有的所有Context
  • Host匹配到路径为/test的Context(如果匹配不到就把该请求交给路径名为""的Context去处理)
  • path="/test"的Context获得请求/index.jsp,在它的mapping table中寻找对应的servlet
  • Context匹配到URL PATTERN为*.jsp的servlet,对应于JspServlet类,构造HttpServletRequest对象和HttpServletResponse对象,作为参数调用JspServlet的doGet或doPost方法
  • Context把执行完了之后的HttpServletResponse对象返回给Host
  • Host把HttpServletResponse对象返回给Engine
  • Engine把HttpServletResponse对象返回给Connector
  • Connector把HttpServletResponse对象返回给客户browser

三、Connector源码分析

1 Tomcat的启动分析与集成设想

我 们知道,启动tomcat有两种方式:双击bin/startup.bat、运行bin/catalina.bat run。它们对应于Bootstrap与Catalina两个类,我们现在只关心Catalina这个类,这个类使用Apache Digester解析conf/server.xml文件生成tomcat组件,然后再调用Embedded类的start方法启动tomcat。

所以,集成Tomcat的方式就有以下两种了:

①沿用tomcat自身的server.xml

②自己定义一个xml格式来配置tocmat的各参数,自己再写解析这段xml,然后使用tomcat提供的API根据这些xml来生成Tomcat组件,最后调用Embedded类的start方法启动tomcat

个人觉得第一种方式要优越,给开发者比较好的用户体验,如果使用这种,直接模仿Catalina类的方法即可实现集成。

目前,JOnAS就使用了这种集成方式,JBoss、GlassFish使用的第二种自定义XML的方式。

2. Connector类图与顺序图

  Tomcat源码之Connector_第2张图片

 

  Tomcat源码之Connector_第3张图片

从上面二图中我们可以得到如下信息:

Tomcat中有四种容器(Context、Engine、Host、Wrapper),前三者常见,第四个不常见但它也是实现了Container接口的容器

如果要自定义一个Connector的话,只需要实现org.apache.coyote.ProtocolHander接口,该接口定义如下:

[java] view plain copy
  1. <span style="font-size:14px;">/* 
  2.  *  Licensed to the Apache Software Foundation (ASF) under one or more 
  3.  *  contributor license agreements.  See the NOTICE file distributed with 
  4.  *  this work for additional information regarding copyright ownership. 
  5.  *  The ASF licenses this file to You under the Apache License, Version 2.0 
  6.  *  (the "License"); you may not use this file except in compliance with 
  7.  *  the License.  You may obtain a copy of the License at 
  8.  * 
  9.  *      http://www.apache.org/licenses/LICENSE-2.0 
  10.  * 
  11.  *  Unless required by applicable law or agreed to in writing, software 
  12.  *  distributed under the License is distributed on an "AS IS" BASIS, 
  13.  *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 
  14.  *  See the License for the specific language governing permissions and 
  15.  *  limitations under the License. 
  16.  */  
  17.   
  18. package org.apache.coyote;  
  19.   
  20. import java.util.concurrent.Executor;  
  21.   
  22.   
  23. /** 
  24.  * Abstract the protocol implementation, including threading, etc. 
  25.  * Processor is single threaded and specific to stream-based protocols, 
  26.  * will not fit Jk protocols like JNI. 
  27.  * 
  28.  * This is the main interface to be implemented by a coyote connector. 
  29.  * Adapter is the main interface to be implemented by a coyote servlet 
  30.  * container. 
  31.  * 
  32.  * @author Remy Maucherat 
  33.  * @author Costin Manolache 
  34.  * @see Adapter 
  35.  */  
  36. public interface ProtocolHandler {   //Tomcat 中的Connector实现了这个接口  
  37.   
  38.     /** 
  39.      * The adapter, used to call the connector. 
  40.      */  
  41.     public void setAdapter(Adapter adapter);  
  42.     public Adapter getAdapter();  
  43.   
  44.   
  45.     /** 
  46.      * The executor, provide access to the underlying thread pool. 
  47.      */  
  48.     public Executor getExecutor();  
  49.   
  50.   
  51.     /** 
  52.      * Initialise the protocol. 
  53.      */  
  54.     public void init() throws Exception;  
  55.   
  56.   
  57.     /** 
  58.      * Start the protocol. 
  59.      */  
  60.     public void start() throws Exception;  
  61.   
  62.   
  63.     /** 
  64.      * Pause the protocol (optional). 
  65.      */  
  66.     public void pause() throws Exception;  
  67.   
  68.   
  69.     /** 
  70.      * Resume the protocol (optional). 
  71.      */  
  72.     public void resume() throws Exception;  
  73.   
  74.   
  75.     /** 
  76.      * Stop the protocol. 
  77.      */  
  78.     public void stop() throws Exception;  
  79.   
  80.   
  81.     /** 
  82.      * Destroy the protocol (optional). 
  83.      */  
  84.     public void destroy() throws Exception;  
  85.   
  86.   
  87.     /** 
  88.      * Requires APR/native library 
  89.      */  
  90.     public boolean isAprRequired();  
  91. }  
  92. </span>  

自定义Connector时需实现的ProtoclHandler接口

Tomcat 以HTTP(包括BIO与NIO)、AJP、APR、内存四种协议实现了该接口(它们分别是:AjpAprProtocol、AjpProtocol、 Http11AprProtocol、Http11NioProtocol、Http11Protocal、JkCoyoteHandler、 MemoryProtocolHandler),要使用哪种Connector就在conf/server.xml中配置,在Connector的构造函 数中会通过反射实例化所配置的实现类:

<Connector port="8181" 
   protocol="org.apache.coyote.http11.Http11AprProtocol " />

3.Connector的工作流程
下面我们以org.apache.coyote.http11.Http11AprProtocol为例说明Connector的工作流程。
①它将工作委托给AprEndpoint类。
[java] view plain copy
  1. <span style="font-size:14px;">    public Http11AprProtocol() {  
  2.         endpoint = new AprEndpoint();                    // 主要工作由AprEndpoint来完成  
  3.         cHandler = new Http11ConnectionHandler(this);    // inner class  
  4.         ((AprEndpoint) endpoint).setHandler(cHandler);</span>  

②在AprEndpoint.Acceptor类中的run()方法会接收一个客户端新的连接请求.

[java] view plain copy
  1. /** 
  2.  * The background thread that listens for incoming TCP/IP connections and 
  3.  * hands them off to an appropriate processor. 
  4.  */  
  5. protected class Acceptor extends AbstractEndpoint.Acceptor {  
  6.   
  7.     private final Log log = LogFactory.getLog(AprEndpoint.Acceptor.class);  
  8.   
  9.     @Override  
  10.     public void run() {  
  11.   
  12.         int errorDelay = 0;  
  13.   
  14.         // Loop until we receive a shutdown command  
  15.         while (running) {  


③在AprEndpoint类中,有一个内部接口Handler,该接口定义如下:


[java] view plain copy
  1. <span style="font-size:14px;">/** 
  2.      * Bare bones interface used for socket processing. Per thread data is to be 
  3.      * stored in the ThreadWithAttributes extra folders, or alternately in 
  4.      * thread local fields. 
  5.      */  
  6.     public interface Handler extends AbstractEndpoint.Handler {  
  7.         public SocketState process(SocketWrapper<Long> socket,  
  8.                 SocketStatus status);  
  9.     }</span>  


④在Http11AprProtocol类中实现了AprEndpoint中的Handler接口,

[java] view plain copy
  1. protected static class Http11ConnectionHandler  
  2.            extends AbstractConnectionHandler<Long,Http11AprProcessor> implements Handler {  
  3.          

并调用Http11AprProcessor类(该类实现了ActionHook回调接口)。

       @Override


[java] view plain copy
  1. <span style="font-size:14px;">        protected Http11AprProcessor createProcessor() {  
  2.             Http11AprProcessor processor = new Http11AprProcessor(</span>  


四、 通过Connector实现一个简单的Server。
通过以下这些步骤,你就可以建立一个很简单的服务器,除了用到tomcat的一些类之外,跟Tomcat没有任何关系。
首先,建立一个含有main()方法的java类,详细代码如下:
[java] view plain copy
  1. package myConnector;  
  2.   
  3. import java.util.concurrent.BlockingQueue;  
  4. import java.util.concurrent.LinkedBlockingQueue;  
  5. import java.util.concurrent.ThreadPoolExecutor;  
  6. import java.util.concurrent.TimeUnit;  
  7.   
  8. import org.apache.coyote.http11.Http11Protocol;  
  9.   
  10.   
  11. /** 
  12.  * 基于Http11Protocol实现一个简单的服务器 
  13.  * @author bingduanLin 
  14.  * 
  15.  */  
  16. public class MyServer {  
  17.   
  18.     /** 
  19.      * @param args 
  20.      */  
  21.     public static void main(String[] args) throws Exception{  
  22.         Http11Protocol hp = new Http11Protocol();  
  23.         hp.setPort(9000);  
  24.         ThreadPoolExecutor threadPoolExecutor = createThreadPoolExecutor();  
  25.         threadPoolExecutor.prestartCoreThread();  
  26.         hp.setExecutor(threadPoolExecutor);  
  27.         hp.setAdapter(new myHandler());  
  28.         hp.init();  
  29.         hp.start();  
  30.         System.out.println("My Server has started successfully!");  
  31.           
  32.   
  33.     }  
  34.       
  35.     public static ThreadPoolExecutor createThreadPoolExecutor() {  
  36.         int corePoolSite = 2 ;  
  37.         int maxPoolSite = 10 ;  
  38.         long keepAliveTime = 60 ;  
  39.         TimeUnit unit = TimeUnit.SECONDS;  
  40.         BlockingQueue<Runnable> workQueue = new LinkedBlockingQueue<Runnable>();  
  41.         ThreadPoolExecutor threadPoolExecutor =   
  42.                 new ThreadPoolExecutor(corePoolSite, maxPoolSite,  
  43.                         keepAliveTime, unit, workQueue);  
  44.         return threadPoolExecutor;  
  45.     }  
  46.   
  47. }  

上面这个类用到的MyHandler类代码如下:
[java] view plain copy
  1. package myConnector;  
  2.   
  3. import java.io.ByteArrayOutputStream;  
  4. import java.io.OutputStreamWriter;  
  5. import java.io.PrintWriter;  
  6.   
  7. import org.apache.coyote.Adapter;  
  8. import org.apache.coyote.Request;  
  9. import org.apache.coyote.Response;  
  10. import org.apache.tomcat.util.buf.ByteChunk;  
  11. import org.apache.tomcat.util.net.SocketStatus;  
  12.   
  13. public class myHandler implements Adapter {  
  14.   
  15.     @Override  
  16.     public void service(Request req, Response res) throws Exception {  
  17.         // 请求处理  
  18.         System.out.println("Hi, Boss. I am handling the reuqest!");  
  19.         ByteArrayOutputStream baos = new ByteArrayOutputStream();     
  20.         PrintWriter writer = new PrintWriter(new OutputStreamWriter(baos));     
  21.         writer.println("Not Hello World");     
  22.         writer.flush();     
  23.     
  24.         ByteChunk byteChunk = new ByteChunk();     
  25.         byteChunk.append(baos.toByteArray(), 0, baos.size());     
  26.         res.doWrite(byteChunk);    
  27.   
  28.     }  
  29.   
  30.     @Override  
  31.     public boolean event(Request req, Response res, SocketStatus status)  
  32.             throws Exception {  
  33.         System.out.println("Event-Event");  
  34.         return false;  
  35.     }  
  36.   
  37.     @Override  
  38.     public boolean asyncDispatch(Request req, Response res, SocketStatus status)  
  39.             throws Exception {  
  40.         // TODO Auto-generated method stub  
  41.         return false;  
  42.     }  
  43.   
  44.     @Override  
  45.     public void log(Request req, Response res, long time) {  
  46.         // TODO Auto-generated method stub  
  47.   
  48.     }  
  49.   
  50.     @Override  
  51.     public String getDomain() {  
  52.         // TODO Auto-generated method stub  
  53.         return null;  
  54.     }  
  55.   
  56. }  

注意,必须导入相关的tomcat源码文件。完成之后运行主程序即:MyServer。然后在浏览器中输入:localhost:9000就可以看到“Not Hello World”。
下面是主程序的一些输出:
十二月 20, 2012 8:46:12 下午 org.apache.coyote.AbstractProtocol init
INFO: Initializing ProtocolHandler ["http-bio-9000"]
十二月 20, 2012 8:46:13 下午 org.apache.coyote.AbstractProtocol start
INFO: Starting ProtocolHandler ["http-bio-9000"]
My Server has started successfully!
Hi, Boss. I am handling the reuqest!
Hi, Boss. I am handling the reuqest!
Hi, Boss. I am handling the reuqest!
Hi, Boss. I am handling the reuqest!

是不是很酷??

备注: (本文以ChinaUnix.net的原文为基础做修改,原文请参考:http://tech.chinaunix.net/a2011/1214/1288/000001288290_2.shtml)

-----------------------------------------------------------------------------------------------------------

Connector是Tomcat最核心的组件之一,负责处理一个WebServer最核心的连接管理、Net IO、线程(可选)、协议解析和处理的工作。
一、连接器介绍
在开始Connector探索之路之前,先看看Connector几个关键字

  • NIO:Tomcat可以利用Java比较新的NIO技术,提升高并发下的Socket性能
  • AJP:Apache JServ Protocol,AJP的提出当然还是为了解决java亘古不变的问题——性能,AJP协议是基于包的长连接协议,以减少前端Proxy与Tomcat连接Socket连接创建的代价,目前Apache通过JK和AJP_ROXY的方式支持AJP协议,需要注意的是,虽然Nginx作为代理服务器性能强劲,但其只能通过HTTP PROXY的方式与后端的Tomcat联系,因此如果从作为代理服务器的角度上讲,在这种情况下Nginx未必会比Apache体现出更优的性能
  • APR/Native:Apache Portable Runtime,还是一个词,性能。APR的提出利用Native代码更好地解决性能问题,更好地与本地服务器(linux)打交道。让我们看看Tomcat文档对APR的介绍
Tomcat6文档 写道
Tomcat can use the Apache Portable Runtime to provide superior scalability, performance, and better integration with native server technologies. The Apache Portable Runtime is a highly portable library that is at the heart of Apache HTTP Server 2.x. APR has many uses, including access to advanced IO functionality (such as sendfile, epoll and OpenSSL), OS level functionality (random number generation, system status, etc), and native process handling (shared memory, NT pipes and Unix sockets).
These features allows making  Tomcat a general purpose webserver, will enable much better integration with other native web technologies, and overall make Java much more viable as a full fledged webserver platform. rather than simply a backend focused technology.

通过对如上名词的组合,Tomcat组成了如下的Connector系列:

  • Http11Protocol:支持HTTP1.1协议的连接器
  • Http11NioProtocol:支持HTTP1.1 协议+ NIO的连接器
  • Http11AprProtocol:使用APR技术处理连接的连接器
  • AjpProtocol:支持AJP协议的连接器
  • AjpAprProtocol:使用APR技术处理连接的连接器

二、范例
      我们以最简单的Http11Protocol为例,看看从请求进来到处理完毕,连接器部件是处理处理的。首先我们利用Tomcat组件组成我们一个最简单的WebServer,其具备如下功能:

  • 监停某个端口,接受客户端的请求,并将请求分配给处理线程
  • 处理线程处理请求,分析HTTP1.1请求,封装Request/Response对象,并将请求由请求处理器处理
  • 实现最简单的请求处理器,向客户端打印Hello World

代码非常简单,首先是主功能(这里,我们利用JDK5.0的线程池,连接器不再管理线程功能):

Java代码
  1. package ray.tomcat.test;   
  2.   
  3. import java.util.concurrent.BlockingQueue;   
  4. import java.util.concurrent.LinkedBlockingQueue;   
  5. import java.util.concurrent.ThreadPoolExecutor;   
  6. import java.util.concurrent.TimeUnit;   
  7.   
  8. import org.apache.coyote.http11.Http11Protocol;   
  9.   
  10. public class TomcatMainV2   
  11. {   
  12.     public static void main(String[] args) throws Exception   
  13.     {   
  14.         Http11Protocol protocol = new Http11Protocol();   
  15.         protocol.setPort(8000);   
  16.         ThreadPoolExecutor threadPoolExecutor = createThreadPoolExecutor();   
  17.         threadPoolExecutor.prestartCoreThread();   
  18.         protocol.setExecutor(threadPoolExecutor);   
  19.         protocol.setAdapter(new MyHandler());   
  20.         protocol.init();   
  21.         protocol.start();   
  22.     }   
  23.   
  24.     public static ThreadPoolExecutor createThreadPoolExecutor()   
  25.     {   
  26.         int corePoolSize = 2;   
  27.         int maximumPoolSize = 10;   
  28.         long keepAliveTime = 60;   
  29.         TimeUnit unit = TimeUnit.SECONDS;   
  30.         BlockingQueue<Runnable> workQueue = new LinkedBlockingQueue<Runnable>();   
  31.         ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(   
  32.                 corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue);   
  33.         return threadPoolExecutor;   
  34.     }   
  35. }  
 

请求处理器向客户端打引Hello World,代码如下

Java代码
  1. package ray.tomcat.test;   
  2.   
  3. import java.io.ByteArrayOutputStream;   
  4. import java.io.OutputStreamWriter;   
  5. import java.io.PrintWriter;   
  6.   
  7. import org.apache.coyote.Adapter;   
  8. import org.apache.coyote.Request;   
  9. import org.apache.coyote.Response;   
  10. import org.apache.tomcat.util.buf.ByteChunk;   
  11. import org.apache.tomcat.util.net.SocketStatus;   
  12.   
  13. public class MyHandler implements Adapter   
  14. {   
  15.     //支持Comet,Servlet3.0将对Comet提供支持,Tomcat6目前是非标准的实现   
  16.     public boolean event(Request req, Response res, SocketStatus status)   
  17.             throws Exception   
  18.     {   
  19.         System.out.println("event");   
  20.         return false;   
  21.     }   
  22.   
  23.     //请求处理   
  24.     public void service(Request req, Response res) throws Exception   
  25.     {   
  26.         System.out.println("service");   
  27.   
  28.         ByteArrayOutputStream baos = new ByteArrayOutputStream();   
  29.         PrintWriter writer = new PrintWriter(new OutputStreamWriter(baos));   
  30.         writer.println("Hello World");   
  31.         writer.flush();   
  32.   
  33.         ByteChunk byteChunk = new ByteChunk();   
  34.         byteChunk.append(baos.toByteArray(), 0, baos.size());   
  35.         res.doWrite(byteChunk);   
  36.     }      
  37. }  
 

     运行主程序,在浏览器中输入http://127.0.0.1:8000,我们可以看到打印”Hello World”
三、分析
     以如上Http11Protocol为例,我们可以看到,Tomcat实现一个最简单的处理Web请求的代码其实非常简单,其主要包括如下核心处理类:

  • Http11Protocol:Http1.1协议处理入口类,其本身没有太多逻辑,对请求主要由JIoEndPoint类处理
  • Http11Protocol$Http11ConnectionHandler:连接管理器,管理连接处理队列,并分配Http11Processor对请求进行处理
  • Http11Processor:请求处理器,负责HTTP1.0协议相关的工作,包括解析请求和处理响应信息,并调用Adapter做实际的处理工作,如上我们看到了我们自定义的Adapter实现响应”Hello World”
  • JIoEndPoint:监停端口,启动接受线程准备接收请求,在请求接受后转给工作线程处理
  • JIoEndPoint$Acceptor:请求接收器,接收后将Socket分配给工作线程继续后续处理
  • JIoEndPoint$Worker: 工作线程,使用Handler来处理请求,对于我们的HTTP1.1协议来说,其实现是 Http11Protocol$Http11ConnectionHandler。这部分不是必须的,也可以选择JDK的concurrent包的线程池

      实际上各种连接器实现基本大同小异,基本上都是由如上部分组合而成

      1.初始化:首先,还是从入口开始,先看看初始化init

Java代码
  1. public void init() throws Exception {   
  2.         endpoint.setName(getName());   
  3.         endpoint.setHandler(cHandler); //请求处理器,对于HTTP1.1协议,是Http11Protocol$Http11ConnectionHandler   
  4.   
  5.           
  6. // 初始化ServerSocket工厂类,如果需SSL/TLS支持,使用JSSESocketFactory/PureTLSSocketFactory   
  7. . . . (略)          
  8.               //主要的初始化过程实际是在endpoint(JIoEndpoint)   
  9.         endpoint.init();   
  10.         . . . (略)   
  11.     }  

      Http11Protocol的初始化非常简单,准备好ServerSocket工厂,调用JIoEndPoint的初始化。让我们接下来看看JIoEndPoint的初始化过程

Java代码
  1. public void init()   
  2.         throws Exception {   
  3.   
  4.         if (initialized)   
  5.             return;   
  6.           
  7.         // Initialize thread count defaults for acceptor   
  8.         // 请求接收处理线程,这个值实际1已经足够   
  9.         if (acceptorThreadCount == 0) {   
  10.             acceptorThreadCount = 1;   
  11.         }   
  12.         if (serverSocketFactory == null) {   
  13.             serverSocketFactory = ServerSocketFactory.getDefault();   
  14.         }   
  15.           
  16.         //创建监停ServerSocket,port为监听端口,address为监停地址   
  17.         // backlog为连接请求队列容量(@param backlog how many connections are queued)   
  18.         if (serverSocket == null) {   
  19.             try {   
  20.                 if (address == null) {   
  21.                     serverSocket = serverSocketFactory.createSocket(port, backlog);   
  22.                 } else {   
  23.                     serverSocket = serverSocketFactory.createSocket(port, backlog, address);   
  24.                 }   
  25.             } catch (BindException be) {   
  26.                 throw new BindException(be.getMessage() + ":" + port);   
  27.             }   
  28.         }   
  29.         //if( serverTimeout >= 0 )   
  30.         //    serverSocket.setSoTimeout( serverTimeout );   
  31.           
  32.         initialized = true;     
  33.     }  

        可以看到,监停端口在此处准备就绪

Java代码
  1. public void start()   
  2.      throws Exception {   
  3.      // Initialize socket if not done before   
  4.      if (!initialized) {   
  5.          init();   
  6.      }   
  7.      if (!running) {   
  8.          running = true;   
  9.          paused = false;   
  10.   
  11.          // Create worker collection   
  12.          // 初始化工作线程池,有WorkerStack(Tomcat自实现)和Executor(JDK concurrent包)两种实现  
  13.          if (executor == null) {   
  14.              workers = new WorkerStack(maxThreads);   
  15.          }   
  16.   
  17.          // Start acceptor threads   
  18.          // 启动请求连接接收处理线程   
  19.          for (int i = 0; i < acceptorThreadCount; i++) {   
  20.              Thread acceptorThread = new Thread(new Acceptor(), getName() + "-Acceptor-" + i);   
  21.              acceptorThread.setPriority(threadPriority);   
  22.              acceptorThread.setDaemon(daemon); //设置是否daemon参数,默认为true   
  23.              acceptorThread.start();   
  24.          }   
  25.      }   
  26.  }  
 


      2.准备好连接处理:初始化完毕,准备好连接处理,准备接收连接上来,同样的,Http11Protocol的start基本没干啥事,调用一下JIoEndPoint的start,我们来看看JIoEndPoint的start

Java代码
  1. public void start()   
  2.      throws Exception {   
  3.      // Initialize socket if not done before   
  4.      if (!initialized) {   
  5.          init();   
  6.      }   
  7.      if (!running) {   
  8.          running = true;   
  9.          paused = false;   
  10.   
  11.          // Create worker collection   
  12.          // 初始化工作线程池,有WorkerStack(Tomcat自实现)和Executor(JDK concurrent包)两种实现  
  13.          if (executor == null) {   
  14.              workers = new WorkerStack(maxThreads);   
  15.          }   
  16.   
  17.          // Start acceptor threads   
  18.          // 启动请求连接接收处理线程   
  19.          for (int i = 0; i < acceptorThreadCount; i++) {   
  20.              Thread acceptorThread = new Thread(new Acceptor(), getName() + "-Acceptor-" + i);   
  21.              acceptorThread.setPriority(threadPriority);   
  22.              acceptorThread.setDaemon(daemon); //设置是否daemon参数,默认为true   
  23.              acceptorThread.start();   
  24.          }   
  25.      }   
  26.  }  

      主要处理的事情无非就是准备和工作线程(处理具体请求的线程度池,可选,也可以使用JDK5.0的线程池),连接请求接收处理线程(代码中,一般acceptorThreadCount=1)

      3.连接请求接收处理:准备就绪,可以连接入请求了。现在工作已经转到了Acceptor(JIoEndPoint$Acceptor)这里,我们看看Acceptor到底做了些啥

Java代码
  1. public void run() {   
  2.   
  3.             // Loop until we receive a shutdown command   
  4.             while (running) {   
  5.                 . . . (略)   
  6.                     //阻塞等待客户端连接   
  7.                     Socket socket = serverSocketFactory.acceptSocket(serverSocket);   
  8.                     serverSocketFactory.initSocket(socket);   
  9.                     // Hand this socket off to an appropriate processor   
  10.                     if (!processSocket(socket)) {   
  11.                         // Close socket right away   
  12.                         try {   
  13.                             socket.close();   
  14.                         } catch (IOException e) {   
  15.                             // Ignore   
  16.                         }   
  17.                     }   
  18.                   . . . (略)   
  19.             }   
  20.  }   
  21. . . . (略)   
  22.     protected boolean processSocket(Socket socket) {   
  23.         try {   
  24.             //由工作线程继续后续的处理   
  25.             if (executor == null) {   
  26.                 getWorkerThread().assign(socket);   
  27.             } else {   
  28.                 executor.execute(new SocketProcessor(socket));   
  29.             }   
  30.         } catch (Throwable t) {   
  31.             . . . (略)              
  32. return false;   
  33.         }   
  34.         return true;   
  35.     }  

       实际上也没有什么复杂的工作,无非就是有连接上来之后,将连接转交给工作线程(SocketProcessor)去处理

       4.工作线程:SocketProcessor

Java代码
  1. public void run() {   
  2.             // Process the request from this socket   
  3.             if (!setSocketOptions(socket) || !handler.process(socket)) {   
  4.                 // Close socket   
  5.                 try {   
  6.                     socket.close();   
  7.                 } catch (IOException e) {   
  8.                 }   
  9.             }   
  10.             // Finish up this request   
  11.             socket = null;   
  12.         }     

    工作线程主要是设置一下Socket参数,然后将请求转交给handler去处理,需要注意一下如下几个连接参数的意义:

  • SO_LINGER:若设置了SO_LINGER并确定了非零的超时间隔,则closesocket()调用阻塞进程,直到所剩数据发送完毕或超 时。这种关闭称为“优雅的”关 闭。请注意如果套接口置为非阻塞且SO_LINGER设为非零超时,则closesocket()调用将以WSAEWOULDBLOCK错误返回。若在一 个流类套接口上设置了SO_DONTLINGER,则closesocket()调用立即返回。但是,如果可能,排队的数据将在套接口关闭前发送。请注 意,在这种情况下WINDOWS套接口实现将在 一段不确定的时间内保留套接口以及其他资源(TIME_WAIT),这对于想用所以套接口的应用程序来说有一定影响。默认此参数不打开
  • TCP_NODELAY:是否打开Nagle,默认打开,使用Nagle算法是为了避免多次发送小的分组,而是累计到一定程度或者超过一定时间后才一起发送。对于AJP连接,可能需要关注一下这个选项。
  • SO_TIMEOUT:JDK API注释如下,With this option set to a non-zero timeout,a read() call on the InputStream associated with this Socket will block for only this amount of time.  If the timeout expires, a java.net.SocketTimeoutException is raised, though the Socket is still valid. The option must be enabled prior to entering the blocking operation to have effect. The timeout must be > 0。默认设置的是60秒

        关于默认的设置,可以参见org.apache.coyote.http11.Constants定义
      5.最终请求终于回到了Handler,此处的Handler实现是 org.apache.coyote.http11.Http11Processor,其主要处理一些HTTP协议性细节的东西,此处代码不再列出,有兴 趣可以自行读代码。最终请求终于回到了我们的Adapter对象,一个请求处理完毕,功德圆满。

(转载自: http://1632004.blog.163.com/blog/static/29991497201201912858468)





你可能感兴趣的:(Tomcat源码之Connector)