【Tomcat 原始碼系列】認識 Tomcat
阿新 • • 發佈:2021-01-16
# 一,前言
說一句大實話,“平時一直在用 Tomcat,但是我從來沒有用過 Tomcat”。
**“平時一直在用 Tomcat”**,是因為搬磚用的 SpringBoot,內嵌了 Tomcat,每次啟動程式的時候,都需要啟動 Tomcat。
**“我從來沒有用過 Tomcat”**,是因為沒有專門去用過 Tomcat,沒有寫過 Servlet,沒有寫過 JSP,沒有配置過 Tomcat。
這篇部落格介紹如何使用 Tomcat,根據官方提供的例子,分析如何寫 Servlet 程式,JSP 頁面,WebSocket 程式。
在繼續原始碼之前,不妨先用用 Tomcat 吧。程式碼請看這裡:https://github.com/zzk0/tomcat-example
# 二,Tomcat
## 2.1 執行 Tomcat
首先[點選這裡](https://tomcat.apache.org/download-10.cgi)去下載一個 Tomcat 先吧。
解壓一下,我們來看看裡面都有些什麼東西。
```
bin: 啟動關閉指令碼等
conf: 配置檔案,server.xml 伺服器配置,web.xml 應用配置
lib: Tomcat 的包,比如有 catalina.jar
logs: 日誌
temp: 臨時檔案
webapps: 存放網站應用(webapp),一個資料夾對應一個 webapp,在域名埠後面,輸入資料夾名字就可以訪問對應的 webapp,比如 localhost:8080/examples
work: Tomcat 的工作目錄,不斷點進去,會發現一些 .class 檔案,這些對應動態生成的頁面。
```
![](https://img2020.cnblogs.com/blog/1616773/202101/1616773-20210116115924056-2016051748.png)
進入 `bin` 目錄,點選 startup 指令碼。啟動之後,介面顯示如下。
![](https://img2020.cnblogs.com/blog/1616773/202101/1616773-20210116115643947-1011876680.png)
進入 `work` 目錄,不斷深入。我們可以發現有一個 index_jsp.java 及其 class 檔案。
![](https://img2020.cnblogs.com/blog/1616773/202101/1616773-20210116120356836-1556056341.png)
用 IDE 看看 index_jsp.java,看 _jspService 方法,裡面有很多 out.write,而寫出去的內容正是我們上面看到的網頁。這啟示我們,其實 JSP 的原理就是生成 java 檔案,並通過 out.write 寫到網頁中,因此可以將一些變數動態的寫入到網頁,而不是隻能看到一個靜態的 html。
![](https://img2020.cnblogs.com/blog/1616773/202101/1616773-20210116120624481-1896390664.png)
## 2.2 Tomcat 概念和結構
有一些基本概念需要理解,請看[這裡](https://juejin.cn/post/6844903952991911949)。這些概念有: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 對應於一個應用。
![](https://img2020.cnblogs.com/blog/1616773/202101/1616773-20210115231202448-1934948369.png)
一張更全的結構圖。一個請求,從 Connector 進來,通過 Pipeline 進入 Engine,再進入 Host、Context,最終找到對應的 Servlet 然後進行呼叫。
![](https://img2020.cnblogs.com/blog/1616773/202101/1616773-20210115232446725-1958187542.png)
# 三,例子
執行 startup,輸入 http://localhost:8080/examples/ 檢視官方的例子。
官方提供了三類例子,分別是 Servlet,JSP,WebSocket 的例子。我們可以點進去看看 Tomcat 能夠做什麼。後面我們來開發一下自己的 Servlet,JSP,WebSocket 程式,看看這些程式是如何建立的。
那麼這些例子在哪裡呢?我們可以進入到 webapps 目錄下面。我們可以看到有 examples。一個目錄對應一個網站應用,比如 examples,我們可以用 http://localhost:8080/examples/ 來訪問。對於 ROOT,可以直接用域名和埠訪問。
![](https://img2020.cnblogs.com/blog/1616773/202101/1616773-20210116141308771-2009768397.png)
進入 examples 目錄,我們看看一個 webapp 有哪些組成部分。其中 WBE-INF 裡面包含了網站的配置,類檔案。META-INF 是打包的時候,提供的元資料。
![](https://img2020.cnblogs.com/blog/1616773/202101/1616773-20210116141522485-1674909270.png)
# 四,自己動手
## 3.1 開發和部署
我們怎麼開發一個 Tomcat 的 webapp 呢?開發完了之後,又需要如何部署呢?我們需要配置哪些東西呢?
接下來,我們用 IDEA 來開發和部署。我用的版本是:IntelliJ IDEA 2020.2.1 (Ultimate Edition)。
### 建專案
首先我們來新建一個專案,使用 Gradle 來構建,勾選 Web。
![](https://img2020.cnblogs.com/blog/1616773/202101/1616773-20210116142435078-179564037.png)
設定專案名稱。
![](https://img2020.cnblogs.com/blog/1616773/202101/1616773-20210116142531230-1795585441.png)
在 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'
```
### 配置專案
點選右上角,新增配置。
![](https://img2020.cnblogs.com/blog/1616773/202101/1616773-20210116142840117-1389339115.png)
新增 Tomcat Server,注意不要選到後面的 TomcatEE 版本了。選擇 Local 版本。
![](https://img2020.cnblogs.com/blog/1616773/202101/1616773-20210116143004322-226396779.png)
點選 Configure 按鈕,找到 Tomcat 解壓目錄即可。不需要進入到 bin 當中。我們還可以看到左下角有個 Warning,它提示你需要配置部署。於是,我們選中 Deployment,去配置。
![](https://img2020.cnblogs.com/blog/1616773/202101/1616773-20210116143113625-2140857260.png)
點選那個加號,然後選擇 exploded 版本。
![](https://img2020.cnblogs.com/blog/1616773/202101/1616773-20210116143508613-1499849135.png)
點選 ok 之後,修改 Application Context,這個 Context 用來配置訪問時候 url 的名字。可以理解為這個 webapp 的名字。之後,我們可以使用 `localhost:8080/example` 來訪問。
![](https://img2020.cnblogs.com/blog/1616773/202101/1616773-20210116143626876-1385009163.png)
至此,我們的第一個 webapp 就配置好了。
## 3.2 JSP
接下來,展開 src,main,webapp,找到 index.jsp。我們可以在這裡開始寫程式碼。
![](https://img2020.cnblogs.com/blog/1616773/202101/1616773-20210116143940689-1912219262.png)
編輯內容,注意到下面有 java 程式碼,其實 jsp 就是 html 和 java 的混合體。下面的 jsp,就是向瀏覽器輸出了 Hello World 這個字串。我們點選執行,啟動一下。這裡就不再展開 JSP 了,如果又需要再去學一學吧。
```
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
$Title$
<%
String s = "Hello World";
out.write(s);
%>
```
可以看到 Hello World 了。
![](https://img2020.cnblogs.com/blog/1616773/202101/1616773-20210116144355793-508358031.png)
## 3.3 Servlet
接下來,我們來寫第一個 Servlet 程式。寫個鬼咧,寫程式碼是不可能寫的,這輩子都不會寫程式碼。直接從 `webapps\examples\WEB-INF\classes` 中複製一個過來。你也可以複製我的程式碼。
![](https://img2020.cnblogs.com/blog/1616773/202101/1616773-20210116145148490-22733553.png)
下面這段程式碼,可以視為一個 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("");
out.println("");
out.println("Hello World! ");
out.println("");
out.println("");
out.println("
Welcome to Tomcat
Welcome to Tomcat
ExampleServlet
ExampleServlet
ExampleServlet
/hello
```
點選啟動,訪問這個連結 http://localhost:8080/example/hello
![](https://img2020.cnblogs.com/blog/1616773/202101/1616773-20210116145906491-450016916.png)
## 3.4 WebSocket
接下來,我們參考官方的例子,搞一個基於 WebSocket 的聊天室。不寫程式碼,全靠複製貼上。
我們需要從 `\webapps\examples\WEB-INF\classes\websocket\chat` 複製程式碼。
![](https://img2020.cnblogs.com/blog/1616773/202101/1616773-20210116150209966-1002210028.png)
將下面程式碼複製到 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 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 下面就好了。
![](https://img2020.cnblogs.com/blog/1616773/202101/1616773-20210116150531614-1358548585.png)
之後還需要修改 `chat.xhtml` 中 websocket 的端點。將下面紅框中的東西,改成一開始 IDEA 啟動配置中的 Application Context。在這裡,我們只需要去掉 s 就好了。
![](https://img2020.cnblogs.com/blog/1616773/202101/1616773-20210116150653491-1696254635.png)
接下來啟動!
通過這個地方訪問聊天室:http://localhost:8080/example/chat.xhtml
![](https://img2020.cnblogs.com/blog/1616773/202101/1616773-20210116150926269-611086163.png)
傳送的訊息,都可以即時被推送。
![](https://img2020.cnblogs.com/blog/1616773/202101/1616773-20210116151113447-1297291907.png)
# 五,總結
這篇部落格展示瞭如何使用 Tomcat,開發使用 Servlet,JSP,WebSocket 的 Demo。
總結一下,Tomcat 就是一個實現了 Servlet,JSP,WebSocket 規範的 HTTP 伺服器。上面展示了使用這些技術的例子,要明白這背後做了什麼,還得了解這些技術的規範,還要去看實現,看 Tomcat