【JavaWeb学习笔记】10 - 手写Tomcat底层,Maven的初步使用

一、Maven

1.Maven示意图

类似Java访问数据库

2.创建Maven案例演示

配置阿里镜像

找到setting目录

但一开始配置不存在该文件

需要去Maven主目录下的conf拿到settings拷贝到上述目录

拷贝到admin/.m2后打开该settings

在<mirrors>内输入镜像地址

<mirror>
         <id>alimaven</id>
         <name>aliyun maven</name>
         <url>https://maven.aliyun.com/nexus/content/groups/publichttps://maven.aliyun.com/repository/publichttps://maven.aliyun.com/nexus/content/groups/public</url>
         <mirrorOf>central</mirrorOf>
     </mirror>

配置pom.xml文件

    </dependency>
    <!--引入servlet.jar包-->
    <!--
    1.入servlet-api.jar ,为J开发servlet
    2. dependency 标签是表示引入-一个包
    3. groupId包的公司/ 组织/开发团队/个人信息javax. servlet
    4. artifactId :项目名javax .servlet-api
    5. version 版本
    6. scope 表示引入的包的作用范围
    7. provided: 表示tomcat 本身有jar包,这里你引入的jar包,在编译,测试有效
    但是在打包的时候不要带上这个jar包
    8.下载的包在你指定的目录:C:\Users\Administrator\.m2\repository
    9.可以去修改我们要下载包的位置->
    10.我们可以去指定maven仓库,即配置maven镜像C:\Users\Administratorl.m2\settings.xml

    -->
    <dependency>
      <groupId>javax.servlet</groupId>
      <artifactId>javax.servlet-api</artifactId>
      <version>3.1.0</version>
      <scope>provided</scope>
    </dependency>

3.实现计算器效果

创建Tomcat的时候不要使用xxx_war包而要使用explore的

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>计算器</title>
</head>
<body>
    <form action="/yhtomcat/calServlet" method="post">
        num1:<input type="text" name="num1"><br/>
        num2:<input type="text" name="num2"><br/>
        <input type="submit" value="submit">
    </form>
</body>
</html>
@WebServlet(name = "CalServlet",urlPatterns = "/calServlet")
public class CalServlet extends HttpServlet {
    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        String strnum1 = request.getParameter("num1");
        String strnum2 = request.getParameter("num2");
        int num1 = 0;
        int num2 = 0;
        int sum = -1;
        try {
            num1 = Integer.parseInt(strnum1);
            num2 = Integer.parseInt(strnum2);
            System.out.println("res = " + num1 + num2);
            sum = num1 + num2;
        } catch (NumberFormatException e) {
            System.out.println("form wrong , continue");
        }
        response.setContentType("text/html;charset=utf-8");
        PrintWriter writer = response.getWriter();
        if (!(sum == -1)) {
            writer.print("<h1> res = " + sum + "</h1>");

        }else{
            writer.print("<h1> wrong date please try again!!  </h1>");
        }
        writer.flush();
        writer.close();
    }


    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        doPost(request,response);
    }
}

二、Tomcat架构分析

我们的目标:不用Tomcat,不用系统提供的Servlet,

模拟Tomcat底层实现并能调用我们自己设计的Servle,也能完成相同的功能

说明: Tomcat有三种运行模式(BIO, NIO, APR) ,因为老师核心讲解的是Tomcat如何接收客户端请求,解析请求,调用Servlet并返回结果的机制流程,采用BIO线程模型来模拟.

模拟Tomcat底层机制

一、编写自己Tomcat

1.基于socket开发服务端流程

1. ServerSocket

在服务端监听指定端口,如果浏览器/客户端连接该端口,则建立连接,返回Socket对象

2. Socket

表示服务端和客户端/浏览器间的连接,通过Socket可以得到InputStream和OutputStream流对象。
 

public class YhTomcatV1 {
    public static void main(String[] args) throws IOException {
        ServerSocket serverSocket = new ServerSocket(8080);
        System.out.println("======yhtomcat 在8080端口监听");
        while (!serverSocket.isClosed()){
            //等待连接
            //如果有连接来,就创建一个socket
            //这socket就是服务端和浏觉器端的连接/通道
            Socket socket = serverSocket.accept();

            //先接受浏览器发来的数据
            //字节流
            InputStream inputStream = socket.getInputStream();
            BufferedReader bufferedReader = new BufferedReader
                    (new InputStreamReader(inputStream,"utf-8"));
            String mes = null;
            System.out.println("=====接受到浏览器发送的数据======");
            while ((mes = bufferedReader.readLine()) != null){
                if(mes.length() == 0){//读到空字符串
                    break;
                }
                System.out.println(mes);
            }
            //我们的tomcat会送-http响应方式
            OutputStream outputStream = socket.getOutputStream();
            //构建一个http响应的头
            //\r\n 表示回车换行
            //http响应体,需要前面有两个换行 \r\n\r\n
            String respHeader = "HTTP/1.1 200 OK\r\n" +
                    "Content-Type: text/html;charset=utf-8\r\n\r\n";
            String resp = respHeader + "<h1>hi, 这是模拟Tomcat</h1>";

            System.out.println("========我们的tomcat 给浏览器会送的数据======");
            System.out.println(resp);
            outputStream.write(resp.getBytes());//将resp字符串以byte[] 方式返回

            outputStream.flush();
            outputStream.close();
            inputStream.close();
            socket.close();

        }
    }
}

2.使用BIO线程模型,支持多线程

BIO线程模型介绍

需求分析

浏览器请求http:/ /localhost:8080,服务端返回hi , hspedu,后台hsptomcat使用BIO线程模型,支持多线程=>对前面的开发模式进行改造

一个持有线程的对象

public class YhRequestHandler extends Thread {
/*
 * 1. HspRequestHandler 对象是一个线程对象
 * 2. 处理一个http请求的
 */
    //定义Socket
    private Socket socket = null;

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

    @Override
    public void run() {

        //这里我们可以对客户端/浏览器进行IO编程/交互
        try {
            //1.使用BIO线程模型,支持多线程
            InputStream inputStream = socket.getInputStream();

            // //把inputStream -> BufferedReader -> 方便进行按行读取
            BufferedReader bufferedReader =
                    new BufferedReader(new InputStreamReader(inputStream, "utf-8"));
            //
            // //不同的线程在和浏览器和客户端交互
            System.out.println("当前线程= " + Thread.currentThread().getName());

            System.out.println("=========hsptomcatv2 接收到的数据如下=========");
            String mes = null;
            // io - 网络 - 线程 - 反射 - 注解 - OOP [都会学会,也会学好]
            //
            while ((mes = bufferedReader.readLine()) != null) {
                //如果长度为0 ""
                if (mes.length() == 0) {
                    break; //退出
                }
                System.out.println(mes);
            }

            //构建一下http响应头
            //返回的http的响应体和响应头之间有两个换行 \r\n\r\n
            String respHeader = "HTTP/1.1 200 OK\r\n" +
                    "Content-Type: text/html;charset=utf-8\r\n\r\n";
            String resp = respHeader + "<h1>hi this is ThreadServlet</h1>";
            System.out.println("========Yhtomcatv2返回的数据是=========");
            System.out.println(resp);
            //返回数据给我们的浏览器/客户端-> 封装成http响应
            OutputStream outputStream = socket.getOutputStream();
            //resp.getBytes() 是把字符串转成字节数组
            outputStream.write(resp.getBytes());
            outputStream.flush();
            outputStream.close();
            inputStream.close();
            socket.close();


        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            //最后一定确保socket要关闭
            if (socket != null) {
                try {
                    socket.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }

    }
}

Tomcat

public class YhTomcatV2 {
    public static void main(String[] args) throws IOException {
        //在8080端口监听
        ServerSocket serverSocket = new ServerSocket(8080);
        System.out.println("=======hsptomcatV2 在8080监听=======");
        //只要 serverSocket没有关闭,就一直等待浏览器/客户端的连接
        while (!serverSocket.isClosed()) {
            //1. 接收到浏览器的连接后,如果成功,就会得到socket
            //2. 这个socket 就是 服务器和 浏览器的数据通道
            Socket socket = serverSocket.accept();
            //3. 创建一个线程对象,并且把socket给该线程
            //  这个是java线程基础
            YhRequestHandler hspRequestHandler =
                    new YhRequestHandler(socket);
            new Thread(hspRequestHandler).start();
        }
    }
}

问题分析: MyT omcat只是简单返回结果,没有和Servlet,web.xml关联

3.处理 Servlet 

Request处理请求信息

public class YhRequest {
    /**
     * 1. YhRequest 作用是封装http请求的数据
     * get /hspCalServlet?num1=10&num2=30
     * 2. 比如 method(get) 、 uri(/hspCalServlet) 、 还有参数列表 (num1=10&num2=30)
     * 3. HspRequest 作用就等价原生的servlet 中的HttpServletRequest 这里考虑的是GET请求
     */

    private String method;
    private String uri;
    //存放参数列表 参数名-参数值 => HashMap
    private HashMap<String, String> parametersMapping =
            new HashMap<>();
    private InputStream inputStream = null;


    //构造器=> 对http请求进行封装 => 可以将老师写的代码封装成方法
    //inputStream 是和 对应http请求的socket关联
    public YhRequest(InputStream inputStream) {
        this.inputStream = inputStream;
        //完成对http请求数据的封装..
        encapHttpRequest();
    }

    /**
     * 将http请求的相关数据,进行封装,然后提供相关的方法,进行获取
     */
    private void encapHttpRequest() {
        System.out.println("yhRequest init()");
        try {
            //inputstream -> BufferedReader
            BufferedReader bufferedReader =
                    new BufferedReader(new InputStreamReader(inputStream, "utf-8"));

            //读取第一行
            /**
             * GET /hspCalServlet?num1=10&num2=30 HTTP/1.1
             * Host: localhost:8080
             * User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:97.0) Gecko/20100101 Fi
             */
            String requestLine = bufferedReader.readLine();
            //GET - /hspCalServlet?num1=10&num2=30 - HTTP/1.1
            String[] requestLineArr = requestLine.split(" ");
            //得到method
            method = requestLineArr[0];
            //解析得到 /hspCalServlet
            //1. 先看看uri 有没有参数列表
            int index = requestLineArr[1].indexOf("?");
            if (index == -1) { //说明没有参数列表
                uri = requestLineArr[1];
            } else {
                //[0,index)
                uri = requestLineArr[1].substring(0, index);
                //获取参数列表->parametersMapping
                //parameters => num1=10&num2=30
                String parameters = requestLineArr[1].substring(index + 1);
                //num1=10 , num2=30 .... parametersPair= ["num1=10","num2=30" ]
                String[] parametersPair = parameters.split("&");
                //防止用户提交时 /hspCalServlet?
                if (null != parametersPair && !"".equals(parametersPair)) {
                    //再次分割 parameterPair = num1=10
                    for (String parameterPair : parametersPair) {
                        //parameterVal ["num1", "10"]
                        String[] parameterVal = parameterPair.split("=");
                        if (parameterVal.length == 2) {
                            //放入到 parametersMapping
                            parametersMapping.put(parameterVal[0], parameterVal[1]);
                        }
                    }
                }
            }
            //这里不能关闭流 inputStream 和 socket关联
            //inputStream.close();

        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    //request对象有一个特别重要方法
    public String getParameter(String name) {
        if (parametersMapping.containsKey(name)) {
            return parametersMapping.get(name);
        } else {
            return "";
        }
    }

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

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

注意 这里不能关闭流 inputStream 和 socket关联

Response对象处理响应 持有socket

public class YhResponse {
    /**
     * 1. HspResponse对象可以封装OutputStream(是socket关联)
     * 2. 即可以通过 HspResponse对象 返回Http响应给浏览器/客户端
     * 3. HspResponse对象 的作用等价于原生的servlet的 HttpServletResponse
     */

    private OutputStream outputStream = null;

    //写一个http的响应头 => 先死后活
    public static final String respHeader = "HTTP/1.1 200 OK\r\n" +
            "Content-Type: text/html;charset=utf-8\r\n\r\n";

    //说明同学们如果有兴趣, 在编写更多的方法
    //比如 setContentType

    //在创建 YhResponse 对象时,传入的outputStream是和Socket关联的
    public YhResponse(OutputStream outputStream) {
        this.outputStream = outputStream;
    }

    //当我们需要给浏览器返回数据时,可以通过HspResponse 的输出流完成
    //
    public OutputStream getOutputStream() {
        return outputStream;
    }


}

设计Servlet规范类以及Servlet接口

public interface YhServlet {
    void init() throws Exception;

    void service(YhRequest request, YhResponse response) throws IOException;

    void destroy();
}
public abstract class YhHttpServlet implements YhServlet {
    @Override
    public void service(YhRequest request, YhResponse response) throws IOException {
        //老师说明 equalsIgnoreCase 比较字符串内容是相同,不区别大小写
        if("GET".equalsIgnoreCase(request.getMethod())) {
            //这里会有动态绑定
            this.doGet(request,response);
        } else if("POST".equalsIgnoreCase(request.getMethod())) {
            this.doPost(request,response);
        }
    }

    //这里我们使用的了模板设计模式 => java 基础的 抽象类专门讲过模板设计模式
    //让HspHttpServlet 子类 HspCalServlet 实现

    public abstract void doGet(YhRequest request, YhResponse response);
    public abstract void doPost(YhRequest request, YhResponse response);
}

YhCalServlet实现该Servlet并写自己的业务代码 

public class YhCalServlet extends YhHttpServlet {
    @Override
    public void doGet(YhRequest request, YhResponse response) throws IOException {
        doPost(request,response);
    }

    @Override
    public void doPost(YhRequest request, YhResponse response) throws IOException {
        String strnum1 = request.getParameter("num1");
        String strnum2 = request.getParameter("num2");
        int num1 = 0;
        int num2 = 0;
        int sum = -1;
        try {
            num1 = Integer.parseInt(strnum1);
            num2 = Integer.parseInt(strnum2);
            System.out.println("res = " + num1 + num2);
            sum = num1 + num2;
        } catch (NumberFormatException e) {
            System.out.println("form wrong , continue");
        }
        // response.setContentType("text/html;charset=utf-8"); response内已经做了
        OutputStream outputStream = response.getOutputStream();
        if (!(sum == -1)) {
            outputStream.write((YhResponse.respHeader + "<h1> res = " + sum + "</h1>").getBytes());

        }else{
            outputStream.write((YhResponse.respHeader + "<h1> wrong date please try again!!  </h1>").getBytes());
        }
        outputStream.flush();
        outputStream.close();
    }

    @Override
    public void init() {

    }

    @Override
    public void destroy() {

    }
}

4.使用反射去处理查找哪个calServlet

handler管理线程代码 

            //=====================通过反射来实现==========
            // 先说明一把实现思路->【停一下】 -> 如果你自己完成?10min
            // 1. 得到 uri => 就是 servletUrlMapping 的 url-pattern
            YhRequest yhRequest = new YhRequest(socket.getInputStream());
            YhResponse yhResponse = new YhResponse(socket.getOutputStream());
            String uri = yhRequest.getUri();
            String servletName = YhTomcatV3.servletUrlMapping.get(uri);
            if(servletName == null){
                servletName = "";
            }
            //2. 通过uri->servletName->servlet的实例 , 真正的运行类型是其子类 HspCalServlet
            YhHttpServlet yhHttpServlet =
                    YhTomcatV3.servletMapping.
                            get(servletName);
            //3. 调用service , 通过OOP的动态绑定机制,调用运行类型的 doGet/doPost

            if (yhHttpServlet != null) {//得到
                yhHttpServlet.service(yhRequest, yhResponse);
            } else {
                //没有这个servlet , 返回404的提示信息
                String resp = YhResponse.respHeader + "<h1>404 Not Found</h1>";
                OutputStream outputStream = yhResponse.getOutputStream();
                outputStream.write(resp.getBytes());
                outputStream.flush();
                outputStream.close();
            }

模拟Tomcat利用反射和dom4j处理xml文件获取Servlet

public class YhTomcatV3 {
    //1. 存放容器 servletMapping
    // -ConcurrentHashMap
    // -HashMap
    // key            - value
    // ServletName    对应的实例
    public static final ConcurrentHashMap<String, YhHttpServlet>
            servletMapping = new ConcurrentHashMap<>();


    //2容器 servletUrlMapping
    // -ConcurrentHashMap
    // -HashMap
    // key                    - value
    // url-pattern       ServletName

    public static final ConcurrentHashMap<String, String>
            servletUrlMapping = new ConcurrentHashMap<>();


    //你可以这里理解session, tomcat还维护一个容器
    public static final ConcurrentHashMap<String, HttpSession>
            sessionMapping = new ConcurrentHashMap<>();


    // //你可以这里理解filter, tomcat还维护了filter的容器
    // public static final ConcurrentHashMap<String, String>
    //         filterUrlMapping = new ConcurrentHashMap<>();
    //
    // public static final ConcurrentHashMap<String, Filter>
    //         filterMapping = new ConcurrentHashMap<>();
    public static void main(String[] args) throws MalformedURLException, DocumentException {
        YhTomcatV3 yhTomcatV3 = new YhTomcatV3();
        yhTomcatV3.init();
        //启动hsptomcat容器
        yhTomcatV3.startTomcatV3();
    }


    //启动HspTomcatV3容器
    public void startTomcatV3() {

        try {
            ServerSocket serverSocket = new ServerSocket(8080);
            System.out.println("=====hsptomcatv3在8080监听======");
            while (!serverSocket.isClosed()) {
                Socket socket = serverSocket.accept();
                YhRequestHandler yhRequestHandler =
                        new YhRequestHandler(socket);
                new Thread(yhRequestHandler).start();
            }
        } catch (IOException e) {
            e.printStackTrace();
        }

    }

    //直接对两个容器进行初始化
    @Test
    public void init() throws MalformedURLException, DocumentException {
        //读取web.xml => dom4j =>
        //得到web.xml文件的路径 => 拷贝一份.
        String path = YhTomcatV3.class.getResource("/").getPath();
        System.out.println("path= " + path);
        //使用dom4j技术完成读取
        SAXReader saxReader = new SAXReader();
        //困难->真的掌握
        try {
            Document document = saxReader.read(new File(path + "web.xml"));
            System.out.println("document= " + document);
            //得到根元素
            Element rootElement = document.getRootElement();
            //得到根元素下面的所有元素
            List<Element> elements = rootElement.elements();
            //遍历并过滤到 servlet servlet-mapping
            for (Element element : elements) {
                if ("servlet".equalsIgnoreCase(element.getName())) {
                    //这是一个servlet配置
                    //System.out.println("发现 servlet");
                    //使用反射将该servlet实例放入到servletMapping
                    Element servletName = element.element("servlet-name");
                    Element servletClass = element.element("servlet-class");
                    servletMapping.put(servletName.getText(),
                            (YhHttpServlet) Class.forName(servletClass.getText().trim()).newInstance());
                } else if ("servlet-mapping".equalsIgnoreCase(element.getName())) {
                    //这是一个servlet-mapping
                    //System.out.println("发现 servlet-mapping");
                    Element servletName = element.element("servlet-name");
                    Element urlPatter = element.element("url-pattern");
                    servletUrlMapping.put(urlPatter.getText(), servletName.getText());

                }
            }
        } catch (Exception e) {
            e.printStackTrace();
        }

        //老韩验证,这两个容器是否初始化成功
        System.out.println("servletMapping= " + servletMapping);
        System.out.println("servletUrlMapping= " + servletUrlMapping);
    }
}

二、课后作业

<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>计算器</title>
</head>
<body>
    <form action="/yhCalServlet" method="GET">
        num1:<input type="text" name="num1"><br/>
        num2:<input type="text" name="num2"><br/>
        <input type="submit" value="submit">
    </form>
</body>
</html>

在工具类内写方法判断  如果不是servlet 就判断是不是html

public static String readHtml(String filename) {
        String path = com.yinhai.utils.WebUtils.class.getResource("/").getPath();
        StringBuilder stringBuilder = new StringBuilder();

        try {
            BufferedReader bufferedReader = new BufferedReader(new FileReader(path + filename));
            String buf = "";
            while ((buf = bufferedReader.readLine()) != null) {
                stringBuilder.append(buf);
            }
        } catch (Exception e) {
            e.printStackTrace();
        }

        return stringBuilder.toString();
    }

如果是html 就走该if体 将方法返回 

 // ====================新增业务逻辑===========
            //(1) 判断uri是什么资源 => 工具方法
            //(2) 如果是静态资源,就读取该资源,并返回给浏览器 content-type text/html
            //(3) 因为目前老师并没有起到tomcat, 不是一个标准的web项目
            //(4) 把读取的静态资源放到 target/classes/cal.html
            //过滤,拦截 , 权限等待 => Handler.... => 分发
            if(WebUtils.isHtml(uri)) {//就是静态页面
                String content = WebUtils.readHtml(uri.substring(1));
                content = yhResponse.respHeader + content;
                //得到outputstream , 返回信息(静态页面)给浏览器
                OutputStream outputStream = yhResponse.getOutputStream();
                outputStream.write(content.getBytes());
                outputStream.flush();
                outputStream.close();
                socket.close();
                return;
            }
            //===========================================

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

SpringBoot中处理处理国际化

SpringBoot中处理处理国际化 1. 创建SpringBoot项目2. resource下创建i18n目录3. 右键i18n新建资源包4. 弹框中添加需要支持的国际化语言5. messages.properties中添加需要国际化的键6. application.yaml添加配置7. 国际化工具8. 使用功能9 场景问题 1. 创建SpringBoot项目 2.…

【Flink-cdc-Mysql-To-Kafka】使用 Flinksql 利用集成的 connector 实现 Mysql 数据写入 Kafka

【Flink-cdc-Mysql-To-Kafka】使用 Flinksql 利用集成的 connector 实现 Mysql 数据写入 Kafka 1&#xff09;环境准备2&#xff09;准备相关 jar 包3&#xff09;实现场景4&#xff09;准备工作4.1.Mysql4.2.Kafka 5&#xff09;Flink-Sql6&#xff09;验证 1&#xff09;环境…

毅速:3D打印随形水路 提高良品率和生产效率的新利器

随着科技的不断发展&#xff0c;3D打印技术已经成为模具制造领域的一种重要技术。其中&#xff0c;模具随形水路的设计和制造是提高注塑产品良品率和生产效率的关键环节。 模具随形水路是一种根据产品形状设计的水路&#xff0c;可以更靠近产品&#xff0c;并在模具内热点集中区…

这一篇就够了!全套SpringBoot教程02

SpringBoot运维实用篇 基础篇发布以后&#xff0c;看到了很多小伙伴在网上的留言&#xff0c;也帮助超过100位小伙伴解决了一些遇到的问题&#xff0c;并且已经发现了部分问题具有典型性&#xff0c;预计将有些问题在后面篇章的合适位置添加到本套课程中&#xff0c;作为解决方…

【docker 】Compose 使用介绍

Docker Compose Docker Compose文档 Docker Compose GitHub地址 Docker Compose 是用于定义和运行多容器 Docker 应用程序的工具。通过 Compose&#xff0c;您可以使用 YML 文件来配置应用程序需要的所有服务。然后&#xff0c;使用一个命令&#xff0c;就可以从 YML 文件配…

【图的应用一:最小生成树】- 用 C 语言实现普里姆算法

目录 一、最小生成树 二、普里姆算法的构造过程 三、普里姆算法的实现 一、最小生成树 假设要在 n 个城市之间建立通信联络网&#xff0c;则连通 n 个城市只需要 n - 1 条线路。这时&#xff0c;自然会考虑这样一个问题&#xff0c;如何在最节省经费的前提下建立这个通信…

针对网页html中插入动图gif不能循环播放只播放一次的解决方案

针对网页html中插入动图gif不能循环播放只播放一次的解决方案 原因分析解决方案 原因分析 使用图片编辑软件制作的过程中未启用“循环播放”功能&#xff0c;这里以Photoshop为例&#xff0c;演示设置GIF图片循环播放的操作流程&#xff1a;所需材料&#xff1a;PS。第一步&am…

使用Audition录制电脑内部声音

在电脑上播放的媒体文件&#xff0c;包括视频和声音&#xff0c;很多是可以播放却无法保存的。例如一些网页播放的视频&#xff0c;或者在线播放的音乐。 视频的话&#xff0c;可以使用工具来截图&#xff0c;抓取GIF或录屏。 声音的话&#xff0c;也可以使用工具进行录制。这里…

【算法刷题】Day17

文章目录 1. 不同路径 II题干&#xff1a;算法原理&#xff1a;代码&#xff1a; 2. 在排序数组中查找元素的第一个和最后一个位置题干&#xff1a;算法原理&#xff1a;解法一&#xff1a;暴力解法 O(n)解法二&#xff1a;朴素二分解法三&#xff1a;查找区间左右端点 代码&am…

redis未授权漏洞复现

什么是redis redis就是个数据库&#xff0c;跟mysql不同的地方在于redis主要将数据存在内存中&#xff0c;读写速度非常快 redis未授权 其原因很简单&#xff0c;就是redis服务器在默认安装好不配置的情况下可以直接免密码登录&#xff0c;登录后在web目录写入一句话木马&am…

为什么选择国产WordPress:HelpLook的优势解析

如今网站建设可以说已经是企业必备。而在众多的网站建设工具中&#xff0c;WordPress无疑是其中的佼佼者。作为一款开源的CMS&#xff08;内容管理系统&#xff09;&#xff0c;WordPress拥有丰富的插件和主题&#xff0c;以及强大的功能&#xff0c;使得用户可以轻松地构建出符…

web前端之若依二次开发经验、使用IDEA启动若依项目、sysConfigController报错提示的解决办法、环境搭建

MENU 前言前端创建路由的细节 后端启动后端项目细节 前言 1、官网地址 2、在线文档 3、演示地址 4、代码下载 5、野生版的若依开发文档 此文章包括前端和后端&#xff0c;记录开发中遇到的一些问题。 前端 创建路由的细节 1、从系统管理进入菜单管理页面创建菜单&#xff0c;菜…

最强Pose模型RTMO开源 | 基于YOLO架构再设计,9MB+9ms性能完爆YOLO-Pose

实时多人在图像中的姿态估计面临着在速度和精度之间实现平衡的重大挑战。尽管两阶段的上下文方法在图像中人数增加时会减慢速度&#xff0c;但现有的单阶段方法往往无法同时实现高精度和实时性能。 本文介绍了RTMO&#xff0c;这是一个单阶段姿态估计框架&#xff0c;通过在YOL…

03进程基础-学习笔记

Process 进程 进程为操作系统的基本调度单位&#xff0c;占用系统资源(cpu,内存)完成特定任务&#xff0c;所有说进程是操作系统的标准执行单元 进程与程序的差别 程序是静态资源&#xff0c;存储与电脑磁盘中(disk磁盘资源)程序执行后会创建进程&#xff0c;负责完成功能&a…

第十五章总结

一.输入/输出流 1.输入流 InputStrema类是字节输入流的抽象类&#xff0c;它是所有字节输入流的父类。 该类中所有方法遇到错误都会引发IOException异常。 read()方法&#xff1a;从输入流中读取数据的下一个字节。返回0~255的int字节值。如果因为已经到达流末尾而没有可用的…

完美解决labelimg xml转可视化中文乱码问题,不用matplotlib

背景简述 我们有一批标注项目要转可视化&#xff0c;因为之前没有做过&#xff0c;然后网上随意找了一段代码测试完美&#xff08;并没有&#xff09;搞定&#xff0c;开始疯狂标注&#xff0c;当真正要转的时候傻眼了&#xff0c;因为测试的时候用的是英文标签&#xff0c;实…

重生奇迹mu再生原石介绍

再生原石的作用&#xff1a; 可以通过坎特鲁提炼之塔的NPC艾尔菲丝提炼成功就可以可获得再生宝石。 再生原石的用法&#xff1a; 1、打怪获得再生原石去提炼之塔&#xff08;进入坎特鲁遗址的141188位置的传送台&#xff09;。 2、找到&#xff08;艾儿菲丝&#xff09;把原…

【程序】STM32 读取光栅_编码器_光栅传感器_7针OLED

文章目录 源代码工程编码器基础程序参考资料 源代码工程 源代码工程打开获取&#xff1a; http://dt2.8tupian.net/2/28880a55b6666.pg3这里做了四倍细分&#xff0c;在屏幕上显示 速度、路程、方向。 接线方法&#xff1a; 单片机--------------串口模块 单片机的5V-------…

【JAVA基础(对象和封装以及构造方法)】----第四天

对象和封装以及构造方法 面向对象和面向过程面向过程面向对象 类与对象及其使用定义类创建一个对象&#xff0c;操作类补充&#xff08;成员变量和局部变量&#xff09; private 修饰类 封装练习编写类编写测试输出结果 面向对象和面向过程 面向过程 在了解面向对象之前先来了…

C语言刷题每日一题——求1到100中包含数字9的整数的个数

思路分析 创建一个变量count记录个数使用一个for循环完成从1到100的循环每次循环判断该数字是否包含数字9——第一种情况 &#xff1a;个位包含9&#xff0c;即求模10的结果为9 &#xff1b;第二种情况&#xff1a;十位包含9&#xff0c;即除以10的结果为9&#xff08;两种情况…