【Tomcat 源码系列】认识 Tomcat
一,前言
说一句大实话,“平时一直在用 Tomcat,但是我从来没有用过 Tomcat”。
“平时一直在用 Tomcat”,是因为搬砖用的 SpringBoot,内嵌了 Tomcat,每次启动程序的时候,都需要启动 Tomcat。
“我从来没有用过 Tomcat”,是因为没有专门去用过 Tomcat,没有写过 Servlet,没有写过 JSP,没有配置过 Tomcat。
这篇博客介绍如何使用 Tomcat,根据官方提供的例子,分析如何写 Servlet 程序,JSP 页面,WebSocket 程序。
在继续源码之前,不妨先用用 Tomcat 吧。
二,Tomcat
2.1 运行 Tomcat
首先点击这里去下载一个 Tomcat 先吧。
解压一下,我们来看看里面都有些什么东西。
bin: 启动关闭脚本等
conf: 配置文件,server.xml 服务器配置,web.xml 应用配置
lib: Tomcat 的包,比如有 catalina.jar
logs: 日志
temp: 临时文件
webapps: 存放网站应用(webapp),一个文件夹对应一个 webapp,在域名端口后面,输入文件夹名字就可以访问对应的 webapp,比如 localhost:8080/examples
work: Tomcat 的工作目录,不断点进去,会发现一些 .class 文件,这些对应动态生成的页面。

进入 bin 目录,点击 startup 脚本。启动之后,界面显示如下。

进入 work 目录,不断深入。我们可以发现有一个 index_jsp.java 及其 class 文件。

用 IDE 看看 index_jsp.java,看 _jspService 方法,里面有很多 out.write,而写出去的内容正是我们上面看到的网页。这启示我们,其实 JSP 的原理就是生成 java 文件,并通过 out.write 写到网页中,因此可以将一些变量动态的写入到网页,而不是只能看到一个静态的 html。

2.2 Tomcat 概念和结构
有一些基本概念需要理解,请看这里。这些概念有:Server,Service,Engine,Host,Context,Wrapper,Pipeline,Valve,Realm,Connector。名词很多,知道个大概意思和作用就行了。
下面这个图就清晰地展示了 Tomcat 的结构图,仔细去看 conf/server.xml 这个文件的 xml 树结构。一个 Server 可以跑多个 Service,默认配置了一个名字为 Catalina 的 Service,这个 Service 下面可以配置多个 Connector 和 一个 Engine。这个 Connector 负责监听端口,并将客户端请求转发给 Engine。一个 Engine 可以有多个 Host,每个 Host 对应一个站点。一个 Host 中可以有多个 Context,一个 Context 对应于一个应用。

一张更全的结构图。一个请求,从 Connector 进来,通过 Pipeline 进入 Engine,再进入 Host、Context,最终找到对应的 Servlet 然后进行调用。

三,例子
运行 startup,输入 http://localhost:8080/examples/ 查看官方的例子。
官方提供了三类例子,分别是 Servlet,JSP,WebSocket 的例子。我们可以点进去看看 Tomcat 能够做什么。后面我们来开发一下自己的 Servlet,JSP,WebSocket 程序,看看这些程序是如何创建的。
那么这些例子在哪里呢?我们可以进入到 webapps 目录下面。我们可以看到有 examples。一个目录对应一个网站应用,比如 examples,我们可以用 http://localhost:8080/examples/ 来访问。对于 ROOT,可以直接用域名和端口访问。

进入 examples 目录,我们看看一个 webapp 有哪些组成部分。其中 WBE-INF 里面包含了网站的配置,类文件。META-INF 是打包的时候,提供的元数据。

四,自己动手
3.1 开发和部署
我们怎么开发一个 Tomcat 的 webapp 呢?开发完了之后,又需要如何部署呢?我们需要配置哪些东西呢?
接下来,我们用 IDEA 来开发和部署。我用的版本是:IntelliJ IDEA 2020.2.1 (Ultimate Edition)。
建项目
首先我们来新建一个项目,使用 Gradle 来构建,勾选 Web。

设置项目名称。

在 build.gradle 中引入下面的依赖,我用的是 Tomcat 10,所以需要引入 Jakarta 开头的包,如果你用的是别的版本的 Tomcat,请自行找到对应版本的包。
// https://mvnrepository.com/artifact/jakarta.servlet/jakarta.servlet-api
providedCompile group: 'jakarta.servlet', name: 'jakarta.servlet-api', version: '5.0.0'
// https://mvnrepository.com/artifact/jakarta.websocket/jakarta.websocket-api
providedCompile group: 'jakarta.websocket', name: 'jakarta.websocket-api', version: '2.0.0'
配置项目
点击右上角,添加配置。

添加 Tomcat Server,注意不要选到后面的 TomcatEE 版本了。选择 Local 版本。

点击 Configure 按钮,找到 Tomcat 解压目录即可。不需要进入到 bin 当中。我们还可以看到左下角有个 Warning,它提示你需要配置部署。于是,我们选中 Deployment,去配置。

点击那个加号,然后选择 exploded 版本。

点击 ok 之后,修改 Application Context,这个 Context 用来配置访问时候 url 的名字。可以理解为这个 webapp 的名字。之后,我们可以使用 localhost:8080/example 来访问。

至此,我们的第一个 webapp 就配置好了。
3.2 JSP
接下来,展开 src,main,webapp,找到 index.jsp。我们可以在这里开始写代码。

编辑内容,注意到下面有 java 代码,其实 jsp 就是 html 和 java 的混合体。下面的 jsp,就是向浏览器输出了 Hello World 这个字符串。我们点击运行,启动一下。这里就不再展开 JSP 了,如果又需要再去学一学吧。
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>$Title$</title>
</head>
<body>
<%
String s = "Hello World";
out.write(s);
%>
</body>
</html>
可以看到 Hello World 了。

3.3 Servlet
接下来,我们来写第一个 Servlet 程序。写个鬼咧,写代码是不可能写的,这辈子都不会写代码。直接从 webapps\examples\WEB-INF\classes 中复制一个过来。你也可以复制我的代码。

下面这段代码,可以视为一个 Servlet,它接收 GET 请求,并将一个 html 逐行逐行写给前端。因为 Java 代码里面太多这些 out.println 了,导致要修改前端必须要改 Java,这样不好。因此,才有了 JSP。
import java.io.*;
import jakarta.servlet.*;
import jakarta.servlet.http.*;
public class ExampleServlet extends HttpServlet {
public void doGet(HttpServletRequest request, HttpServletResponse response)
throws IOException, ServletException
{
response.setContentType("text/html");
PrintWriter out = response.getWriter();
out.println("<html>");
out.println("<head>");
out.println("<title>Hello World!</title>");
out.println("</head>");
out.println("<body>");
out.println("<h1>Hello World!</h1>");
out.println("</body>");
out.println("</html>");
}
}
接下来,我们还要配置,如何去调用这个 Servlet 程序。在 webapp 下面新建文件夹 WEB-INF,并在下面新建一个 web.xml 文件。

同样,我去找一份配置,这次我在 webapps/ROOT 下面到 web.xml,然后添加一些信息来配置 url。servlet 标签定义了一个 servlet 的名字及其所在地点。这个 servlet-class 需要根据包的路径来,前面我新建的 ExampleServlet 并没有包,所以直接这样子配就行。配好了 servlet,还要去配调用这个 servlet 的 URL。
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="https://jakarta.ee/xml/ns/jakartaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="https://jakarta.ee/xml/ns/jakartaee
https://jakarta.ee/xml/ns/jakartaee/web-app_5_0.xsd"
version="5.0"
metadata-complete="true">
<display-name>Welcome to Tomcat</display-name>
<description>
Welcome to Tomcat
</description>
<servlet>
<servlet-name>ExampleServlet</servlet-name>
<servlet-class>ExampleServlet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>ExampleServlet</servlet-name>
<url-pattern>/hello</url-pattern>
</servlet-mapping>
</web-app>
点击启动,访问这个链接 http://localhost:8080/example/hello

3.4 WebSocket
接下来,我们参考官方的例子,搞一个基于 WebSocket 的聊天室。不写代码,全靠复制粘贴。
我们需要从 \webapps\examples\WEB-INF\classes\websocket\chat 复制代码。

将下面代码复制到 ChatAnnotation 中,@ServerEndpoint 用来配置提供 websocket 协议服务的端点,它支持服务端推送消息。
import jakarta.websocket.*;
import jakarta.websocket.server.ServerEndpoint;
import java.io.IOException;
import java.util.Set;
import java.util.concurrent.CopyOnWriteArraySet;
import java.util.concurrent.atomic.AtomicInteger;
@ServerEndpoint(value = "/websocket/chat")
public class ChatAnnotation {
private static final String GUEST_PREFIX = "Guest";
private static final AtomicInteger connectionIds = new AtomicInteger(0);
private static final Set<ChatAnnotation> connections =
new CopyOnWriteArraySet<>();
private final String nickname;
private Session session;
public ChatAnnotation() {
nickname = GUEST_PREFIX + connectionIds.getAndIncrement();
}
@OnOpen
public void start(Session session) {
this.session = session;
connections.add(this);
String message = String.format("* %s %s", nickname, "has joined.");
broadcast(message);
}
@OnClose
public void end() {
connections.remove(this);
String message = String.format("* %s %s",
nickname, "has disconnected.");
broadcast(message);
}
@OnMessage
public void incoming(String message) {
// Never trust the client
String filteredMessage = String.format("%s: %s",
nickname, message.toString());
broadcast(filteredMessage);
}
@OnError
public void onError(Throwable t) throws Throwable {
}
private static void broadcast(String msg) {
for (ChatAnnotation client : connections) {
try {
synchronized (client) {
client.session.getBasicRemote().sendText(msg);
}
} catch (IOException e) {
connections.remove(client);
try {
client.session.close();
} catch (IOException e1) {
// Ignore
}
String message = String.format("* %s %s",
client.nickname, "has been disconnected.");
broadcast(message);
}
}
}
}
然后,我们再从 \webapps\examples\websocket 偷一个 chat.xhtml 文件。放到 webapp 下面就好了。

之后还需要修改 chat.xhtml 中 websocket 的端点。将下面红框中的东西,改成一开始 IDEA 启动配置中的 Application Context。在这里,我们只需要去掉 s 就好了。

接下来启动!
通过这个地方访问聊天室:http://localhost:8080/example/chat.xhtml

发送的消息,都可以即时被推送。

五,总结
这篇博客展示了如何使用 Tomcat,开发使用 Servlet,JSP,WebSocket 的 Demo。
总结一下,Tomcat 就是一个实现了 Servlet,JSP,WebSocket 规范的 HTTP 服务器。上面展示了使用这些技术的例子,要明白这背后做了什么,还得了解这些技术的规范,还要去看实现,看 Tomcat 源码。
【Tomcat 源码系列】认识 Tomcat的更多相关文章
- 【Tomcat 源码系列】Tomcat 整体结构
一,前言 在开始看源码细节之前,首先要想好要看的问题.想好问题之后,我们该如何寻找要看的代码呢? 其实,这就好像去爬山的时候,突然想去上厕所,如果有一副地图,那么我们可以很快就找到厕所的位置.带着问题 ...
- 【Tomcat 源码系列】源码构建 Tomcat
一,前言 这篇博客写于 12 月 12 日,从 github[1] 上 fork 了一份 tomcat 的源代码,clone 到了本地.最近想把 tomcat 的源代码分析一下,寒假的时候有完整的时间 ...
- tomcat源码--springboot整合tomcat源码分析
1.测试代码,一个简单的springboot web项目:地址:https://gitee.com/yangxioahui/demo_mybatis.git 一:tomcat的主要架构:1.如果我们下 ...
- tomcat源码分析(一)- tomcat源码导入IDEA并正常启动
项目导入 代码下载 打开GitHub网站:https://github.com/apache/tomcat 下载对应的zip包 解压对应的压缩包(当然你也可以用工具对其进行解压) unzip tomc ...
- Tomcat源码分析一:编译Tomcat源码
Tomcat源码分析一:编译Tomcat源码 1 内容介绍 在之前的<Servlet与Tomcat运行示例>一文中,给大家带来如何在Tomcat中部署Servlet应用的相关步骤,本文将就 ...
- idea中以maven工程的方式运行tomcat源码
0. 准备环境 idea+jdk8+tomcat源码 1.下载tomcat源码: http://mirrors.tuna.tsinghua.edu.cn/apache/tomcat/tomcat-8/ ...
- [Tomcat 源码分析系列] (二) : Tomcat 启动脚本-catalina.bat
概述 Tomcat 的三个最重要的启动脚本: startup.bat catalina.bat setclasspath.bat 上一篇咱们分析了 startup.bat 脚本 这一篇咱们来分析 ca ...
- tomcat 源码分析
Tomcat源码分析——Session管理分析(下) Tomcat源码分析——Session管理分析(上) Tomcat源码分析——请求原理分析(下) Tomcat源码分析——请 ...
- svn工具安装下载Tomcat源码以及导入eclipse
安装 1.svn下载地址 https://tortoisesvn.net/downloads.html 2.语言包下载 3.先安装svn,在直接安装语言包 4.桌面右键可以看到相关svn信息 下载To ...
随机推荐
- MySQL技术内幕InnoDB存储引擎(七)——事务
什么是数据库的事务? 事务是访问并更新数据库中各种数据的一个程序执行单元.事务也是数据库区别于文件系统的一个重要特性. 事务需要满足的特性 1.原子性 原子性就是指数据库中的一个完整的事务是不可分割的 ...
- 深入理解Java虚拟机(五)——JDK故障处理工具
进程状况工具:jps jps(JVM Process Status Tool) 作用 用于虚拟机中正在运行的所有进程. 显示虚拟机执行的主类名称以及这些进程的本地虚拟机唯一ID. 可以通过RMI协议查 ...
- linux下为什么每次修改完配置文件之后都需要重新加载配置文件
目录 一.关于inode 二.inode的作用 二.为什么每次修改完服务器配置文件之后,都需要重新加载一下配置文件? 一.关于inode 1.在linux下一切皆文件,linux文件由三部分组成:文件 ...
- Java IO流 BufferedInputStream、BufferedOutputStream的基本使用
BufferedInputStream.BufferedOutputStream的基本使用 BufferedInputStream是FilterInputStream流的子类,FilterInputS ...
- centos 7 安装vncserver遇到错误解决
安装网上一般的教程安装配置,略. 执行 systemctl start vncserver@:1.service 启动服务报错: Job for vncserver@:1.service failed ...
- html 01-认识Web和Web标准
01-认识Web和Web标准 #Web.网页.浏览器 #Web Web(World Wide Web)即全球广域网,也称为万维网. 我们常说的Web端就是网页端. #网页 网页是构成网站的基本元素.网 ...
- Flink连接器-批处理-读写Hbase
Flink批处理与hbase的读写 source-hbase 父类 是模仿官方写的. import org.apache.flink.api.common.io.LocatableInputSplit ...
- 微服务之服务治理_Eureka
首先需要明确,不管是什么事物需要"治理",那一定是该事物存在一定问题.比如环境治理.那么服务,或者说微服务为什么需要治理?对于服务来说,如果它承担的业务职责简单,那其实治理的必要性 ...
- python菜鸟教程学习:数据结构
列表方法 list.append(x):把一个元素添加到列表的结尾,相当于 a[len(a):] = [x]. list.extend(L):通过添加指定列表的所有元素来扩充列表,相当于 a[len( ...
- Java基础进阶:APi使用,Math,Arrarys,Objects工具类,自动拆装箱,字符串与基本数据类型互转,递归算法源码,冒泡排序源码实现,快排实现源码,附重难点,代码实现源码,课堂笔记,课后扩展及答案
要点摘要 Math: 类中么有构造方法,内部方法是静态的,可以直接类名.方式调用 常用: Math.abs(int a):返回参数绝对值 Math.ceil(double a):返回大于或等于参数的最 ...