【学习笔记】手写 Tomcat 六

news/2024/10/18 6:26:54/

目录

一、线程池

1. 构建线程池的类

2. 创建任务

3. 执行任务

测试

二、URL编码

解决方案

测试

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

解决方案

测试

四、作业

1. 了解工厂模式

2. 了解反射技术


一、线程池

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

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

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

1. 构建线程池的类

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

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

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

java">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 

java">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 方法

java">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"开头,后面跟着若干位十六进制数字的字符串。

解决方案

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

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

在 MyTask 类中添加

测试

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

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

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

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

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

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

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

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

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

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

解决方案

java">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 对象


http://www.ppmy.cn/news/1532831.html

相关文章

学MybatisPlus

1.设置MySql的数据库 spring:datasource:url: jdbc:mysql://127.0.0.1:3306/mp?useUnicodetrue&characterEncodingUTF-8&autoReconnecttrue&serverTimezoneAsia/Shanghaidriver-class-name: com.mysql.cj.jdbc.Driverusername: rootpassword: MySQL123 logging:l…

如何使用Optuna在PyTorch中进行超参数优化

所有神经网络在训练过程中都需要选择超参数,而这些超参数对收敛速度和最终性能有着非常显著的影响。 这些超参数需要特别调整,以充分发挥模型的潜力。超参数调优过程是神经网络训练中不可或缺的一部分,某种程度上,它是一个主要基于梯度优化问题中的“无梯度”部分。 在这…

H.264编解码介绍

一、简介 H.264,又称为AVC(Advanced Video Coding),是一种广泛使用的视频压缩标准。它由国际电信联盟(ITU)和国际标准化组织(ISO)联合开发,并于2003年发布。 H.264的发展历史可以追溯到上个世纪90年代。当时,视频压缩技术的主要标准是MPEG-2,但它在压缩率和视频质…

Spring Boot房屋租赁平台:现代化解决方案

1 绪论 1.1 研究背景 中国的科技的不断进步&#xff0c;计算机发展也慢慢的越来越成熟&#xff0c;人们对计算机也是越来越更加的依赖&#xff0c;科研、教育慢慢用于计算机进行管理。从第一台计算机的产生&#xff0c;到现在计算机已经发展到我们无法想象。给我们的生活改变很…

学Java还是c++好?

Java在互联网行业中的应用非常广泛&#xff0c;就业方面&#xff0c;Java岗位较多&#xff0c;就业市场相对稳定&#xff0c;尤其是在Android应用开发、企业级应用、大数据技术等领域有大量的岗位。 Java的语法相对简单&#xff0c;对于初学者来说更容易上手。有很多成熟的框架…

git直接推送项目到仓库

要将您的本地项目推送到新创建的 GitHub 仓库&#xff0c;请按照以下步骤操作&#xff1a; 定位到项目目录&#xff1a; 确保你的命令行或终端当前目录为你的项目根目录。 初始化 Git 仓库&#xff08;如果尚未初始化&#xff09;&#xff1a; git init添加文件到暂存区&#…

獨立IP和共用IP有什麼區別?

什麼是獨立IP&#xff1f; 獨立IP指的是一個IP地址專屬於一個用戶或設備。無論是網站、伺服器還是其他線上服務&#xff0c;獨立IP都意味著該IP地址不會與其他用戶或設備共用。獨立IP通常用於需要高安全性和穩定性的場景&#xff0c;比如企業網站、電子商務平臺和需要SSL證書的…

【计算机网络】数据链路层理解

文章目录 一、引言二、MAC地址三、以太网四、交换机五、ARP与RARP1、ARP2、RARP 一、引言 数据链路&#xff0c;有时也指以太网、无线局域网等通信手段。数据链路层的协议定义了通过通信媒介互联的设备之间传输的规定。通信媒介包括双绞线电缆、同轴电缆、光纤、点播以及红外线…