【学习笔记】手写 Tomcat 六
目录
一、线程池
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() {@Overridepublic 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;}@Overridepublic 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;}@Overridepublic 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 对象
相关文章:

【学习笔记】手写 Tomcat 六
目录 一、线程池 1. 构建线程池的类 2. 创建任务 3. 执行任务 测试 二、URL编码 解决方案 测试 三、如何接收客户端发送的全部信息 解决方案 测试 四、作业 1. 了解工厂模式 2. 了解反射技术 一、线程池 昨天使用了数据库连接池,我们了解了连接池的优…...

打靶记录18——narak
靶机: https://download.vulnhub.com/ha/narak.ova 推荐使用 VM Ware 打开靶机 难度:中 目标:取得 root 权限 2 Flag 攻击方法: 主机发现端口扫描信息收集密码字典定制爆破密码Webdav 漏洞PUT 方法上传BF 语言解码MOTD 注入CVE-2021-3…...

LabVIEW编程能力如何能突飞猛进
要想让LabVIEW编程能力实现突飞猛进,需要采取系统化的学习方法,并结合实际项目进行不断的实践。以下是一些提高LabVIEW编程能力的关键策略: 1. 扎实掌握基础 LabVIEW的编程本质与其他编程语言不同,它是基于图形化的编程方式&…...
代码随想录算法训练营第四四天| 1143.最长公共子序列 1035.不相交的线 53. 最大子序和 392.判断子序列
今日任务 1143.最长公共子序列 1035.不相交的线 53. 最大子序和 392.判断子序列 1143.最长公共子序列 题目链接: . - 力扣(LeetCode) class Solution {public int longestCommonSubsequence(String text1, String text2) {int[][] dp ne…...

2024.9.26 作业 +思维导图
一、作业 1、什么是虚函数?什么是纯虚函数 虚函数:函数前加关键字virtual,就定义为虚函数,虚函数能够被子类中相同函数名的函数重写 纯虚函数:把虚函数的函数体去掉然后加0;就能定义出一个纯虚函数。 2、基…...

WSL进阶体验:gnome-terminal启动指南与中文显示问题一网打尽
起因 我们都知道 wsl 启动后就死一个纯命令行终端,一直以来我都是使用纯命令行工具管理Linux的。今天看到网上有人在 wsl 中启动带图形界面的软件。没错,就是在wsl中启动带有图形界面的Linux软件。比如下面这个编辑器。 出于好奇,我就…...
recoil和redux之间的选择
Recoil 和 Redux 是两个流行的 JavaScript 状态管理库,它们各自有不同的设计理念和使用场景。选择哪一个更好用,取决于你的具体需求、项目规模和个人偏好。 1. 设计理念 Redux 单向数据流:Redux 采用单向数据流模型,所有的状态变…...

无人机的作战指挥中心-地面站!
无人机与地面站的关系 指挥与控制:地面站是无人机系统的核心控制部分,负责对无人机进行远程指挥和控制。无人机根据地面站下达的任务自主完成飞行任务,并实时向地面站反馈飞行状态和任务执行情况。 任务规划与执行:地面站具备任…...
Vue 23进阶面试题:(第八天)
目录 29.vue2.0和vue3.0区别? 30.事件中心的原理 31.使用基于token的登录流程 32.防抖和节流 防抖(debounce) 节流(throttle) 29.vue2.0和vue3.0区别? 1.由选项API转变为组合API。 2.vue3将全局配置…...

Acwing 最小生成树
最小生成树 最小生成树:由n个节点,和n-1条边构成的无向图被称为G的一棵生成树,在G的所有生成树中,边的权值之和最小的生成树,被称为G的最小生成树。(换句话说就是用最小的代价把n个点都连起来) Prim 算法…...
VIM简要介绍
安装 大多数 Linux 发行版和 macOS 都预装了 VIM。如果没有,你可以通过包管理器安装: Ubuntu/Debian: sudo apt-get install vimFedora: sudo dnf install vimmacOS: brew install vim(使用 Homebrew)Windows: 可以从 VIM 官网下…...

.NET 6.0 使用log4net配置日志记录方法
1.包管理器引入相关包 2.添加Log4net文件夹和log4net.config配置文件(配置文件属性设为始终复制)。 3.替换 log4net.config的内容(3.1与3.2选择一个就好,只是创建日志文件有所区别) 3.1: <?xml version"1.0" encoding"utf-8"?> <configuration…...
Unity角色控制及Animator动画切换如走跑跳攻击
Unity角色控制及 Animator动画切换如走跑跳攻击 目录 Unity角色控制及 一、 概念 1、角色控制 1) CharacterController(角色控制器) 2) CapsuleCollider + Rigidbody(使用物理刚体控制) 2、角色动画-Animation、Animator 1) 旧版动画系统...

JSP+Servlet+Mybatis实现列表显示和批量删除等功能
前言 使用JSP回显用户列表,可以进行批量删除(有删除确认步骤),和修改用户数据(用户数据回显步骤)使用servlet处理传递进来的请求参数,并调用dao处理数据并返回使用mybatis,书写dao层…...

Cannot read properties of undefined (reading ‘upgrade‘)
前端开发工具:VSCODE 报错信息: INFO Starting development server...10% building 2/2 modules 0 active ERROR TypeError: Cannot read properties of undefined (reading upgrade)TypeError: Cannot read properties of undefined (reading upgrade…...

javaJUC基础
JUC基础知识 多线程 管程 Monitor,也就是平时所说的锁。Monitor其实是一种同步机制,它的义务是保证(同一时间)只有一个线程可以访问被保护的数据和代码块,JVM中同步是基于进入和退出监视器(Monitor管程对…...
std::distance 函数介绍
std::distance 是 C 标准库中的一个函数模板,用于计算两个迭代器之间的距离。它的主要作用是返回从第一个迭代器到第二个迭代器之间的元素数量。这个函数对于不同类型的迭代器(如随机访问、双向、前向等)都能有效工作。 函数原型 template …...

如何在Windows和Linux之间实现粘贴复制
第一步 sudo apt-get autorremove open-vm-tools第二步 sudo apt-get update第三步 sudo apt-get install open-vm-tools-desktop第四步 一直按Y,希望执行 Y第四步 重启 reboot然后可以实现粘贴复制。...

【第十七章:Sentosa_DSML社区版-机器学习之异常检测】
【第十七章:Sentosa_DSML社区版-机器学习之异常检测】 机器学习异常检测是检测数据集中的异常数据的算子,一种高效的异常检测算法。它和随机森林类似,但每次选择划分属性和划分点(值)时都是随机的,而不是根…...
【Vue】为什么 Vue 不使用 React 的分片更新?
第一,首先时间分片是为了解决 CPU 进行大量计算的问题,因为 React 本身架构的问题,在默认的情况下更新会进行很多的计算,就算使用 React 提供的性能优化 API,进行设置,也会因为开发者本身的问题,…...

高等数学(下)题型笔记(八)空间解析几何与向量代数
目录 0 前言 1 向量的点乘 1.1 基本公式 1.2 例题 2 向量的叉乘 2.1 基础知识 2.2 例题 3 空间平面方程 3.1 基础知识 3.2 例题 4 空间直线方程 4.1 基础知识 4.2 例题 5 旋转曲面及其方程 5.1 基础知识 5.2 例题 6 空间曲面的法线与切平面 6.1 基础知识 6.2…...
WEB3全栈开发——面试专业技能点P2智能合约开发(Solidity)
一、Solidity合约开发 下面是 Solidity 合约开发 的概念、代码示例及讲解,适合用作学习或写简历项目背景说明。 🧠 一、概念简介:Solidity 合约开发 Solidity 是一种专门为 以太坊(Ethereum)平台编写智能合约的高级编…...

ardupilot 开发环境eclipse 中import 缺少C++
目录 文章目录 目录摘要1.修复过程摘要 本节主要解决ardupilot 开发环境eclipse 中import 缺少C++,无法导入ardupilot代码,会引起查看不方便的问题。如下图所示 1.修复过程 0.安装ubuntu 软件中自带的eclipse 1.打开eclipse—Help—install new software 2.在 Work with中…...
.Net Framework 4/C# 关键字(非常用,持续更新...)
一、is 关键字 is 关键字用于检查对象是否于给定类型兼容,如果兼容将返回 true,如果不兼容则返回 false,在进行类型转换前,可以先使用 is 关键字判断对象是否与指定类型兼容,如果兼容才进行转换,这样的转换是安全的。 例如有:首先创建一个字符串对象,然后将字符串对象隐…...

ABAP设计模式之---“简单设计原则(Simple Design)”
“Simple Design”(简单设计)是软件开发中的一个重要理念,倡导以最简单的方式实现软件功能,以确保代码清晰易懂、易维护,并在项目需求变化时能够快速适应。 其核心目标是避免复杂和过度设计,遵循“让事情保…...

短视频矩阵系统文案创作功能开发实践,定制化开发
在短视频行业迅猛发展的当下,企业和个人创作者为了扩大影响力、提升传播效果,纷纷采用短视频矩阵运营策略,同时管理多个平台、多个账号的内容发布。然而,频繁的文案创作需求让运营者疲于应对,如何高效产出高质量文案成…...

【VLNs篇】07:NavRL—在动态环境中学习安全飞行
项目内容论文标题NavRL: 在动态环境中学习安全飞行 (NavRL: Learning Safe Flight in Dynamic Environments)核心问题解决无人机在包含静态和动态障碍物的复杂环境中进行安全、高效自主导航的挑战,克服传统方法和现有强化学习方法的局限性。核心算法基于近端策略优化…...
腾讯云V3签名
想要接入腾讯云的Api,必然先按其文档计算出所要求的签名。 之前也调用过腾讯云的接口,但总是卡在签名这一步,最后放弃选择SDK,这次终于自己代码实现。 可能腾讯云翻新了接口文档,现在阅读起来,清晰了很多&…...
pycharm 设置环境出错
pycharm 设置环境出错 pycharm 新建项目,设置虚拟环境,出错 pycharm 出错 Cannot open Local Failed to start [powershell.exe, -NoExit, -ExecutionPolicy, Bypass, -File, C:\Program Files\JetBrains\PyCharm 2024.1.3\plugins\terminal\shell-int…...
vue3 daterange正则踩坑
<el-form-item label"空置时间" prop"vacantTime"> <el-date-picker v-model"form.vacantTime" type"daterange" start-placeholder"开始日期" end-placeholder"结束日期" clearable :editable"fal…...