JavaWeb 手写Tomcat底层机制

目录

一、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 {@Overrideprotected 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>");}}@Overrideprotected 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;}@Overridepublic 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(" ");//获取methodmethod = requestLineArr[0];//获取URIint 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;}}@Overridepublic 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{@Overridepublic 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{@Overridepublic void deGet(CyanServletRequest req, CyanServletResponse resp) {doPost(req, resp);}@Overridepublic 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);}}@Overridepublic void init() throws Exception {}@Overridepublic 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;}@Overridepublic 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,返回404String 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.mzph.cn/news/25173.shtml

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

相关文章

《面试1v1》ElasticSearch基础

&#x1f345; 作者简介&#xff1a;王哥&#xff0c;CSDN2022博客总榜Top100&#x1f3c6;、博客专家&#x1f4aa; &#x1f345; 技术交流&#xff1a;定期更新Java硬核干货&#xff0c;不定期送书活动 &#x1f345; 王哥多年工作总结&#xff1a;Java学习路线总结&#xf…

Kafka的配置和使用

目录 1.服务器用docker安装kafka 2.springboot集成kafka实现生产者和消费者 1.服务器用docker安装kafka ①、安装docker&#xff08;docker类似于linux的软件商店&#xff0c;下载所有应用都能从docker去下载&#xff09; a、自动安装 curl -fsSL https://get.docker.com | b…

Visual Studio配置PCL库

Visual Studio配置PCL库 Debug和Release配置新建项目配置属性表测试参考 Debug和Release Debug和Release的配置过程一模一样&#xff0c;唯一区别就在于最后一步插入的附加依赖项不同&#xff0c;因此下面以debug为例。 配置新建项目 1、新建一个C空项目&#xff0c;模式设置…

Linux文本三剑客之awk

目录 前言 awk 1.认识awk 2.使用awk 2.1语法 2.2常用命令选项 2.3awk变量 2.3.1内置变量 2.3.2自定义变量 2.4printf命令 awk例题 前言 awk、grep、sed是linux操作文本的三大利器&#xff0c;合称文本三剑客&#xff0c;也是必须掌握的linux命令之一。三者的功能都是…

FreeRTOS源码分析-9 互斥信号量

目录 1 优先级翻转问题 2 互斥信号量概念及其应用 2.2FreeRTOS互斥信号量介绍 2.3FreeRTOS互斥信号量工作原理 3 互斥信号量函数应用 3.1功能分析 3.2API详解 3.3功能实现 4 递归互斥信号量函数应用 4.1死锁现象 ​编辑 4.2API详解 4.3解决死锁 5 互斥信号量实现原…

C++数据结构之平衡二叉搜索树(一)——AVL的实现(zig与zag/左右双旋/3+4重构)

本文目录 00.BBST——平衡二叉搜索树01.AVL树02.AVL的插入2.1单旋——zig 与 zag2.2插入节点后的单旋实例2.3手玩小样例2.4双旋实例2.5小结 03.AVL的删除3.1单旋删除3.2双旋删除3.3小结 04.34重构05.综合评价AVL5.1优点5.2缺点 06.代码注意插入算法删除算法完整代码&#xff1a…

关于接口测试用例设计的一些思考

接口测试发现的典型问题 传入参数处理不当&#xff0c;引起程序错误类型溢出&#xff0c;导致数据读取和写入不一致对象权限校验出错&#xff0c;可获取其他角色信息状态出错&#xff0c;导致逻辑处理出现问题逻辑校验不完善定时任务执行出错 接口测试用例设计 接口测试用例…

redis入门3-在java中操作redis

Redis的java客户端 Jedis、Lettuce、Redisson、以及spring提供的spring data redis Jedis操作redis //添加依赖 <dependency><groupId>redis.clients</groupId><artifactId>jedis</artifactId><version>2.8.0</version> </dep…

JJWT快速入门

本篇介绍使用 JJWT&#xff08;Java JWT&#xff09;库来生成 JWT Token&#xff0c;步骤如下&#xff1a; 添加依赖&#xff1a; 在项目中添加 JJWT 依赖项。对于 Maven 项目&#xff0c;可以在 pom.xml 文件中添加以下依赖项&#xff1a; <dependency><groupId>…

python解析帆软cpt及frm文件(xml)获取源数据表及下游依赖表

#!/user/bin/evn python import os,re,openpyxl 输入&#xff1a;帆软脚本文件路径输出&#xff1a;帆软文件检查结果Excel#获取来源表 def table_scan(sql_str):# remove the /* */ commentsq re.sub(r"/\*[^*]*\*(?:[^*/][^*]*\*)*/", "", sql_str)# r…

c++学习(特殊类设计)[30]

只能在堆上创建对象的类 如果你想要确保对象只能在堆上创建&#xff0c;可以通过将析构函数声明为私有&#xff0c;并提供一个静态成员函数来创建对象。这样&#xff0c;类的实例化只能通过调用静态成员函数来完成&#xff0c;而无法直接在栈上创建对象。 以下是一个示例&…

【开源项目--稻草】Day03

【开源项目--稻草】Day03 1. 续Spring-Security1.1 自定义登录界面 2. 用户注册2.1 将注册页面显示2.2 编写控制器进行测试2.3 编写注册业务逻辑2.4 注册功能的收尾 3. VUE3.1 VUE的基本使用3.1.1 什么是VUE 3.2 使用VUEAjax完善稻草问答的注册功能 1. 续Spring-Security 1.1 …

Kubespray-offline v2.21.0-1 下载 Kubespray v2.22.1 离线部署 kubernetes v1.25.6

文章目录 1. 目标2. 预备条件3. vcenter 创建虚拟机4. 系统初始化4.1 配置网卡4.2 配置主机名4.3 内核参数 5. 打快照6. 安装 git7. 配置科学8. 安装 docker9. 下载介质9.1 下载安装 docker 介质9.2 下载 kubespray-offline-ansible 介质9.3 下载 kubernetes 介质 10. 搬运介质…

6.物联网操作系统信号量

一。信号量的概念与应用 信号量定义 FreeRTOS信号量介绍 FreeRTOS信号量工作原理 1.信号量的定义 多任务环境下使用&#xff0c;用来协调多个任务正确合理使用临界资源。 2.FreeRTOS信号量介绍 Semaphore包括Binary&#xff0c;Count&#xff0c;Mutex&#xff1b; Mutex包…

【2种方法,jmeter用一个正则提取器提取多个值!】

jmeter中&#xff0c;用json提取器&#xff0c;一次提取多个值&#xff0c;这个很多人都会。但是&#xff0c;用正则提取器一次提取多个&#xff0c;是否可以呢&#xff1f; 肯定&#xff0c;很多人都自信满满的说&#xff0c;可以&#xff01;形如&#xff1a;token":&q…

Jenkins触发器时间、次数设定

触发器触发条件介绍 触发器触发条件公式&#xff1a;由5颗星组成 * * * * * 分别代表&#xff1a;分钟(0-59) 小时(0-23) 日期(1-31) 月份(1-12) 星期(0-6) 企业项目中常用场景介绍 场景1&#xff1a;接口脚本部分测试通过&#xff0c;部分还在进行&#xff0c;回归测试脚本执行…

Windows上安装 jdk 环境并配置环境变量 (超详细教程)

&#x1f468;‍&#x1f393;博主简介 &#x1f3c5;云计算领域优质创作者   &#x1f3c5;华为云开发者社区专家博主   &#x1f3c5;阿里云开发者社区专家博主 &#x1f48a;交流社区&#xff1a;运维交流社区 欢迎大家的加入&#xff01; &#x1f40b; 希望大家多多支…

torchvision.datasets数据加载失败

torchvision.datasets数据加载失败 如何使用torchvision.datasets进行自动下载数据失败&#xff0c;可以使用手动下载数据 Ctrl点击可以进入相关包文件&#xff0c;查找下载地址&#xff1a;https://www.cs.toronto.edu/~kriz/cifar-10-python.tar.gz 手动下载之后解压&#x…

企业微信小程序在调用wx.qy.login时返回错误信息qy.login:fail

原因是大概是绑定了多个企业但是在开发者工具中没有选择正确的企业 解决方法&#xff1a; 重新选择企业后即可成功获取code

RabbitMQ(一) - 基本结构、SpringBoot整合RabbitMQ、工作队列、发布订阅、直接、主题交换机模式

RabbitMQ结构 Publisher &#xff1a; 生产者 Queue: 存储消息的容器队列&#xff1b; Consumer:消费者 Connection&#xff1a;消费者与消息服务的TCP连接 Channel:信道&#xff0c;是TCP里面的虚拟连接。例如&#xff1a;电缆相当于TCP&#xff0c;信道是一条独立光纤束&…