JavaWeb handwritten Tomcat underlying mechanism

Table of contents

1. Tomcat underlying overall architecture

        1 Introduction: 

        2. Analysis chart: 

        3. Process of developing server based on Socket: 

        4. Open the data channel between the server and the client: 

2. Implementation of multi-threading model

        1. Idea analysis: 

        2. Handle HTTP requests: 

        3. Customize Tomcat: 

3. Custom Servlet specifications

        1. HTTP request and response: 

            1° CyanServletRequest

            2° CyanServletResponse

        2.Servlet specification: 

            1° CyanServlet

            2° CyanHttpServlet

            3° CyanLoginServlet

        3. Container implementation: 

            1° Idea analysis

            2° web.xml configuration file

            3° Final version of customized Tomcat

            4° Final version of custom thread class

            5° Container startup test


1. Tomcat underlying overall architecture

        1 Introduction: 

        Tomcat has three operating modes (BIO [blocking], NIO [non-blocking], APR) , and the BIO thread model is used here to simulate the implementation.

        2. Analysis chart: 

                As shown below: 

                After the browser requests the servlet resource, the bottom layer of Tomcat will receive the request through Socket network programming. For each request, a new thread will be created to call the corresponding Web resource and return.

        3. Process of developing server based on Socket: 

                As shown below: 

                By obtaining the Socket object, obtain the byte input stream and byte output stream corresponding to the Socket object.
                You can use the object conversion stream to convert the byte stream into a character stream object (InputStreamReader implements the Reader abstract class), and then convert the node stream into a packaging stream (processing stream) through the packaging of BufferedReader.

        4. Open the data channel between the server and the client: 

                PS: 
                Maven configures the Web application, and a jakarta.servlet.ServletException occurs when running:
                Because tomcat10 and later is not javax.servlet, but jakarta.servlet , so the Web dependency should be changed to the following: (pom.xml configuration file)

<!--jar包的依赖-->
    <dependency>
      <groupId>jakarta.servlet</groupId>
      <artifactId>jakarta.servlet-api</artifactId>
      <version>5.0.0</version>
      <scope>provided</scope>
    </dependency>

<!--jsp的依赖-->
    <dependency>
      <groupId>jakarta.servlet.jsp</groupId>
      <artifactId>jakarta.servlet.jsp-api</artifactId>
      <version>3.0.0</version>
      <scope>provided</scope>
    </dependency>

                The MyTomcat class code is as follows: (server; customized Tomcat)

package tomcat;

import java.io.*;
import java.net.ServerSocket;
import java.net.Socket;

/**
    服务器端
 */
public class MyTomcat {
    public static void main(String[] args) throws IOException {
        ServerSocket serverSocket = new ServerSocket(8080);
        System.out.println("Tomcat在8080端口进行监听...");
        System.out.println("--------------------------------------");

        while (!serverSocket.isClosed()) {
            //获取Socket对象
            Socket socket = serverSocket.accept();

            //接收来自浏览器端的信息
            InputStream inputStream = socket.getInputStream();
            BufferedReader bufferedReader =
                    new BufferedReader(new InputStreamReader(inputStream, "UTF-8"));

            String content = null;  //局部变量在使用前必须赋初值。

            while ((content = bufferedReader.readLine()) != null) {
                //判断content是否是空串儿
                if (content.length() == 0) {
                    break;
                }
                System.out.println(content);
            }

            //服务器向浏览器回送消息
            OutputStream outputStream = socket.getOutputStream();
            //设置一个HTTP响应包的响应头
            // "/r/n"表示换行
            String respHeader =
                    "HTTP/1.1 200\r\n" +
                            "Content-Type: text/html;charset=utf-8\r\n\r\n";
            //设置HTTP响应的响应体
            String respBody = respHeader + "<h1>你好!</h1>";
            /*
                注意这里不能使用字符包装流来写数据!!!。
             */
            outputStream.write(respBody.getBytes());

            System.out.println("--------------------------------------");
            System.out.println(respBody);

            //释放资源
            outputStream.flush();
            outputStream.close();
            inputStream.close();
            socket.close();
//            serverSocket.close();
        }
    }
}

                Access the local port 8080 in the browser address bar .
                The login.html code is as follows: 

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>login</title>
    <style>
        table, tr, td {
            border:2px cornflowerblue solid;
            border-collapse: collapse;
            padding: 10px;
            background-color: lightcyan
        }
        #tr01 {
            text-align: center
        }
    </style>
</head>
<body>
<form action="/Cyan_Tomcat/login" methods="get">
    <table>
        <tr>
            <th colspan="2">User Logging</th>
        </tr>
        <tr>
            <td>Username: </td>
            <td><input type="text" name="username"/></td>
        </tr>
        <tr>
            <td>Password: </td>
            <td><input type="password" name="password"/></td>
        </tr>
        <tr id="tr01">
            <td><input type="submit" value="Submit"/></td>
            <td><input type="reset" value="Reset"/></td>
        </tr>
    </table>
</form>
</body>
</html>

                The LoginServlet class code is as follows: 

package servlet;

import jakarta.servlet.ServletException;
import jakarta.servlet.annotation.WebServlet;
import jakarta.servlet.http.HttpServlet;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.PrintWriter;

/**
 * @author : Cyan_RA9
 * @version : 21.0
 */
@WebServlet(urlPatterns = {"/login"})
public class LoginServlet extends HttpServlet {
    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        System.out.println("LoginServlet's doPost is invoked~");

        resp.setContentType("text/html; charset=utf-8");
        PrintWriter writer = resp.getWriter();

        req.setCharacterEncoding("utf-8");
        String username = req.getParameter("username");
        String password = req.getParameter("password");

        if ("Cyan".equals(username) && "123".equals(password)) {
            writer.print("<h1>登录成功!</h1>");
        } else {
            writer.print("<h1>登录失败!请重新尝试!</h1>");
        }
    }

    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        doPost(req, resp);
    }
}

                running result: 

                If you run Tomcat configured in the Web project, the business will be processed according to the code logic of the LoginServlet class, as shown in the following figure: (GIF)

                If you run it with a customized Tomcat, the code logic in the MyTomcat class will be used to process the business.
               As shown below: (GIF)


2. Implementation of multi-threading model

        1. Idea analysis: 

                When the server receives the HTTP request from the browser, it starts a new thread so that the thread holds the Socket object corresponding to the browser to complete the connection between the thread and the browser . The thread class HttpRequestHandler
                can be defined by implementing the Runnable interface . The thread object is used to process HTTP requests from the browser .

        2. Handle HTTP requests: 

                The thread class HttpRequestHandler class code is as follows: 

package tomcat.handler;

import tomcat.http.CyanServletRequest;
import tomcat.http.CyanServletResponse;
import tomcat.servlet.CyanLoginServlet;

import java.io.*;
import java.net.Socket;

public class HttpRequestHandler implements Runnable {
    private Socket socket;

    public HttpRequestHandler(Socket socket) {
        this.socket = socket;
    }

    @Override
    public void run() {
        try {
            //接收客户端的信息
            InputStream inputStream = socket.getInputStream();

/*  以下代码已在CyanServletRequest类中实现
            BufferedReader bufferedReader =
                    new BufferedReader(new InputStreamReader(inputStream, "utf-8"));

            //BIO,每次请求都对应一个新的线程
            System.out.println("当前线程 = " + Thread.currentThread().getName());

            String content = null;  //局部变量在使用前必须赋初值。

            while ((content = bufferedReader.readLine()) != null) {
                //判断是否读到了空字符串
                if (content.length() == 0) {
                    break;
                }
                System.out.println(content);
            }
*/
            //Die first and live second
            //获取客户端的信息(利用了CyanServletRequest中封装好的方法)
            //以下代码已在CyanLoginServlet中实现
/*            CyanServletRequest cyanServletRequest = new CyanServletRequest(inputStream);
            String username = cyanServletRequest.getParameter("username");
            String password = cyanServletRequest.getParameter("password");
            System.out.println("username = " + username);
            System.out.println("password = " + password);
            System.out.println(cyanServletRequest);*/

            //给客户端回送信息
            //以下代码已在CyanLoginServlet类中实现。
/*            CyanServletResponse cyanServletResponse = new CyanServletResponse(socket.getOutputStream());
            String resp = CyanServletResponse.respHeader + "<h1>CyanServletResponse!</h1>";
            OutputStream outputStream = cyanServletResponse.getOutputStream();
            outputStream.write(resp.getBytes());
            outputStream.flush();
            outputStream.close();*/

            CyanServletRequest cyanServletRequest = new CyanServletRequest(inputStream);
            CyanServletResponse cyanServletResponse = new CyanServletResponse(socket.getOutputStream());

            CyanLoginServlet cyanLoginServlet = new CyanLoginServlet();
            cyanLoginServlet.doPost(cyanServletRequest, cyanServletResponse);

            //释放资源
            inputStream.close();
            socket.close();


/* 以下代码已在CyanServletResponse类中实现 :
            String respHeader = "HTTP/1.1 200\r\n" +
                    "Content-Type: text/html;charset=utf-8\r\n\r\n";
            String respHttp = respHeader + "<h1>Cyan_RA9</h1>";

            System.out.println("-----------------------------------------------");
            System.out.println("回送的信息如下:(回显)");
            System.out.println(respHttp);

            OutputStream outputStream = socket.getOutputStream();
            outputStream.write(respHttp.getBytes());

            //释放资源
            outputStream.flush();
            outputStream.close();
            inputStream.close();
            socket.close(); */
        } catch (IOException e) {
            throw new RuntimeException(e);
        } finally {
            //确保Socket关闭
            if (socket != null) {
                try {
                    socket.close();
                } catch (IOException e) {
                    throw new RuntimeException(e);
                }
            }
        }
    }
}

        3. Customize Tomcat: 

Implement thread distribution                 in the MyTomcat_EX class .
                The MyTomcat_EX class code is as follows: 

package tomcat;

import tomcat.handler.HttpRequestHandler;

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

/**
 * @author : Cyan_RA9
 * @version : 21.0
 */
public class MyTomcat_EX {
    public static void main(String[] args) throws IOException {
        ServerSocket serverSocket = new ServerSocket(8080);
        System.out.println("MyTomcat_EX在8080端口进行监听...");

        while (!serverSocket.isClosed()) {
            Socket socket = serverSocket.accept();
            HttpRequestHandler httpRequestHandler = new HttpRequestHandler(socket);
            Thread thread = new Thread(httpRequestHandler);

            thread.start();
        }
    }
}

                Running effect: (GIF)


3. Custom Servlet specifications

        1. HTTP request and response: 

            1° CyanServletRequest

                The role of the CyanServletRequest class is equivalent to the original HttpServletRequest . This class is used to encapsulate the data in the HTTP request, such as: method, URI, and parameter list of form data, etc.
                The CyanServletRequest class code is as follows: 

package tomcat.http;

import java.io.*;
import java.util.HashMap;

/**
 * @author : Cyan_RA9
 * @version : 21.0
 * @function : like the original HttpServletRequest.
 */
public class CyanServletRequest {
    private String method;
    private String URI;
    private HashMap<String, String> parametersMapping = new HashMap<>();
    private InputStream inputStream;

    /*
        此处传入的InputStream对象是和Socket关联的InputStream.
     */
    public CyanServletRequest(InputStream inputStream) {
        this.inputStream = inputStream;

        //完成对HTTP请求数据的封装
        this.init();
    }

    private void init() {
        System.out.println("\nCyanServletRequest's init is invoked~");
        try {
            //注意转换流的形参列表
            BufferedReader bufferedReader =
                    new BufferedReader(new InputStreamReader(inputStream, "utf-8"));

            //首先读取HTTP请求的请求行
            /* eg :  GET /Cyan/cyan.html HTTP/1.1 */
            String requestLine = bufferedReader.readLine();
            String[] requestLineArr = requestLine.split(" ");
            //获取method
            method = requestLineArr[0];
            //获取URI
            int index = requestLineArr[1].indexOf("?");
            if (index == -1) {      //if判断成立,说明请求行中没有参数列表
                URI = requestLineArr[1];
            } else {
                URI = requestLineArr[1].substring(0, index);
                //获取参数列表
                String parameters = requestLineArr[1].substring(index + 1);
                String[] parameterPairs = parameters.split("&");
                //兼容性处理,防止?后啥都没有。
                if (null != parameterPairs && !"".equals(parameterPairs)) {
                    for (String parameterPair : parameterPairs) {
                        String[] parameter = parameterPair.split("=");
                        if (parameter.length == 2) { //判断是否为一对完整的"name=value".
                            parametersMapping.put(parameter[0],parameter[1]);
                        }
                    }
                }
            }

            //!!! 直接关闭Socket关联的InputStream,会引起Socket的关闭。
            //inputStream.close();
        } catch (UnsupportedEncodingException e) {
            throw new RuntimeException(e);
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }

    public String getMethod() {
        return method;
    }

    public void setMethod(String method) {
        this.method = method;
    }

    public String getURI() {
        return URI;
    }

    public void setURI(String URI) {
        this.URI = URI;
    }

    //重要
    public String getParameter(String name) {
        if (parametersMapping.containsKey(name)) {      //注意此处API的使用!
            return parametersMapping.get(name);
        } else {
            return null;
        }
    }

    @Override
    public String toString() {
        return "CyanServletRequest{" +
                "method='" + method + '\'' +
                ", URI='" + URI + '\'' +
                ", parametersMapping=" + parametersMapping +
                '}';
    }
}

                CyanServletRequest class test, the running effect is as follows GIF: 

            2° CyanServletResponse

                The CyanServletResponse class is equivalent to the original HttpServletResponse and is used to encapsulate HTTP response-related information.
                The CyanServletResponse class code is as follows: 

package tomcat.http;

import java.io.OutputStream;

/**
 * @author : Cyan_RA9
 * @version : 21.0
 * @function : like the original HttpServletResponse
 */
public class CyanServletResponse {
    private OutputStream outputStream;
    //设置一个HTTP响应头
    public static final String respHeader = "HTTP/1.1 200\r\n" +
            "Content-Type: text/html;charset=utf-8\r\n\r\n";

    //传入与Socket关联的OutputStream对象
    public CyanServletResponse(OutputStream outputStream) {
        this.outputStream = outputStream;
    }

    public OutputStream getOutputStream() {
        return outputStream;
    }
}

                Run the test (GIF below):

        2.Servlet specification: 

            1° CyanServlet

                CyanServlet only retains the init, destroy, and service methods of the native Servlet. Among them, the service method can be rewritten by the abstract implementation class CyanHttpServlet of CyanServlet in the future . Note that the formal parameter list of the service method must use the customized CyanServletRequest and CyanServletResponse.
                CyanServlet interface, the code is as follows: 

package tomcat.servlet;

import tomcat.http.CyanServletRequest;
import tomcat.http.CyanServletResponse;

import java.io.IOException;

public interface CyanServlet {
    void init() throws Exception;

    void service(CyanServletRequest req, CyanServletResponse resp) throws IOException;

    void destroy();
}

            2° CyanHttpServlet

                The function of CyanHttpServlet is similar to the native HttpServlet; the service method in the CyanServlet interface is implemented in CyanHttpServlet. In the service method, the method type of the HTTP request must be judged.

                The CyanHttpServlet abstract class code is as follows: 

package tomcat.servlet;

import tomcat.http.CyanServletRequest;
import tomcat.http.CyanServletResponse;

import java.io.IOException;

public abstract class CyanHttpServlet implements CyanServlet{
    @Override
    public void service(CyanServletRequest req, CyanServletResponse resp) throws IOException {
        //忽略大小写
        if ("GET".equalsIgnoreCase(req.getMethod())) {
            this.deGet(req, resp);
        } else if ("POST".equalsIgnoreCase(req.getMethod())) {
            this.doPost(req, resp);
        }
    }

    //模板设计模式
    public abstract void deGet(CyanServletRequest req, CyanServletResponse resp);
    public abstract void doPost(CyanServletRequest req, CyanServletResponse resp);
}

            3° CyanLoginServlet

                CyanLoginServlet is a simple servlet instance that inherits the CyanHttpServlet abstract class and implements the doGet and doPost abstract methods in the CyanHttpServlet class. After that, first try to call the CyanLoginServlet instance directly in the HttpRequestHandler thread class.
                The CyanLoginServlet code is as follows: 

package tomcat.servlet;

import tomcat.http.CyanServletRequest;
import tomcat.http.CyanServletResponse;

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

public class CyanLoginServlet extends CyanHttpServlet{
    @Override
    public void deGet(CyanServletRequest req, CyanServletResponse resp) {
        doPost(req, resp);
    }

    @Override
    public void doPost(CyanServletRequest req, CyanServletResponse resp) {
        String username = req.getParameter("username");
        String password = req.getParameter("password");

        //获取与当前Socket相关联的OutputStream对象
        OutputStream outputStream = resp.getOutputStream();
        String respInfo = CyanServletResponse.respHeader + "<h1>username = " +
                username + "</h1><br/> " + "<h1>password = " + password + "</h1>" + "<br/>" +
                "<h3>Cyan_RA9</h3>";
        try {
            outputStream.write(respInfo.getBytes());
            outputStream.flush();
            outputStream.close();
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }

    @Override
    public void init() throws Exception {

    }

    @Override
    public void destroy() {

    }
}

                In addition, you also need to update the types in the HttpServletHandler class and comment out the encapsulated code. The HttpServletHandler class has been updated , in the HTTP request processing module of "Multi-threading Model Implementation" above.
                The running effect is shown in the figure below: 

        3. Container implementation: 

            1° Idea analysis

                There are at least two large HashMap containers maintained in Tomcat. Take the web.xml configuration file as an example . In one HashMap container, the key is stored in <url-pattern> and the value is stored in <servlet-name> ; in another HashMap container, the key is stored in <servlet-name> and the value is stored in <servlet -class> to reflect the generated servlet instance .

            2° web.xml configuration file

                The web.xml configuration file is as follows: 

<!DOCTYPE web-app PUBLIC
 "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"
 "http://java.sun.com/dtd/web-app_2_3.dtd" >

<!--xmlns="http://xmlns.jcp.org/xml/ns/javaee"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd"
         version="4.0"-->
<!--IDEA报错——
  因为这是我们自定义的servlet,IDEA无法识别;无所谓!继续用!-->
<web-app>
  <display-name>Archetype Created Web Application</display-name>
  <servlet>
    <servlet-name>CyanLoginServlet</servlet-name>
    <servlet-class>tomcat.servlet.CyanLoginServlet</servlet-class>
  </servlet>
  <servlet-mapping>
    <servlet-name>CyanLoginServlet</servlet-name>
    <url-pattern>/cyanLogin</url-pattern>
  </servlet-mapping>
</web-app>

           3° Final version of customized Tomcat

                Define two CurrentHashMap objects in the MyTomcat_Pro class; define the init method to complete the initialization of the two CurrentHashMap objects (use Dom4J to read the web.xml configuration file).
                First, you need to introduce the dom4j dependency in Maven's pom.xml configuration file , as shown in the following figure: 

                Then, copy the web.xml configuration file to the /target/classes/ directory , as shown in the following figure: 

               MyTomcat_Pro code is as follows: 

package tomcat;

import org.dom4j.Document;
import org.dom4j.Element;
import org.dom4j.io.SAXReader;
import tomcat.handler.HttpRequestHandler;
import tomcat.servlet.CyanHttpServlet;

import java.io.File;
import java.io.IOException;
import java.lang.reflect.Constructor;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.List;
import java.util.concurrent.ConcurrentHashMap;

/**
 * The final own custom Tomcat.
 */
public class MyTomcat_Pro {
    //Tomcat维护的第一个容器
        //String --> <servlet-name>
        //CyanHttpServlet --> 可存放它的子类(即各种servlet实例)
    public static final ConcurrentHashMap<String, CyanHttpServlet> servletMapping =
            new ConcurrentHashMap<>();

    //Tomcat维护的第二个容器
        //String --> <url-patterns>
        //String --> <servlet-name>
    public static final ConcurrentHashMap<String, String> servletURLMapping =
            new ConcurrentHashMap<>();

    public static void main(String[] args) {
        MyTomcat_Pro myTomcat_pro = new MyTomcat_Pro();
        myTomcat_pro.init();
        myTomcat_pro.run();
    }

    public void run() {
        try {
            ServerSocket serverSocket = new ServerSocket(8080);
            System.out.println("MyTomcat_Pro在8080端口进行监听...");

            while (!serverSocket.isClosed()) {
                Socket socket = serverSocket.accept();
                Thread thread = new Thread(new HttpRequestHandler(socket));
                thread.start();
            }

        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }

    public void init() {
        String path = MyTomcat_Pro.class.getResource("/").getPath();
        //path = "/javaProject/Servlet/Cyan_Tomcat/target/classes/"

        //使用Dom4J技术解析web.xml文件
        SAXReader saxReader = new SAXReader();
        try {
            //注意文件名
            Document document = saxReader.read(new File(path + "web.xml"));
            System.out.println("document = " + document);
            //获取根元素<web-app>
            Element rootElement = document.getRootElement();
            //得到根元素下面的所有子元素
            List<Element> elements = rootElement.elements();
            //遍历并判断
            for (Element element : elements) {
                if ("servlet".equals(element.getName())) {
                    Element servlet_name = element.element("servlet-name");
                    Element servlet_class = element.element("servlet-class");

                    //反射机制创建servlet实例 (注意getText()方法的使用!)
                    Class<?> clazz = Class.forName(servlet_class.getText().trim());
                    Constructor<?> constructor = clazz.getConstructor();
                    CyanHttpServlet o = (CyanHttpServlet) constructor.newInstance();
                    servletMapping.put(servlet_name.getText(), o);
                } else if ("servlet-mapping".equals(element.getName())) {
                    Element url_pattern = element.element("url-pattern");
                    Element servlet_name = element.element("servlet-name");

                    servletURLMapping.put(url_pattern.getText(), servlet_name.getText());
                }
            }

        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }
}

            4° Final version of custom thread class

package tomcat.handler;

import tomcat.MyTomcat_Pro;
import tomcat.http.CyanServletRequest;
import tomcat.http.CyanServletResponse;
import tomcat.servlet.CyanHttpServlet;
import tomcat.servlet.CyanLoginServlet;

import java.io.*;
import java.net.Socket;

public class HttpRequestHandler implements Runnable {
    private Socket socket;

    public HttpRequestHandler(Socket socket) {
        this.socket = socket;
    }

    @Override
    public void run() {
        try {
            //接收客户端的信息
            CyanServletRequest cyanServletRequest = new CyanServletRequest(socket.getInputStream());
            CyanServletResponse cyanServletResponse = new CyanServletResponse(socket.getOutputStream());

            String uri = cyanServletRequest.getURI();
            System.out.println("uri = " + uri);
            String servlet_name = MyTomcat_Pro.servletURLMapping.get(uri);
            /*
                这里的servlet_name可能为空。
                解决方式一 : 将CurrentHashMap替换为HashMap
                解决方式二 : 增加一个是否为null的判断。
             */
            if (servlet_name == null) {
                servlet_name = "";
            }

            //多态 --> 动态绑定
            CyanHttpServlet cyanHttpServlet = MyTomcat_Pro.servletMapping.get(servlet_name);
            if (cyanHttpServlet != null) {  //判断是否正常得到servlet实例
                cyanHttpServlet.service(cyanServletRequest, cyanServletResponse);
            } else {
                //如果没有找到servlet,返回404
                String resp = CyanServletResponse.respHeader + "<h1>404 Not Found!</h1>";
                OutputStream outputStream = cyanServletResponse.getOutputStream();
                outputStream.write(resp.getBytes());
                outputStream.flush();
                outputStream.close();
            }

            //释放资源
            socket.close();
        } catch (IOException e) {
            throw new RuntimeException(e);
        } finally {
            //确保Socket关闭
            if (socket != null) {
                try {
                    socket.close();
                } catch (IOException e) {
                    throw new RuntimeException(e);
                }
            }
        }
    }
}

            5° Container startup test

                As shown below (GIF):

 

Guess you like

Origin blog.csdn.net/TYRA9/article/details/131883276