tomcat架构原理

目录
前言
Tomcat底层架构组成
1、Context——servlet容器
2、Host——servlet容器
3、Engine——servlet容器
4、Wrapper——servlet容器
5、Pipiline管道
Tomcat的请求处理流程
Tomcat架构平视图
Java Socket底层实现
Connector组件
1、BIO的方式取数据
2、NIO的方式取数据
3、解析数据
前言
基于Tomcat7

1、Tomcat是一个Servlet容器。
2、使用Java代码模拟一个Tomcat容器:

class Tomcat{
List servlets;
Connector connect;//处理请求,生成了Request
}
1
2
3
4
3、回顾servlet的定义

public class MyHttpServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
resp.getWriter().println(“http”);
}
}
1
2
3
4
5
6
问题1:我们定义的servlet,它的实例是怎么生成的?doGet方法又是怎么调用到的呢?在哪执行的?

MyHttpServlet myHttpServlet=new MyHttpServlet ();
myHttpServlet.doGet();
1
2
问题2:调用doGet()方法时,HttpServletRequest,HttpServletResponse都是接口,那么入参的实际类型是什么?
实际上HttpServletRequest,HttpServletResponse接口的实现类,由servlet容器实现,比如:tomcat,jetty。我们把项目部署到tomcat中,tomcat就提供了一个HttpServletRequest和HttpServletResponse的实现类,部署到jetty,jetty就提供了相应的实现类,这是一个规范。
其中,在Tomcat中,HttpServletRequest的实现类就是RequestFacade

4、RequestFacade-门面模式
RequestFacade实现了HttpServletRequest,充当门面模式中的外观类。RequestFacade屏蔽内部子系统的细节。
RequestFacade代表的是一个请求,实际上是RequestFacade的属性Request,才是真正代表的一个请求,外界的http请求信息,都封装在这个Request对象中,只是利用门面模式的外观类,把它屏蔽在里面了。

Request本身也是实现了接口HttpServletRequest

5、应用部署到Tomcat有几种部署方式。

war包部署
文件夹部署
描述符部署

1
好了,现在这个Context就引出来一个容器,在Tomcat里有一个接口就叫Context

Tomcat底层架构组成
Container容器是父接口,所有的子容器都必须实现这个接口,在Tomcat中Container容器的设计是典型的责任链设计模式,其有四个子容器:Engine、Host、Context和Wrapper。这四个容器之间是父子关系,Engine容器包含Host,Host包含Context,Context包含Wrapper。

我们在web项目中的一个Servlet类对应一个Wrapper,多个Servlet就对应多个Wrapper,当有多个Wrapper的时候就需要一个容器来管理这些Wrapper了,这就是Context容器了,Context容器对应一个工程,所以我们新部署一个工程到Tomcat中就会新创建一个Context容器。

Engine->Host->Context->Wrapper->Servlet
1
1、Context——servlet容器
Context继承自Container容器接口

context表示的就是应用,这个应用是一个servlet容器。

path:是访问时的根地址,表示访问的路径。就是项目路径,根据请求带的项目路径,来确定使用哪个Context来处理请求
docbase:表示应用程序的路径,注意斜杠的方向“/”。应用编译后的class文件的路径。

2、Host——servlet容器
Host表示虚拟主机,一个虚拟主机下可以定义很多个Context,即可以部署多个项目

name:表示虚拟主机的名字,就是对应请求的域名,根据域名来确定使用哪个虚拟主机
appBase:表示应用存放的目录
unpackWARs:表示是否需要解压
autoDeploy:热部署
3、Engine——servlet容器
Engine引擎包含多个Host,它的责任就是将用户请求分配给一个虚拟上机处理。

name:表示引擎的逻辑名称,在日志和错误消息中会用到,在同一台服务器上有多个Service时,name必须唯一。
defaultHost:指定默认主机,如果没有分配哪个主机来执行用户请求,由这个值所指定的主机来处理,这个值必须和元素中的其中一个相同。
4、Wrapper——servlet容器
Context是一个servlet容器,但是它并不是直接装servlet实例,可以简单的理解,Context包含了多个Wrapper。

class Context{
List wrappers;
}
1
2
3
Wrapper才是装了多个Servlet实例,注意装的是某一个类型的servlet实例,比如,我自定一了一个servlet,就叫MyServlet,那么就有一个Wrapper里装的都是MyServlet的实例。

class Wrapper{
List servlet;//装的是某一个类型的servlet实例
}
1
2
3
一般servlet都是单例的,所有访问同一个servlet的请求是共用同一个servlet实例的。
定义的servlet实现了SingleThreadModel接口,每一个访问这个servlet的请求,单独有一个servlet实例,既然servlet支持了这个功能,肯定要去实现这个功能,因此,就有了wrapper
5、Pipiline管道
前面的4个容器都包含Pipiline管道

在Tomcat中,对于每一个容器,都有一个公共的组件Pipiline管道,每个管道下可以有多个阀门Valve,一个阀表示一个具体的执行任务,在servlet容器的管道中,除了有一个基础阀BaseValve,还可以添加任意数量的阀。阀的数量指的是额外添加的阀数量,即不包括基础阀。可以通过编辑Tomcat的配置文件(server.xml)来动态地添加阀。

例如:

还可以自定义阀门

public class TestValve extends RequestFilterValve {
@Override
public void invoke(Request request, Response response) throws IOException, ServletException {

}
@Override
protected Log getLog() {
    return null;
}

}
1
2
3
4
5
6
7
8
9
10
下图显示了一条管道及其阀:

如果对servlet编程中的过滤器有所了解的话,那么应该不难想像管道和阀的工作机制。管道就像过滤器链一样,而阀则好似是过滤器。阀与过滤器类似,可以处理传递给它的request对象和response对象。当一个阀执行完成后,会调用下一个阀继续执行。基础阀总是最后一个执行的。

Tomcat的请求处理流程
问:Wrapper容器的管道中的最后一个阀门,是怎样把请求转发给对应的servlet的?

看一下阀门的StandardWrapperValve的invoke方法的核心代码:

allocate方法里的loadServlet方法,直接newInstance一个serlvet实例

servlet = (Servlet) instanceManager.newInstance(servletClass);
1
这也回答了前言里的问题1,servlet是怎么生成的,在哪生成的?
servlet是在Wrapper的基础阀里生成的。

servlet实例有了,那么doGet/doPost在哪执行呢?
回到阀门的StandardWrapperValve的invoke方法的核心代码:来到这一行代码,传进servlet实例,并且返回一个过滤器链

我们定义一个Filter的时候,可以像下面这样写:

执行过程:filter->servlet->再回到filter
因此,filterChain.doFilter(servletRequest, servletResponse);虽然是调用其他过滤器,但是过滤器调用完之后,必然要去调用servlet的doget方法。
所以,上面才需要传入servlet实例,然后获取一个过滤器链,因为要用到这个servlet

继续往下走:

进入doFilter,再进入internalDoFilter方法:发现并没有执行,servlet的doGet/dopost方法,执行的是servlet的service方法

我们自定义servlet的时候,并没有service啊

往父类HttpServlet中找找:

因此,在哪里调用的doGet、doPost方法,与Tomcat没有关系,这个实际上是servlet规范所定义的。

Tomcat架构平视图

右边的部分,已经说过了,看看左边。

Request对象怎么生成的?

1、Request对象表示的是一个请求,Tomcat要生成一个Request对象,首先就要有数据,这个数据拿来的呢?
操作系统。Tomcat仅仅是操作系统上的一个应用程序,因此,它的数据开源于操作系统。

2、那操作系统的数据又从哪来的呢?
操作系统安装在服务器上面的,因此,操作系统的数据来源于服务器。

3、那服务器的数据又来源于哪里呢?
一个服务器通过网络将数据发给另一个服务器的。这就涉及到很多很多的协议了,服务器之间想要完成数据的传输和接受,就和计算机网络有关系了,跟各种协议有关系。

4、如果服务器A有数据,想把数据发给服务器B,但是仅仅有数据和IP(没有端口,端口是和应用程序对应的),服务器A能够保证数据安全可靠的发给服务器B吗
不能。可能数据非常大,数据在网络的传输过程中,会经过机房或者交换机,很有可能数据就会丢失,是不可靠的。

5、如何保证数据的可靠传输呢?
使用Tcp协议。该协议是一个可靠的协议,但是该协议,毕竟只是一个协议,这个协议肯定是需要去实现的。

6、Tcp协议由谁实现?
操作系统。linux和Windows操作系统,或者其他操作系统都会去实现Tcp协议。
Tcp协议在服务器之间建立连接时,会进行三次握手,linux源码就有关于Tcp三次握手的相关源码:

注意:Tcp协议只是保证数据在传输层可靠的传输,但是它并不关心数据长什么样子,也不关心数据的格式以及代表的意义,谁才关系数据的格式是怎么的,内容是怎么的呢?当然是使用数据的人和发送数据的人啊。浏览器和应用程序,因此,Http协议就有了,它是应用层协议,对我们要发送的数据进行规范,数据的格式,内容,数据的意义。

7、Http协议由谁实现?
浏览器、应用程序(包括Tomcat)

8、如果用java代码去实现一个浏览器,当用户在浏览器的地址栏输入地址后,按下回车键,代码执行的流程是怎么样的
1、肯定是要根据Http协议,去构造出符合Http协议的数据格式
2、发送数据,建立Tcp连接。
3、应用程序,接受数据

问题来了,java代码里怎么去建立Tcp连接,我们知道操作系统的源码里有建立Tcp连接的代码,那么Java能不能去调用操作系统的建立三次握手的代码,比如:tcp_connect()方法,以此来建立Tcp连接。

实际上建立Tcp连接的方法,java是不能直接调用的,因为这些方法是linux操作系统非常核心的方法,不会直接让你调的,实际上像这种情况,我们通常会想到,写一个API,重新定义一个方法,比如:

create_tcp(){
//验证
xxxx
//验证通过之后,才让调这个方法
tcp_connect();
}
1
2
3
4
5
6
liunx里也一样,不会让我们直接调用tcp_connect方法,它提供了一个对外的接口,就是socket,别人不能直接访问tcp_connect,只能通过socket去访问。

因此,回到java代码里怎么建立Tcp连接?通过Socket接口,建立Tcp连接。

其实不仅仅Java应用程序,运行在操作系统上的各种程序,都只能通过Socket去建立Tcp连接。

Java Socket底层实现
使用Java Socket建立一个tcp连接,如下:

public static void main(String[] args) throws IOException {
Socket socket = new Socket();//tcp
socket.connect(new InetSocketAddress(“localhost”,9090));

 DatagramSocket datagramSocket = new DatagramSocket();//udp

}
1
2
3
4
5
6
Java 的Socket类底层是不是直接调的操作系统的Socket呢?它们有没有什么联系呢?
1、进入connect方法

2、进入createImpl

3、进入AbstractPlainSocketImpl的create

4、进入socketCreate,创建一个Socket

5、进入DualStackPlainSocketImpl的socketCreate

发现socket0是一个native方法

native的socket0,代码只能去open jdk中去看socket0是怎么实现的.
7、DualStackPlainSocketImpl.c文件:

8、net_util_md.c文件:

socket(domain,type,protocol)又是怎么实现的呢?
但是该方法的实现,是在open jdk中找不到的。那么它到底在哪里实现的
9、看到net_util_md.c文件的头文件

那么,在当前的windows操作系统中找找有没有这个头文件。
win10如下:

这个就是上面socket(domain,type,protocol)的真正实现

10、回头看Java在创建一个Socket连接的时候,socket.connect(new InetSocketAddress(“localhost”,9090))这行代码,会先去调用Jdk代码,jdk最终调用的是操作系统的代码

Connector组件
回到Tomcat架构平视图中的8,浏览器会负责去构造数据,发送数据,那么Tomcat接受数据后,需要解析数据,这个时候就要去实现Http协议。

Tomcat使用socket接受数据,然后就要取数据,这里就涉及到一个概念,叫做IO模型,就是你通过什么方式去取数据的呢,是以BIO还是NIO呢?

Connector组件,会从socket中去取数据,然后根据Http协议去解析数据,解析成Request对象。

1、BIO的方式取数据
这个Connector组件,在Tomcat中对应有一个类Connector,有一个方法setProtocol,入参就是上图的protocol属性(Tomcat启动会去解析server.xml)

public void setProtocol(String protocol) {
if (AprLifecycleListener.isAprAvailable()) {
if (“HTTP/1.1”.equals(protocol)) {
setProtocolHandlerClassName
(“org.apache.coyote.http11.Http11AprProtocol”);
} else if (“AJP/1.3”.equals(protocol)) {
setProtocolHandlerClassName
(“org.apache.coyote.ajp.AjpAprProtocol”);
} else if (protocol != null) {
setProtocolHandlerClassName(protocol);
} else {
setProtocolHandlerClassName
(“org.apache.coyote.http11.Http11AprProtocol”);
}
} else {
//当protocol 属性设置为Http1.1时,对应的类是org.apache.coyote.http11.Http11Protocol
if (“HTTP/1.1”.equals(protocol)) {
setProtocolHandlerClassName
(“org.apache.coyote.http11.Http11Protocol”);
} else if (“AJP/1.3”.equals(protocol)) {
setProtocolHandlerClassName
(“org.apache.coyote.ajp.AjpProtocol”);
} else if (protocol != null) {
setProtocolHandlerClassName(protocol);
}
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
当protocol 属性设置为Http1.1时,代码里对应的类是org.apache.coyote.http11.Http11Protocol,它对应的协议是Http1.1,对应的io模型是BIO。
1、看看Http11Protocol源码,为啥说它对应的是BIO

2、看看JIoEndpoint

使用的工厂模式创建socket对象。

2、NIO的方式取数据
如果想使用NIO,只需要修改:protocol="HTTP/1.1"变为org.apache.coyote.http11.Http11NioProtocol


1
2
3
启动Tomcat就户发现走的是下面的红框了

1、看看Http11NioProtocol。查看方法类似上面查看BIO

2、看看NioEndpoint

使用的是SocketChannel,明显使用的是NIO,学过nio就知道了

3、解析数据
不管是BIO还是NIO方式取数据,反正是获取到了对应的Socket。接下来就是解析数据了。

对于BIO方式:会在processSocket方法中处理数据,对应nio方式。猜想Tomcat取数据,应该是socket.getInputStream()来取数据,然后按照Http1.1的格式解析数据

1、processSocket方法

Http协议的格式:

2、包装socket,然后把这个socket连接交给线程池,去处理。这个线程池在Tomcat7中默认10条线程,private int minSpareThreads = 10;

3、进入SocketProcessor,是一个线程

4、process方法

5、AbstractHttp11Processor的process,因为BIO对应的是Http11

解析请求行和请求头的方法里,都会把解析出来的数据,设置到Request对象里。

socket.getInputStream()取出来的数据,不是直接使用的,会放到缓存中。
————————————————
版权声明:本文为CSDN博主「weixin_42412601」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/weixin_42412601/article/details/113925346

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值