0 系列目录#
本篇文章将给大家讲述Servlet容器中请求处理的过程,在给本篇文章起标题时,一直在“应用服务器”与“Servlet容器”这两者之间拿捏不定,主要是因为要清晰的区分开这两者的关系:Servlet容器可以说是应用服务器的一个子集。又由于本文的初衷是讲述大家平常使用比较多的Servlet为主,所以,给本篇就起了《Servlet容器请求处理》的名字。
先说下在整个WEB请求处理过程中,本篇文章讲述的是哪个流程模块。为直观明了,先上一张图,红色部分为本章所述模块:
![](http://upload-images.jianshu.io/upload_images/2062729-51a5b660527f207a.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/960/format/webp)
所讲述的请求流程模块,大家已经很清楚了。那怎么给大家去讲的更清晰,大家理解的更容易呢?当然是,带着问题去学习,吸收或许会更快些啦。:)
开篇之前,给大家提以下几个问题,这些问题是本文的主体思路(也是个人学习路线):
WEB服务器那么多,Apache、Tomcat、Nginx、Jetty、Resin,名词那么多,HTTP Server、Application Server、Web Server、Servlet Container,他们是什么?之间关系是什么?区别又在哪?
CGI、WSGI、Servlet、JSP、FastCGI等等,他们是什么?他们之间区别又在哪?和上面WEB服务器之间关系是什么?
Servlet生命周期及工作原理是什么?
HTTP Request进入到Tomcat中执行,请求处理流程如何?如何找到对应的Application并进行请求处理?
1 WEB服务器#
只要Web上的Server都叫Web Server,但是大家分工不同,解决的问题也不同,所以根据Web Server提供的功能,每个Web Server的名字也会不一样。
按功能分类,Web Server可以分为:
|- Web Server
|- Http Server
|- Application Server
|- Servlet Container
|- CGI Server
|- ......
1.1 Http Server##
HTTP Server本质上也是一种应用程序——它通常运行在服务器之上,绑定服务器的IP地址并监听某一个tcp端口来接收并处理HTTP请求,这样客户端(一般来说是IE, Firefox,Chrome这样的浏览器)就能够通过HTTP协议来获取服务器上的网页(HTML格式)、文档(PDF格式)、音频(MP4格式)、视频(MOV格式)等等资源。下图描述的就是这一过程:
![](http://upload-images.jianshu.io/upload_images/2062729-06843ed113217f03.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/600/format/webp)
一个HTTP Server关心的是HTTP协议层面的传输和访问控制,所以在Apache/Nginx上你可以看到代理、负载均衡等功能。
- 客户端通过HTTP Server访问服务器上存储的静态资源(HTML文件、图片文件等等)。
- 通过CGI/Servlet技术,也可以将处理过的动态内容通过HTTP Server分发,但是一个HTTP Server始终只是把服务器上的文件如实的通过HTTP协议传输给客户端。
HTTP Server中经常使用的是Apache、Nginx两种,HTTP Server主要用来做静态内容服务、代理服务器、负载均衡等。直面外来请求转发给后面的应用服务(Tomcat,django什么的)。
|- Http Server
|- Apache
|- Nginx
1.1.1 Apache HTTP服务器###
Apache HTTP服务器是一个模块化的服务器,可以运行在几乎所有广泛使用的计算机平台上。Apache支持模块多,性能稳定,Apache本身是静态解析,适合静态HTML、图片等,但可以通过扩展脚本、模块等支持动态页面等。
Apache可以支持PHPcgiperl,但是要使用Java的话,你需要Tomcat在Apache后台支撑,将Java请求由Apache转发给Tomcat处理。
1.1.2 Nginx HTTP服务器###
Nginx是一个高性能的HTTP和反向代理服务器,同时也是一个IMAP/POP3/SMTP代理服务器。
其特点是占有内存少,并发能力强。Nginx代码完全用C语言从头写成。
具有很高的稳定性。其它HTTP服务器,当遇到访问的峰值,或者有人恶意发起慢速连接时,也很可能会导致服务器物理内存耗尽频繁交换,失去响应,只能重启服务器。例如当前apache一旦上到200个以上进程,web响应速度就明显非常缓慢了。
而Nginx采取了分阶段资源分配技术,使得它的CPU与内存占用率非常低。Nginx官方表示保持10000个没有活动的连接,它只占2.5M内存,所以类似DOS这样的攻击对nginx来说基本上是毫无用处的。就稳定性而言,Nginx比Lighthttpd更胜一筹。
1.1.3 Nginx与Apache比较###
Nginx相对于Apache的优点:
- 轻量级,同样启动WEB服务,比Apache占用更少的内存以及资源;
- 抗并发性能高,核心区别在于Apache是同步多进程模型,一个连接对应一个进程。Nginx是异步的,多个连接(万级别)可以对应一个进程;
- Nginx模块较少,配置简单,所以Nginx可以将资源用在数据处理以及进程上面,Apache模块较多比较全,相对稳定,但在内存资源上消耗比较大;
- Nginx可以在不间断的情况下进行软件版本的升级;
- Nginx处理静态页面性能比apache高3倍多;
选择高并发高性能就选择Nginx,如果要稳定,选择Apache,主要根据服务器要面临的需求而定。
当然,两者也可以组合使用:
- Nginx放前端+apache放后端+MYSQL+PHP:可以提高服务器负载能力
- Nginx处理静态页面请求如MP3,GIF.JPG.JS,apache处理动态页面请求,充分结合了二者的优势;
1.2 Application Server##
Application Server 是一个应用执行的服务器。它首先需要支持开发语言的 Runtime(对于 Tomcat 来说,就是 Java),保证应用能够在应用服务器上正常运行。其次,需要支持应用相关的规范,例如类库、安全方面的特性。与HTTP Server相比,Application Server能够动态的生成资源并返回到客户端。
|- Application Server
|- Tomcat
|- Jetty
当初在Apache Server开发时还未出现Servlet的概念,所以Apache不能内置支持Servlet。实际上,除了Apache,其他许多HTTP Server软件都不能直接支持Servlet。为了支持Servlet,通常要单独开发程序,这种程序一般称为服务器小程序容器(Servlet Container),有时也叫做服务器小程序引擎(Servlet Engine)。它是Web服务器或应用程序服务器的一部分,用于在发送的请求和响应之上提供网络服务,解码基于MIME的请求,格式化基于MIME的响应,它在Servlet的生命周期内包容和管理Servlet,是一个实时运行的外壳程序。运行时由Web服务器软件处理一般请求,并把Servlet调用传递给“容器”来处理。
比如,对于 Tomcat 来说,就是需要提供 JSP/Sevlet 运行需要的标准类库、Interface 等。为了方便,应用服务器往往也会集成 HTTP Server 的功能,但是不如专业的 HTTP Server 那么强大,所以Application Server往往是运行在 HTTP Server 的背后,执行应用,将动态的内容转化为静态的内容之后,通过 HTTP Server 分发到客户端。
![](http://upload-images.jianshu.io/upload_images/2062729-8cf5a891988e314f.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/600/format/webp)
Tomcat运行在JVM之上,它和HTTP服务器一样,绑定IP地址并监听TCP端口,同时还包含以下指责:
- 管理Servlet程序的生命周期;
- 将URL映射到指定的Servlet进行处理;
- 与Servlet程序合作处理HTTP请求——根据HTTP请求生成HttpServletRequest/Response对象并传递给Servlet进行处理,将Servlet中的HttpServletResponse对象生成的内容返回给浏览器;
所以 Tomcat 属于是一个「Application Server」,但是更准确的来说,是一个「Servlet/JSP」应用的容器(Ruby/Python 等其他语言开发的应用也无法直接运行在 Tomcat 上)。
1.2.1 Servlet容器工作模式###
按照工作模式的不同,Servlet容器可以分为以下3类:
- 独立运行的Servlet容器
在这种模式下,Servlet容器作为构成Web服务器的一部分而存在。当使用基于Java的Web服务器时,就属于这种情况。这种方式是Tomcat的默认模式,然而大多数Web服务器并不是基于Java的,所以就产生了下面的两种其他类型。
- 内置的Servlet容器
Servlet容器由Web服务器插件和Java容器两部分组成。采用这种方式时,Web服务器插件需要在某个Web服务器内部地址空间中打开一个JVM(Java虚拟机),在此JVM上加载Java容器并运行Servlet。如果客户端调用Servlet,Web服务器插件首先获得此请求的控制并将它传递(使用JNI技术)给Java容器,然后Java容器把此请求交给Servlet来处理。这种方式运行速度较快,并且能够提供良好的性能,适用于单进程、多线程服务器,但是在伸缩性方面存在不足。
- 外置的Servlet容器
采用这种方式时,Servlet容器运行在Web服务器外部地址空间。先由Web服务器插件在某个Web服务器外部地址空间打开一个JVM(Java虚拟机),然后加载Java容器来运行Servlet。Web服务器插件和JVM之间使用IPC(进程间通信)机制(通常是TCP/IPSockets)。如果客户端调用Servlet,Web服务器插件首先获得此请求的控制并将它传递(使用IPC技术)给Java容器,然后Java容器把此请求交给Servlet来处理。这种方式对客户端请求的处理速度不如内置Servlet那样快,但是在其他方面(如可伸缩性、稳定性等)具有优势。
Tomcat属于Servlet容器,其工作模式也分为上述3种,所以Tomcat既可被用作独立运行的Servlet引擎(便于开发和调试),又可作为一个需要增强功能的Web服务器(如当前的Apache、IIS和Netscape服务器)插件。在配置Tomcat之前,就需要确定采用哪种工作模式,工作模式(1)比较简单,直接安装Tomcat即可,工作模式(2)和(3)有些复杂,除了安装Tomcat、Web服务器之外,还需要安装连接两者的中间连接件。
1.2.2 Apache与Tomcat整合使用###
虽然Tomcat也可以认为是HTTP服务器,但通常它仍然会和Apache/Nginx配合在一起使用:
动静态资源分离——运用Nginx的反向代理功能分发请求:所有动态资源的请求交给Tomcat,而静态资源的请求(例如图片、视频、CSS、JavaScript文件等)则直接由Nginx返回到浏览器,这样能大大减轻Tomcat的压力;
负载均衡——当业务压力增大时,可能一个Tomcat的实例不足以处理,那么这时可以启动多个Tomcat实例进行水平扩展,而Nginx的负载均衡功能可以把请求通过算法分发到各个不同的实例进行处理;
整合的好处:
- 如果客户端请求的是静态页面,则只需要Apache服务器响应请求。
- 如果客户端请求动态页面,则是Tomcat服务器响应请求。
- 因为JSP是服务器端解释代码的,这样整合就可以减少Tomcat的服务开销。
2 什么是CGI#
如上文所述,HTTP服务器是一个很简单的东西,并不负责动态网页的构建,只能转发静态网页。事物总是不断发展,网站也越来越复杂,所以出现动态技术。同时Apache也说,它能支持perl,生成动态网页。这个支持perl,其实是Apache越位了,做了一件额外的事情。
既然HTTP Server自己不能做,外包给别人吧,但是要与第三做个约定,我给你什么,然后你给我什么,就是握把请求参数发送给你,然后我接收你的处理结果给客户端。那这个约定就是Common Gateway Interface,简称CGI。
CGI全称是“通用网关接口”(Common Gateway Interface),是HTTP服务器与你的或其它机器上的程序进行“交谈”的一种工具,其程序须运行在网络服务器上,是一种根据请求信息动态产生响应内容的接口协议。CGI可以用任何一种语言编写,只要这种语言具有标准输入、输出和环境变量。如php,perl,tcl等。
通过CGI,HTTP Server可以将根据请求不同启动不同的外部程序,并将请求内容转发给该程序,在程序执行结束后,将执行结果作为回应返回给客户端。也就是说,对于每个请求,都要产生一个新的进程进行处理。因为每个进程都会占有很多服务器的资源和时间,这就导致服务器无法同时处理很多的并发请求。另外CGI程序都是与操作系统平台相关的,虽然在互联网爆发的初期,CGI为开发互联网应用做出了很大的贡献,但是随着技术的发展,开始逐渐衰落。
所以,CGI的定义是:外部应用程序与HTTP 服务器之间的接口协议。
2.1 CGI工作原理##
HTTP Server与CGI程序请求处理流程:
![](http://upload-images.jianshu.io/upload_images/2062729-d4362f1690abe6c7.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/626/format/webp)
HTTP服务器将根据CGI程序的类型决定数据向CGI程序的传送方式,一般来讲是通过标准输入/输出流和环境变量来与CGI程序间传递数据。 如下图所示:
![](http://upload-images.jianshu.io/upload_images/2062729-2a42265d64384741.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/964/format/webp)
CGI程序通过标准输入(STDIN)和标准输出(STDOUT)来进行输入输出。此外CGI程序还通过环境变量来得到输入,操作系统提供了许多环境变量,它们定义了程序的执行环境,应用程序可以存取它们。HTTP服务器和CGI接口又另外设置了一些环境变量,用来向CGI程序传递一些重要的参数。CGI的GET方法还通过环境变量QUERY-STRING向CGI程序传递Form中的数据。
2.2 CGI环境变量##
下面是一些常用的CGI环境变量:
![](http://upload-images.jianshu.io/upload_images/2062729-a66fbee5cbdd5f0b.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1000/format/webp)
每当客户请求CGI的时候,HTTP服务器就请求操作系统生成一个新的CGI解释器进程(如php-cgi.exe),CGI的一个进程则处理完一个请求后退出,下一个请求来时再创建新进程。当然,这样在访问量很少没有并发的情况也行。可是当访问量增大,并发存在,这种方式就不适合了。于是就有了FastCGI。
3 什么是FastCGI#
FastCGI像是一个常驻(long-live)型的CGI,它可以一直执行着,只要激活后,不会每次都要花费时间去fork一次(这是CGI最为人诟病的fork-and-execute 模式)。它还支持分布式的运算, 即 FastCGI 程序可以在网站服务器以外的主机上执行并且接受来自其它网站服务器来的请求。
FastCGI是语言无关的、可伸缩架构的CGI开放扩展,其主要行为是将CGI解释器进程保持在内存中并因此获得较高的性能。众所周知,CGI解释器的反复加载是CGI性能低下的主要原因,如果CGI解释器保持在内存中并接受FastCGI进程管理器调度,则可以提供良好的性能、伸缩性、Fail- Over特性等等。
3.1 FastCGI工作原理##
- HTTP Server启动时载入FastCGI进程管理器(IIS ISAPI或Apache Module);
- FastCGI进程管理器自身初始化,启动多个CGI解释器进程(可见多个php-cgi)并等待来自HTTP Server的连接;
- 当客户端请求到达HTTP Server时,FastCGI进程管理器选择并连接到一个CGI解释器。HTTP Server将CGI环境变量和标准输入发送到FastCGI子进程php-cgi;
- FastCGI子进程完成处理后将标准输出和错误信息从同一连接返回HTTP Server。当FastCGI子进程关闭连接时,请求便告处理完成。FastCGI子进程接着等待并处理来自FastCGI进程管理器(运行在HTTP Server中)的下一个连接。在CGI模式中,php-cgi在此便退出了。
在上述情况中,你可以想象CGI通常有多慢。每一个Web请求PHP都必须重新解析php.ini、重新载入全部扩展并重初始化全部数据结构。使用FastCGI,所有这些都只在进程启动时发生一次。一个额外的好处是,持续数据库连接(Persistent database connection)可以工作。
3.2 FastCGI与CGI特点##
- 如CGI,FastCGI也具有语言无关性;
- 如CGI,FastCGI在进程中的应用程序,独立于核心web服务器运行,提供了一个比API更安全的环境。(API把应用程序的代码与核心的web服务器链接在一起,这意味着在一个错误的API的应用程序可能会损坏其他应用程序或核心服务器; 恶意的API的应用程序代码甚至可以窃取另一个应用程序或核心服务器的密钥。)
- FastCGI技术目前支持语言有:C/C++、Java、Perl、Tcl、Python、SmallTalk、Ruby等。相关模块在Apache, ISS, Lighttpd等流行的服务器上也是可用的。
- 如CGI,FastCGI的不依赖于任何Web服务器的内部架构,因此即使服务器技术的变化, FastCGI依然稳定不变。
4 什么是PHP-CGI#
PHP-CGI是PHP自带的FastCGI管理器。PHP-CGI的不足:
- PHP-CGI变更php.ini配置后,需重启PHP-CGI才能让新的php-ini生效,不可以平滑重启;
- 直接杀死PHP-CGI进程,php就不能运行了。(PHP-FPM和Spawn-FCGI就没有这个问题,守护进程会平滑从新生成新的子进程。)
5 什么是PHP-FPM#
PHP-FPM是一个PHP FastCGI管理器,是只用于PHP的,使用PHP-FPM来控制PHP-CGI的FastCGI进程,它负责管理一个进程池,来处理来自Web服务器的请求。可以在 http://php-fpm.org/download 下载得到。
相对Spawn-FCGI,PHP-FPM在CPU和内存方面的控制都更胜一筹,而且前者很容易崩溃,必须用crontab进行监控,而PHP-FPM则没有这种烦恼。
PHP-FPM提供了更好的PHP进程管理方式,可以有效控制内存和进程、可以平滑重载PHP配置,比Spawn-FCGI具有更多优点,所以被PHP官方收录了。在PHP 5.3.3中可以直接使用PHP-FPM了。
在./configure的时候带 –enable-fpm参数即可开启PHP-FPM。
5.1 PHP-FPM工作原理##
Apache+PHP配合使用,会在Apache配置下面一段:
LoadModule php5_module C:/php/php5apache2_2.dll
当PHP需要在Apache服务器下运行时,一般来说,它可以模块的形式集成,此时模块的作用是接收Apache传递过来的PHP文件请求,并处理这些请求,然后将处理后的结果返回给Apache。如果我们在Apache启动前在其配置文件中配置好了PHP模块,PHP模块通过注册apache2的ap_hook_post_config挂钩,在Apache启动的时候启动此模块以接受PHP文件的请求。
Apache的Hook机制是指:Apache允许模块(包括内部模块和外部模块,例如mod_php5.so,mod_perl.so等)将自定义的函数注入到请求处理循环中。换句话说,模块可以在Apache的任何一个处理阶段中挂接(Hook)上自己的处理函数,从而参与Apache的请求处理过程。mod_php5.so/php5apache2.dll就是将所包含的自定义函数,通过Hook机制注入到Apache中,在Apache处理流程的各个阶段负责处理php请求。
有人测试Nginx+PHP-FPM在高并发情况下可能会达到Apache+mod_php5的5~10倍,现在Nginx+PHP-FPM使用的人越来越多。
6 什么是Spawn-FCGI#
Spawn-FCGI是一个通用的FastCGI管理服务器,它是lighttpd中的一部份,很多人都用Lighttpd的Spawn-FCGI进行FastCGI模式下的管理工作,不过有不少缺点。而PHP-FPM的出现多少缓解了一些问题,但PHP-FPM有个缺点就是要重新编译,这对于一些已经运行的环境可能有不小的风险(refer)。
Spawn-FCGI目前已经独成为一个项目,更加稳定一些,也给很多Web 站点的配置带来便利。已经有不少站点将它与nginx搭配来解决动态网页。
6.1 PHP-FPM与Spawn-CGI对比##
PHP-FPM、Spawn-FCGI都是守护PHP-CGI的进程管理器。
PHP-FPM的使用非常方便,配置都是在PHP-FPM.ini的文件内,而启动、重启都可以从php/sbin/PHP-FPM中进行。更方便的是修改php.ini后可以直接使用PHP-FPM reload进行加载,无需杀掉进程就可以完成php.ini的修改加载。使用PHP-FPM可以使PHP有不小的性能提升。PHP-FPM控制的进程CPU回收的速度比较慢,内存分配的很均匀。
Spawn-FCGI控制的进程CPU下降的很快,而内存分配的比较不均匀。有很多进程似乎未分配到,而另外一些却占用很高。可能是由于进程任务分配的不均匀导致的。而这也导致了总体响应速度的下降。而PHP-FPM合理的分配,导致总体响应的提到以及任务的平均。
7 什么是Servlet#
Servlet最初是在1995年由James Gosling提出的,因为使用该技术需要复杂的Web服务器支持,所以当时并没有得到重视,也就放弃了。后来随着Web应用复杂度的提升,并要求提供更高的并发处理能力,Servlet被重新捡起,并在Java平台上得到实现,现在提起Servlet,指的都是Java Servlet。Java Servlet要求必须运行在Web服务器当中,与Web服务器之间属于分工和互补关系。确切的说,在实际运行的时候Java Servlet与Web服务器会融为一体,如同一个程序一样运行在同一个Java虚拟机(JVM)当中。与CGI不同的是,Servlet对每个请求都是单独启动一个线程,而不是进程。这种处理方式大幅度地降低了系统里的进程数量,提高了系统的并发处理能力。另外因为Java Servlet是运行在虚拟机之上的,也就解决了跨平台问题。如果没有Servlet的出现,也就没有互联网的今天。
在Servlet出现之后,随着使用范围的扩大,人们发现了它的一个很大的一个弊端。那就是为了能够输出HTML格式内容,需要编写大量重复代码,造成不必要的重复劳动。为了解决这个问题,基于Servlet技术产生了JavaServet Pages技术,也就是JSP。Servlet和JSP两者分工协作,Servlet侧重于解决运算和业务逻辑问题,JSP则侧重于解决展示问题。Servlet与JSP一起为Web应用开发带来了巨大的贡献,后来出现的众多Java Web应用开发框架都是基于这两种技术的,更确切的说,都是基于Servlet技术的。
7.1 Servlet生命周期##
作为一名专业编程人员,您碰到的大多数 Java servlet 都是为响应 Web 应用程序上下文中的 HTTP 请求而设计的。因此,javax.servlet 和 javax.servlet.http 包中特定于 HTTP 的类是您应该关心的。对于Servlet容器(Tomcat)与HttpServlet是怎样进行交互的呢,看下类图:
![](http://upload-images.jianshu.io/upload_images/2062729-fa39524956cd25e0.jpg?imageMogr2/auto-orient/strip%7CimageView2/2/w/672/format/webp)
Servlet的框架是由两个Java包组成的:javax.servlet与javax.servlet.http。在javax.servlet包中定义了所有的Servlet类都必须实现或者扩展的通用接口和类。在javax.servlet.http包中定义了采用Http协议通信的HttpServlet类。Servlet的框架的核心是javax.servlet.Servlet接口,所有的Servlet都必须实现这个接口。在Servlet接口中定义了5个方法,其中3个方法代表了Servlet的生命周期:
- init(ServletConfig)方法:负责初始化Servlet对象,在Servlet的生命周期中,该方法执行一次;该方法执行在单线程的环境下,因此开发者不用考虑线程安全的问题;
- service(ServletRequest req,ServletResponse res)方法:负责响应客户的请求;为了提高效率,Servlet规范要求一个Servlet实例必须能够同时服务于多个客户端请求,即service()方法运行在多线程的环境下,Servlet开发者必须保证该方法的线程安全性;
- destroy()方法:当Servlet对象退出生命周期时,负责释放占用的资源;
编程注意事项说明:
- 当Server Thread线程执行Servlet实例的init()方法时,所有的Client Service Thread线程都不能执行该实例的service()方法,更没有线程能够执行该实例的destroy()方法,因此Servlet的init()方法是工作在单线程的环境下,开发者不必考虑任何线程安全的问题。
- 当服务器接收到来自客户端的多个请求时,服务器会在单独的Client Service Thread线程中执行Servlet实例的service()方法服务于每个客户端。此时会有多个线程同时执行同一个Servlet实例的service()方法,因此必须考虑线程安全的问题。
- 请大家注意,虽然service()方法运行在多线程的环境下,并不一定要同步该方法。而是要看这个方法在执行过程中访问的资源类型及对资源的访问方式。分析如下:
如果service()方法没有访问Servlet的成员变量也没有访问全局的资源比如静态变量、文件、数据库连接等,而是只使用了当前线程自己的资源,比如非指向全局资源的临时变量、request和response对象等。该方法本身就是线程安全的,不必进行任何的同步控制。
如果service()方法访问了Servlet的成员变量,但是对该变量的操作是只读操作,该方法本身就是线程安全的,不必进行任何的同步控制。
如果service()方法访问了Servlet的成员变量,并且对该变量的操作既有读又有写,通常需要加上同步控制语句。
如果service()方法访问了全局的静态变量,如果同一时刻系统中也可能有其它线程访问该静态变量,如果既有读也有写的操作,通常需要加上同步控制语句。
如果service()方法访问了全局的资源,比如文件、数据库连接等,通常需要加上同步控制语句。
在创建一个 Java servlet 时,一般需要子类 HttpServlet。该类中的方法允许您访问请求和响应包装器(wrapper),您可以用这个包装器来处理请求和创建响应。大多数程序员都知道Servlet的生命周期,简单的概括这就分为四步:
Servlet类加载--->实例化--->服务--->销毁;
![](http://upload-images.jianshu.io/upload_images/2062729-7f3b114e0350a670.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/988/format/webp)
创建Servlet对象的时机:
- 默认情况下,在Servlet容器启动后:客户首次向Servlet发出请求,Servlet容器会判断内存中是否存在指定的Servlet对象,如果没有则创建它,然后根据客户的请求创建HttpRequest、HttpResponse对象,从而调用Servlet对象的service方法;
- Servlet容器启动时:当web.xml文件中如果<servlet>元素中指定了<load-on-startup>子元素时,Servlet容器在启动web服务器时,将按照顺序创建并初始化Servlet对象;
- Servlet的类文件被更新后,重新创建Servlet。Servlet容器在启动时自动创建Servlet,这是由在web.xml文件中为Servlet设置的<load-on-startup>属性决定的。从中我们也能看到同一个类型的Servlet对象在Servlet容器中以单例的形式存在;
注意:在web.xml文件中,某些Servlet只有
<serlvet>
元素,没有<servlet-mapping>
元素,这样我们无法通过url的方式访问这些Servlet,这种Servlet通常会在<servlet>
元素中配置一个<load-on-startup>
子元素,让容器在启动的时候自动加载这些Servlet并调用init(ServletConfig config)方法来初始化该Servlet。其中方法参数config中包含了Servlet的配置信息,比如初始化参数,该对象由服务器创建。
销毁Servlet对象的时机:
Servlet容器停止或者重新启动:Servlet容器调用Servlet对象的destroy方法来释放资源。以上所讲的就是Servlet对象的生命周期。那么Servlet容器如何知道创建哪一个Servlet对象?Servlet对象如何配置?实际上这些信息是通过读取web.xml配置文件来实现的。
<servlet>
<!-- Servlet对象的名称 -->
<servlet-name>action<servlet-name>
<!-- 创建Servlet对象所要调用的类 -->
<servlet-class>org.apache.struts.action.ActionServlet</servlet-class>
<init-param>
<!-- 参数名称 -->
<param-name>config</param-name>
<!-- 参数值 -->
<param-value>/WEB-INF/struts-config.xml</param-value>
</init-param>
<init-param>
<param-name>detail</param-name>
<param-value>2</param-value>
</init-param>
<init-param>
<param-name>debug</param-name>
<param-value>2</param-value>
</init-param>
<!-- Servlet容器启动时加载Servlet对象的顺序 -->
<load-on-startup>2</load-on-startup>
</servlet>
<!-- 要与servlet中的servlet-name配置节内容对应 -->
<servlet-mapping>
<servlet-name>action</servlet-name>
<!-- 客户访问的Servlet的相对URL路径 -->
<url-pattern>*.do</url-pattern>
</servlet-mapping>
当Servlet容器启动的时候读取<servlet>配置节信息,根据<servlet-class>配置节信息创建Servlet对象,同时根据<init-param>配置节信息创建HttpServletConfig对象,然后执行Servlet对象的init方法,并且根据<load-on-startup>配置节信息来决定创建Servlet对象的顺序,如果此配置节信息为负数或者没有配置,那么在Servlet容器启动时,将不加载此Servlet对象。当客户访问Servlet容器时,Servlet容器根据客户访问的URL地址,通过<servlet-mapping>配置节中的<url-pattern>配置节信息找到指定的Servlet对象,并调用此Servlet对象的service方法。
在整个Servlet的生命周期过程中,创建Servlet实例、调用实例的init()和destroy()方法都只进行一次,当初始化完成后,Servlet容器会将该实例保存在内存中,通过调用它的service()方法,为接收到的请求服务。下面给出Servlet整个生命周期过程的UML序列图,如图所示:
![](http://upload-images.jianshu.io/upload_images/2062729-89d2fd9e92040ff9.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/898/format/webp)
如果需要让Servlet容器在启动时即加载Servlet,可以在web.xml文件中配置<load-on-startup>元素。
7.2 Servlet工作原理##
上面描述了Servlet的生命周期,接着我们描述一下Tomcat与Servlet是如何工作的,首先看下面的时序图:
![](http://upload-images.jianshu.io/upload_images/2062729-14290e2fa96e4d4a.jpg?imageMogr2/auto-orient/strip%7CimageView2/2/w/894/format/webp)
- Web Client 向Servlet容器(Tomcat)发出Http请求;
- Servlet容器接收Web Client的请求;
- Servlet容器创建一个HttpRequest对象,将Web Client请求的信息封装到这个对象中;
- Servlet容器创建一个HttpResponse对象;
- Servlet容器调用HttpServlet对象的service方法,把HttpRequest对象与HttpResponse对象作为参数传给 HttpServlet对象;
- HttpServlet调用HttpRequest对象的有关方法,获取Http请求信息;
- HttpServlet调用HttpResponse对象的有关方法,生成响应数据;
- Servlet容器把HttpServlet的响应结果传给Web Client;
7.3 CGI与Servlet比较##
CGI应用开发比较困难,因为它要求程序员有处理参数传递的知识,这不是一种通用的技能。CGI不可移植,为某一特定平台编写的CGI应用只能运行于这一环境中。每一个CGI应用存在于一个由客户端请求激活的进程中,并且在请求被服务后被卸载。这种模式将引起很高的内存、CPU开销,而且在同一进程中不能服务多个客户。
Servlet对CGI的最主要优势在于一个Servlet被客户端发送的第一个请求激活,然后它将继续运行于后台,等待以后的请求。每个请求将生成一个新的线程,而不是一个完整的进程。多个客户能够在同一个进程中同时得到服务。一般来说,Servlet进程只是在Web Server卸载时被卸载。
Servlet提供了Java应用程序的所有优势——可移植、稳健、易开发。使用Servlet Tag技术,Servlet能够生成嵌于静态HTML页面中的动态内容。
综上,Servlet处于服务器进程中,它通过多线程方式运行其service方法,一个实例可以服务于多个请求,并且其实例一般不会销毁。 而CGI对每个请求都产生新的进程,服务完成后就销毁,所以效率上低于Servlet。
CGI与Servlet的对比:
对比一:当用户浏览器发出一个Http/CGI的请求,或者说调用一个CGI程序的时候,服务器端就要新启用一个进程(而且是每次都要调用),调用CGI程序越多(特别是访问量高的时候),就要消耗系统越多的处理时间,只剩下越来越少的系统资源,对于用户来说,只能是漫长的等待服务器端的返回页面了,这对于电子商务激烈发展的今天来说,不能不说是一种技术上的遗憾。
而Servlet充分发挥了服务器端的资源并高效的利用。每次调用Servlet时并不是新启用一个进程,而是在一个Web服务器的进程中共享和分离线程,而线程最大的好处在于可以共享一个数据源,使系统资源被有效利用。
对比二:传统的CGI程序,不具备平台无关性特征,系统环境发生变化,CGI程序就要瘫痪,而Servlet具备Java的平台无关性,在系统开发过程中保持了系统的可扩展性、高效性。
对比三:传统技术中,一般大都为二层的系统架构,即Web服务器+数据库服务器,导致网站访问量大的时候,无法克服CGI程序与数据库建立连接时速度慢的瓶颈,从而死机、数据库死锁现象频繁发生。而Servlet有连接池的概念,它可以利用多线程的优点,在系统缓存中事先建立好若干与数据库的连接,到时候若想和数据库打交道可以随时跟系统"要"一个连接即可,反应速度可想而知。
8 Tomcat工作原理#
Tomcat 的结构很复杂,但是 Tomcat 也非常的模块化,找到了 Tomcat 最核心的模块,您就抓住了 Tomcat 的“七寸”。下面是 Tomcat 的总体结构图:
![](http://upload-images.jianshu.io/upload_images/2062729-15bb966deef614f5.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1000/format/webp)
从上图可以看出Tomcat的核心是两个组件:连接器(Connector)和容器(Container)。Connector组件是负责生成请求对象和响应对象的,Tomcat默认的是HttpConnector,负责根据收到的Http请求报文生成Request对象和Response对象,并把这两个对象传递给Container,然后根据Response中的内容生成相应的HTTP报文。
Container是容器的父接口,所有子容器都必须实现这个接口,简单来说就是服务器部署的项目是运行在Container中的。Container里面的项目获取到Connector传递过来对应的的Request对象和Response对象进行相应的操作。
Connector可以根据不同的设计和应用场景进行替换。一个Container可以选择对应多个Connector。多个Connector和一个Container就形成了一个Service,有了Service就可以对外提供服务了。
Tomcat要为一个Servlet的请求提供服务,需要做三件事:
- 创建一个request对象并填充那些有可能被所引用的Servlet使用的信息,如参数,头部、cookies、查询字符串等。一个request对象就是javax.servlet.ServletRequest或javax.servlet.http.ServletRequest接口的一个实例。
- 创建一个response对象,所引用的servlet使用它来给客户端发送响应。一个response对象是javax.servlet.ServletResponse或javax.servlet.http.ServletResponse接口的一个实例。
- 调用servlet的service方法,并传入request和response对象。这里servlet会从request对象取值,给response写值。
- 根据servlet返回的response生成相应的HTTP响应报文。
既然我们已经抓到Tomcat的“七寸”,两个核心组件:连接器(Connector)和容器(Container),那这样从连接器(Connector)入手,来看下Tomcat处理HTTP请求的流程。
很多开源应用服务器都是集成tomcat作为web container的,而且对于tomcat的servlet container这部分代码很少改动。这样,这些应用服务器的性能基本上就取决于Tomcat处理HTTP请求的connector模块的性能。
8.1 Connector种类##
Tomcat源码中与connector相关的类位于org.apache.coyote包中,Connector分为以下几类:
Http Connector,基于HTTP协议,负责建立HTTP连接。它又分为BIO Http Connector与NIO Http Connector两种,后者提供非阻塞IO与长连接Comet支持。
AJP Connector,基于AJP协议,AJP是专门设计用来为tomcat与http服务器之间通信专门定制的协议,能提供较高的通信速度和效率。如与Apache服务器集成时,采用这个协议。
APR HTTP Connector,用C实现,通过JNI调用的。主要提升对静态资源(如HTML、图片、CSS、JS等)的访问性能。现在这个库已独立出来可用在任何项目中。Tomcat在配置APR之后性能非常强劲。
8.2 Connector配置##
对Connector的配置位于conf/server.xml文件中。
8.2.1 BIO HTTP/1.1 Connector配置###
<Connector port=”8080” protocol=”HTTP/1.1” maxThreads=”150”
connectionTimeout=”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为证书密码。
8.2.2 NIO HTTP/1.1 Connector配置###
<Connector port=”8080” protocol=”org.apache.coyote.http11.Http11NioProtocol”
maxThreads=”150” connectionTimeout=”20000” redirectPort=”8443” />
8.2.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
8.3 Tomcat架构模块##
![](http://upload-images.jianshu.io/upload_images/2062729-3da95ca10cc5d75f.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/804/format/webp)
- Server(服务器)是Tomcat构成的顶级构成元素,所有一切均包含在Server中,Server的实现类StandardServer可以包含一个到多个Services;
- 次顶级元素Service的实现类为StandardService调用了容器(Container)接口,其实是调用了Servlet Engine(引擎),而且StandardService类中也指明了该Service归属的Server;
- 接下来次级的构成元素就是容器(Container):主机(Host)、上下文(Context)和引擎(Engine)均继承自Container接口,所以它们都是容器。但是,它们是有父子关系的,在主机(Host)、上下文(Context)和引擎(Engine)这三类容器中,引擎是顶级容器,直接包含是主机容器,而主机容器又包含上下文容器,所以引擎、主机和上下文从大小上来说又构成父子关系,虽然它们都继承自Container接口。
- 连接器(Connector)将Service和Container连接起来,首先它需要注册到一个Service,它的作用就是把来自客户端的请求转发到Container(容器),这就是它为什么称作连接器的原因。
8.4 Tomcat运行流程##
![](http://upload-images.jianshu.io/upload_images/2062729-fefd9142bc563fd5.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1000/format/webp)
假设来自客户的请求为: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;
作者:猿码道
链接:https://www.jianshu.com/p/571c474279af
来源:简书
简书著作权归作者所有,任何形式的转载都请联系作者获得授权并注明出处。