【Tomcat】Tomcat工作原理及简单模拟实现
Tomcat应该都不陌生,我们经常会把写好的代码打包放在Tomcat里并启动,然后在浏览器里就能愉快的调用我们写的代码来实现相应的功能了,那么Tomcat是如何工作的?
一、Tomcat工作原理
我们启动Tomcat时双击的startup.bat文件的主要作用是找到catalina.bat,并且把参数传递给它,而catalina.bat中有这样一段话:

Bootstrap.class是整个Tomcat 的入口,我们在Tomcat源码里找到这个类,其中就有我们经常使用的main方法:

这个类有两个作用 :1.初始化一个守护进程变量、加载类和相应参数。2.解析命令,并执行。
源码不过多赘述,我们在这里只需要把握整体架构,有兴趣的同学可以自己研究下源码。Tomcat的server.xml配置文件中可以对应构架图中位置,多层的表示可以配置多个:

即一个由 Server->Service->Engine->Host->Context 组成的结构,从里层向外层分别是:
- Server:服务器Tomcat的顶级元素,它包含了所有东西。
- Service:一组 Engine(引擎) 的集合,包括线程池 Executor 和连接器 Connector 的定义。
- Engine(引擎):一个 Engine代表一个完整的 Servlet 引擎,它接收来自Connector的请求,并决定传给哪个Host来处理。
- Container(容器):Host、Context、Engine和Wraper都继承自Container接口,它们都是容器。
- Connector(连接器):将Service和Container连接起来,注册到一个Service,把来自客户端的请求转发到Container。
- Host:即虚拟主机,所谓的”一个虚拟主机”可简单理解为”一个网站”。
- Context(上下文 ): 即 Web 应用程序,一个 Context 即对于一个 Web 应用程序。Context容器直接管理Servlet的运行,Servlet会被其给包装成一个StandardWrapper类去运行。Wrapper负责管理一个Servlet的装载、初始化、执行以及资源回收,它是最底层容器。
比如现在有以下网址,根据“/”切割的链接就会定位到具体的处理逻辑上,且每个容器都有过滤功能。

二、Tomcat实现思路
下面只是简单实现效果,当浏览器访问对应地址时:

实现以上效果整体思路如下:
1.ServerSocket占用8080端口,用while(true)循环等待用户发请求。
2.拿到浏览器的请求,解析并返回URL地址,用I/O输入流读取本地磁盘上相应文件。
3.读取文件,不存在构建响应报文头、HTML正文内容,存在则写到浏览器端。
三、实现Tomcat
工程文件结构和pom.xml文件:

1.HttpServer核心处理类,用于接受用户请求,传递HTTP请求头信息,关闭容器:
public class HttpServer {
// 用于判断是否需要关闭容器
private boolean shutdown = false;
public void acceptWait() {
ServerSocket serverSocket = null;
try {
//端口号,最大链接数,ip地址
serverSocket = new ServerSocket(8080, 1, InetAddress.getByName("127.0.0.1"));
}
catch (IOException e) {
e.printStackTrace();
System.exit(1);
}
// 等待用户发请求
while (!shutdown) {
try {
Socket socket = serverSocket.accept();
InputStream is = socket.getInputStream();
OutputStream os = socket.getOutputStream();
// 接受请求参数
Request request = new Request(is);
request.parse();
// 创建用于返回浏览器的对象
Response response = new Response(os);
response.setRequest(request);
response.sendStaticResource();
//关闭一次请求的socket,因为http请求就是采用短连接的方式
socket.close();
//如果请求地址是/shutdown 则关闭容器
if(null != request){
shutdown = request.getUrL().equals("/shutdown");
}
}
catch (Exception e) {
e.printStackTrace();
continue;
}
}
}
public static void main(String[] args) {
HttpServer server = new HttpServer();
server.acceptWait();
}
}
2.创建Request类,获取HTTP的请求头所有信息并截取URL地址返回:
public class Request {
private InputStream is;
private String url;
public Request(InputStream input) {
this.is = input;
}
public void parse() {
//从socket中读取一个2048长度字符
StringBuffer request = new StringBuffer(Response.BUFFER_SIZE);
int i;
byte[] buffer = new byte[Response.BUFFER_SIZE];
try {
i = is.read(buffer);
}
catch (IOException e) {
e.printStackTrace();
i = -1;
}
for (int j=0; j<i; j++) {
request.append((char) buffer[j]);
}
//打印读取的socket中的内容
System.out.print(request.toString());
url = parseUrL(request.toString());
}
private String parseUrL(String requestString) {
int index1, index2;
index1 = requestString.indexOf(' ');//看socket获取请求头是否有值
if (index1 != -1) {
index2 = requestString.indexOf(' ', index1 + 1);
if (index2 > index1)
return requestString.substring(index1 + 1, index2);
}
return null;
}
public String getUrL() {
return url;
}
}
3.创建Response类,响应请求读取文件并写回到浏览器
public class Response {
public static final int BUFFER_SIZE = 2048;
//浏览器访问D盘的文件
private static final String WEB_ROOT ="D:";
private Request request;
private OutputStream output;
public Response(OutputStream output) {
this.output = output;
}
public void setRequest(Request request) {
this.request = request;
}
public void sendStaticResource() throws IOException {
byte[] bytes = new byte[BUFFER_SIZE];
FileInputStream fis = null;
try {
//拼接本地目录和浏览器端口号后面的目录
File file = new File(WEB_ROOT, request.getUrL());
//如果文件存在,且不是个目录
if (file.exists() && !file.isDirectory()) {
fis = new FileInputStream(file);
int ch = fis.read(bytes, 0, BUFFER_SIZE);
while (ch!=-1) {
output.write(bytes, 0, ch);
ch = fis.read(bytes, 0, BUFFER_SIZE);
}
}else {
//文件不存在,返回给浏览器响应提示,这里可以拼接HTML任何元素
String retMessage = "<h1>"+file.getName()+" file or directory not exists</h1>";
String returnMessage ="HTTP/1.1 404 File Not Found\r\n" +
"Content-Type: text/html\r\n" +
"Content-Length: "+retMessage.length()+"\r\n" +
"\r\n" +
retMessage;
output.write(returnMessage.getBytes());
}
}
catch (Exception e) {
System.out.println(e.toString() );
}
finally {
if (fis!=null)
fis.close();
}
}
}
四、扩展点
1.在WEB_INF文件夹下读取web.xml解析,通过请求名找到对应的类名,通过类名创建对象,用反射来初始化配置信息,如welcome页面,Servlet、servlet-mapping,filter,listener,启动加载级别等。
2.抽象Servlet类来转码处理请求和响应的业务。发过来的请求会有很多,也就意味着我们应该会有很多的Servlet,例如:RegisterServlet、LoginServlet等等还有很多其他的访问。可以用到类似于工厂模式的方法处理,随时产生很多的Servlet,来满足不同的功能性的请求。
3.使用多线程。本文的代码是死循环,且只能有一个链接,而现实中的情况是往往会有很多很多的客户端发请求,可以把每个浏览器的通信封装到一个线程当中。
【Tomcat】Tomcat工作原理及简单模拟实现的更多相关文章
- 你还记得 Tomcat 的工作原理么
SpringBoot 就像一条巨蟒,慢慢缠绕着我们,使我们麻痹.不得不承认,使用了 SpringBoot 确实提高了工作效率,但同时也让我们遗忘了很多技能.刚入社会的时候,我还是通过 Tomcat 手 ...
- Optaplanner规划引擎的工作原理及简单示例(2)
开篇 在前面一篇关于规划引擎Optapalnner的文章里(Optaplanner规划引擎的工作原理及简单示例(1)),老农介绍了应用Optaplanner过程中需要掌握的一些基本概念,这些概念有且于 ...
- RabbitMQ系列(二)深入了解RabbitMQ工作原理及简单使用
深入了解RabbitMQ工作原理及简单使用 RabbitMQ系列文章 RabbitMQ在Ubuntu上的环境搭建 深入了解RabbitMQ工作原理及简单使用 RabbitMQ交换器Exchange介绍 ...
- 深入解读RabbitMQ工作原理及简单使用
RabbitMQ系列目录 RabbitMQ在Ubuntu上的环境搭建 深入解读RabbitMQ工作原理及简单使用 Rabbit的几种工作模式介绍与实践 Rabbit事务与消息确认 Rabbit集群搭建 ...
- 深入了解RabbitMQ工作原理及简单使用
深入了解RabbitMQ工作原理及简单使用 RabbitMQ系列文章 RabbitMQ在Ubuntu上的环境搭建 深入了解RabbitMQ工作原理及简单使用 RabbitMQ交换器Exchange介绍 ...
- LVS负载均衡机制之LVS-DR模式工作原理以及简单配置
本博文主要简单介绍一下LVS负载均衡集群的一个基本负载均衡机制:LVS-DR:如有汇总不当之处,请各位在评论中多多指出. LVS-DR原理: LVS的英文全称是Linux Virtual Server ...
- JDBC【2】-- JDBC工作原理以及简单封装
目录 1. 工作原理 1.1 加载驱动 1.1.1 类加载相关知识 1.1.2 为什么JDK 1.6之后不需要显示加载了? 1.2 驱动加载完成了,然后呢? 2. 简单封装 1. 工作原理 一般我们主 ...
- Tomcat Servlet工作原理
前言 Tomcat的启动过程 Web应用初始化 创建Servlet实例 初始化Servlet 执行service方法 前言 Servlet实际上就是一个java类,只不过可以和浏览器进行一些数据的交换 ...
- 【IntelliJ IDEA】idea部署服务到Tomcat的工作原理
参考地址: https://blog.csdn.net/qq_41116058/article/details/81435084 为什么idea部署服务到tomcat时候,一定要修改Applicati ...
随机推荐
- 转载 python实例手册
python实例手册 #encoding:utf8# 设定编码-支持中文 0说明 手册制作: 雪松 更新日期: 2013-12-19 欢迎系统运维加入Q群: 198173206 # 加群请回答问题 请 ...
- PCA与LDA介绍
PCA(主成分分析) PCA是一种无监督降维方式,它将数据投影到一组互相正交的loading vectors(principal axes)之上,并保证投影后的点在新的坐标轴上的方差最大 记数据集\( ...
- H5单张、多张图片保存续篇
前言 这篇是接上篇内容.还没看的可以看H5单张.多张图片上传这篇文章预热. 图片入库 本章我们就来看看如何让多种图片保存至数据库中.数据库:mysql 后端:.NET Core 我们回顾一下上篇我 ...
- javascript入门篇(六、正则表达式)
JavaScript 正则表达式 正则表达式是由一个字符序列形成的搜索模式.当你在文本中搜索数据时,你可以用搜索模式来描述你要查询的内容. 正则表达式可以是一个简单的字符,或一个更复杂的模式.正则表达 ...
- 5.2基于JWT的令牌生成和定制「深入浅出ASP.NET Core系列」
希望给你3-5分钟的碎片化学习,可能是坐地铁.等公交,积少成多,水滴石穿,码字辛苦,如果你吃了蛋觉得味道不错,希望点个赞,谢谢关注. Token生成 我们知道一旦我们给API添加[Authorize] ...
- Fiddler 接口测试(Composer)的使用方法及并发测试
下载地址:https://www.telerik.com/download/fiddler 一.Composer简介 右侧Composer区域,是测试接口的界面: 相关说明: 1.请求方式:点开可以勾 ...
- ArcGIS注册数据库问题分析
本文是'猴妹'师妹授权给我来发表的,介绍都是师妹的研究成果,在此,非常感谢'猴妹'师妹. 用ArcGIS Server在发布地图服务时,注册数据库是很常见的,几年前就开始注册数据库,直到昨天,才有点顿 ...
- SQL 高效运行注意事项(二)
SQL Server高效运行总的来说有两种方式: 一. 扩容,提高服务器性能,显著提高CPU.内存,解决磁盘I/O瓶颈.硬件的提升是立竿见影的,而且是风险小,在硬件更新换代非常快的年代, 当SQLSe ...
- 网络I/O 工作机制
数据从一台主机发送到网络中的另一台主机需要经过很多步骤,先得有相互沟通的意向,然后得有物理渠道(物理链路),其次双方还得有语言能够交流,且步调要一致. TCP状态转化 如图,是TCP/IP 的握手过程 ...
- AI应用开发实战 - 从零开始配置环境
AI应用开发实战 - 从零开始配置环境 与本篇配套的视频教程请访问:https://www.bilibili.com/video/av24421492/ 建议和反馈,请发送到 https://gith ...