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工作原理及简单模拟实现的更多相关文章

  1. 你还记得 Tomcat 的工作原理么

    SpringBoot 就像一条巨蟒,慢慢缠绕着我们,使我们麻痹.不得不承认,使用了 SpringBoot 确实提高了工作效率,但同时也让我们遗忘了很多技能.刚入社会的时候,我还是通过 Tomcat 手 ...

  2. Optaplanner规划引擎的工作原理及简单示例(2)

    开篇 在前面一篇关于规划引擎Optapalnner的文章里(Optaplanner规划引擎的工作原理及简单示例(1)),老农介绍了应用Optaplanner过程中需要掌握的一些基本概念,这些概念有且于 ...

  3. RabbitMQ系列(二)深入了解RabbitMQ工作原理及简单使用

    深入了解RabbitMQ工作原理及简单使用 RabbitMQ系列文章 RabbitMQ在Ubuntu上的环境搭建 深入了解RabbitMQ工作原理及简单使用 RabbitMQ交换器Exchange介绍 ...

  4. 深入解读RabbitMQ工作原理及简单使用

    RabbitMQ系列目录 RabbitMQ在Ubuntu上的环境搭建 深入解读RabbitMQ工作原理及简单使用 Rabbit的几种工作模式介绍与实践 Rabbit事务与消息确认 Rabbit集群搭建 ...

  5. 深入了解RabbitMQ工作原理及简单使用

    深入了解RabbitMQ工作原理及简单使用 RabbitMQ系列文章 RabbitMQ在Ubuntu上的环境搭建 深入了解RabbitMQ工作原理及简单使用 RabbitMQ交换器Exchange介绍 ...

  6. LVS负载均衡机制之LVS-DR模式工作原理以及简单配置

    本博文主要简单介绍一下LVS负载均衡集群的一个基本负载均衡机制:LVS-DR:如有汇总不当之处,请各位在评论中多多指出. LVS-DR原理: LVS的英文全称是Linux Virtual Server ...

  7. JDBC【2】-- JDBC工作原理以及简单封装

    目录 1. 工作原理 1.1 加载驱动 1.1.1 类加载相关知识 1.1.2 为什么JDK 1.6之后不需要显示加载了? 1.2 驱动加载完成了,然后呢? 2. 简单封装 1. 工作原理 一般我们主 ...

  8. Tomcat Servlet工作原理

    前言 Tomcat的启动过程 Web应用初始化 创建Servlet实例 初始化Servlet 执行service方法 前言 Servlet实际上就是一个java类,只不过可以和浏览器进行一些数据的交换 ...

  9. 【IntelliJ IDEA】idea部署服务到Tomcat的工作原理

    参考地址: https://blog.csdn.net/qq_41116058/article/details/81435084 为什么idea部署服务到tomcat时候,一定要修改Applicati ...

随机推荐

  1. 转载 python实例手册

    python实例手册 #encoding:utf8# 设定编码-支持中文 0说明 手册制作: 雪松 更新日期: 2013-12-19 欢迎系统运维加入Q群: 198173206 # 加群请回答问题 请 ...

  2. PCA与LDA介绍

    PCA(主成分分析) PCA是一种无监督降维方式,它将数据投影到一组互相正交的loading vectors(principal axes)之上,并保证投影后的点在新的坐标轴上的方差最大 记数据集\( ...

  3. H5单张、多张图片保存续篇

    前言 这篇是接上篇内容.还没看的可以看H5单张.多张图片上传这篇文章预热. 图片入库 本章我们就来看看如何让多种图片保存至数据库中.数据库:mysql   后端:.NET Core 我们回顾一下上篇我 ...

  4. javascript入门篇(六、正则表达式)

    JavaScript 正则表达式 正则表达式是由一个字符序列形成的搜索模式.当你在文本中搜索数据时,你可以用搜索模式来描述你要查询的内容. 正则表达式可以是一个简单的字符,或一个更复杂的模式.正则表达 ...

  5. 5.2基于JWT的令牌生成和定制「深入浅出ASP.NET Core系列」

    希望给你3-5分钟的碎片化学习,可能是坐地铁.等公交,积少成多,水滴石穿,码字辛苦,如果你吃了蛋觉得味道不错,希望点个赞,谢谢关注. Token生成 我们知道一旦我们给API添加[Authorize] ...

  6. Fiddler 接口测试(Composer)的使用方法及并发测试

    下载地址:https://www.telerik.com/download/fiddler 一.Composer简介 右侧Composer区域,是测试接口的界面: 相关说明: 1.请求方式:点开可以勾 ...

  7. ArcGIS注册数据库问题分析

    本文是'猴妹'师妹授权给我来发表的,介绍都是师妹的研究成果,在此,非常感谢'猴妹'师妹. 用ArcGIS Server在发布地图服务时,注册数据库是很常见的,几年前就开始注册数据库,直到昨天,才有点顿 ...

  8. SQL 高效运行注意事项(二)

    SQL Server高效运行总的来说有两种方式: 一. 扩容,提高服务器性能,显著提高CPU.内存,解决磁盘I/O瓶颈.硬件的提升是立竿见影的,而且是风险小,在硬件更新换代非常快的年代, 当SQLSe ...

  9. 网络I/O 工作机制

    数据从一台主机发送到网络中的另一台主机需要经过很多步骤,先得有相互沟通的意向,然后得有物理渠道(物理链路),其次双方还得有语言能够交流,且步调要一致. TCP状态转化 如图,是TCP/IP 的握手过程 ...

  10. AI应用开发实战 - 从零开始配置环境

    AI应用开发实战 - 从零开始配置环境 与本篇配套的视频教程请访问:https://www.bilibili.com/video/av24421492/ 建议和反馈,请发送到 https://gith ...