【学习笔记】手写 Tomcat 六

news2024/12/23 4:21:20

目录

一、线程池

1. 构建线程池的类

2. 创建任务

3. 执行任务

测试

二、URL编码

解决方案

测试

三、如何接收客户端发送的全部信息

解决方案

测试

四、作业

1. 了解工厂模式

2. 了解反射技术


一、线程池

昨天使用了数据库连接池,我们了解了连接池的优点,那么也可以使用线程池来管理线程,

java自带的线程池的参数有 核心线程数,最大线程数,线程活跃时间,时间单位,任务队列,线程工厂,拒绝策略

为了学习了解线程池,我们先手写一个简单的线程池,只需要做到核心线程可重复利用就行

1. 构建线程池的类

属性:核心线程数,任务队列

方法:获取线程(静态代码块),执行任务(需要的参数:线程任务 Runnable)

为了避免创建多个对象,还需要设置单例模式

package com.shao.net;

import java.util.concurrent.LinkedBlockingQueue;

public class ThreadPool {
    // 定义一个成员静态变量,存储单例对象
    private static ThreadPool instance;

    // 线程池核心线程数
    private final static int MAX_THREAD_NUM = 10;
    // 存放任务的队列
    private static final LinkedBlockingQueue<Runnable> taskQueue = new LinkedBlockingQueue<>();

    static {
        for (int i = 0; i < MAX_THREAD_NUM; i++) {
            final int finalI = i;
            new Thread(new Runnable() {
                @Override
                public void run() {
                    /**
                     *  线程池的线程,从队列中取出任务,这时线程不在临界区了,自动释放锁,然后执行任务,当执行完任务后,
                     *  因为是while循环,所以会在 synchronized (taskQueue) 等待,
                     *  当锁释放后,并且当前线程被唤醒时,会尝试获取锁,
                     *  如果获取到锁,会进入临界区,如果队列中有任务,则取出,然后执行任务,如果没有,则等待
                     *  等待下次获取到锁,会继续从上次进入等待态的位置继续往下执行,也就是 taskQueue.wait() 开始往下执行
                     * */
                    while (true) {
                        Runnable task = null;
                        synchronized (taskQueue) {
                            System.out.println("线程" + finalI + "准备完成");
                            // 队列为空,等待
                            while (taskQueue.isEmpty()) {
                                try {
                                    taskQueue.wait();   // 使当前线程等待,释放锁
                                } catch (InterruptedException e) {
                                    e.printStackTrace();
                                }
                            }
                            System.out.println("线程" + finalI + "开始执行");
                            // 从队列中取出任务
                            task = taskQueue.poll();
                        }
                        if (task != null) {
                            // 执行任务
                            task.run();
                        }
                    }
                }

            }).start();
        }
    }

    // 私有化构造函数
    private ThreadPool() {
    }

    // 获取对象
    public static ThreadPool getInstance() {
        synchronized (ThreadPool.class) {
            if (instance == null) {
                instance = new ThreadPool();
            }
            return instance;
        }
    }

    public void execute(Runnable task) {
        // 当方法被调用时,会尝试获取锁,如果获取到锁,则将任务加入队列,并唤醒等待的线程
        synchronized (taskQueue) {
            taskQueue.add(task);
            taskQueue.notify();
        }
    }
}

2. 创建任务

这里的任务是之前线程执行的代码,我们把需要线程执行的任务放到一个类里,然后实现Runnable 

package com.shao.net;

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

public class MyTask implements Runnable {
    private InputStream is;
    private OutputStream os;

    public MyTask(InputStream is, OutputStream os) {
        this.is = is;
        this.os = os;
    }

    @Override
    public void run() {
        // 定义一个字节数组,存放客户端发送的请求信息
        byte[] bytes = new byte[1024];

        // 读取客户端发送的数据,返回读取的字节数
        int len = 0;
        try {
            len = is.read(bytes);

            if (len == -1) {
                return;
            }
            // 将读取的字节数组转换为字符串
            String msg = new String(bytes, 0, len);

            // 调用HttpRequest类解析请求信息
            HttpRequest httpRequest = new HttpRequest(msg);

            // 拼接请求的静态资源的路径
            // 路径是相对路径,从模块的根路径开始
            String filePath = "webs/" + httpRequest.getRequestModule();
            HttpResponse httpResponse = new HttpResponse(os, httpRequest);
            // 响应数据
            httpResponse.response(filePath);

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

3. 执行任务

初始化线程池,来一个用户连接时,就创建一个任务,然后交给线程池,线程池取出一条线程执行任务的 run 方法

package com.shao.net;

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

public class Tomcat {

    // 初始化线程池
    ThreadPool threadPool = ThreadPool.getInstance();

    public Tomcat() {
        ServerSocket ss = null;
        try {
            ss = new ServerSocket(8080);
            while (true) {

                // 调用accept()方法阻塞等待,直到有客户端连接到服务器,返回一个Socket对象用于与该客户端通信
                Socket socket = ss.accept();

                System.out.println("客户端连接成功");

                // 获取Socket对象的输入流,用于读取客户端发送的数据
                InputStream is = socket.getInputStream();

                // 获取Socket对象的输出流,用于向客户端发送数据
                OutputStream os = socket.getOutputStream();

                // 创建一个任务对象,将输入输出流作为参数传过去
                MyTask myTask = new MyTask(is, os);

                // 把任务作为参数传递给ThreadPool的execute()方法,启动一个线程执行MyTask对象中的run()方法
                threadPool.execute(myTask);

            }
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            //关闭连接通道
            try {
                ss.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
}

测试

二、URL编码

在HTTP请求中,如果参数包含中文字符,会进行URL编码,以避免乱码或传输错误。URL编码是一种将URL中的非ASCII字符(如中文字符)转换为可以在Web浏览器和服务器之间传输的格式的过程。

URL编码会将非ASCII字符转换为十六进制编码,以便于在HTTP请求中安全传输。

‌URL编码的基本原理‌: URL编码将非ASCII字符(如中文字符)转换为"%"后跟两位十六进制数字的形式。例如,空格在URL编码中通常被转换为"%20"。对于中文字符,它们会被转码为以"%E"开头,后面跟着若干位十六进制数字的字符串。

解决方案

在接收到请求信息后,先进行解码,然后再解析信息

URLDecoder.decode(需要解码的字符串, 字符集或编码方式)

在 MyTask 类中添加

测试

三、如何接收客户端发送的全部信息

目前,我们的 Tomcat 最多只能一次接收 1KB,因为定义的字节数组只有1024个字节

但是,如果客户端发送的请求参数非常非常多呢?超过了 1024 个字节了怎么办?

把字节数组定义的大一点?不行的,因为网络传输一次最多传输 8KB,超过 8KB 就会分批传输,接收参数时也需要分批接收

那怎么判断参数已经接收完?

参数有很多一般是使用POST方法,而POST方法的请求头有 Content-Length 的字段,表示请求体的总长度

我们来试一下,打印一下请求的参数信息

这里可以看到 Content-Length 的值是 26,表示请求体的参数长度为26字节,图中显示参数的长度为 25,因为解析后没有显示参数连接符 & 

我们来使用 Apipost 来压力测试一下,参数很多是什么样子

可以看到,只读取到了一部分 出师表 的内容,而且还有乱码,这是因为没有完整读取一个 汉字的字节,UTF-8 编码中一个汉字需要 3 个字节

解决方案

package com.shao.net;

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.URLDecoder;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;

public class MyTask implements Runnable {
    private InputStream is;
    private OutputStream os;
    private int totalLength;
    private StringBuilder sb;

    public MyTask(InputStream is, OutputStream os) {
        this.is = is;
        this.os = os;
    }

    @Override
    public void run() {
        // 定义一个字节数组,存放客户端发送的请求信息
        byte[] bytes = new byte[1024];

        // 读取客户端发送的数据,返回读取的字节数
        int len = 0;
        try {

            // 第一次读取请求信息
            len = is.read(bytes);

            if (len == -1) {
                return;
            }
            // 将读取的字节数组转换为字符串
            String msg = new String(bytes, 0, len);

            // 调用HttpRequest类解析请求信息
            HttpRequest httpRequest = new HttpRequest(msg);

            /*
             *  如果已读取的数据长度等于请求体的总长度,并且请求方法是 POST,说明请求体可能还没有读取完,需要读取剩余的数据
             * */
            if (bytes.length == len && "POST".equals(httpRequest.getRequestMethod())) {
                // 创建一个StringBuilder对象,用于拼接请求信息
                sb = new StringBuilder();
                sb.append(msg);

                // 获取 POST 请求方法中的请求体的总长度
                String length = httpRequest.getRequestHeaderParams().get("Content-Length");
                if (length != null) {
                    totalLength = Integer.parseInt(length);
                }
                // 调用方法,读取剩余的请求体数据
                msg = getNotReadMsg(httpRequest, bytes, msg);
            }


            // 把请求信息进行URL解码,然后根据 UTF-8 进行编码
            String decodedMsg = URLDecoder.decode(msg, "utf-8");

            // 调用HttpRequest类解析请求信息
            httpRequest = new HttpRequest(decodedMsg);

            // 拼接请求的静态资源的路径
            // 路径是相对路径,从模块的根路径开始
            String filePath = "webs/" + httpRequest.getRequestModule();
            HttpResponse httpResponse = new HttpResponse(os, httpRequest);
            // 响应数据
            httpResponse.response(filePath);

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

    /**
     * 读取剩余的请求体数据
     */
    private String getNotReadMsg(HttpRequest httpRequest, byte[] bytes, String msg) throws IOException {
        int len;
        // 获取请求的参数
        HashMap<String, String> requestBodyParams = httpRequest.getRequestBodyParams();

        // 请求参数的数量
        int size = requestBodyParams.size();

        // 计算第一次读取到的请求体中参数的长度
        Set<Map.Entry<String, String>> entries = requestBodyParams.entrySet();
        int partLength = 0;
        for (Map.Entry<String, String> entry : entries) {
            partLength += (entry.getKey() + "=" + entry.getValue()).length();
        }

        // 减去第一次读取到的请求体中参数的长度,如果存在多个参数,需要考虑到 '&' 的个数
        if (size > 1) {
            totalLength -= (partLength + (size - 1));
        } else {
            totalLength -= partLength;
        }

        // 判断是否还有数据没有读完
        while (totalLength > 0) {
            // 第二次读取请求信息
            len = is.read(bytes);
            // 如果读取的字节数大于0,表示读取到数据了
            if (len > 0) {
                // 将读取的字节数组转换为字符串
                msg = new String(bytes, 0, len);
                // 拼接字符串
                sb.append(msg);
                // 减去读取的字节数
                totalLength -= len;
            } else {
                break;
            }
        }
        // 转成字符串格式返回
        return sb.toString();
    }
}

测试

可以看出已经全部读取到了,第二个参数也读取到了

四、作业

1. 了解工厂模式

优化 Dao,现在在 Service 层,调用Dao层都要 new 一下,这样就比较占内存,比如调用的都是 UserDao,那么只需要创建一次 UserDao 的对象就行了

2. 了解反射技术

优化 Servlet ,通过配置文件可以动态的创建 Servlet 对象

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

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

相关文章

在 Docker 版 RStudio 中安装 Seurat V4 的完整教程 (同样适用于普通R环境安装)

在单细胞RNA测序&#xff08;scRNA-seq&#xff09;数据分析领域&#xff0c;Seurat 是一个广泛使用且功能强大的R包&#xff0c;提供了丰富的数据处理和可视化工具。为了简化环境配置和依赖管理&#xff0c;使用Docker来部署RStudio并安装Seurat V4是一种高效且可重复的方法。…

SpringCloud 2023各依赖版本选择、核心功能与组件、创建项目(注意事项、依赖)

目录 1. 各依赖版本选择2. 核心功能与组件3. 创建项目3.1 注意事项3.2 依赖 1. 各依赖版本选择 SpringCloud: 2023.0.1SpringBoot: 3.2.4。参考Spring Cloud Train Reference Documentation选择版本 SpringCloud Alibaba: 2023.0.1.0*: 参考Spring Cloud Alibaba选择版本。同时…

TMR技术的发展及其应用技术的介绍

目录 概述 1 TMR传感器介绍 1.1 原理介绍 1.2 技术演进历史 2 TMR技术的应用 2.1 电阻特性 2.2 技术比较 2.3 磁道特性 3 多维科技的芯片&#xff08;TMR1202&#xff09; 3.1 芯片介绍 3.2 特性 ​3.3 典型应用 参考文献 概述 本文主要介绍TMR技术的发展及其技术…

修改系统显示大小修改系统屏幕密度

文章目录 需求及场景一、界面位置及密度位置测试 一、修改density修改属性值属性位置修改默认值获取需要修改的值 二、部分资源参考总结 需求及场景 国内Android系统平台太多了&#xff0c;RK、全志、mtk、展讯&#xff0c;相同平台也会有不同的Android版本&#xff0c;不同版…

使用电子模拟器 Wokwi 运行 ESP32 示例(Arduino IDE、VSCode、ESP32C3)

文章目录 Wokwi 简介安装客户端&#xff08;Mac/Linux&#xff09;创建 Token Arduino IDEVSCode 配置安装 wokwi 插件打开编译后目录 ESP32C3 示例Arduino IDE创建模拟器运行模拟器 Wokwi 简介 Wokwi 是一款在线电子模拟器。您可以使用它来模拟 Arduino、ESP32、STM32 以及许…

【azure-openai】批量翻译demo【python】【gradio】

要求&#xff1a;拥有azure-openai-api&#xff0c;上传文件为csv格式&#xff0c;utf-8编码。 注意&#xff1a;如果出现乱码&#xff0c;重新运行&#xff0c;换种方式打开&#xff0c;有时候wps会自动改编码。 实现功能&#xff1a;选择语言&#xff0c;使用gpt4omini&…

IOS-IPA签名工具 request_post 任意文件读取复现

0x01 产品描述&#xff1a; 苹果手机中的IPA是指iOS应用程序&#xff08;iPhone应用程序&#xff09;的安装包文件&#xff0c;其文件扩展名为.ipa。IPA文件是经过编译的、已签名的应用程序文件&#xff0c;可以在iOS设备上安装和运行。通常&#xff0c;开发者通过Xcode等开发工…

OpenHarmony(鸿蒙南向)——平台驱动指南【I2C】

往期知识点记录&#xff1a; 鸿蒙&#xff08;HarmonyOS&#xff09;应用层开发&#xff08;北向&#xff09;知识点汇总 鸿蒙&#xff08;OpenHarmony&#xff09;南向开发保姆级知识点汇总~ 持续更新中…… 概述 功能简介 I2C&#xff08;Inter Integrated Circuit&#x…

【HarmonyOS】分页滚动文本组件

【HarmonyOS】实现分页滚动文本组件&#xff1a;为何选择 Scroll Text 而非 textOverflow import { promptAction } from kit.ArkUIEntry Component struct Page37 {State lineHeight: number 0 // 单行文本的高度State pageHeight: number 0 // 每页的最大高度State totalC…

相机、镜头参数详解以及相关计算公式

一、工业相机参数 1、分辨率 相机每次采集图像的像素点数&#xff0c;也是指这个相机总共有多少个感光晶片。在采集图像时&#xff0c;相机的分辨率对检测精度有很大的影响&#xff0c;在对同样打的视场成像时&#xff0c;分辨率越高&#xff0c;对细节的展示越明显。 相机像素…

【高频SQL基础50题】1-5

目录 1.可回收且低脂的产品 2. 使用唯一标识码替换员工ID 3.有趣的电影 4.每位教师所教授的科目种类的数量 5.每位经理的下属员工数量 1.可回收且低脂的产品 查询题。 # Write your MySQL query statement below SELECT product_id FROM Products WHERE low_fats"…

基于微信的原创音乐小程序的设计与实现+ssm论文源码调试讲解

第二章 开发工具及关键技术介绍 2.1 JAVA技术 Java主要采用CORBA技术和安全模型&#xff0c;可以在互联网应用的数据保护。它还提供了对EJB&#xff08;Enterrise JavaBeans&#xff09;的全面支持&#xff0c;java servlet AI&#xff0c;JS&#xff08;java server ages&…

基于单片机的宠物喂食(ESP8266、红外、电机)

目录 一、主要功能 二、硬件资源 三、程序编程 四、实现现象 一、主要功能 基于STC89C52单片机&#xff0c;采用L298N驱动连接P2.3和P2.4口进行电机驱动&#xff0c; 然后串口连接P3.0和P3.1模拟ESP8266&#xff0c; 红外传感器连接ADC0832数模转换器连接单片机的P1.0~P1.…

网络基础概念和 socket 编程

网络基础概念和 socket 编程 学习目标&#xff1a; 了解 OSI 七层模型、TCP/IP 四层模型结构了解常见的网络协议格式掌握网络字节序和主机字节序之间的转换理解 TCP 服务器端通信流程理解 TCP 客户端通信流程实现 TCP 服务器端和客户端的代码 推荐一个非常好的学习资料仓库 协…

RPA自动化流程机器人有哪些优势?

在数字化快速推进的大背景下&#xff0c;人工智能正在以前所未有的速度改变着生活和生产方式&#xff0c;而RPA自动化流程机器人作为其中一种最重要的革命性的技术&#xff0c;已经成为企业数字化中不可或缺的重要力量&#xff0c;让员工加速从“重复性工作”中摆脱出来。 金智…

OceanBase技术解析: 执行器中的自适应技术

在《OceanBase 数据库源码解析》这本书中&#xff0c;对于执行器的探讨还不够深入&#xff0c;它更多地聚焦于执行器的并行处理机制。因此&#xff0c;通过本文与大家分享OceanBase执行器中几种典型的自适应技术&#xff0c;作为对书中执行器部分的一个补充。 提升数据库分析性…

[Redis][主从复制][下]详细讲解

目录 1.复制1.全量复制2.1部分复制2.2复制积压缓冲区3.实时复制 2.总结 1.复制 1.全量复制 什么时候进行全量复制&#xff1f; 首次和主节点进行数据同步主节点不方便进行部分复制的时候 全量复制流程&#xff1a; 从节点发送psync命令给主节点进⾏数据同步&#xff0c;由于是…

①EtherCAT转Modbus485RTU网关多路同步高速采集无需编程串口服务器

EtherCAT转Modbus485RTU网关多路同步高速采集无需编程串口服务器https://item.taobao.com/item.htm?ftt&id798036415719 EtherCAT 串口网关 EtherCAT 转 RS485 型号&#xff1a; 1路总线EC网关 MS-A2-1011 2路总线EC网关 MS-A2-1021 4路总线EC网关 MS-A2-1041 技…

arcgis for js实现阴影立体效果

效果 实现 主要通过effect属性实现 代码: (这里以GeoJSON图层为例, 代码复制即可用) <!DOCTYPE html> <html lang"zn"><head><meta charset"UTF-8" /><meta http-equiv"X-UA-Compatible" content"IEedge&quo…

PHP 于小项目:从鉴权说起

PHP 于小项目&#xff1a;从鉴权说起 在当今这个开发技术多样化的时代&#xff0c;选择合适的开发语言和框架常常决定了项目的效率与成败。对于个人开发者&#xff0c;特别是那些进行小型、短期项目的人来说&#xff0c;PHP 是一种特别友好的选择。本文将通过介绍 PHP 实现鉴权…