`
dicmo
  • 浏览: 68553 次
  • 性别: Icon_minigender_1
  • 来自: 成都
社区版块
存档分类
最新评论

Coder 爱翻译 How Tomcat Works 第四章 第一部分

    博客分类:
  • j2ee
阅读更多
Chapter 4: Tomcat Default Connector
一个Tomcat连接器就是一个独立的模块,它可以被当做插件一样安置在servlet容器中。这里已经存在了许多连接器,包括:Coyote, mod_jk, mod_jk2, 和 mod_webapp都是连接器。作为一个Tomcat连接器,它必须符合一下要求:
 它必须实现org.apache.catalina.Connector接口
 它必须创建实现org.apache.catalina.Request接口的request对象。
 它必须创建实现org.apache.catalina.Response接口的response对象。

Tomcat 4的默认连接器跟第三章中的简单连接器很类似。它等来HTTP请求,创建request和response对象,然后把request和response对象传递给容器。一个连接器通过调用org.apache.catalina.Container interface'的invoke方法把request和response对象传递给容器。
public void invoke( 
   org.apache.catalina.Request request, 
   org.apache.catalina.Response response);
}

在invoke方法里面,连接器加载servlet类,调用service方法,管理session,记录log错误信息等。

默认的连接器也一些优化策略是与第三章的连接器不一样的。首先提供了不同的对象池来避免创建对象的开销。第二,在很多地方使用字符数组代替字符串。

这章的应用程序的连接器可以和默认连接器联系起来。默认的连接器实现了HTTP1.1到HTTP 0.9和1.0的所有特性。本章会讲解HTTP1.1的新特性。

HTTP 1.1 New Features

Persistent Connections (持久连接)

在HTTP1.1之前,一个浏览器不管什么时候连接到web服务器,这个连接会在服务器响应了请求资源后立马关闭。但是,一个网页可以包含其他资源:图片文件,applet等。当一个页面被请求,浏览器也需要通过这个页面下载相关资源。(创建新的连接请求该网页的其他相关资源)如果一个页面和这个页面相关的所有资源的下载使用不同的链接,这种处理方式会变得很慢。这就是为什么HTTP1.1提出的持久连接。有了持久连接,当一个页面被下载,这个服务器不会立马关闭连接。它会等待web客户端请求所有与该页面相关的资源。这种方式,一个网页和该网页相关的资源可以使用同一个连接下载。这样对于服务器,客户端和网络来所都节省了许多工作和时间。因为建立和拆除连接是开销很大的操作。

持久连接是HTTP1.1默认的连接。也可以明确告诉浏览器发送头信息的connection值:
connection: keep-alive

Chunked Encoding(块编码)

建立一个持久连接的结果是:服务器可以通过多媒体资源发送字节流,客户端可以接收使用同样的链接接收者多媒体资源。结果,发送者必须在每一个request和response的头部信息中发送内容的长度以便接收者知道怎么来解释这些字节。但是,通常情况是发送者不知道究竟要发送多少字节。例如:一个servlet容器在当持有一些可以(available)使用的字节就可以开始发送response,而不是在等到所有的都准备好了之后再一起发送。这种方式,必须有一种方法可以告诉接收者在不能提前知道content-length头部信息的情况下怎么解释字节流。

就算没有多媒体资源请求或许多响应,一个服务器或客户端不必要它要发送知道多少数据。在HTTP1.0。一个服务器可以忽略content-length这个头部信息,保持等待连接。当完成了,它就关闭连接。这种情况下,客户端在读取到以-l作为到达文件结束的标识符之前一直保持读取的状态。

HTTP1.1有一个叫做transfer-encoding的特殊头部信息表明字节流将会以块状形式发送。对于每一个块:长度(16进制数表示)后面紧跟CR/LF且位于要发送的数据之前。一个事务被用一个0(zero)标示的长度块。假设你发送下面的38个字节在两个块里面。第一个块长度为29,第二个块长度为9

I'm as helpless as a kitten up a tree.

你是按下面的格式发送出去:

1D\r\n
I'm as helpless as a kitten u
9\r\n
p a tree.
0\r\n

1D,是29的十六进制标示数,表明第一块是由29个字节组成。0\r\n表明是事务的结束。

Use of the 100 (Continue) Status (使用100状态号)

HTTP1.1客户端可以在发送请求体之前发送Expect:100-continue头部信息到服务器,然后等待服务器的确认。这是很正常的,如果客户端打算发送一个长请求体,但是不确定服务器是否会接受。如果客户端发送长请求体到服务器仅仅只是为了确认服务器是绝接收长请求体本身就是浪费。

在接受到Expect: 100-continue头部信息,服务器如果可以处理请求,就回应一个100-continue再加上两对CRLF字符的头部信息。

HTTP/1.1 100 Continue
这服务器就可以继续读取输入流。

The Connector Interface

一个Tomcat连接器必须实现org.apache.catalina.Connector接口。这个接口中最重要的方法就是:getContainer, setContainer, createRequest和createResponse.

getContainer方法用来把连接器和容器联系起来。GetContainer方法返回被联系的容器。CreateRequest给到来的HTTP请求构建一个request对象,createReponse创建response对象。

org.apache.catalina.connector.http.HttpConnector是一个实现了Connector接口的类,它将在下一节讨论。

一个Connector和一个Container是一对一的关系。

The HttpConnector Class

你在第三章中的简单版本的org.apache.catalina.connector.http.HttpConnector已经知道这个类是怎么工作的。它实现了org.apache.catalina.Connector接口(更合适与Catalina工作),java.lang.Runnable接口(这样它的实例可以工作在自己的线程里面),org.apache.catalina.Lifecycle接口。Lifecycle接口用来维护每一个实现了该接口的Catalina组件的生命周期。

Lifecycle在第六章讲解。现在你只需要了解:通过实现Lifecycle接口,你创建了一个HttpConnector实例后,你可以调用它的initialize核start方法。两个方法在组件的生命期间都只必须调用一次。你可以看到和第三章中HttpConnector的不同的地方:HttpConnector怎么创建一个服务器socket,怎么维护一个HttpProcessor池和为HTTP请求服务。

Creating a Server Socket

HttpConnector的initialize方法调用一个private修饰的的open方法。Open方法返回一个java.net.ServerSocket实例,把它指配给serverSocket。但是,不是调用java.net.ServerSocket的构造函数,open方法从一个服务器socket工厂中获取一个ServerSocket实例。如果你想知道这个工厂的细节,读在org.apache.catalina.net package包中的ServerSocketFactory接口和DefaultServerSocketFactory类。

Maintaining HttpProcessor Instances
HttpConnector实例在同一时刻只有一个HttpProcessor实例,所以它在一个时刻只能处理一个HTTP请求。在默认连接器,HttpConnector有一个HttpProcessor池对象。每一个HttpProcessor实例都有自己的线程。所以HttpConnector可以在同一时刻为多个HTTP请求服务。

HttpConnector维护一个HttpProcessor实例池来避免一直创建HttpProcessor对象。HttpProcessor实例被存储在一个叫做processors的java.io.Stack里面:
private Stack processors = new Stack();

在HttpConnector里,HttpProcessor实例被创建的数量是由minProcessors 和maxProcessors这两个变量决定。默认情况下,minProcessors被设置为5maxProcessors设置为20,但是你可以用setMinProcessors和setMaxProcessors方法来修该这些值。
protected int minProcessors = 5; 
 
private int maxProcessors = 20;

起初,HttpConnector对象创建minProcessors个HttpProcessor实例。如果在同一时刻有更多的超过HttpProcessor实例个数请求,HttpConnector创建更多HttpProcessor实例,直到实例数到达了maxProcessors。当到达了maxProcessors后,HttpProcessor实例还是不够请求使用,这些到来的HTTP请求将会被忽略。如果你想HttpConnector一直创建HttpProcessor实例,把maxProcessors值设置为负数。此外,curProcessors变量保存当前的HttpProcessor实例数。
HttpConnector类start方法创建HttpProcessor实例。

  while (curProcessors < minProcessors) { 
     if ((maxProcessors > 0) && (curProcessors >= maxProcessors)) 
       break; 
     HttpProcessor processor = newProcessor(); 
     recycle(processor); 
   }

newProcessor方法创建一个新的HttpProcessor对象,curProcessors增加。recycle方法把HttpProcessor推回栈中。

每一个HttpProcessor实例负责解析HTTP请求行,头部信息和填充request对象。此外,每一个HttpProcessor实例把一个request对象和一个response对象关联起来。HttpProcessor类的构造函数包含了调用HttpConnector类的createRequest方法和createResponse方法。

Serving HTTP Requests

HttpConnector类在它的run方法里有它的主逻辑:包含一个while循环,服务器socket一直等待HTTP请求,知道HttpConnector被停止。
   while (!stopped) { 
     Socket socket = null; 
     try { 
       socket = serverSocket.accept(); 
     ...

对于每一个到服务器访问的HTTP的请求,它通过调用createProcessor方法获得一个HttpProcessor实例。
   HttpProcessor processor = createProcessor();

但是,大多数时间createProcessor方法不会创建一个新的HttpProcessor对象,而是在HttpProcessor池中获得一个HttpProcessor实例。如果任然有一个HttpProcessor实例在栈中,createProcessor从栈中弹出一个HttpProcessor实例。如果栈是空的且还没达到HttpProcessor实例的最大数量,createProcessor创建一个HttpProcessor实例。但是如果到达HttpProcessor实例的最大数量,createProcessor返回null。如果发生了,socket只是简单的关闭,来访的HTTP请求不会被处理。
if (processor == null) { 
       try { 
         log(sm.getString("httpConnector.noProcessor")); 
         socket.close(); 
       } 
       ... 
       continue;
 
如果createProcessor不返回null,客户端socket被传递给HttpProcessor类的assign方法:

processor.assign(socket);

现在就该HttpProcessor实例读取socket的输入流和解析HTTP请求。很重要的一点是。assign方法必须马上返回,不能等到HttpProcessor处理完解析工作才返回。这样才能让下一个来访的HTTP请求得到服务。每一个HttpProcessor实例有它自己的线程来解析,这也很容易办到。

The HttpProcessor Class

HttpProcessor类在默认连接器是一个完整的版本。你可以知道它是怎么工作,HttpProcessor类让它的assign方法实现异步,这样就可以让HttpConnector实例在同一时刻服务许多的HTTP请求。
注意:HttpProcessor类另外一个很重要的方法:process方法。将诶西HTTP请求和调用容器的invoke方法。

第三章,HttpConnector运行在自己的线程中。但是,它还必须等待当前处理的HTTP请求完成后才能处理下一个请求。下面是第三章中的run方法:
public void run() { 
   ... 
    while (!stopped) { 
      Socket socket = null; 
      try { 
        socket = serversocket.accept();
}  catch (Exception e) { 
         continue; 
       } 
       // Hand this socket off to an Httpprocessor 
       HttpProcessor processor = new Httpprocessor(this); 
       processor.process(socket); 
     } 
   }

第三章HttpProcessor类的process方法是同步的。所以,它的run方法必须等到process方法处理完成了一个请求后,才能接收下一个请求。

在默认的连接器里,HttpProcessor类实现了java.lang.Runnable接口,每一个HttpProcessor实例运行在自己的被叫做“processor thread”的线程里。下面是HttpProcessor类的run方法:
Listing 4.1: The HttpProcessor class's run method.   
 
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(); 
   } 
}

run方法里的while循环一直保持做下面的处理:获得一个socket,处理socket,调用容器的recycle方法把当前的HttpProcessor实例推回到栈中。下面是HttpConenctor类的recycle方法:
void recycle(HttpProcessor processor) { 
    processors.push(processor); 
}

注意到这run方法里的while循环的结束受控于await方法。await方法持有"processor thread"的控制流,直到它从HttpConnector获得一个新的socket。换句话说,直到HttpConnector调用HttpProcessor实例的assign方法。await方法跟assign方法是运行在不同的线程里。assign方法是在HttpConnector 的run方法里面调用的。我们把这个HttpConnector实例run方法里面运行的线程叫做"connector thread"。assign方法怎么通知await方法,告诉它assign方法被调用了呢?使用一个叫做available的boolean变量和使用java.lang.Object的wait和notifyAll方法。

注意:wait方法让当前的线程处于等待状态,直到另外一个线程调用notify或notifyAll方法,这个等待的线程就恢复运行。
这里是HttpProcessor类的assign和await方法:
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(); 
   ... 
} 

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); 
}

所有方法的程序流在下面总结出来
The processor thread (the await method)    The connector thread (the assign method) 
while (!available) {   			          while (available) {   
wait();   				                wait();  
}   					             }
Socket socket = this.socket;   		          this.socket = socket;  
available = false;   			          available = true;
notifyAll();   			                  notifyAll();   
...                                     
return socket;                            
// to the run                           
// method                                 

首先,当"processor thread"开始,available值是false,线程在while循环里面等待。直到另外一个线程调用notify或notifyAll结束循环。就是说调用wait方法让"processor thread"暂停,直到"connector thread"的HttpProcessor实例调用notifyAll方法。

现在,当一个新的socket被指配,"connector thread"调用HttpProcessor的assign方法。available的值为faslse,所以从while循环跳出,socket被指配到HttpProcessor实例的socket变量:
this.socket = socket;

然后"connector thread"把available值设置为true,调用notifyAll方法。这是唤醒"processor thread",现在available的值是true,程序跳出while循环:把实例的socket指配给一个局部变量,把available设置为false,调用notifyAll方法,返回这个socket,让这个socket得到处理。

为什么await方法需要使用局部变(socket)而不是返回一个实例的socket变量呢?这是因为实例socket变量可以在当前socket被处理完毕之前,这个实例socket变量可以指配给下一个到来的socket。

为什么await方法需要调用notifyAll?当available变量是true时另外一个socket到达了。在这种情况,"connector thread"的assign方法里面的while循环在"processor thread"中的notifyAll方法得到调用时才停止。
2
0
分享到:
评论

相关推荐

    tomcat原理解析书(how-tomcat-works)中文版

    适合读者 1.jsp/servlet 开发人员,想了解 tomcat 内部机制的 coder; 2.想加入 tomcat 开发团队的 coder; 3.web 开发人员,但对软件开发很有兴趣的 coder; 4.想要对 tomcat 进行定制的 coder。

    Bad Programming Practices 101 Become a Better Coder by Learning How (Not) epub

    Bad Programming Practices 101 Become a Better Coder by Learning How (Not) to Program 英文epub 本资源转载自网络,如有侵权,请联系上传者或csdn删除 查看此书详细信息请在美国亚马逊官网搜索此书

    phpcoder.rar

    PHPCoder是一款专为PHP开发者设计的高效代码编辑器,旨在提供一个轻量级且功能丰富的开发平台。这款软件以其便捷性、易用性和强大的功能深受程序员喜爱。在本篇文章中,我们将深入探讨PHPCoder的各项特性、安装过程...

    MediaCoder答题器

    6. 插件扩展:MediaCoder有一个开放的插件架构,用户可以通过安装第三方插件来增加新的编码解码器、滤镜和其他功能,扩展其应用范围。 7. 用户友好:MediaCoder的界面简洁直观,即便是对编码不熟悉的用户也能快速...

    mediacoder专业版

    mediacoder 5685专业版,无普通版的限制

    simulink hdl coder 用户手册pdf

    综上所述,Simulink HDL Coder 作为一款先进的工具,极大地简化了从算法设计到 FPGA 实现的整个过程,是现代电子设计自动化领域不可或缺的一部分。通过掌握其使用方法和技术要点,工程师们能够更高效地完成复杂的 ...

    Mediacoder基础教程

    Mediacoder是一款强大的多媒体编码工具,专为音频和视频转换而设计,支持多种格式,如MP4、AVI、MKV等。本教程将深入讲解如何利用Mediacoder进行视频压制,优化视频质量,同时合理权衡码率与视频大小的关系。 **1. ...

    matlab Embedded Coder Getting Started Guide.pdf

    Embedded Coder用于产生嵌入式处理器、目标快速原型板和大规模生产中使用的微处理器的可读的、紧凑的、快速的C和C++代码。Embedded Coder支持附加的MATLAB Coder™和Simulink Coder™配置选项,以及对生成代码的功能...

    The Clean Coder

    Martin, "The Clean Coder: A Code of Conduct for Professional Programmers" Prentice Hall | 2011 | ISBN: 0137081073 | 256 pages | PDF | 6 MB Programmers who endure and succeed amidst swirling ...

    CoolCoder 类生成工具

    虽然CoolCoder能够自动化大部分工作,但开发者依然可以自定义模板,对生成的代码进行微调,以满足特定项目的需求。这种灵活性确保了生成的代码既高效又符合项目风格。 6. **提高开发效率**: 使用CoolCoder,...

    MediaCoder

    MediaCoder是一款功能强大的多媒体转换工具,它支持广泛的音频和视频编码格式,使用户能够轻松地在不同设备之间转换媒体文件。这款软件适用于个人用户、专业音频和视频制作人员,以及那些希望在各种设备上享受多媒体...

    php coder编辑器

    PHPCoder用于快速开发和调试PHP应用程序,它很容易扩展和定制,完全能够符合开发者的个性要求.PHPCoder是一个非常实用的,功能强大的编程环境,而且它是免费的!

    matlab coder基本函数教程

    ### MATLAB Coder 基本函数教程 #### MATLAB Coder 概述 MATLAB Coder 是一款能够将 MATLAB 代码转换成独立的 C 或 C++ 代码的强大工具。这一过程对于那些希望在非 MATLAB 环境下部署 MATLAB 代码的应用开发者来说...

    HDL-Coder详细教程

    ### HDL-Coder详细教程知识点概述 #### 一、生成HDL代码前的准备工作 在开始从Simulink模型生成HDL代码之前,需要完成一系列的准备工作,确保模型能够顺利生成高质量的代码。 ##### 1.1 使用`hdlsetup`进行模型...

    MediaCoder.5755专业破解版

    MediaCoder行业版一款针对VOD及KTV视频点播行业开发的,用于转换和处理带有多音轨内容的视频节目的软件。它具备业界领先的视频编码引擎,在高性能转码的同时保持高画质,并通过丰富的视频滤镜增强画面视觉效果。作为...

    MediaCoder使用说明文档

    MediaCoder使用说明文档, mediaCoder usermanual,

    MatlabCoder使用-Matlab Coder的基本使用.pdf

    Matlab Coder是Mathworks公司推出的一款用于将Matlab代码转换成高效C代码的工具。从2004年开始,Matlab陆续在Simulink中添加了Embeded Matlab Function模块,2007年在Real-Time Workshop中引入了emlc函数(现在的...

    Embedded Coder.rar

    texasinstrumentsc2000.mlpkginstall 支持TI的C2000系列工具包,要求MATLAB R2017a及其以上版本。 安装方法:打开matlab,调整路径到mlpkginstall文件所在目录;在current folder窗口里双击mlpkginstall文件即可开始...

    MediaCoder64位专业破解版

    MediaCoder-Premium-x64 MediaCoder是最早开始使用GPU进行视频编码加速的影音转码软件之一。通过将原本完全依赖CPU的计算工作转移到GPU上进行,H.264和H.265编码所需的时间被大幅缩短。

Global site tag (gtag.js) - Google Analytics