JavaWeb 手写Tomcat底层机制

news2024/9/29 11:29:02

目录

一、Tomcat底层整体架构

        1.简介 : 

        2.分析图 : 

        3.基于Socket开发服务端的流程 : 

        4.打通服务器端和客户端的数据通道 : 

二、多线程模型的实现

        1.思路分析 : 

        2.处理HTTP请求 : 

        3.自定义Tomcat : 

三、自定义Servlet规范

        1. HTTP请求和响应 : 

            1° CyanServletRequest

            2° CyanServletResponse

        2.Servlet规范 : 

            1° CyanServlet

            2° CyanHttpServlet

            3° CyanLoginServlet

        3.容器实现 : 

            1° 思路分析

            2° web.xml配置文件

            3° 最终版自定义Tomcat

            4° 最终版自定义线程类

            5° 容器启动测试


一、Tomcat底层整体架构

        1.简介 : 

        Tomcat 有三种运行模式 (BIO[阻塞], NIO[非阻塞], APR),这里采用 BIO 线程模型来模拟实现。

        2.分析图 : 

                如下图所示 : 

                浏览器请求servlet资源后,Tomcat底层会通过Socket网络编程来接收请求,每次请求,都会创建一个新的线程去调用相应的Web资源,并返回。

        3.基于Socket开发服务端的流程 : 

                如下图所示 : 

                通过获取的Socket对象,来获取Socket对象对应的字节输入流和字节输出流。
                可以利用对象转换流将字节流转换为字符流对象(InputStreamReader实现了Reader抽象类),然后再通过BufferedReader的包装,将节点流转换成包装流(处理流)。

        4.打通服务器端和客户端的数据通道 : 

                PS : 
                Maven配置Web应用,运行出现jakarta.servlet.ServletException:
                因为tomcat10之后不是javax.servlet,而是jakarta.servlet,所以Web的依赖应该换成如下所示 : (pom.xml配置文件

<!--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>

                MyTomcat类代码如下 : (服务端;自定义的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();
        }
    }
}

                在浏览器地址栏访问本机8080端口
                login.html代码如下 : 

<!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>

                LoginServlet类代码如下 : 

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);
    }
}

                运行效果 : 

                如果以Web工程配置好的Tomcat运行,就会按照LoginServlet类的代码逻辑来处理业务,如下图所示 : (GIF)

                如果以自定义的Tomcat运行,就会以MyTomcat类中的代码逻辑来处理业务。
                如下图所示 : (GIF)


二、多线程模型的实现

        1.思路分析 : 

                当服务器端接收到浏览器端的HTTP请求后,启动一个新的线程,令该线程持有浏览器对应的Socket对象,完成线程和浏览器的对接
                可通过实现Runnable接口的方式定义线程类HttpRequestHandler线程对象用于处理来自浏览器的HTTP请求

        2.处理HTTP请求 : 

                线程类HttpRequestHandler类代码如下 : 

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.自定义Tomcat : 

                在MyTomcat_EX类中实现线程的分发
                MyTomcat_EX类代码如下 : 

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();
        }
    }
}

                运行效果 : (GIF)


三、自定义Servlet规范

        1. HTTP请求和响应 : 

            1° CyanServletRequest

                CyanServletRequest类的作用等同于原始的HttpServletRequest,该类用于封装HTTP请求中的数据,eg : method, URI, 以及表单数据的参数列表等。
                CyanServletRequest类代码如下 : 

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类测试,运行效果如下GIF : 

            2° CyanServletResponse

                CyanServletResponse类的作用等同于原始的HttpServletResponse,用于封装HTTP响应的相关信息。
                CyanServletResponse类代码如下 : 

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;
    }
}

                运行测试(如下GIF图):

        2.Servlet规范 : 

            1° CyanServlet

                CyanServlet仅保留原生Servlet的init, destroy, service方法,其中,service方法供将来CyanServlet的抽象实现类CyanHttpServlet去重写。注意,service方法的形参列表,要使用自定义的CyanServletRequest 和 CyanServletResponse.
                CyanServlet接口,代码如下 : 

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

                CyanHttpServlet的作用,类似于原生的HttpServlet;在CyanHttpServlet中实现CyanServlet接口中的service方法,在service方法中,要对HTTP请求的method类型进行判断。

                CyanHttpServlet抽象类代码如下 : 

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是一个简单的servlet实例,用于继承CyanHttpServlet抽象类,并实现CyanHttpServlet类中的doGet和doPost抽象方法。之后,在HttpRequestHandler线程类中先尝试直接调用CyanLoginServlet实例。
                CyanLoginServlet代码如下 : 

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() {

    }
}

                此外,还需要更新HttpServletHandler类中的类型,将已封装好的代码注释掉,HttpServletHandler类已更新,在上文“多线程模型实现”的HTTP请求处理模块。
                运行效果如下图所示 : 

        3.容器实现 : 

            1° 思路分析

                Tomcat中维护有至少两个大的HashMap容器。以web.xml配置文件的方式为例,其中一个HashMap容器,key存放<url-pattern>,value存放<servlet-name>;另一个HashMap容器,key存放<servlet-name>,value存放通过<servlet-class>来反射生成的servlet实例

            2° web.xml配置文件

                web.xml配置文件如下 : 

<!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° 最终版自定义Tomcat

                在MyTomcat_Pro类中定义两个CurrentHashMap对象;定义init方法完成对两个CurrentHashMap对象的初始化(使用Dom4J读取web.xml配置文件)。
                首先,需要在Maven的pom.xml配置文件中,引入dom4j依赖,如下图所示 : 

                然后,将web.xml配置文件拷贝到/target/classes/目录下一份,如下图所示 : 

               MyTomcat_Pro代码如下 : 

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° 最终版自定义线程类

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° 容器启动测试

                如下图所示(GIF):

 

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/837528.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

提高测试用例质量的6大注意事项

在软件测试中&#xff0c;经常会遇到测试用例设计不完整&#xff0c;用例没有完全覆盖需求等问题&#xff0c;这样往往容易造成测试工作效率低下&#xff0c;不能及时发现项目问题&#xff0c;无形中增加了项目风险。 因此提高测试用例质量&#xff0c;就显得尤为重要。一般来说…

SQL基础复习与进阶

SQL进阶 文章目录 SQL进阶关键字复习ALLANYEXISTS 内置函数ROUND&#xff08;四舍五入&#xff09;TRUNCATE&#xff08;截断函数&#xff09;SEILING&#xff08;向上取整&#xff09;FLOOR&#xff08;向下取整&#xff09;ABS&#xff08;获取绝对值&#xff09;RAND&#x…

采用 SpringDoc、javadoc 无注解零入侵基于java注释

今天在gitee上学习到一个&#xff0c;个人觉得非常使用的功能。就是采用 SpringDoc、javadoc 无注解零入侵基于java注释&#xff0c;只需把注释写好 无需再写一大堆的文档注解了。 下面是源码的地址 RuoYi-Vue-Plus: 后台管理系统 重写RuoYi-Vue所有功能 集成 Sa-TokenMybati…

【数模】预测模型

一、灰色系统 白色系统&#xff1a;系统信息完全明确灰色系统&#xff1a;系统部分信息已知&#xff0c;部分信息未知 对在一定范围内变化的、与时间有关的灰色过程进行预测。过程&#xff1a;原始数据找规律→生成强规律性的数据序列→建立微分方程来预测未来趋势 黑色系统&a…

企业上云实施路线图

企业上云步骤主要分为规划、设计、实施、验证、运维五个阶段。https://articles.e-works.net.cn/cloud/article144684.htm

idea 2023 新版ui中git的相关操作

前两个月换了新电脑&#xff0c;下了最新版的idea发现可以切换一套新的ui了 切换新ui肯定不太习惯&#xff0c;很多操作都得重新摸索一下 在这里记录几个git相关的操作 忽略我下面截图中当前项目是js的后端项目…… 切换ui 首先说一下怎么切换新旧版ui&#xff0c;我这里就…

【前端实习生备战秋招】—CSS面试题汇总,建议收藏系列

【前端实习生备战秋招】—CSS面试题汇总&#xff0c;建议收藏系列 文章目录 【前端实习生备战秋招】—CSS面试题汇总&#xff0c;建议收藏系列一、CSS基础1. CSS选择器及其优先级2. CSS中可继承与不可继承属性有哪些3. display的属性值及其作用4. display的block、inline和inli…

2023年电赛A题报告模板--可直接使用

任务 图1 任务内容 要求 图2 基本要求内容 图3 发挥部分内容 说明 图4 说明内容 评分标准 图5 评分内容 正文 &#xff08;部分&#xff09; 摘要 本实验旨在设计和制作一个由两个单相逆变器组成的并联系统&#xff0c;用于为电阻负载供电或并入220V电网。采用基于STM…

MobaXterm - 使用技巧

MobaXterm - 使用技巧 介绍安装SSH会话保存密码快捷键 介绍 MobaXterm是一款增强型Windows终端、X 服务器和 Unix 命令集(GNU/ Cygwin)工具箱。有点是功能全&#xff0c;免费&#xff0c;有便携版&#xff0c;支持多标签&#xff0c;同时自带文件传输系统。下文介绍其安装和使…

产险精算GLM-GAM案例

继续使用北美产险精算学会CAS网站上的一个案例&#xff0c;对案例略作修改后进行验证。 ClassAOITerrExposureClaims1Low1762Medium1108443High11791054Low2130625Medium2126826High21291207Low3143848Medium31261019High34046 GLM 模型结果&#xff0c;见文章&#xff1a;产…

特斯拉墨西哥工厂风波:2.5万美金的车型何时开造?

作者 | Amy 编辑 | 德新 去年10月&#xff0c;马斯克闪现墨西哥新莱昂州&#xff0c;会见了当地官员。考虑到新莱昂州是通用和现代工厂所在地&#xff0c;特斯拉第五大工厂花落墨西哥的消息不胫而走。 今年3月&#xff0c;特斯拉正式宣布&#xff0c;将在墨西哥北部新莱昂州的…

新手用户选择阿里云服务器地域、实例、带宽、操作系统经验参考

无论是个人还是企业用户&#xff0c;部署自己的网站或者APP客户端、小程序等&#xff0c;都需要用到服务器&#xff0c;现在流行的都是使用云服务器&#xff0c;考虑到性价比大家现在都喜欢选择阿里云服务器。但是新手用户往往在面对阿里云服务器地域、实例、带宽、操作系统等众…

Redis集群部署教程详解

一、什么是redis集群 Redis集群是一个提供在多个Redis节点之间共享数据的程序集。它并不像Redis主从复制模式那样只提供一个master节点提供写服务&#xff0c;而是会提供多个master节点提供写服务&#xff0c;每个master节点中存储的数据都不一样&#xff0c;这些数据通过数据分…

sql语句字符函数,数学函数

一、trim&#xff08;&#xff09;去掉前后单元格 SELECT LENGTH(TRIM( 张三 )) AS 姓名 trim&#xff08;aa from bb) 除掉bb中前后包含的aa&#xff0c;中间的保留 SELECT TRIM(班 FROM class) AS 姓名 FROM user_test 二、lpad&#xff08;&#xff09;用指定字符做左…

【机器学习】对 MLOps 的友好的介绍(MLOps1)

一、说明 我对 MLOps 感兴趣已经有一段时间了。我第一次从机器学习工程师那里了解到它&#xff0c;由于我当时还是一名博士生&#xff0c;我并不知道它的存在。然而&#xff0c;我的好奇心被激起了&#xff0c;我开始了解它。回想起来&#xff0c;我很后悔没有早点了解它&#…

C#控制台程序+Window增加右键菜单

有时候我们可能会想定制一些自己的右键菜单功能&#xff0c;帮我们减少重复的操作。那么使用控制台程序加自定义右键菜单&#xff0c;就可以很好地满足我们的需求。 1 编写控制台程序 因为我只用到了在文件夹中空白处的右键菜单&#xff0c;所以这里提供了一个对应的模板&…

DC-4靶机

信息收集 先查看靶机的MAC地址 arp-scan -l 找到目标靶机的IP地址&#xff0c;对其进行扫描 发现开放了80端口和ssh&#xff0c;浏览器访问靶机的80端口&#xff0c;看看有没有可以利用的东西 目录爆破发现也没有什么东西 dirsearch -u http://192.168.80.146 漏洞利用 利用…

Web Worker API

Web Worker API Web Worker 使得在一个独立于 Web 应用程序主执行线程的后台线程中运行脚本操作成为可能。这样做的好处是可以在独立线程中执行费时的处理任务&#xff0c;使主线程&#xff08;通常是 UI 线程&#xff09;的运行不会被阻塞/放慢。 Web Worker概念与用法 Wor…

【iOS安全】OpenSSH使用

安装OpenSSH 在 Cydia 中直接查找和安装 OpenSSH 使用OpenSSH http://orinchen.github.io/blog/2014/01/15/install-and-use-openssh-on-ios/ 保证PC和iPhone在同一网段下 查看iPhone的IP地址 ssh root10.168.xx.xx 口令默认是alpine 或者也可以使用XShell等集成终端

自动化测试的统筹规划

背景 回顾以前自动化测试编写的经历&#xff0c;主要是以开发者自驱动的方式进行&#xff0c;测试的编写随心而动&#xff0c;没有规划&#xff0c;也没有章法&#xff0c;这样就面临如下的一些问题&#xff1a; 测试用例设计不到位&#xff0c;覆盖不全&#xff0c;或者不够…