手写tomcat

420 阅读15分钟

手写Tomcat

image.png 所有新知识都应该本着:是什么,为什么,怎么做,这三步来执行。接下来是我对tomcat的一些新认识,对于这个项目应该有的大体掌握。

1.手写Tomcat前期准备

1.Tomcat是什么

  • tomcat是一个开源的轻量级web应用服务器
  • 也是个servlet容器

2.web应用服务器的设计思想

  • web应用服务器的核心是对Http协议的实现。那么tomcat服务器的核心也是对http协议的实现。

3.Tomcat主要做什么

  • 接受请求
  • 解析请求数据
  • 处理请求
  • 发送响应 tomcat可以找到部署的应用,对应的资源,返回给浏览器。也就是说tomcat可以解析我们的java应用,并且能够调用我们的java程序(Servlet java)。

4.Tomcat实现思路

  • 收集部署的servlet
  • 读取解析http请求
  • 匹配servlet
  • 执行servlet
  • 响应数据回浏览器

5.Tomcat两大核心容器及八大组件

  • container--engine,host,context,wrapper

  • connector--endpoiont,processor,protocalhandler,adapter

6.http协议基本原理

http协议是基于Tcp/Ip协议的应用层协议,不涉及数据包(packet)的传输,关注数据传输的格式是否规范。 所有类型的格式,均可采用Http传输。例如:图片,html网页,视频mp4,pdf,zip.

  • 协议:约定的规则

  • 网络协议:约定一种如何在网络上进行数据传输的规则

  • http协议:hyper text transform protocal

  • 超文本传输协议 约定如何在网络上进行传输超文本的

  • http协议给予请求响应模式,分为请求部分以及响应部分

  • 请求部分:

    请求行:方法名 url 协议以及版本号

    请求头:kv 作用:告诉服务器浏览器的一些信息,对本次请求的一些描述

    请求体:Get无请求体,post有请求体

  • 响应部分:

    响应行:协议以及版本号 状态码 状态描述信息

    其中状态码:200-OK 客户端请求成功

    404-Not Found 请求资源不存在

    响应头:kv 作用:告诉客户端服务端的一些信息,对本次响应的一些描述

    响应体:服务端响应回客户端的部分,比如是html页面

http协议的基本工作流程

  • 用户通过浏览器访问网址,或点击链接。接着,浏览器获取发送请求事件

  • 浏览器向服务端发出TCP连接请求

  • 服务程序接受浏览器连接请求,经过TCP三次握手建立连接

  • 浏览器将请求数据打包【HTTP协议格式数据包】

  • 浏览器将该数据包推入网络。数据包经过网络传输,最终达到端服务程序。

  • 服务端拿到数据包后,以HTTP协议格式解包,获取到客户端的意图。

  • 得知客户端意图后进行处理。比如:提供静态文件或者调用服务端程序获得动态结果。

  • 服务器将响应结果(可能是HTML或图片)打包【HTTP协议格式数据包】

  • 服务器将响应数据包推入网络,数据包经过网络传输最终到达浏览器

  • 浏览器拿到数据包后,以HTTP协议的格式解包,然后解析数据,假设这里的数据是HTML。浏览器将HTML文件展示在页面上

5.socket连接

使用Tcp/Ip和UDP协议在客户端和服务端之间进行通信的技术,是网络编程的基础。

首先要先了解,浏览器与服务器之间是如何进行数据传输的?

IMG_0845.jpg

1.浏览器与服务器之间基于B/S架构。
2.当我们在浏览器上输入网址在敲回车后,实际是向服务端发请求(浏览器底层去创建一个socket对象, 去连接服务端的serversocket)
3.服务端会创建一个serversocket对象监听本机的端口,一旦监听到客户端的请求就会接受,数据以字节流的方式流到socket连接上。
4.与此同时服务端也会创建一个socket对象,通过这个socket对象getInputStream()和OutputStream()方法来获取输入流输出流对象
5.通过输入流来读取客户端的数据,解析这些数据,通过输出流响应到客户端

6.servlet

  • Servlet(Server Applet)全称Java Servlet,是Java编写的服务器端程序

  • 从广义上讲,Servlet指任何实现了Servlet接口的类

  • 功能:在于交互式的浏览修改数据,生成动态web内容。

  • Servlet运行在java的应用服务器上,从实现上讲Servlet可以响应任何类型的请求,但绝大多数情况Servlet只用来扩展基于http协议的web服务器。可以理解成Servlet是java中的一个接口。

7.tomcat如何设计http服务器的

浏览器发给服务端的是一个HTTP格式的请求,HTTP服务器收到这个请求后,需要调用服务端程序来处理,所谓的服务端程序就是你写的Java类,一般来说不同的请求需要由不同的Java类来处理。 image.png 右边主要是为了解耦:http服务器不直接调用具体业务类而是把请求交给容器去处理,容器调用servlet接口调用业务类。

8.Servlet容器工作流程

web服务器的架构设计基本原理基于Servlet规范实现

当客户请求某个资源时:

HTTP服务器会用一个ServletRequest对象把客户的请求信息封装起来,然后调用Servlet容器的service()方法

Servlet容器根据请求的URL和Servlet的映射关系,找到相应的Servlet

  • 如果Servlet还没有被加载,就用反射机制创建Servlet对象,并调用Servlet的init方法来完成初始化,接着调用Servlet的service方法来处理请求
  • 如果Servlet已经加载,直接调用Servlet的service方法来处理请求

请求处理完毕,Servlet容器把ServletResponse对象返回给HTTP服务器,HTTP服务器会把响应发送给客户端

2.tomcat整体架构设计

设计架构之前我们应该先了解需求。tomcat两个核心需求:

  • 接收客户端浏览器发送请求,处理socket连接,将字节流与request和response对象进行转化
  • 加载管理servlet,以及处理request请求

3.容器及组件

container:Servlet容器 负责内部处理

image.png 想一个问题,Tomcat接收到数据并解析为HttpServletRequest对象后,就直接把请求交给Servlet了吗? 并不是,Tomcat还有其他考虑。

比如:1.假如,现在有一段逻辑,想让多个Servlet共用,就像切面一下,我们希望在请求被这些Servlet处理之前,能先执行公共逻辑.大家会有点懵,没关系,请直接往下面看,在Tomcat中存在四大Servlet容器:

1.Engine:表示Catalina的Servlet引擎,一个Service只能有一个engine,但是一个Engine可以包含多个Host

2.Host:一个Host表示一个虚拟主机,或者一个站点,可以给每个tomcat配置多个虚拟主机地址,而一个虚拟主机下面可以包含多个context。

3.Context:一个Context就是一个web应用,一个web应用下面可以包含多个wrapper

4.Wrapper:一个Wrapper表示一个Servlet的包装

这四个Servlet容器是具有层次关系的:

一个Engine下可以有多个Host

一个Host下可以有多个Context

一个Context下可以有多个Wrapper,

一个Wrapper下可以有多个Servlet实例对象。

Tomcat接收到某个请求后,首先会判断该请求的域名,根据域名找到对应的Host对象,Host对象再根据请求信息找到请求所要访问的应用,也就是找到一个Context对象,Context对象拿到请求后,会根据请求信息找到对应的Servlet,那么Wrapper是什么?

我们在定义一个Servlet时,如果额外实现了SingleThreadModel接口,那么就表示该Servlet是单线程模式

1.定义Servlet时如果没有实现SingleThreadModel接口,那么在Tomcat中只会产生该Servlet的一个实例对象,如果多个请求同时访问该Servlet,那么这多个请 求线程访问的是同一个Servlet对象,所以是并发不安全的

2.定义Servlet时如果实现了SingleThreadModel接口,那么在Tomcat中可能会产生多个该Servlet的实例对象,多个请求同时访问该Servlet,那么每个请求线程 会有一个单独的Servlet对象,所以是并发安全的

所以,我们发现,我们定义的某个Servlet,在Tomcat中可能会存在多个该类型的实例对象,所以Tomcat需要再抽象出来一层,这一层就是Wrapper,一个 Wrapper对应一个Servlet类型,Wrapper中有一个集合,用来存储该Wrapper对应的Servlet类型的实例对象。

所以一个Context表示一个应用,如果一个应用中定义了10个Servlet,那么Context下面就有10个Wrapper对象,而每个Wrapper中可能又会存在多个Servlet对象。

还有一点,在这个四个容器内部,有一个组件叫做Pipeline,也就管道,每个管道中可以设置多个Valve,也就是阀门。

管道与阀门的作用是,每个容器在接收到请求时会先把请求交给容器中的每个阀门处理,所有阀门都处理完了之后,在会将请求交给下层容器,通过这种机制,就解决了上面所提到的假设。

1.Engine:可以处理Tomcat所接收到所有请求,不管这些请求是请求哪个应用或哪个Servlet的。

3.Context:可以处理某个应用的所有请求

2.Host:可以处理某个特定域名的所有请求

4.Wrapper:可以处理某个Servlet的所有请求

值得一说的是:Wrapper还会负责调用Servlet对象的service()方法。到此,Tomcat接收到字节流并解析为HttpServetRequest对象之后,HttpServletRequest对象是如何流转的给大家分析完了

总结一下就是: image.png 2.connector coyote连接器

  • coyote是tomcat服务器提供的供浏览器访问的外部接口;
  • 客户端通过coyote与服务器建立连接,发送请求,接收响应

一次请求对应一次service连接

接下来再来给大家分析一下,Tomcat是如何将字节流解析为HttpServletRequest对象的,这个问题其实不难,只要想到这些字节流是谁发过来的?比如浏览器。而浏览器在发送数据时,会先将数据按HTTP协议的格式进行包装,再把HTTP数据包通过TCP协议发送出去,Tomcat就是从TCP协议中接收到数据的,只是从TCP协议中接收的数据是字节流,接下来要做的就是同样按照HTTP协议进行解析,比如解析出请求行、请求头等,从而就可以生成 HttpServletRequest对象。不过,整个解析过程还是比较复杂的,包括Tomcat底层的NIO模型、BIO模型、线程模型的实现也是比较复杂的

相关链接www.yuque.com/renyong-jmo…

这就是浏览器与服务器之间进行通信的大体流程,现在我们主要来看一下手写实现tomcat具体流程

4.手写Tomcat流程

1.先写一个tomcat启动类
截屏2023-03-31 14.05.42.png

2.启动完成后,需要接收到网络请求,这里面要用到socket.

截屏2023-03-31 14.10.15.png

输出结果 截屏2023-03-31 14.15.21.png 遇到了bio阻塞,请求还没有拿到响应,浏览器还在等,当第二次访问的时候服务器已经不运行了,接收不到socket连接。

解决方案:加上while true 主线程处理完第一个socket连接之后,继续接收下一个socket连接 截屏2023-03-31 14.25.51.png 但是现在socket连接是串行的,如何去提高并发量?

解决方案:引入线程池,扔给线程池里面的线程并发处理socket连接 截屏2023-03-31 14.39.49.png

3.处理socket连接,⽐如从Socket连接上读取数据,或者向Socket连接上写⼊数据(返回数据给浏览器)。

处理socket连接就是从socket连接上面读数据,执行业务逻辑,将结果写回到socket连接上。对于tomcat而言就是读取http请求,执行servlet,将响应的结果返回到socket连接上.

从socket连接上读数据,socket连接上读到的都是字节流,通过字节数组的方式去解析请求行(包括请求方法,请求路径,协议以及版本号),请求头以及请求体。

回顾下http格式: image.png 浏览器发送数据时就会遵守这个格式来发,所以对于Tomcat⽽⾔,就需要按这个格式来解析,⽐如逐个字节判断,如果遇到⼀个字节对应的是空格,那么之前的字节对应的就是请求⽅法,⽐如: 截屏2023-03-31 16.18.06.png 解析完之后我们就可以构造出来一个request对象了,这就是我们接收到的请求.构造一个request类 构造这个request对象是最终要传给servlet,所以要实现HttpServletRequest接口,这里要实现的方法过多,我们需要在创建一个AbstractHttpServletRequest类去实现HttpServletRequest接口。然后request对象继承AbstractHttpServletRequest

出现问题: 截屏2023-03-31 15.30.31.png

解决方案:注入java servlet-api依赖 blog.csdn.net/weixin_4734…

4.解析完请求后接下来我们就可以把这个request对象传递给某个Servlet了。实际上根据请求找Servlet也是⼀个复杂的流程后续会说。 先⾃⼰定义⼀个Servlet,根据servlet的service方法去决定调用doget还是dopost方法,获取请求方法。根据servlet规范发现还需要去传一个response对象,我们需要根据请求去构造响应对象。 截屏2023-04-02 14.00.53.png 5.根据请求构造响应对象,response里面根据每个请求去构造对应的响应信息,响应行,响应头 截屏2023-04-02 14.23.19.png 6.生成response对象,最终也要传给servlet,所以要实现HttpServletRequest接口,这里要实现的方法过多,我们需要在创建一个AbstractHttpServletResponse类去实现HttpServletResponse接口。然后response对象继承AbstractHttpServletResponse.

7.解析完执行servlet

截屏2023-03-31 18.07.20.png
8.servlet在service方法中service方法进行判断用doGet还是doPost向输出流里面写响应体数据
截屏2023-04-02 14.34.44.png

9.因为socket连接上都是以字节流的方式进行输入输出,所以我们在绑定一个ResponseServletOutputStream对象,他的write方法只需要将响应体数据写到字节数组里面就好了 截屏2023-04-02 14.40.26.png 10.service方法执行之后才将数据真正写到socket连接里面,就可以响应数据了。

11.再次启动tomcat,就能接收请求,执行Servlet,并响应请求。

image.png

5.Tomcat部署应用

大体流程 image.png 现在说一下根据请求去找servlet,嗯确实,有点复杂。

tomcat处理请求时候,需要根据请求找到servlet对象,执行service方法。

要先找出有哪些servlet对象

那在接收请求之前,需要知道tomcat中有哪些Servlet对象。tomcat启动过程中要做的就是收集tomcat中部署那些servlet。

1.Tomcat在启动时会在固定目录下去检查部署那些应用,并且看每个应用下有哪些servlet。

我们直接在tomcat⼯程⽬录下新建⼀个webapps⽬录,表示该⽬录下就是⽤来部署应⽤的,hello⽂件夹就表示⼀个应⽤,classes⽂件就表示这个应⽤下的class⽂件,其中就包括了servlet。

编译到我们的项目中 截屏2023-03-31 19.54.54.png

截屏2023-03-31 19.55.15.png 3.规定好⽬录后,Tomcat在启动时就要去进⾏应⽤部署的逻辑了:

1.找到webapps⽂件下部署了哪些应⽤

截屏2023-03-31 20.17.09.png
输出hello文件夹

2.拿到hello文件夹下面的classes目录

截屏2023-03-31 22.06.58.png

2.遍历底下的每一个文件夹,拿到该目录下面的所有文件

截屏2023-03-31 22.08.09.png

3.在遍历文件的时候,找到File对象,利用反射得到它对应的class对象,这就需要进⾏类的加载,在加载类之前,我们需要得到File对象对应的包格式(全限定名) 截屏2023-03-31 22.21.37.png 4这样我们就能拿到对应class⽂件的类全限定名,然后就可以利⽤类加载器去加载了,注意此时⽤AppClassLoader去加载这个类是加载不到的,因为webapps⽬录并不在classpath中,在Tomcat中是通过⾃定义类加载器来进⾏加载的。 截屏2023-03-31 22.31.58.png 5.这样就能拿到对应的class对象了,接下来就可以判断这个类是不是⼀个servlet了。 截屏2023-03-31 23.26.28.png 6.如果是就解析WebServlet注解得到对应的urlPatterns,有了urlPatterns那么我们就可以把映射关系进⾏保存,

截屏2023-03-31 23.38.57.png 7.当然我们不⽤把所有应⽤中的映射关系都保存在⼀起,最好是按应⽤隔开,所以我们定义⼀个Context表示⼀个应⽤: 截屏2023-04-01 02.01.49.png 8.找到了所有应⽤和Servlet之后,就可以在处理请求时进行匹配了。

9.如果匹配不到,我们可以定义⼀个DefaultServlet。

image.png

6.在学习的过程中感觉很有帮助的博客链接🔗

tomcat大框:t.csdn.cn/qykcJ

juejin.cn/post/684490… juejin.cn/post/713063… blog.csdn.net/qq_45547688…

❗️www.yuque.com/renyong-jmo… ❗️www.yuque.com/renyong-jmo…

7.我的收获

计算机网络,jvm,网络IO,线程池

8.涉及到的知识点以及扩展知识点

对于这些知识点我也进行了初步的总结 BIO,NIO,AIO juejin.cn/post/721876… OSI七层参考模型:juejin.cn/post/721884… Tcp/Ip,Udp

线程池juejin.cn/post/721511…

类加载

9.过程中遇到的难点,痛点以及解决方案

bio阻塞--while true

程序串行--引入线程池

继承HttpServletRequest找不到HttpServletRequest类--注入java servlet.api依赖

servlet里面写的doget,tomcat里面写的service。未搞清doget和service的原理及区别,抓包时请求没过来。

tomcat类加载与jvm类加载原理的不同之处及原理分析。