惊呆了!手写4个mini版的Tomcat!

服务器
Apache Tomcat 是Java Servlet, JavaServer Pages (JSP),Java表达式语言和Java的WebSocket技术的一个开源实现 ,通常我们将Tomcat称为Web容器或者Servlet容器 。

[[388146]]

 

写在前面

Apache Tomcat 是Java Servlet, JavaServer Pages (JSP),Java表达式语言和Java的WebSocket技术的一个开源实现 ,通常我们将Tomcat称为Web容器或者Servlet容器 。

今天,我们就来手写tomcat,但是说明一下:咱们不是为了装逼才来写tomcat,而是希望大家能更多的理解和掌握tomcat。

废话不多说了,直接开干。

基本结构

tomcat架构图

我们可以把上面这张架构图做简化,简化后为:

什么是http协议

Http是一种网络应用层协议,规定了浏览器与web服务器之间如何通信以及数据包的结构。

通信大致可以分为四步:

  1. 先建立连接。
  2. 发送请求数据包。
  3. 发送响应数据包。
  4. 关闭连接。

优点

web服务器可以利用有限的连接为尽可能多的客户请求服务。

tomcat中Servlet的运作方式

  1. 在浏览器地址栏输入http://ip:port/servlet-day01/hello
  2. 浏览器依据IP、port建立连接(即与web服务器之间建立网络连接)。
  3. 浏览器需要将相关数据打包(即按照http协议要求,制作一个 请求数据包,包含了一些数据,比如请求资源路径),并且将请求 数据包发送出去。
  4. web服务器会将请求数据包中数据解析出来,并且将这些数据添加 到request对象,同时,还会创建一个response对象。
  5. web服务器创建Servlet对象,然后调用该对象的service方法(会将request和response作为参数)。注:在service方法里面,通过使用request获得请求相关的数据, 比如请求参数值,然后将处理结果写到response。
  6. web服务器将response中的数据取出来,制作响应数据包,然后发送给浏览器。
  7. 浏览器解析响应数据包,然后展现。

可以总结唯一张图:

什么是Servlet呢?

Servlet是JavaEE规范的一种,主要是为了扩展Java作为Web服务的功能,统一接口。由其他内部厂商如tomcat,jetty内部实现web的功能。如一个http请求到来:容器将请求封装为servlet中的HttpServletRequest对象,调用init(),service()等方法输出response,由容器包装为httpresponse返回给客户端的过程。

什么是Servlet规范?

  1. 从 Jar 包上来说,Servlet 规范就是两个 Jar 文件。servlet-api.jar 和 jsp-api.jar,Jsp 也是一种 Servlet。
  2. 从package上来说,就是 javax.servlet 和 javax.servlet.http 两个包。
  3. 从接口来说,就是规范了 Servlet 接口、Filter 接口、Listener 接口、ServletRequest 接口、ServletResponse 接口等。类图如下:

第一版:Socket版

使用Socket编程,实现简单的客户端和服务端的聊天。

服务端代码如下:

package com.tian.v1; 
 
import java.io.*; 
import java.net.*; 
 
 
public class Server { 
 
    public static String readline = null
    public static String inTemp = null
    public static String turnLine = "\n"
    public static final String client = "客户端:"
    public static final String server = "服务端:"
    public static final int PORT = 8090; 
 
    public static void main(String[] args) throws Exception { 
        ServerSocket serverSocket = new ServerSocket(PORT); 
        System.out.println("服务端已经准备好了"); 
        Socket socket = serverSocket.accept(); 
 
        BufferedReader systemIn = new BufferedReader(new InputStreamReader(System.in)); 
        BufferedReader socketIn = new BufferedReader(new InputStreamReader(socket.getInputStream())); 
        PrintWriter socketOut = new PrintWriter(socket.getOutputStream()); 
        while (true) { 
            inTemp = socketIn.readLine(); 
            if (inTemp != null &&inTemp.contains("over")) { 
                systemIn.close(); 
                socketIn.close(); 
                socketOut.close(); 
                socket.close(); 
                serverSocket.close(); 
            } 
            System.out.println(client + inTemp); 
            System.out.print(server); 
 
            readline = systemIn.readLine(); 
 
            socketOut.println(readline); 
            socketOut.flush(); 
        } 
    } 

  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.
  • 20.
  • 21.
  • 22.
  • 23.
  • 24.
  • 25.
  • 26.
  • 27.
  • 28.
  • 29.
  • 30.
  • 31.
  • 32.
  • 33.
  • 34.
  • 35.
  • 36.
  • 37.
  • 38.
  • 39.
  • 40.
  • 41.
  • 42.

客户端代码如下:

package com.tian.v1; 
 
import java.io.*; 
import java.net.*; 
 
public class Client { 
 
    public static void main(String[] args) throws Exception { 
        String readline; 
        String inTemp; 
        final String client = "客户端说:"
        final String server = "服务端回复:"
 
        int port = 8090; 
        byte[] ipAddressTemp = {127, 0, 0, 1}; 
        InetAddress ipAddress = InetAddress.getByAddress(ipAddressTemp); 
 
        //首先直接创建socket,端口号1~1023为系统保存,一般设在1023之外 
        Socket socket = new Socket(ipAddress, port); 
 
        BufferedReader systemIn = new BufferedReader(new InputStreamReader(System.in)); 
        BufferedReader socketIn = new BufferedReader(new InputStreamReader(socket.getInputStream())); 
        PrintWriter socketOut = new PrintWriter(socket.getOutputStream()); 
        while (true) { 
            System.out.print(client); 
            readline = systemIn.readLine(); 
 
            socketOut.println(readline); 
            socketOut.flush(); 
            //处理 
            inTemp = socketIn.readLine(); 
            if (inTemp != null && inTemp.contains("over")) { 
                systemIn.close(); 
                socketIn.close(); 
                socketOut.close(); 
                socket.close(); 
            } 
            System.out.println(server + inTemp); 
        } 
    } 

  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.
  • 20.
  • 21.
  • 22.
  • 23.
  • 24.
  • 25.
  • 26.
  • 27.
  • 28.
  • 29.
  • 30.
  • 31.
  • 32.
  • 33.
  • 34.
  • 35.
  • 36.
  • 37.
  • 38.
  • 39.
  • 40.
  • 41.

过程如下:

第二版:我们直接请求http://localhost:8090

实现代码如下:

package com.tian.v2; 
 
import java.io.IOException; 
import java.io.OutputStream; 
import java.net.ServerSocket; 
import java.net.Socket; 
 
public class MyTomcat { 
    /** 
     * 设定启动和监听端口 
     */ 
    private int port = 8090; 
 
    /** 
     * 启动函数 
     * 
     * @throws IOException 
     */ 
    public void start() throws IOException { 
        System.out.println("my tomcat starting..."); 
        String responseData = "6666666"
        ServerSocket socket = new ServerSocket(port); 
        while (true) { 
            Socket accept = socket.accept(); 
            OutputStream outputStream = accept.getOutputStream(); 
            String responseText = HttpProtocolUtil.getHttpHeader200(responseData.length()) + responseData; 
            outputStream.write(responseText.getBytes()); 
            accept.close(); 
        } 
    } 
 
    /** 
     * 启动入口 
     */ 
    public static void main(String[] args) throws IOException { 
        MyTomcat tomcat = new MyTomcat(); 
        tomcat.start(); 
    } 

  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.
  • 20.
  • 21.
  • 22.
  • 23.
  • 24.
  • 25.
  • 26.
  • 27.
  • 28.
  • 29.
  • 30.
  • 31.
  • 32.
  • 33.
  • 34.
  • 35.
  • 36.
  • 37.
  • 38.
  • 39.

再写一个工具类,内容如下;

ackage com.tian.v2; 
 
public class HttpProtocolUtil { 
 
    /** 
     * 200 状态码,头信息 
     * 
     * @param contentLength 响应信息长度 
     * @return 200 header info 
     */ 
    public static String getHttpHeader200(long contentLength) { 
        return "HTTP/1.1 200 OK \n" + "Content-Type: text/html \n" 
                + "Content-Length: " + contentLength + " \n" + "\r\n"
    } 
 
    /** 
     * 为响应码 404 提供请求头信息(此处也包含了数据内容) 
     * 
     * @return 404 header info 
     */ 
    public static String getHttpHeader404() { 
        String str404 = "<h1>404 not found</h1>"
        return "HTTP/1.1 404 NOT Found \n" + "Content-Type: text/html \n" 
                + "Content-Length: " + str404.getBytes().length + " \n" + "\r\n" + str404; 
    } 

  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.
  • 20.
  • 21.
  • 22.
  • 23.
  • 24.
  • 25.
  • 26.

启动main方法:

使用IDEA访问:

在浏览器访问:

自此,我们的第二版本搞定。下面继续第三个版本;

第三版:封装请求信息和响应信息

一个http协议的请求包含三部分:

  • 方法 URI 协议/版本
  • 请求的头部
  • 主体内容

比如

POST /index.html HTTP/1.1 
Accept: text/plain; text/html 
Accept-Language: en-gb 
Connection: Keep-Alive 
Host: localhost 
User-Agent: Mozilla/4.0 (compatible; MSIE 4.01; Windows 98) 
Content-Length: 33 
Content-Type: application/x-www-form-urlencoded 
Accept-Encoding: gzip, deflate 
 
lastName=tian&firstName=JohnTian 
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.

简单的解释

  • 数据的第一行包括:方法、URI、协议和版本。在这个例子里,方法为POST,URI为/index.html,协议为HTTP/1.1,协议版本号为1.1。他们之间通过空格来分离。
  • 请求头部从第二行开始,使用英文冒号(:)来分离键和值。
  • 请求头部和主体内容之间通过空行来分离,例子中的请求体为表单数据。

类似于http协议的请求,响应也包含三个部分。

  • 协议 状态 状态描述
  • 响应的头部
  • 主体内容

比如:

HTTP/1.1 200 OK 
Server: Microsoft-IIS/4.0 
Date: Mon, 5 Jan 2004 13:13:33 GMT 
Content-Type: text/html 
Last-Modified: Mon, 5 Jan 2004 13:13:12 GMT 
Content-Length: 112 
 
<html> 
<head> 
<title>HTTP Response Example</title> </head> 
<body> 
Welcome to Brainy Software 
</body> 
</html> 
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.

 

 

简单解释

  • 第一行,HTTP/1.1 200 OK表示协议、状态和状态描述。
  • 之后表示响应头部。
  • 响应头部和主体内容之间使用空行来分离。

代码实现

创建一个工具类,用来获取静态资源信息。

package com.tian.v3; 
 
import com.tian.v2.HttpProtocolUtil; 
 
import java.io.IOException; 
import java.io.InputStream; 
import java.io.OutputStream; 
 
/** 
 * 提取了一些共用类和函数 
 */ 
public class ResourceUtil { 
 
    /** 
     * 根据请求 url 获取完整绝对路径 
     */ 
    public static String getPath(String url) { 
        String path = ResourceUtil.class.getResource("/").getPath(); 
        return path.replaceAll("\\\\", "/") + url; 
    } 
 
    /** 
     * 输出静态资源信息 
     */ 
    public static void outputResource(InputStream input, OutputStream output) throws IOException { 
        int count = 0; 
        while (count == 0) { 
            count = input.available(); 
        } 
        int resourceSize = count
        output.write(HttpProtocolUtil.getHttpHeader200(resourceSize).getBytes()); 
        long written = 0; 
        int byteSize = 1024; 
        byte[] bytes = new byte[byteSize]; 
        while (written < resourceSize) { 
            if (written + byteSize > resourceSize) { 
                byteSize = (int) (resourceSize - written); 
                bytes = new byte[byteSize]; 
            } 
            input.read(bytes); 
            output.write(bytes); 
            output.flush(); 
            written += byteSize; 
        } 
    } 

  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.
  • 20.
  • 21.
  • 22.
  • 23.
  • 24.
  • 25.
  • 26.
  • 27.
  • 28.
  • 29.
  • 30.
  • 31.
  • 32.
  • 33.
  • 34.
  • 35.
  • 36.
  • 37.
  • 38.
  • 39.
  • 40.
  • 41.
  • 42.
  • 43.
  • 44.
  • 45.
  • 46.

另外HttpProtocolUtil照样用第二版本中。

再创建Request类,用来解析并存放请求相关参数。

package com.tian.v3; 
 
import java.io.IOException; 
import java.io.InputStream; 
 
public class Request { 
    /** 
     * 请求方式, eg: GET、POST 
     */ 
    private String method; 
 
    /** 
     * 请求路径,eg: /index.html 
     */ 
    private String url; 
 
    /** 
     * 请求信息输入流 <br> 
     * 示例 
     * <pre> 
     *  GET / HTTP/1.1 
     *  Host: localhost 
     *  Connection: keep-alive 
     *  Pragma: no-cache 
     *  Cache-Control: no-cache 
     *  Upgrade-Insecure-Requests: 1 
     *  User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/85.0.4183.83 Safari/537.36 
     * </pre> 
     */ 
    private InputStream inputStream; 
 
    public Request() { 
    } 
 
    public Request(InputStream inputStream) throws IOException { 
        this.inputStream = inputStream; 
        int count = 0; 
        while (count == 0) { 
            count = inputStream.available(); 
        } 
        byte[] bytes = new byte[count]; 
        inputStream.read(bytes); 
        // requestString 参考:this.inputStream 示例 
        String requestString = new String(bytes); 
        // 按换行分隔 
        String[] requestStringArray = requestString.split("\\n"); 
        // 读取第一行数据,即:GET / HTTP/1.1 
        String firstLine = requestStringArray[0]; 
        // 遍历第一行数据按空格分隔 
        String[] firstLineArray = firstLine.split(" "); 
        this.method = firstLineArray[0]; 
        this.url = firstLineArray[1]; 
    } 
 
    public String getMethod() { 
        return method; 
    } 
 
    public void setMethod(String method) { 
        this.method = method; 
    } 
 
    public String getUrl() { 
        return url; 
    } 
 
    public void setUrl(String url) { 
        this.url = url; 
    } 
 
    public InputStream getInputStream() { 
        return inputStream; 
    } 
 
    public void setInputStream(InputStream inputStream) { 
        this.inputStream = inputStream; 
    } 

  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.
  • 20.
  • 21.
  • 22.
  • 23.
  • 24.
  • 25.
  • 26.
  • 27.
  • 28.
  • 29.
  • 30.
  • 31.
  • 32.
  • 33.
  • 34.
  • 35.
  • 36.
  • 37.
  • 38.
  • 39.
  • 40.
  • 41.
  • 42.
  • 43.
  • 44.
  • 45.
  • 46.
  • 47.
  • 48.
  • 49.
  • 50.
  • 51.
  • 52.
  • 53.
  • 54.
  • 55.
  • 56.
  • 57.
  • 58.
  • 59.
  • 60.
  • 61.
  • 62.
  • 63.
  • 64.
  • 65.
  • 66.
  • 67.
  • 68.
  • 69.
  • 70.
  • 71.
  • 72.
  • 73.
  • 74.
  • 75.
  • 76.
  • 77.
  • 78.

把第二版的MyTomcat进行小小调整:

package com.tian.v3; 
 
import java.io.IOException; 
import java.io.OutputStream; 
import java.net.ServerSocket; 
import java.net.Socket; 
 
public class MyTomcat { 
 
    private static final int PORT = 8090; 
    public void start() throws IOException { 
        System.out.println("my tomcat starting..."); 
        ServerSocket socket = new ServerSocket(PORT); 
        while (true) { 
            Socket accept = socket.accept(); 
            OutputStream outputStream = accept.getOutputStream(); 
            // 分别封装 Request 和 Response 
            Request request = new Request(accept.getInputStream()); 
            Response response = new Response(outputStream); 
            // 根据 request 中的 url,输出 
            response.outputHtml(request.getUrl()); 
            accept.close(); 
        } 
    } 
    
    public static void main(String[] args) throws IOException { 
        MyTomcat tomcat = new MyTomcat(); 
        tomcat.start(); 
    } 

  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.
  • 20.
  • 21.
  • 22.
  • 23.
  • 24.
  • 25.
  • 26.
  • 27.
  • 28.
  • 29.
  • 30.

然后再创建一个index.html,内容很简单:

 

 

<!DOCTYPE html> 
<html lang="en"
<head> 
    <meta charset="UTF-8"
    <title>hello world</title> 
</head> 
<body> 
<h2> you already succeed!</h2> 
</body> 
</html> 
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.

 

 

 

这一需要注意,index.html文件的存放路径不放错了,视本地路径来定哈,放在classes文件夹下的。你可以debug试试,看看你应该放在那个目录下。

启动MyTomcat。

访问http://localhost:8090/index.html

自此,我们针对于Http请求参数和相应参数做了一个简单的解析以及封装。

尽管其中还有很多问题,但是字少看起来有那点像样了。我们继续第四版,

第四版:实现动态请求资源

用过servlet的同学都知道,Servlet中有三个很重要的方法init、destroy 、service 。其中还记得我们自己写LoginServlet的时候,还会重写HttpServlet中的doGet()和doPost()方法。下面们就自己来搞一个:

Servlet类代码如下:

public interface Servlet { 
    void init() throws Exception; 
    void destroy() throws Exception; 
    void service(Request request, Response response) throws Exception; 

  • 1.
  • 2.
  • 3.
  • 4.
  • 5.

然后再写一个HttpServlet来实现Servlet。

代码实现如下:

package com.tian.v4; 
 
public abstract class HttpServlet implements Servlet { 
    @Override 
    public void init() throws Exception { 
 
    } 
 
    @Override 
    public void destroy() throws Exception { 
 
    } 
 
    @Override 
    public void service(Request request, Response response) throws Exception { 
        String method = request.getMethod(); 
        if ("GET".equalsIgnoreCase(method)) { 
            doGet(request, response); 
        } else { 
            doPost(request, response); 
        } 
    } 
    public abstract void doGet(Request request, Response response) throws Exception; 
 
    public abstract void doPost(Request request, Response response) throws Exception; 

  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.
  • 20.
  • 21.
  • 22.
  • 23.
  • 24.
  • 25.
  • 26.

下面我们就来写一个自己的Servlet,比如LoginServlet。

package com.tian.v4; 
 
public class LoginServlet  extends HttpServlet { 
 
    @Override 
    public void doGet(Request request, Response response) throws Exception { 
        String repText = "<h1> LoginServlet by GET method</h1>"
        response.output(HttpProtocolUtil.getHttpHeader200(repText.length()) + repText); 
    } 
 
    @Override 
    public void doPost(Request request, Response response) throws Exception { 
        String repText = "<h1>LoginServlet by POST method</h1>"
        response.output(HttpProtocolUtil.getHttpHeader200(repText.length()) + repText); 
    } 
 
    @Override 
    public void init() throws Exception { 
    } 
 
    @Override 
    public void destroy() throws Exception { 
    } 

  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.
  • 20.
  • 21.
  • 22.
  • 23.
  • 24.

大家是否还记得,我们在学习Servlet的时候,在resources目录下面有个web.xml。我们这个版本也把这个xml文件给引入。

<?xml version="1.0" encoding="utf-8"?> 
<web-app> 
    <servlet> 
        <servlet-name>login</servlet-name
        <servlet-class>com.tian.v4.LoginServlet</servlet-class> 
    </servlet> 
 
    <servlet-mapping> 
        <servlet-name>login</servlet-name
        <url-pattern>/login</url-pattern> 
    </servlet-mapping> 
</web-app> 
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.

 

 

既然引入了xml文件,那我们就需要去读取这个xml文件,并解析器内容。所以这里我们需要引入两个jar包。

<dependencies> 
    <dependency> 
        <groupId>dom4j</groupId> 
        <artifactId>dom4j</artifactId> 
        <version>1.6.1</version> 
    </dependency> 
    <dependency> 
        <groupId>jaxen</groupId> 
        <artifactId>jaxen</artifactId> 
        <version>1.1.6</version> 
    </dependency> 
</dependencies> 
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.

 

 

万事俱备,只欠东风了。这时候我们来吧MyTomcat这个类做一些调整即可。

下面有个很重要的initServlet()方法,刚刚是对应下面这张图中的List servlets,但是我们代码里使用的是Map来存储Servlet的,意思就那么个意思,把Servlet放在集合里。

这也就是为什么大家都把Tomcat叫做Servlet容器的原因,其实真正的容器还是java集合。

package com.tian.v4; 
 
import com.tian.v3.RequestV3; 
import com.tian.v3.ResponseV3; 
import org.dom4j.Document; 
import org.dom4j.Element; 
import org.dom4j.io.SAXReader; 
 
import java.io.IOException; 
import java.io.InputStream; 
import java.io.OutputStream; 
import java.net.ServerSocket; 
import java.net.Socket; 
import java.util.HashMap; 
import java.util.List; 
import java.util.Map; 
 
public class MyTomcat { 
    /** 
     * 设定启动和监听端口 
     */ 
    private static final int PORT = 8090; 
    /** 
     * 存放 Servlet信息,url: Servlet 实例 
     */ 
    private Map<String, HttpServlet> servletMap = new HashMap<>(); 
  
    public void start() throws Exception { 
 
        System.out.println("my tomcat starting..."); 
        initServlet(); 
        ServerSocket socket = new ServerSocket(PORT); 
        while (true) { 
            Socket accept = socket.accept(); 
            OutputStream outputStream = accept.getOutputStream(); 
            // 分别封装 RequestV3 和 ResponseV3 
            RequestV4 requestV3 = new RequestV4(accept.getInputStream()); 
            ResponseV4 responseV3 = new ResponseV4(outputStream); 
            // 根据 url 来获取 Servlet 
            HttpServlet httpServlet = servletMap.get(requestV3.getUrl()); 
            // 如果 Servlet 为空,说明是静态资源,不为空即为动态资源,需要执行 Servlet 里的方法 
            if (httpServlet == null) { 
                responseV3.outputHtml(requestV3.getUrl()); 
            } else { 
                httpServlet.service(requestV3, responseV3); 
            } 
            accept.close(); 
        } 
    }  
     
    public static void main(String[] args) throws Exception { 
        MyTomcat tomcat = new MyTomcat(); 
        tomcat.start(); 
    } 
 
 
    /** 
     * 解析web.xml文件,把url和servlet解析出来, 
     * 并保存到一个java集合里(Map) 
     */ 
    public void initServlet() throws Exception { 
        InputStream resourceAsStream = this.getClass().getClassLoader().getResourceAsStream("web.xml"); 
        SAXReader saxReader = new SAXReader(); 
        Document document = saxReader.read(resourceAsStream); 
        Element rootElement = document.getRootElement(); 
        List<Element> list = rootElement.selectNodes("//servlet"); 
        for (Element element : list) { 
            // <servlet-name>show</servlet-name
            Element servletnameElement = (Element) element.selectSingleNode("servlet-name"); 
            String servletName = servletnameElement.getStringValue(); 
            // <servlet-class>server.ShowServlet</servlet-class> 
            Element servletclassElement = (Element) element.selectSingleNode("servlet-class"); 
            String servletClass = servletclassElement.getStringValue(); 
 
            // 根据 servlet-name 的值找到 url-pattern 
            Element servletMapping = (Element) rootElement.selectSingleNode("/web-app/servlet-mapping[servlet-name='" + servletName + "']"); 
            // /show 
            String urlPattern = servletMapping.selectSingleNode("url-pattern").getStringValue(); 
            servletMap.put(urlPattern, (HttpServlet) Class.forName(servletClass).getDeclaredConstructor().newInstance()); 
        } 
    } 

  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.
  • 20.
  • 21.
  • 22.
  • 23.
  • 24.
  • 25.
  • 26.
  • 27.
  • 28.
  • 29.
  • 30.
  • 31.
  • 32.
  • 33.
  • 34.
  • 35.
  • 36.
  • 37.
  • 38.
  • 39.
  • 40.
  • 41.
  • 42.
  • 43.
  • 44.
  • 45.
  • 46.
  • 47.
  • 48.
  • 49.
  • 50.
  • 51.
  • 52.
  • 53.
  • 54.
  • 55.
  • 56.
  • 57.
  • 58.
  • 59.
  • 60.
  • 61.
  • 62.
  • 63.
  • 64.
  • 65.
  • 66.
  • 67.
  • 68.
  • 69.
  • 70.
  • 71.
  • 72.
  • 73.
  • 74.
  • 75.
  • 76.
  • 77.
  • 78.
  • 79.
  • 80.
  • 81.
  • 82.

启动,再次访问http://localhost:8090/index.html

同时,我们可以访问http://localhost:8090/login图片

到此,第四个版本也搞定了。

但是前面四个版本都有一个共同的问题,全部使用的是BIO。

BIO:同步并阻塞,服务器实现模式为一个连接一个线程,即客户端有连接请求时服务器端就需要启动一个线程进行处理,如果这个连接不做任何事情会造成不必要的线程开销,当然可以通过线程池机制改善。

所以,大家在网上看到的手写tomcat的,也有使用线程池来做的,这里希望大家能get到为什么使用线程池来实现。另外,其实在tomcat高版本中已经没有使用BIO了。

而 HTTP/1.1默认使用的就是NIO了。

但这个只是通信方式,重点是我们要理解和掌握tomcat的整体实现。

总结

另外,发现上面都是讲配置文件解析,并将对应数据保存起来。熟悉这个套路后,大家是不是想到,我们很多配置项都是在server.xml中,还记得否?也是可以通过解析某个目录下的server.xml文件,并把内容赋给java中相应的变量罢了。

比如:

1.server.xml中的端口配置,我们是在代码里写死的而已,改成MyTomcat启动的时候去解析并获取不久得了吗?

2.我们通常是将我们项目的打成war,然后解压到某个目录下,最后还不是可以通过读取这个解压后的某个目录中找到web.xml,然后用回到上面的web.xml解析了。

本文主要是分享如何从一个塑料版到黄金版、然后铂金版,最后到砖石版。可以把加入线程池的版本称之为星耀版,最后把相关server.xml解析,以及读取我们放入到tomcat中项目解析可以称之为王者版。

技术点:Socket编程、InputStream、OutputStream、线程池、xml文件解析、反射。更高级版本中NIO,AIO等。

不是为了装逼而来搞这个tomcat,而是为了我们更深刻的理解tomcat的原理。

本文转载自微信公众号「Java后端技术全栈」,可以通过以下二维码关注。转载本文请联系Java后端技术全栈公众号。

 

责任编辑:武晓燕 来源: Java后端技术全栈
相关推荐

2015-06-24 16:09:54

Easy Connec深信服

2015-05-19 14:30:48

加密视频加密亿赛通

2024-07-05 11:47:43

2021-11-02 11:31:47

Go代码模式

2021-12-13 22:52:37

iphone iOSHTML

2021-05-28 10:09:22

GC详解Java JVM

2020-01-06 09:14:59

Java程序员线程

2020-04-02 07:31:53

RPC超时服务端

2020-10-31 09:06:37

C语言编程语言

2021-07-05 18:05:40

SpringBean方法

2013-08-09 10:37:31

代码数据

2013-12-27 09:46:40

Windows 9Windows 9桌面

2016-12-21 12:19:57

AR广告奥迪

2013-07-22 11:06:37

2022-06-24 14:52:34

AI模型

2024-05-16 13:51:00

AI

2022-11-26 21:34:08

Python可视化世界杯

2015-12-15 10:33:59

域名网络域名

2019-05-13 15:05:34

TomcatWeb Server协议

2010-03-24 09:09:00

Opera MiniIphone
点赞
收藏

51CTO技术栈公众号