`
grunt1223
  • 浏览: 422036 次
  • 性别: Icon_minigender_1
  • 来自: 杭州
社区版块
存档分类
最新评论

庖丁解”猫“——Tomcat Connector 源码分析

阅读更多
首先推荐一本好书,Budi Kurniawan以及Paul Deck所著的《How Tomcat Works》,这本书在豆瓣上的评分达到了史无前例的9.8分,而同为经典的《JAVA编程思想》以及GOF《设计模式》则为9.2分。序言是则精确定位了该书的读者群体,如下:

引用
How Tomcat Works is the only book that explains the internal workings of Tomcat, the open source project used by millions of Java developers. Unlike other Tomcat titles, it is unique because it does not simply covers the configuration or servlet development with Tomcat. Rather, this book is meant for advanced readers interested in writing their own Tomcat modules or in understanding more beyond servlet/JSP programming.


本书是当今仅有的解释Tomcat——这一被数以百万计JAVA开发人员所使用的服务器其内部工作机理的参考书。不想其它相关Tomcat书籍仅仅是叫你如何配置或者进行servlet编程,它的主要读者群体应该是那些感兴趣自己编写Tomcat模块、或是不满足于仅仅写servlet/JSP的开发人员。

Tomcat其核心是一个称为Catalina的模块,其又由两部分组成:Connector以及Container,前者主要负责连接的管理,后者主要负责servlet的解析,这是对Tomcat的最抽象概括。本文主要介绍Connector,Container会在后面的文章中介绍。

每当我们启动Tomcat的时候,其内部做了一下工作:
  • 创建Connector
  • 创建Container,并将其赋值给Connector
  • 初始化Connector
  • 启动Connector所在工作线程

public final class Bootstrap {
  public static void main(String[] args) {
    HttpConnector connector = new HttpConnector();
    SimpleContainer container = new SimpleContainer();
    connector.setContainer(container);
    try {
      connector.initialize();
      connector.start();

      // make the application wait until we press any key.
      System.in.read();
    }
    catch (Exception e) {
      e.printStackTrace();
    }
  }
}


Tomcat的Connector启动会涉及到LifeCycle的管理,为简单起见,这里我们不必管这些,只需简单的认为是启动了Connector本身的后台线程(事实上它也是一个守护线程)。Connector的代码如下:
    /**
     * The background thread that listens for incoming TCP/IP connections and
     * hands them off to an appropriate processor.
     */
    public void run() {

        // Loop until we receive a shutdown command
        while (!stopped) {

            // Accept the next incoming connection from the server socket
            Socket socket = null;
            try {
                socket = serverSocket.accept();
                if (connectionTimeout > 0)
                    socket.setSoTimeout(connectionTimeout);
            } catch (AccessControlException ace) {
                log("socket accept security exception: " + ace.getMessage());
                continue;
            } catch (IOException e) {
                if (started && !stopped)
                    log("accept: ", e);
                break;
            }

            // Hand this socket off to an appropriate processor
            HttpProcessor processor = createProcessor();
            if (processor == null) {
                try {
                    log(sm.getString("httpConnector.noProcessor"));
                    socket.close();
                } catch (IOException e) {
                    ;
                }
                continue;
            }
            processor.assign(socket);

            // The processor will recycle itself when it finishes

        }

        // Notify the threadStop() method that we have shut ourselves down
        synchronized (threadSync) {
            threadSync.notifyAll();
        }

    }

前面的逻辑,有过socket编程基础的朋友都不难理解,仅仅是创建一个socket,并调用serverSocket的accept方法,该方法会阻塞直到请求来到。之后创建了一个HttpProcesser实例,代码如下:
HttpProcessor processor = createProcessor();

该方法的内部实现如下:
    /**
     * Create (or allocate) and return an available processor for use in
     * processing a specific HTTP request, if possible.  If the maximum
     * allowed processors have already been created and are in use, return
     * <code>null</code> instead.
     */
    private HttpProcessor createProcessor() {

        synchronized (processors) {
            if (processors.size() > 0)
                return ((HttpProcessor) processors.pop());
            if ((maxProcessors > 0) && (curProcessors < maxProcessors)) {
                return (newProcessor());
            } else {
                if (maxProcessors < 0) {
                    return (newProcessor());
                } else {
                    return (null);
                }
            }
        }

    }

在Tomcat的Connector实现中,通过一个池(pool)来管理HttpProcessor对象,每一个HttpProcessor都是一个单独的线程,因此HttpConnector可以同时服务多个Http请求。Tomcat通过一个java.io.Stack来保存多个HttpProcessor对象,如下:
/**
     * The set of processors that have been created but are not currently
     * being used to process a request.
     */
    private Stack processors = new Stack();

在HttpConnector中,HttpProcessor的实例数量由两个变量来决定:minProcessors以及maxProcessors。默认情况下,分别设置为5和20,可以通过相应的setMinProcessors以及setMaxProcessors方法来修改该值。Tomcat启动时,HttpConnector会创建数量为minProcessors的HttpProcessor实例并放入池中;如果在某个时刻,请求的数量会多于当前池中HttpProcessor的数量,Tomcat会不断地创建HttpProcessor实例并放入池中,直至池中的数量达到maxProcessors。此时如果再有请求到来,Tomcat会选择忽略这些Http请求;当然,你可以讲maxProcessors设置为负数以取消这个限制。另外,代码中的curProcessors保存了当前池中HttpProcessor实例的数量。

再看前面createProcessor的代码,每次在池中创建新的Processor的时候,都会调用newProcessor方法,其实现如下:
    /**
     * Create and return a new processor suitable for processing HTTP
     * requests and returning the corresponding responses.
     */
    private HttpProcessor newProcessor() {

        //        if (debug >= 2)
        //            log("newProcessor: Creating new processor");
        HttpProcessor processor = new HttpProcessor(this, curProcessors++);
        if (processor instanceof Lifecycle) {
            try {
                ((Lifecycle) processor).start();
            } catch (LifecycleException e) {
                log("newProcessor", e);
                return (null);
            }
        }
        created.addElement(processor);
        return (processor);

    }

如前所述,我们照常忽略代码中的LifeCycle,这将会在以后的文章中解释;从上面可以看出,每次创建Processor,都会通过调用start方法,直接启动后台线程,事实上,Tomcat一旦启动Processor线程,就不会关闭它,即使处理完毕被放回到池中;除非是整个Tomcat被关闭。Processor后台线程的工作很简单,它会一直等待,一旦有socket返回(该Processor被分配到处理request),就立即处理,其代码如下:
    
     /**
     * The background thread that listens for incoming TCP/IP connections and
     * hands them off to an appropriate processor.
     */
    public void run() {

        // Process requests until we receive a shutdown signal
        while (!stopped) {

            // Wait for the next socket to be assigned
            Socket socket = await();
            if (socket == null)
                continue;

            // Process the request from this socket
            try {
                process(socket);
            } catch (Throwable t) {
                log("process.invoke", t);
            }

            // Finish up this request
            connector.recycle(this);

        }

        // Tell threadStop() we have shut ourselves down successfully
        synchronized (threadSync) {
            threadSync.notifyAll();
        }
    }

除非stopped置位,否则HttpProcessor会一直遵循“等待——处理——回收”这一流程周而复始。问题是,等待谁?看下await方法的具体实现:
   /**
     * Await a newly assigned Socket from our Connector, or <code>null</code>
     * if we are supposed to shut down.
     */
    private synchronized Socket await() {

        // Wait for the Connector to provide a new Socket
        while (!available) {
            try {
                wait();
            } catch (InterruptedException e) {
            }
        }

        // Notify the Connector that we have received this Socket
        Socket socket = this.socket;
        available = false;
        notifyAll();

        if ((debug >= 1) && (socket != null))
            log("  The incoming request has been awaited");

        return (socket);
    }

available的意思,应该是指目前连接的状态,它被声明为全局变量。整个await方法与下面的assign方法构成了一个同步队列,或者称为“单商品生产者消费者队列”。翻看前面粘贴Connector中的代码,可知其是在每次处理连接的时候调用的。
    /**
     * Process an incoming TCP/IP connection on the specified socket.  Any
     * exception that occurs during processing must be logged and swallowed.
     * <b>NOTE</b>:  This method is called from our Connector's thread.  We
     * must assign it to our own thread so that multiple simultaneous
     * requests can be handled.
     *
     * @param socket TCP socket to process
     */
    synchronized void assign(Socket socket) {

        // Wait for the Processor to get the previous Socket
        while (available) {
            try {
                wait();
            } catch (InterruptedException e) {
            }
        }

        // Store the newly available Socket and notify our thread
        this.socket = socket;
        available = true;
        notifyAll();

        if ((debug >= 1) && (socket != null))
            log(" An incoming request is being assigned");

    }

这里我们模拟两个线程(调用assign方法的Connector Thread以及调用await的Processor Thread)的通知方式。一开始由于没有请求的连接,因此available设置为false,因此线程一直在while循环中(确切地说是在wait方法中),直到其它线程调用notify或者notifyAll将其唤醒。

当连接请求到达时,一个socket被assign,此时由于前面available中已经被设置为false,因此跳过整个while-wait循环而直接进入到下面语句:
        
        this.socket = socket;
        available = true;
        notifyAll();

Connector Thread继续将available设置为true,并直接返回;此时它还不能接受另外的连接请求,因为available为true会导致它一直处在while-wait循环中(然而这只是表面原因,本质是由于该连接还没有被处理导致socket变量没能释放);接着notifyAll将会唤醒前面的Processor Thread。此时由于available被设为true,await方法会跳出while-wait循环而直接进入到下面语句:
        // Notify the Connector that we have received this Socket
        Socket socket = this.socket;
        available = false;
        notifyAll();

        if ((debug >= 1) && (socket != null))
            log("  The incoming request has been awaited");

        return (socket);

正如想象的那样,await方法创造了一个Socket的局部变量返回,同时释放了this.socket这一个实例变量,将available设置为false并调用notifyAll唤醒Connector Thread。现在,它又可以接收请求连接了……

整个过程中,Connect所需的时间要远小于Process,因此Tomcat将Connector与Processor分开来,并通过await与assign两个方法进行同步,以达到最优的效率。
1
2
分享到:
评论

相关推荐

    庖丁解牛——纵向切入ASP.NET 3.5 开发技术

    《庖丁解牛——纵向切入ASP.NET 3.5 开发技术》这本书聚焦于ASP.NET 3.5框架中的控件和组件开发,是深入理解Web应用程序开发的宝贵资源。 ASP.NET 3.5作为微软.NET Framework的重要组成部分,提供了丰富的功能和工具...

    地产行业专题报告:庖丁解牛——如何看2019年地产基本面.zip

    《地产行业专题报告:庖丁解牛——如何看2019年地产基本面》这份报告深入剖析了2019年中国房地产市场的基本态势,通过庖丁解牛的比喻,将复杂的市场现象分解为多个关键部分,帮助读者清晰理解地产行业的运行逻辑。...

    地产行业专题报告:庖丁解牛——如何看2019年地产基本面.pdf

    根据提供的文件内容,这篇地产行业专题报告主要围绕2019年地产市场的基本面情况进行深入分析,并提出相应的投资建议与风险提示。以下是报告中的主要知识点概述: 1. 房地产市场概况 报告指出,2019年一二线城市的房...

    《庖丁解牛》——优秀实用修改.ppt

    《庖丁解牛》——优秀实用修改.ppt

    《庖丁解牛》——优秀实用修改 .ppt

    《庖丁解牛》——优秀实用修改 .ppt

    2庖丁解牛——学生学习课件

    ### 2庖丁解牛——学生学习课件 #### 知识点概览 1. **庄子及其思想** - 庄子的基本生平与思想背景 - “天道无为”、“精神自由”等核心理念 - 庄子文章的特点:浪漫主义色彩与文学价值 2. **庖丁解牛的故事...

    《庖丁解牛》——实用修改.ppt

    文章通过“解牛之美”的分析,展现了庖丁解牛的动作犹如舞蹈,充满韵律和美感,这体现了技艺的熟练和对规律的把握。从“始臣之解牛之时”到“未尝见全牛也”,再到“以神遇而不以目视”,庖丁的技艺经历了从初识牛体...

    《庖丁解牛》——实用修改剖析.ppt

    《庖丁解牛》是《庄子》一书中的经典篇章,主要讲述了庖丁通过长期实践,精通解牛之道,从而达到了技艺出神入化的境地。这篇故事深入浅出地传达了庄子哲学的核心思想,包括对自然法则的顺应、心灵自由的追求以及辨证...

    C#庖丁解牛--asp.net开发

    《C#庖丁解牛——ASP.NET开发》是一本深入探讨C#编程语言与ASP.NET 3.5框架结合使用的专业书籍。通过“庖丁解牛”的比喻,作者旨在引导读者像古代名厨庖丁一样,精准而熟练地掌握ASP.NET 3.5中的控件和组件开发技术...

    庖丁解牛分词源码

    通过研究"庖丁解牛分词器"的源码,开发者不仅可以提升在中文分词领域的专业技能,也能深入理解Java编程、算法设计以及软件工程实践,对于从事自然语言处理和信息检索等相关领域的工作大有裨益。

    《庖丁解牛》——优秀实用课件.ppt

    《庖丁解牛》是《庄子》一书中的一个重要寓言故事,出自战国时期的道家代表人物庄子之手。庄子是中国古代哲学的重要人物,他的思想深受老子的影响,同老子并称为“老庄学派”。庄子的生平大约在公元前369年至前286年...

    文件分割压缩软件——庖丁解牛

    可只能的合并分割后的软件,效果很好;...庖丁解牛却比其它同类软件智能得多,能最大限度的减少你操作的步骤。分割后会生成Link.bat文件,在没有安装“文件分割机”的电脑上也能轻松合并文件。为纯绿色软件。

    经典的庖丁解牛通达信主图指标通达信指标公式源码.doc

    标题“经典的庖丁解牛通达信主图指标通达信指标公式源码.doc”表明该资源是一份关于通达信指标公式的经典实现,名称“庖丁解牛”来自中国古典小说《庄子》,指的是一位名叫庖丁的厨师,善于解牛,象征着该指标公式的...

    Linux驱动开发庖丁解牛系类

    "Linux驱动开发庖丁解牛系列"很可能是一个深入解析Linux驱动程序开发的教程或者一系列文档,旨在帮助开发者逐步理解并掌握这一复杂而重要的技术领域。 Linux驱动开发主要包括以下几个关键知识点: 1. **内核结构...

    庖丁解牛工具

    "庖丁解牛工具"是一款基于Java开发的文本分析工具,尤其在中文分词领域有着广泛的应用。这个工具的名字来源于中国古代寓言故事“庖丁解牛”,寓意对文本的精细处理和深入理解,就像庖丁对牛肉的熟练切割一样。在IT...

    庖丁解牛 源码 for Lucene 2.4

    《庖丁解牛 源码 for Lucene 2.4》是一份针对开源全文搜索引擎Lucene 2.4版本的深度解析资料。这个压缩包包含的文件名为"paoding-for-lucene-2.4",很可能是针对中文处理的Paoding Lucene库的源代码分析或扩展。...

    lucene 中文分词 庖丁解牛

    《Lucene中文分词:庖丁解牛》 在信息技术高速发展的今天,全文搜索引擎已经成为网站内容检索不可或缺的一部分。其中,Apache Lucene作为一个开源的全文检索库,被广泛应用于各种项目中,尤其对于处理中文文本,...

    ASP.NET3.5庖丁解牛示例源码

    "ASP.NET 3.5庖丁解牛示例源码"是针对ASP.NET 3.5开发的一系列实例代码,旨在帮助开发者深入理解和掌握该框架的核心概念和技术。这些示例可能涵盖了页面生命周期、控件事件处理、数据访问、状态管理、用户控件、母版...

    sorlr + tomcat+ 庖丁解牛中文分词 配置文档

    标题 "sorlr + tomcat+ 庖丁解牛中文分词 配置文档" 提到的是一个关于在Apache Solr中集成Tomcat服务器,并利用庖丁解牛中文分词工具进行中文处理的配置教程。这个配置过程对于搭建支持中文搜索的Solr环境至关重要。...

Global site tag (gtag.js) - Google Analytics