【Tomcat】手寫迷你版Tomcat

鄧曉暉發表於2020-12-28

原始碼地址

https://github.com/CoderXiaohui/mini-tomcat

一,分析

Mini版Tomcat需要實現的功能

作為一個伺服器軟體提供服務(通過瀏覽器客戶端傳送Http請求,它可以接收到請求進行處理,處理之後的結果返回瀏覽器客戶端)。

  1. 提供服務,接收請求(socket通訊)
  2. 請求資訊封裝成Request物件,封裝響應資訊Response物件
  3. 客戶端請求資源,資源分為靜態資源(html)和動態資源(servlet)
  4. 資源返回給客戶端瀏覽器

*Tomcat的入口就是一個main函式

二,開發——準備工作

2.1 新建Maven工程

image-20201227160427940

image-20201227161023307

2.2 定義編譯級別

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>com.dxh</groupId>
    <artifactId>MiniCat</artifactId>
    <version>1.0-SNAPSHOT</version>

    <build>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <version>3.1</version>
                <configuration>
                    <source>11</source>
                    <target>11</target>
                    <encoding>utf-8</encoding>
                </configuration>
            </plugin>
        </plugins>
    </build>
</project>

2.3 新建主類編寫啟動入口和埠

這裡我們把socket監聽的埠號定義在主類中。

package server;

/**
 * Minicat的主類
 */
public class Bootstrap {
    /**
     * 定義Socket監聽的埠號
     */
    private int port = 8080;

    public int getPort() {
        return port;
    }

    public void setPort(int port) {
        this.port = port;
    }

    /**
     * Minicat的啟動入口
     * @param args
     */
    public static void main(String[] args) {
        
    }
}

三,開發——1.0版本

循序漸進,一點一點的完善,1.0版本我們需要的需求是:

  • 瀏覽器請求http://localhost:8080,返回一個固定的字串到頁面“Hello Minicat”

3.1 編寫start方法以及遇到的問題

start方法主要就是監聽上面配置的埠,然後得到其輸出流,最後寫出。

/**
 * MiniCat啟動需要初始化展開的一些操作
 */
public void start() throws IOException {
    /*
       完成Minicat 1.0版本
       需求:瀏覽器請求http://localhost:8080,返回一個固定的字串到頁面“Hello Minicat!”
     */
    ServerSocket serverSocket = new ServerSocket(port);
    System.out.println("========>>Minicat start on port:"+port);

    while(true){
        Socket socket = serverSocket.accept();
        //有了socket,接收到請求,獲取輸出流
        OutputStream outputStream = socket.getOutputStream();
        outputStream.write("Hello Minicat!".getBytes());
        socket.close();
    } 
}

完整的程式碼:


/**
 * Minicat的主類
 */
public class Bootstrap {
    /**
     * 定義Socket監聽的埠號
     */
    private int port = 8080;

    public int getPort() {
        return port;
    }

    public void setPort(int port) {
        this.port = port;
    }

    /**
     * Minicat的啟動入口
     * @param args
     */
    public static void main(String[] args) {
        Bootstrap bootstrap = new Bootstrap();
        try {
            //啟動Minicat
            bootstrap.start();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    /**
     * MiniCat啟動需要初始化展開的一些操作
     */
    public void start() throws IOException {
        ServerSocket serverSocket = new ServerSocket(port);
        System.out.println("========>>Minicat start on port:"+port);

        while(true){
            Socket socket = serverSocket.accept();
            //有了socket,接收到請求,獲取輸出流
            OutputStream outputStream = socket.getOutputStream();
            outputStream.write("Hello Minicat!".getBytes());
            socket.close();
        }
    }

}

此時,如果啟動專案,從瀏覽器中輸入http://localhost:8080/,能夠正常接收到請求嗎?

不能!

問題分析:

啟動專案,從瀏覽器中輸入http://localhost:8080/,可看到返回結果如下圖:
image-20201227165334660

因為Http協議是一個應用層協議,其規定了請求頭、請求體、響應同樣,如果沒有這些東西的話瀏覽器無法正常顯示。程式碼中直接把”Hello Minicat!“直接輸出了,

3.2 解決問題,修改程式碼:

  1. 新建一個工具類,主要提供響應頭資訊

    package server;
    
    /**
     * http協議工具類,主要提供響應頭資訊,這裡我們只提供200和404的情況
     */
    public class HttpProtocolUtil {
    
        /**
         *  為響應碼200提供請求頭資訊
         */
        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提供請求頭資訊(也包含了資料內容)
         */
        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;
        }
    
    }
    
    
  2. 修改start方法

        public void start() throws IOException {
    
            ServerSocket serverSocket = new ServerSocket(port);
            System.out.println("========>>Minicat start on port:"+port);
    
            while(true){
                Socket socket = serverSocket.accept();
               
                OutputStream outputStream = socket.getOutputStream();
                String data = "Hello Minicat!";
                String responseText = HttpProtocolUtil.getHttpHeader200(data.getBytes().length)+data;
                outputStream.write(responseText.getBytes());
                socket.close();
            }
        }
    
  3. 訪問~
    image-20201227172649749

成功。

四,開發——2.0版本

需求:

  • 封裝Request和Response物件
  • 返回html靜態資原始檔

4.1 封裝前準備

新建一個類,Bootstrap2 (為了方便與1.0版本做對比)。獲得輸入流,並列印出來看看。

package server;

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.ServerSocket;
import java.net.Socket;

/**
 * Minicat的主類
 */
public class Bootstrap2 {
    /**
     * 定義Socket監聽的埠號
     */
    private int port = 8080;

    public int getPort() {
        return port;
    }

    public void setPort(int port) {
        this.port = port;
    }

    /**
     * Minicat的啟動入口
     * @param args
     */
    public static void main(String[] args) {
        Bootstrap2 bootstrap = new Bootstrap2();
        try {
            //啟動Minicat
            bootstrap.start();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    /**
     * MiniCat啟動需要初始化展開的一些操作
     */
    public void start() throws IOException {
        ServerSocket serverSocket = new ServerSocket(port);
        System.out.println("========>>Minicat start on port:"+port);

        while (true){
            Socket socket = serverSocket.accept();
            InputStream inputStream = socket.getInputStream();
            //從輸入流中獲取請求資訊
            int count = 0 ;
            while (count==0){
                count = inputStream.available();
            }
            byte[] bytes = new byte[count];
            inputStream.read(bytes);
            System.out.println("請求資訊=====>>"+new String(bytes));
            socket.close();
        }

    }

}

列印出來的資訊:

image-20201227191058136

這裡我們需要得到的是 請求方式(GET) 和 url (/) ,接下來封裝Request的時候也是隻封裝這兩個屬性

4.2封裝Request、Response物件

4.2.1 封裝Request

只封裝兩個引數——method和url

  1. 新建Request類

  2. 該類有三個屬性(String methodString urlInputStream inputStream
    method和url都是從input流中解析出來的。

  3. GET SET方法

  4. 編寫有參構造

    /**
     * 構造器 輸入流傳入
     */
    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);
        String inputsStr = new String(bytes);
        //獲取第一行資料
        String firstLineStr = inputsStr.split("\\n")[0];  //GET / HTTP/1.1
        String[] strings = firstLineStr.split(" ");
        //把解析出來的資料賦值
        this.method=strings[0];
        this.url= strings[1];
    
        System.out.println("method=====>>"+method);
        System.out.println("url=====>>"+url);
    }
    
  5. 無參構造

完整的Request.java

package server;

import java.io.IOException;
import java.io.InputStream;

/**
 * 把我們用到的請求資訊,封裝成Response物件 (根據inputSteam輸入流封裝)
 */
public class Request {
    /**
     * 請求方式 例如:GET/POST
     */
    private String method;

    /**
     * / , /index.html
     */
    private String url;

    /**
     * 其他的屬性都是通過inputStream解析出來的。
     */
    private InputStream inputStream;

    /**
     * 構造器 輸入流傳入
     */
    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);
        String inputsStr = new String(bytes);
        //獲取第一行資料
        String firstLineStr = inputsStr.split("\\n")[0];  //GET / HTTP/1.1
        String[] strings = firstLineStr.split(" ");
        this.method=strings[0];
        this.url= strings[1];

        System.out.println("method=====>>"+method);
        System.out.println("url=====>>"+url);
    }

    public Request() {
    }

    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;
    }
a
    public InputStream getInputStream() {
        return inputStream;
    }

    public void setInputStream(InputStream inputStream) {
        this.inputStream = inputStream;
    }
}

4.2.2 封裝Response

package server;

import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.OutputStream;

/**
 * 封裝Response物件,需要依賴於OutputStream
 *
 */
public class Response{
    private OutputStream outputStream;

    public Response(OutputStream outputStream) {
        this.outputStream = outputStream;
    }

    public Response() {
    }

    /**
     * @param path 指的就是 Request中的url ,隨後要根據url來獲取到靜態資源的絕對路徑,進一步根據絕對路徑讀取該靜態資原始檔,最終通過輸出流輸出
     */
    public void outputHtml(String path) throws IOException {
        //獲取靜態資源的絕對路徑
        String absoluteResourcePath = StaticResourceUtil.getAbsolutePath(path);

        //輸出靜態資原始檔
        File file = new File(absoluteResourcePath);
        if (file.exists() && file.isFile()){
            //讀取靜態資原始檔,輸出靜態資源
            StaticResourceUtil.outputStaticResource(new FileInputStream(file),outputStream);
        }else{
            //輸出404
            output(HttpProtocolUtil.getHttpHeader404());
        }
    }

    //使用輸出流輸出指定字串
    public void output(String context) throws IOException {
        outputStream.write(context.getBytes());
    }
}

2.0版本只考慮輸出靜態資原始檔

我們來分析一下outputHtml(String path)這個方法

首先,path就指 Request中的url,我們要用這個url找到該資源的絕對路徑:

  1. 根據path,獲取靜態資源的絕對路徑

    public static String getAbsolutePath(String path){
        String absolutePath = StaticResourceUtil.class.getResource("/").getPath();
        return absolutePath.replaceAll("\\\\","/")+path;
    }
    
  2. 判斷靜態資源是否存在

    • 不存在:輸出404
  3. 存在:讀取靜態資原始檔,輸出靜態資源

    public static void outputStaticResource(InputStream inputStream, OutputStream outputStream) throws IOException {
            int count = 0 ;
            while (count==0){
                count=inputStream.available();
            }
            //靜態資源長度
            int resourceSize = count;
            //輸出Http請求頭 , 然後再輸出具體的內容
            outputStream.write(HttpProtocolUtil.getHttpHeader200(resourceSize).getBytes());
    
            //讀取內容輸出
            long written = 0;   //已經讀取的內容長度
            int byteSize = 1024; //計劃每次緩衝的長度
            byte[] bytes = new byte[byteSize];
    
            while (written<resourceSize){
                if (written+byteSize >resourceSize){    //剩餘未讀取大小不足一個1024長度,那就按照真實長度處理
                    byteSize= (int)(resourceSize-written);  //剩餘的檔案內容長度
                    bytes=new byte[byteSize];
                }
                inputStream.read(bytes);
                outputStream.write(bytes);
                outputStream.flush();
    
                written+=byteSize;
            }
        }
    

把上述的第一步和第三步的方法封裝到一個類中:

package server;

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;

public class StaticResourceUtil {

    /**
     * 獲取靜態資源方法的絕對路徑
     */
    public static String getAbsolutePath(String path){
        String absolutePath = StaticResourceUtil.class.getResource("/").getPath();
        return absolutePath.replaceAll("\\\\","/")+path;
    }


    /**
     * 讀取靜態資原始檔輸入流,通過輸出流輸出
     */
    public static void outputStaticResource(InputStream inputStream, OutputStream outputStream) throws IOException {
        int count = 0 ;
        while (count==0){
            count=inputStream.available();
        }
        //靜態資源長度
        int resourceSize = count;
        //輸出Http請求頭 , 然後再輸出具體的內容
        outputStream.write(HttpProtocolUtil.getHttpHeader200(resourceSize).getBytes());

        //讀取內容輸出
        long written = 0;   //已經讀取的內容長度
        int byteSize = 1024; //計劃每次緩衝的長度
        byte[] bytes = new byte[byteSize];

        while (written<resourceSize){
            if (written+byteSize >resourceSize){    //剩餘未讀取大小不足一個1024長度,那就按照真實長度處理
                byteSize= (int)(resourceSize-written);  //剩餘的檔案內容長度
                bytes=new byte[byteSize];
            }
            inputStream.read(bytes);
            outputStream.write(bytes);
            outputStream.flush();

            written+=byteSize;
        }
    }

}

測試:

  1. 修改Bootstrap2.java中的start()方法

    public void start() throws IOException {
            ServerSocket serverSocket = new ServerSocket(port);
            System.out.println("========>>Minicat start on port:"+port);
    
            while (true){
                Socket socket = serverSocket.accept();
                InputStream inputStream = socket.getInputStream();
                //封裝Resuest物件和Response物件
                Request request = new Request(inputStream);
                Response response = new Response(socket.getOutputStream());
                response.outputHtml(request.getUrl());
                socket.close();
           }
    }
    
  2. 在專案的resources資料夾新建index.html檔案

    <!DOCTYPE html>
    <html lang="en">
    <head>
        <meta charset="UTF-8">
        <title>Static resource </title>
    </head>
    <body>
    Hello ~ Static resource
    </body>
    </html>
    
  3. 執行main方法

  4. 瀏覽器輸入:http://localhost:8080/index.html

  5. 結果展現:
    image-20201227205755188

五,開發——3.0版本

3.0版本就要定義Servlet了,大致分為以下幾步:

  1. 定義servlet規範
  2. 編寫Servlet
  3. 載入解析Servlet配置

5.1 定義servlet規範

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

定義一個抽象類,實現Servlet,並且增加兩個抽象方法doGet , doPost.

public abstract class HttpServlet implements Servlet{

    public abstract void doGet(Request request,Response response);
    public abstract void doPost(Request request,Response response);


    @Override
    public void init() throws Exception {

    }

    @Override
    public void destroy() throws Exception {

    }

    @Override
    public void service(Request request, Response response) throws Exception {
        if ("GET".equals(request.getMethod())){
            doGet(request, response);
        }else{
            doPost(request, response);
        }
    }
}

5.2 編寫Servlet繼承HttpServlet

新建DxhServlet.java,並繼承HttpServlet重寫doGet和doPost方法

package server;

import java.io.IOException;

public class DxhServlet extends HttpServlet{
    @Override
    public void doGet(Request request, Response response) {
        String content="<h1>DxhServlet get</h1>";
        try {
            response.output(HttpProtocolUtil.getHttpHeader200(content.getBytes().length)+content);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    @Override
    public void doPost(Request request, Response response) {
        String content="<h1>DxhServlet post</h1>";
        try {
            response.output(HttpProtocolUtil.getHttpHeader200(content.getBytes().length)+content);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    @Override
    public void init() throws Exception {
        super.init();
    }

    @Override
    public void destroy() throws Exception {
        super.destroy();
    }
}

接下來要把DxhServlet配置到一個配置檔案中,當MiniCat啟動時,載入進去。

5.3 載入解析Servlet配置

5.3.1 配置檔案

resources目錄下,新建web.xml

<?xml version="1.0" encoding="UTF-8" ?>
<web-app>
    <servlet>
        <servlet-name>dxh</servlet-name>
        <servlet-class>server.DxhServlet</servlet-class>
    </servlet>

    <servlet-mapping>
        <servlet-name>dxh</servlet-name>
        <url-pattern>/dxh</url-pattern>
    </servlet-mapping>
</web-app>

標準的配置Servlet的標籤。servlet-class改成自己寫的Servlet全限定類名,url-pattern/dxh,一會請求http://localhost:8080/dxh,來訪問這個servlet

5.3.2 解析配置檔案

複製一份Bootstrap2.java,命名為Bootstrap3.java

  1. 載入解析相關的配置 ,web.xml
    引入dom4j和jaxen的jar包

    <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>
    
  2. Bootstrap3.java中增加一個方法

    //用於下面儲存url-pattern以及其對應的servlet-class的例項化物件
    private Map<String,HttpServlet> servletMap = new HashMap<>();
    
    private void loadServlet(){
        InputStream resourceAsStream = this.getClass().getClassLoader().getResourceAsStream("web.xml");
        SAXReader saxReader = new SAXReader();
        try {
            Document document = saxReader.read(resourceAsStream);
            //根元素
            Element rootElement = document.getRootElement();
            /**
             * 1, 找到所有的servlet標籤,找到servlet-name和servlet-class
             * 2, 根據servlet-name找到<servlet-mapping>中與其匹配的<url-pattern>
             */
            List<Element> selectNodes = rootElement.selectNodes("//servlet");
            for (int i = 0; i < selectNodes.size(); i++) {
                Element element = selectNodes.get(i);
                /**
                 * 1, 找到所有的servlet標籤,找到servlet-name和servlet-class
                 */
                //<servlet-name>dxh</servlet-name>
                Element servletNameElement =(Element)element.selectSingleNode("servlet-name");
                String servletName = servletNameElement.getStringValue();
                //<servlet-class>server.DxhServlet</servlet-class>
                Element servletClassElement =(Element)element.selectSingleNode("servlet-class");
                String servletClass = servletClassElement.getStringValue();
                /**
                 * 2, 根據servlet-name找到<servlet-mapping>中與其匹配的<url-pattern>
                 */
                //Xpath表示式:從/web-app/servlet-mapping下查詢,查詢出servlet-name=servletName的元素
                Element servletMapping =(Element)rootElement.selectSingleNode("/web-app/servlet-mapping[servlet-name='" + servletName + "']'");
                // /dxh
                String urlPattern = servletMapping.selectSingleNode("url-pattern").getStringValue();
                servletMap.put(urlPattern,(HttpServlet) Class.forName(servletClass).newInstance());
            }
        } catch (DocumentException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        } catch (InstantiationException e) {
            e.printStackTrace();
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }
    }
    

    這段程式碼的意思就是讀取web.xml轉換成Document,然後遍歷根元素內中的servlet標籤(servlet是可以配置多個的),通過XPath表示式獲得servlet-nameservlet-class,以及與其對應的<servlet-mapping>標籤下的url-pattern,然後存在Map中。注意,這裡Map的Key是url-patternValue是servlet-class的例項化物件

5.4 接收請求,處理請求改造

image-20201227230820862

image-20201227231617987

這裡進行了判斷,判斷servletMap中是否存在url所對應的value,如果沒有,當作靜態資源訪問,如果有,取出並呼叫service方法,在HttpServlet的service方法中已經做了根據request判斷具體呼叫的是doGet還是doPost方法。

測試:

在瀏覽器中輸入:
http://localhost:8080/index.html,可以訪問靜態資源
image-20201227231858786

輸入:http://localhost:8080/dxh
image-20201227231925246

可以訪問【5.2中編寫的Servlet】動態資源~

到此位置,一個簡單的Tomcat Demo已經完成。

六,優化——多執行緒改造(不使用執行緒池)

6.1 問題分析

在現有的程式碼中,接收請求這部分它是一個IO模型——BIO,阻塞IO。

它存在一個問題,當一個請求還未處理完成時,再次訪問,會出現阻塞的情況。

可以在DxhServletdoGet方法中加入Thread.sleep(10000);然後訪問http://localhost:8080/dxhhttp://localhost:8080/index.html做個測試

那麼我們可以使用多執行緒對其進行改造。
image-20201227233057004

把上述程式碼放到一個新的執行緒中處理。

6.2 複製Bootstrap3

複製Bootstrap3,命名為Bootstrap4。把start()方法中上圖的部分(包括socket.close()剪下到下面的執行緒處理類的run方法中:

6.3 定義一個執行緒處理類

package server;

import java.io.InputStream;
import java.net.Socket;
import java.util.Map;

/**
 * 執行緒處理類
 */
public class RequestProcessor extends Thread{
    private  Socket socket;
    private Map<String,HttpServlet> servletMap;

    public RequestProcessor(Socket socket, Map<String, HttpServlet> servletMap) {
        this.socket = socket;
        this.servletMap = servletMap;
    }

    @Override
    public void run() {
        try{
            InputStream inputStream = socket.getInputStream();
            //封裝Resuest物件和Response物件
            Request request = new Request(inputStream);
            Response response = new Response(socket.getOutputStream());
            String url = request.getUrl();
            //靜態資源處理
            if (servletMap.get(url)==null){
                response.outputHtml(request.getUrl());
            }else{
                //動態資源處理
                HttpServlet httpServlet = servletMap.get(url);
                httpServlet.service(request,response);
            }
            socket.close();
        }catch (Exception e){

        }
    }
}

6.4 修改Bootstrap4的start()方法

public void start() throws Exception {
    //載入解析相關的配置 ,web.xml,把配置的servlet存入servletMap中
    loadServlet();

    ServerSocket serverSocket = new ServerSocket(port);
    System.out.println("========>>Minicat start on port:"+port);
    /**
     * 可以請求動態資源
     */
    while (true){
        Socket socket = serverSocket.accept();
        //使用多執行緒處理
        RequestProcessor requestProcessor = new RequestProcessor(socket,servletMap);
        requestProcessor.start();
    }
}

再次做6.1章節的測試, OK 沒有問題了。

七,優化——多執行緒改造(使用執行緒池)

這一步,我們使用執行緒池進行改造。

複製Bootstrap4,命名為Bootstrap5。

修改start()方法。執行緒池的使用不再贅述。程式碼如下:

public void start() throws Exception {
    //載入解析相關的配置 ,web.xml,把配置的servlet存入servletMap中
    loadServlet();

    /**
     * 定義執行緒池
     */
    //基本大小
    int corePoolSize = 10;
    //最大
    int maxPoolSize = 50;
    //如果執行緒空閒的話,超過多久進行銷燬
    long keepAliveTime = 100L;
    //上面keepAliveTime的單位
    TimeUnit unit = TimeUnit.SECONDS;
    //請求佇列
    BlockingQueue<Runnable> workerQueue = new ArrayBlockingQueue<>(50);
    //執行緒工廠,使用預設的即可
    ThreadFactory threadFactory = Executors.defaultThreadFactory();
    //拒絕策略,如果任務太多處理不過來了,如何拒絕
    RejectedExecutionHandler handler = new ThreadPoolExecutor.AbortPolicy();
    ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(corePoolSize
            ,maxPoolSize
            ,keepAliveTime
            ,unit
            ,workerQueue
            ,threadFactory
            ,handler);

    ServerSocket serverSocket = new ServerSocket(port);
    System.out.println("========>>Minicat start on port(多執行緒):"+port);
    /**
     * 可以請求動態資源
     */
    while (true){
        Socket socket = serverSocket.accept();
        RequestProcessor requestProcessor = new RequestProcessor(socket,servletMap);
        threadPoolExecutor.execute(requestProcessor);
    }
}

OK ,再次測試,成功~

MINI版Tomcat到此完成。

八,總結

總結一下編寫一個MINI版本的Tomcat都需要做些什麼:

  1. 定義一個入口類,需要監聽的埠號和入口方法——main方法
  2. 定義servlet規範(介面),並實現它——HttpServlet
  3. 編寫http協議工具類,主要提供響應頭資訊
  4. 在main方法中呼叫start()方法用於啟動初始化和請求進來時的操作
  5. 載入解析配置檔案(web.xml)
  6. 當請求進來時,解析inputStream,並封裝為Request和Response物件。
  7. 判斷請求資源的方式(動態資源還是靜態資源)

相關文章