一.什么是tomcat

Tomcat是一个JSP/Servlet容器。Tomcat 服务器是一个免费的开放源代码的Web 应用服务器,属于轻量级应用服务器,在中小型系统和并发访问用户下被普遍使用,是开发和调试JSP 程序的首选。负责处理客户请求,把请求传送给Servlet并把结果返回给客户。

 

 

二.tomcat文件

tomcat根目录在tomcat中叫<CATALINA_HOME>。

<CATALINA_HOME>/bin:存放各种平台下启动和关闭Tomcat的脚本文件,其中有个档是catalina.bat,打开这个windos配置文件, startup.bat是windows下启动tomcat的文件,shutdown.bat是关闭tomcat的文件。

<CATALINA_HOME>/conf:存放不同的配置文件(如:server.xml和web.xml)

server.xml文件:Tomcat的主配置文件,包含Service, Connector, Engine, Realm, Valve, Hosts主组件的相关配置信息。

web.xml文件:部署描述文件,这个web.xml中描述了一些默认的servlet,部署每个webapp时,都会调用这个文件,配置该web应用的默认servlet,一般用来配置全局设置、数据源,如session生命周期。

tomcat-user.xml:Realm认证时用到的相关角色、用户和密码等信息;Tomcat自带的manager默认情况下会用到此文件;在Tomcat中添加/删除用户,为用户指定角色等将通过编辑此文件实现。

context.xml:定义web应用的默认行为。

catalina.policy:Java相关的安全策略配置文件,在系统资源级别上提供访问控制的能力;

catalina.properties:Tomcat内部package的定义及访问相关控制,也包括对通过类装载器装载的内容的控制;Tomcat在启动时会事先读取此文件的相关设置;

logging.properties: Tomcat通过自己内部实现的JAVA日志记录器来记录操作相关的日志,此文件即为日志记录器相关的配置信息,可以用来定义日志记录的组件级别以及日志文件的存在位置等;

Tomcat依赖<CATALINA_HOME>/conf/server.xml这个配置文件启动server,Tomcat部署Webapp时,依赖context.xml和web.xml(他们定义一些默认行为,具体的要在每个项目webapp的WEB-INF/web.xml 来定义了每个webapp特定的行为)

<CATALINA_HOME>/lib:存放Tomcat运行需要的库文件(JARS),一些需要的jar包,如servlet-api.jar和jsp-api.jar; 
<CATALINA_HOME>/logs:存放Tomcat执行时的日志文件; 
<CATALINA_HOME>/temp: 这个目录是用于临时存放,服务器编译过后的jsp->servlet->字节码文件。都是临时存放,这样不必每次都编译,提高速度。

<CATALINA_HOME>/webapps:Tomcat的主要Web发布目录(包括应用程序示例); 
<CATALINA_HOME>/work:存放jsp编译后产生的class文件;

 

三.tomcat架构和组件

由上图可看出Tomca的心脏是两个组件:Connector和Container(Engine,Host,Context,Wrapper)。一个Container可以选择多个Connecter,多个Connector和一个Container就形成了一个Service。Service可以对外提供服务,而Server服务器控制整个Tomcat的生命周期。

1、Server组件

各Server的定义不能使用同一个端口,这意味着如果在同一个物理机上启动了多个Server实例,必须配置它们使用不同的端口。

最高的组件就是 Server,而控制 Server 的是 Startup,也就是您启动和关闭 Tomcat。其中可以包含一个或者多个Service元素。

 

2.Service组件

主要用于关联一个引擎和与此引擎相关的连接器,每个连接器通过一个特定的端口和协议接收入站请求交将其转发至关联的引擎进行处理。包含一个Engine元素,以及一个或者个多个Connector元素,这些Connector共享同一个Engine元素,负责处理所有Connector所获得的客户请求。

 

3.Connector组件

一个Connecter将在某个指定的端口上侦听客户请求,接收浏览器的发过来的 tcp 连接请求,创建一个 Request 和 Response 对象分别用于和请求端交换数据,然后会产生一个线程来处理这个请求并把产生的 Request 和 Response 对象传给处理Engine(Container中的一部分),从Engine出获得响应并返回客户。

 

 

4.Container组件

Container是容器的父接口,该容器的设计用的是典型的责任链的设计模式,它由四个自容器组件构成,分别是Engine、Host、Context、Wrapper。这四个组件是负责关系,存在包含关系。通常一个Servlet class对应一个Wrapper,如果有多个Servlet定义多个Wrapper,如果有多个Wrapper就要定义一个更高的Container,如Context。Context 还可以定义在父容器 Host 中,Host 不是必须的,但是要运行 war 程序,就必须要 Host,因为 war 中必有 web.xml 文件,这个文件的解析就需要 Host 了,如果要有多个 Host 就要定义一个 top 容器 Engine 了。而 Engine 没有父容器了,一个 Engine 代表一个完整的 Servlet 引擎。

1.Engine 容器 
Engine 容器比较简单,它只定义了一些基本的关联关系,Engine下可以配置多个虚拟主机Virtual Host,每个虚拟主机都有一个域名
当Engine获得一个请求时,它把该请求匹配到某个Host上,然后把该请求交给该Host来处理,Engine有一个默认虚拟主机,当请求无法匹配到任何一个Host上的时候,将交给该默认Host来处理。

2.Host 容器 
Host 是 Engine 的字容器,一个 Host 在 Engine 中代表一个虚拟主机,这个虚拟主机的作用就是运行多个应用,它负责安装和展开这些应用,并且标识这个应用以便能够区分它们。它的子容器通常是 Context,它除了关联子容器外,还有就是保存一个主机应该有的信息。一个虚拟主机下都可以部署一个或者多个Web App,每个Web App对应于一个Context,当Host获得一个请求时,将把该请求匹配到某个Context上,然后把该请求交给该Context来处理。

3.Context 容器 
Context 代表 Servlet 的 Context,它具备了 Servlet 运行的基本环境,理论上只要有 Context 就能运行 Servlet 了。简单的 Tomcat 可以没有 Engine 和 Host。Context 最重要的功能就是管理它里面的 Servlet 实例,Servlet 实例在 Context 中是以 Wrapper 出现的,还有一点就是 Context 如何才能找到正确的 Servlet 来执行它呢? Tomcat5 以前是通过一个 Mapper 类来管理的,Tomcat5 以后这个功能被移到了 request 中,在前面的时序图中就可以发现获取子容器都是通过 request 来分配的。一个Context对应于一个Web Application,一个Web Application由一个或者多个Servlet组成。

4.Wrapper 容器 
Wrapper 代表一个 Servlet,它负责管理一个 Servlet,包括的 Servlet 的装载、初始化、执行以及资源回收。Wrapper 是最底层的容器,它没有子容器了,所以调用它的 addChild 将会报错。 

 

总结一下tomcat启动过程:Tomcat 先根据/conf/server.xml 下的配置启动Server,再加载Service,对于与Engine相匹配的Host,每个Host 下面都有一个或多个Context。Web Application 对应一个Context,每个Web Application 由一个或多个Servlet 组成。当一个Web Application 被初始化的时候,它将用自己的ClassLoader 对象载入部署配置文件web.xml 中定义的每个Servlet 类。每个被载入的Servlet 类都有一个名字,且被填入该Context 的映射表(mapping table)中,和某种URL 路径对应。当该Context 获得请求时,将查询mapping table,找到被请求的Servlet,并执行以获得请求响应。

 

 

四.tomcat处理HTTP请求过程

tomcat实际是运行在jvm中的一个进程。他是一个在java项目与jvm之间的中间容器。我们的web项目没有入口方法(main方法)意味着web项目中的方法不会自动运行起来。这样,我们想想也知道,我们把web项目部署进tomcat的webapp中的目的是很明确的,那就是希望tomcat去调用我们写好的方法去为客户端返回需要的资源和数据。对于tomcat而言,它并不知道我们会有什么样的方法,这些都只是在项目被部署进webapp下后才确定的,由此分析,必然用到了java的反射来实现类的动态加载、实例化、获取方法、调用方法。

 

第一步:用户点击网页内容,请求被发送到本机端口8080,被在那里监听的Coyote HTTP/1.1 Connector获得。 
第二步:Connector把该请求交给它所在的Service的Engine来处理,并等待Engine的回应。 
第三步:Engine获得请求localhost/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

 

 

五.模拟tomcat

tomcat是通过socket和浏览器获得连接,因为可能有多个请求,所以要用到多线程去接收,通过io流来传递数据。

package Server;

import java.io.*;
import java.net.ServerSocket;
import java.net.Socket;
import java.net.URLDecoder;
import java.util.StringTokenizer;

public class TomcatServer {

	private final static int PORT = 8080;

	public static void main(String[] args) {

		try {
			ServerSocket server = new ServerSocket(PORT);// 根据端口号启动一个serverSocket
			ServletHandler servletHandler = new ServletHandler(server);
			servletHandler.start();
		} catch (Exception e) {
			e.printStackTrace();
		}
	}

	private static class ServletHandler extends Thread {

		ServerSocket server = null;

		public ServletHandler(ServerSocket server) {
			this.server = server;
		}

		@Override
		public void run() {

			while (true) {
				try {
					Socket client = null;
					client = server.accept();// ServerSocket阻塞等待客户端请求数据
					if (client != null) {
						try {
							System.out.println("接收到一个客户端的请求");

							// 根据客户端的Socket对象获取输入流对象。

							// 封装字节流到字符流

							BufferedReader reader = new BufferedReader(new InputStreamReader(client.getInputStream()));

							// GET /test.jpg /HTTP1.1

							// http请求由三部分组成,分别是:请求行、消息报头、请求正文。

							// 这里取的第一行数据就是请求行。http协议详解可以参考http://www.cnblogs.com/li0803/archive/2008/11/03/1324746.html说的很详细

							String line = reader.readLine();
							System.out.println("line: " + line);
							// 拆分http请求路径,取http需要请求的资源完整路径

							String resource = line.substring(line.indexOf('/'), line.lastIndexOf('/') - 5);

							System.out.println("the resource you request is: " + resource);

							resource = URLDecoder.decode(resource, "UTF-8");

							// 获取到这次请求的方法类型,比如get或post请求

							String method = new StringTokenizer(line).nextElement().toString();

							System.out.println("the request method you send is: " + method);

							// 继续循环读取浏览器客户端发出的一行一行的数据

							while ((line = reader.readLine()) != null) {

								if (line.equals("")) {// 当line等于空行的时候标志Header消息结束

									break;

								}

								System.out.println("the Http Header is : " + line);

							}

							// 如果是POST的请求,直接打印POST提交上来的数据

							if ("post".equals(method.toLowerCase())) {

								System.out.println("the post request body is: "

										+ reader.readLine());

							} else if ("get".equals(method.toLowerCase())) {

								// 判断是get类型的http请求处理

								// 根据http请求的资源后缀名来确定返回数据

								// 比如下载一个图片文件,我这里直接给定一个图片路径来模拟下载的情况

								if (resource.endsWith(".jpg")) {

									transferFileHandle("d://1.jpg", client);

									closeSocket(client);

									continue;

								} else {

									// 直接返回一个网页数据

									// 其实就是将html的代码以字节流的形式写到IO中反馈给客户端浏览器。

									// 浏览器会根据http报文“Content-Type”来知道反馈给浏览器的数据是什么格式的,并进行什么样的处理

									PrintStream writer = new PrintStream(client.getOutputStream(), true);

									writer.println("HTTP/1.0 200 OK");// 返回应答消息,并结束应答

									writer.println("Content-Type:text/html;charset=utf-8");

									writer.println();

									// writer.println("Content-Length:" +
									// html.getBytes().length);// 返回内容字节数

									writer.println("<html><body>");

									writer.println("<a href='www.baidu.com'>百度</a>");

									writer.println(
											"<img src='https://ss0.bdstatic.com/5aV1bjqh_Q23odCf/static/superman/img/logo/bd_logo1_31bdc765.png'></img>");

									writer.println("</html></body>");

									// writer.println("HTTP/1.0 404 Not
									// found");// 返回应答消息,并结束应答

									writer.println();// 根据 HTTP 协议, 空行将结束头信息

									writer.close();

									closeSocket(client);// 请求资源处理完毕,关闭socket链接

									continue;

								}

							}

						} catch (Exception e) {

							System.out.println("HTTP服务器错误:"

									+ e.getLocalizedMessage());

						}

					}

				} catch (Exception e) {

					e.printStackTrace();

				}

			}

		}

		private void closeSocket(Socket socket) {

			try {

				socket.close();

			} catch (IOException ex) {

				ex.printStackTrace();

			}

			System.out.println(socket + "离开了HTTP服务器");

		}

		private void transferFileHandle(String path, Socket client) {

			File fileToSend = new File(path);

			if (fileToSend.exists() && !fileToSend.isDirectory()) {

				try {

					// 根据Socket获取输出流对象,将访问的资源数据写入到输出流中

					PrintStream writer = new PrintStream(client.getOutputStream());

					writer.println("HTTP/1.0 200 OK");// 返回应答消息,并结束应答

					writer.println("Content-Type:application/binary");

					writer.println("Content-Length:" + fileToSend.length());// 返回内容字节数

					writer.println();// 根据 HTTP 协议, 空行将结束头信息

					FileInputStream fis = new FileInputStream(fileToSend);

					byte[] buf = new byte[fis.available()];

					fis.read(buf);

					writer.write(buf);

					writer.close();

					fis.close();

				} catch (IOException e) {

					e.printStackTrace();

				}

			}

		}

	}

}

 

Logo

权威|前沿|技术|干货|国内首个API全生命周期开发者社区

更多推荐