1. 前言

先看一个小案例, 引出对 Tomcat 底层实现思考

1.1 完成小案例

● 快速给小伙伴完成这个小案例 0. 我们准备使用 Maven 来创建一个 WEB 项目, 老师先简单给小伙伴介绍一下 Maven 是什么, 更加详细的使用,我们还会细讲, 现在先使用一把

先创建一个 Maven 的 Web 项目 hsp-tomcat

补充:如何配置阿里 maven 镜像 (1) 把 maven的安装目录\conf\settings.xml 拷贝默认的 maven 配置目录 (2) C:\Users\Administrator.m2 目录 settings.xml (3) 修改 C:\Users\Administrator.m2\settings.xml , 增加如下的部分

alimaven

aliyun maven

http://maven.aliyun.com/nexus/content/groups/public/

central

(4) 到此 ok

修改 pom.xml

4.0.0

UTF-8

1.8

1.8

junit

junit

4.11

test

javax.servlet

javax.servlet-api

3.1.0

provided

创建 cal.html

计算器

计算器

num1:

num2:

创建 java 目录,存放 java 源文件创建 CalServlet.java 修改 web.xml , 配置 Servlet

"-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"

"http://java.sun.com/dtd/web-app_2_3.dtd" >

Archetype Created Web Application

CalServlet

com.hspedu.servlet.CalServlet

CalServlet

/calServlet

修改 CalServlet.java, 完成计算任务

public class CalServlet extends HttpServlet {

protected void doPost(HttpServletRequest request,

HttpServletResponse response) throws ServletException, IOException {

//接收提交的数据进行计算

//复制当前行 ctrl+alt+下光标

String strNum1 = request.getParameter("num1");

String strNum2 = request.getParameter("num2");

//把strNum1 和 strNum2 转成 int

int num1 = WebUtils.parseInt(strNum1, 0);

int num2 = WebUtils.parseInt(strNum2, 0);

int result = num1 + num2;

response.setContentType("text/html;charset=utf-8");

PrintWriter writer = response.getWriter();

writer.print("

" + num1 + " + " + num2 + " = " + result + "

");

writer.flush();

writer.close();

}

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

doPost(request, response);

}

}

创 建 工 具 类 WebUtils.java

public class WebUtils {

/**

* 将一个字符串数字,转成 int, 如果转换失败,就返回传入 defaultVal

* @param strNum

* @param defaultVal

* @return

*/

public static int parseInt(String strNum, int defaultVal) {

try {

return Integer.parseInt(strNum);

} catch (NumberFormatException e) {

System.out.println(strNum + " 格式不对,转换失败");

}

return defaultVal;

}

}

配置 tomcat 略。。。启动 tomcat浏览器访问: http://localhost:8080/cal.html 完成测试

maven 中央仓库 : https://mvnrepository.com/

1.2 思考

问题: Tomcat 底层实现 和 调用到 Servlet 流程?

我们的目标: 不用 Tomcat, 不用系统提供的 Servlet, 模拟 Tomcat 底层实现并能调用我们自己设计的 Servle, 也能完成相同的功能

1.3 Tomcat 整体架构分析

1.3.1 一图胜千言

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

2. 手动实现 Tomcat 底层机制+ 自己设计 Servlet

2.1 实现任务阶段 1

编写自己 Tomcat, 能给浏览器返回 Hi, Hspedu

基于 socket 开发服务端-流程:

2.1.1 需求分析/图解

需求分析如图, 浏览器请求 http://localhost:8080/??, 服务端返回 hi , hspedu

2.1.2 分析+代码实现

● 分析示意图 ● 代码实现

创 建 TomcatV1.java

/**

* 这是第一个版本的tomcat ,可以完成,接收浏览器的请求,并返回信息

*/

public class HspTomcatV1 {

public static void main(String[] args) throws IOException {

//1. 创建ServerSocket, 在 8080端口监听

ServerSocket serverSocket = new ServerSocket(8080);

System.out.println("=======mytomcat在8080端口监听======");

while (!serverSocket.isClosed()) {

//等待浏览器/客户端的连接

//如果有连接来,就创建一个socket

//这个socket就是服务端和浏览器端的连接/通道

Socket socket = serverSocket.accept();

//先接收浏览器发送的数据

//inputStream 是字节流=> BufferedReader(字符流)

//java基础 IO , 第19章

InputStream inputStream = socket.getInputStream();

BufferedReader bufferedReader =

new BufferedReader(new InputStreamReader(inputStream, "utf-8"));

String mes = null;

System.out.println("=======接收到浏览器发送的数据=======");

//循环的读取

while ((mes = bufferedReader.readLine()) != null) {

//判断mes的长度是否为0:判断字符串长度是否为0的作用是为了处理HTTP请求头和请求体之间的空行。

if (mes.length() == 0) {

break;//退出while

}

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 + "hi, hspedu 韩顺平教育";

System.out.println("========我们的tomcat 给浏览器会送的数据======");

System.out.println(resp);

outputStream.write(resp.getBytes());//将resp字符串以byte[] 方式返回

outputStream.flush();

outputStream.close();

inputStream.close();

socket.close();

}

}

}

细节说明: 1.判断字符串长度是否为0的作用是为了处理HTTP请求头和请求体之间的空行。 在HTTP请求中,请求头和请求体之间必须要有一个空行,表示请求头已经结束,请求体开始。因此,在读取HTTP请求时,需要先读取请求头并解析出请求信息,然后再读取请求体部分。而读取请求头时,每个请求头字段都是以冒号和空格分隔的键值对形式出现的,如果读取到的一行请求头字段的长度为0,就表明请求头已经结束了。此时就可以退出循环,继续处理请求体的内容了。 因此,为了在读取HTTP请求时正确地处理请求头和请求体之间的空行,需要添加判断字符串长度是否为0的逻辑。   2.在HTTP协议中,每行信息都以"\r\n"(回车+换行)作为结束符,表示该行信息结束,并让浏览器接收下一个信息。 所以在构造HTTP响应头部时,需要在每行信息后添加"\r\n"。而在Java中,“\n"表示换行,即使只写”\n"在大多数情况下也能正常工作,但是使用"\r\n"会更加规范和严谨,因为在Windows系统中,只用"\n"可能不会被认为是换行符,而使用"\r\n"则能保证适用于所有操作系统。 因此,这段代码中,在回复客户端的HTTP响应头中使用了"\r\n"作为结束符号,确保HTTP响应头的格式符合HTTP协议的规范。

测试 浏览器 : http://localhost:8080/

2.1.3 问题分析

没有使用 BIO 线程模型,没有实现多线程,性能差

2.2 实现任务阶段 2

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

2.2.1 BIO 线程模型介绍

补充: 1.BIO是阻塞式I/O(Blocking I/O)的缩写,是Java网络编程中常用的一种I/O模型。它指的是在进行网络数据读写时,当用户线程发起了一个I/O操作(如读取数据),该线程会一直等待直到操作完成并返回结果,期间会一直阻塞在那里,无法做其他事情。与之相对的是非阻塞式I/O(NIO)和异步I/O(AIO)两种模型。 BIO模型是传统的线程池模型,即一个请求对应一个线程。在BIO模型中,一个客户端请求到来时,服务器会开启一个新的线程去处理这个请求,直到整个请求处理完毕才会退出线程。这种模型实现简单,易于理解,但同时也会带来一些问题,例如线程池大小的限制、线程上下文切换等问题。当线程池达到最大线程数时,新的请求就会被拒绝或者阻塞,降低系统的吞吐量和性能。 因此,在高并发、大规模的网络编程场景中,BIO不再适用,而需要使用NIO和AIO等更加高效的I/O模型。   2.NIO是Java NIO(New I/O)的缩写,指的是Java提供的一种高效的I/O处理方式。与传统的I/O操作(如BIO)不同,NIO是基于事件驱动的模型,可以在单线程中处理多个并发请求,提升了性能和可扩展性。 通过使用NIO库,Java应用程序可以实现非阻塞式I/O操作,包括文件操作、套接字操作等。它的主要优势在于使用少量线程就能处理大量并发连接,因此适用于高负载、高可靠性的网络应用程序。

2.2.2 需求分析/图解

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

2.2.3 分析+代码实现

● 分析示意图 ● 代码实现

HspTomcatV2.java

public class HspTomcatV2 {

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线程基础

HspRequestHandler hspRequestHandler =

new HspRequestHandler(socket);

new Thread(hspRequestHandler).start();

}

}

}

HspRequestHandler.java

/**

* 解读

* 1. HspRequestHandler 对象是一个线程对象

* 2. 处理一个http请求的

*/

public class HspRequestHandler implements Runnable {

//定义Socket

private Socket socket = null;

public HspRequestHandler(Socket socket) {

this.socket = socket;

}

@Override

public void run() {

//这里我们可以对客户端/浏览器进行IO编程/交互

try {

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;

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

hello world!

";

System.out.println("========hsptomcatv2返回的数据是=========");

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();

}

}

}

}

}

测试: 浏览器 :http://localhost:8080/

2.2.4 问题分析

HspTomcat 只是简单返回结果,没有和 Servlet、web.xml 关联

2.3 实现任务阶段 3

处理 Servlet

2.3.1 Servlet 生命周期-回顾

2.3.2 需求分析/图解

● 需求分析如图, 浏览器请求 http://localhost:8080/hspCalServlet, 提交数据,完成计算任务,如果 servlet 不存在,返回 404

2.3.3 分析+代码实现

● 分析示意图 架构图:

2.3.3.1 优化1阶段

1.搭建结构 2.封装HttpServletRequest

1.模拟servlet HspServlet.java

/**

* 先搭建结构,后面在写内容

*/

public interface HspServlet {

}

HspHttpServlet.java

public abstract class HspHttpServlet implements HspServlet {

}

HspCalServlet.java

public class HspCalServlet extends HspHttpServlet {

}

2.模拟HttpServletRequest和HttpServletResponse HspRequest.java

/**

* 解读

* 1. HspRequest 作用是封装http请求的数据

* get /hspCalServlet?num1=10&num2=30

* 2. 比如 method(get) 、 uri(/hspCalServlet) 、 还有参数列表 (num1=10&num2=30)

* 3. HspRequest 作用就等价原生的servlet 中的HttpServletRequest

* 4. 这里考虑的是GET请求

*/

public class HspRequest {

private String method;

private String uri;

//存放参数列表 参数名-参数值 => HashMap

private HashMap parametersMapping =

new HashMap<>();

private InputStream inputStream = null;

//构造器=> 对http请求进行封装 => 可以将老师写的代码封装成方法

//inputStream 是和 对应http请求的socket关联

public HspRequest(InputStream inputStream) {

this.inputStream = inputStream;

//完成对http请求数据的封装..

encapHttpRequest();

}

/**

* 将http请求的相关数据,进行封装,然后提供相关的方法,进行获取

*/

private void encapHttpRequest() {

System.out.println("HspRequest 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 +

'}';

}

}

细节补充: 在Java中,Socket和流(Stream)通常是一起使用的,用于进行网络数据传输。如果关闭与Socket相关联的流会导致Socket也关闭的问题,取决于关闭流的方式。 如果使用Socket对象的close()方法关闭与其关联的输入流或输出流,则会同时关闭该Socket,因为这些流是通过Socket来创建的。例如:

Socket socket = new Socket("example.com", 80);

OutputStream os = socket.getOutputStream();

os.close(); // 关闭输出流,同时也会关闭socket

HspResponse

/**

* 老师解读

* 1. HspResponse对象可以封装OutputStream(是socket关联)

* 2. 即可以通过 HspResponse对象 返回Http响应给浏览器/客户端

* 3. HspResponse对象 的作用等价于原生的servlet的 HttpServletResponse

*/

public class HspResponse {

}

3.改进HspRequestHandler

/**

* 解读

* 1. HspRequestHandler 对象是一个线程对象

* 2. 处理一个http请求的

*/

public class HspRequestHandler implements Runnable {

//定义Socket

private Socket socket = null;

public HspRequestHandler(Socket socket) {

this.socket = socket;

}

@Override

public void run() {

//这里我们可以对客户端/浏览器进行IO编程/交互

try {

//这里我们先死后活

HspRequest hspRequest = new HspRequest(socket.getInputStream());

String num1 = hspRequest.getParameter("num1");

String num2 = hspRequest.getParameter("num2");

System.out.println("请求的参数num1= " + num1);

System.out.println("请求的参数num2= " + num2);

System.out.println("hspRequest= " + hspRequest);

// 构建一下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 + "

hello world!

";

System.out.println("========hsptomcatv2返回的数据是=========");

System.out.println(resp);

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();

}

}

}

}

}

2.3.3.2 优化2阶段

1.完善HspResponse 2.构建Servlet模型 3.创建Util工具类 4.优化HspRequestHandler处理器

补充:HspResponse.java

/**

* 老师解读

* 1. HspResponse对象可以封装OutputStream(是socket关联)

* 2. 即可以通过 HspResponse对象 返回Http响应给浏览器/客户端

* 3. HspResponse对象 的作用等价于原生的servlet的 HttpServletResponse

*/

public class HspResponse {

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

//在创建 HspResponse 对象时,传入的outputStream是和Socket关联的

public HspResponse(OutputStream outputStream) {

this.outputStream = outputStream;

}

//当我们需要给浏览器返回数据时,可以通过HspResponse 的输出流完成

//

public OutputStream getOutputStream() {

return outputStream;

}

}

完善模拟的Servlet 1.补充Servlet的生命周期

/**

* 先搭建结构,后面在写内容

* 老韩只保留了 三个核心方法声明

*/

public interface HspServlet {

void init() throws Exception;

void service(HspRequest request, HspResponse response) throws IOException;

void destroy();

}

2.完善HttpServlet

public abstract class HspHttpServlet implements HspServlet {

@Override

public void service(HspRequest request, HspResponse 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(HspRequest request, HspResponse response);

public abstract void doPost(HspRequest request, HspResponse response);

}

细节补充: 在这段代码中,this关键字和省略this关键字是没有区别的。因为在Java中,当局部变量和实例变量同名时,编译器会优先使用局部变量,如果需要访问实例变量,可以使用this关键字来代表当前对象。   因此,使用this.doGet()和直接调用doGet()方法实际上是等价的,不会影响程序的执行结果。只要doGet()方法已经定义在了当前类中,都可以直接调用。   不过,在实际开发中,推荐使用this关键字来调用当前对象的方法或访问成员变量,可以提高代码的可读性和可维护性。另外,使用this关键字还有助于避免和局部变量或参数命名冲突的情况。   因此,虽然this.doGet()和doGet()在语义上是一致的,但是在实际开发中,建议使用this.doGet()的方式来调用对象的方法。

3.实现HspCalServlet

public class HspCalServlet extends HspHttpServlet {

@Override

public void doGet(HspRequest request, HspResponse response) {

//java基础的 OOP 的动态绑定机制..

//写业务代码,完成计算任务

int num1 = WebUtils.parseInt(request.getParameter("num1"), 0);

int num2 = WebUtils.parseInt(request.getParameter("num2"), 0);

int sum = num1 + num2;

//返回计算结果给浏览器

//outputStream 和 当前的socket关联

OutputStream outputStream = response.getOutputStream();

String respMes = HspResponse.respHeader

+ "

" + num1 + " + " + num2 + " = " + sum + " HspTomcatV3 - 反射+xml创建

";

try {

outputStream.write(respMes.getBytes());

outputStream.flush();

outputStream.close();

} catch (IOException e) {

e.printStackTrace();

}

}

@Override

public void doPost(HspRequest request, HspResponse response) {

this.doGet(request, response);

}

@Override

public void init() throws Exception {

}

@Override

public void destroy() {

}

}

WebUtils.java

public class WebUtils {

//将字符串转成数字方法

public static int parseInt(String strNum, int defaultVal) {

try {

return Integer.parseInt(strNum);

} catch (NumberFormatException e) {

System.out.println(strNum + " 不能转成数字");

}

return defaultVal;

}

}

改进优化HspRequestHandler处理器

/**

* 老师解读

* 1. HspRequestHandler 对象是一个线程对象

* 2. 处理一个http请求的

*/

public class HspRequestHandler implements Runnable {

//定义Socket

private Socket socket = null;

public HspRequestHandler(Socket socket) {

this.socket = socket;

}

@Override

public void run() {

//这里我们可以对客户端/浏览器进行IO编程/交互

try {

//这里我们先死后活

HspRequest hspRequest = new HspRequest(socket.getInputStream());

//这里我们可以同HspResponse对象,返回数据给浏览器/客户端

HspResponse hspResponse = new HspResponse(socket.getOutputStream());

//创建HspCalServlet对象-> 一会我们再用反射来构建对象

HspCalServlet hspCalServlet = new HspCalServlet();

hspCalServlet.doGet(hspRequest, hspResponse);

socket.close();

} catch (IOException e) {

e.printStackTrace();

} finally {

//最后一定确保socket要关闭

if (socket != null) {

try {

socket.close();

} catch (IOException e) {

e.printStackTrace();

}

}

}

}

}

2.3.3.3 优化3阶段

1.构造容器 2.配置web.xml 3.创建容器(存放servlet和servlet-mapping) 4.利用dom4j技术获取xml文件,反射创建对象

思路分析 拷贝web.xml 引入dom4j的依赖

dom4j

dom4j

1.1

web.xml

"-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"

"http://java.sun.com/dtd/web-app_2_3.dtd" >

Archetype Created Web Application

HspCalServlet

com.hspedu.tomcat.servlet.HspCalServlet

HspCalServlet

/hspCalServlet

HspTomcatV3.java

/**

* 第3版的Tomcat, 实现通过xml+反射来初始化容器

*/

public class HspTomcatV3 {

//1. 存放容器 servletMapping

// -ConcurrentHashMap

// -HashMap

// key - value

// ServletName 对应的实例

public static final ConcurrentHashMap

servletMapping = new ConcurrentHashMap<>();

//2容器 servletUrlMapping

// -ConcurrentHashMap

// -HashMap

// key - value

// url-pattern ServletName

public static final ConcurrentHashMap

servletUrlMapping = new ConcurrentHashMap<>();

//变强..

public static void main(String[] args) {

init();

}

//直接对两个容器进行初始化

public static void init() {

//读取web.xml => dom4j =>

//得到web.xml文件的路径 => 拷贝一份.

String path = HspTomcatV3.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 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(),

(HspHttpServlet) 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);

}

}

2.3.3.4 优化4阶段

1.完善自定义Tomcat 2.修改优化代码 3.完善测试web.xml

改进HspTomcatV3

/**

* 第3版的Tomcat, 实现通过xml+反射来初始化容器

*/

public class HspTomcatV3 {

//1. 存放容器 servletMapping

// -ConcurrentHashMap

// -HashMap

// key - value

// ServletName 对应的实例

public static final ConcurrentHashMap

servletMapping = new ConcurrentHashMap<>();

//2容器 servletUrlMapping

// -ConcurrentHashMap

// -HashMap

// key - value

// url-pattern ServletName

public static final ConcurrentHashMap

servletUrlMapping = new ConcurrentHashMap<>();

//变强..

public static void main(String[] args) {

HspTomcatV3 hspTomcatV3 = new HspTomcatV3();

hspTomcatV3.init();

//启动hsptomcat容器

hspTomcatV3.run();

}

//启动HspTomcatV3容器

public void run() {

try {

ServerSocket serverSocket = new ServerSocket(8080);

System.out.println("=====hsptomcatv3在8080监听======");

while (!serverSocket.isClosed()) {

Socket socket = serverSocket.accept();

HspRequestHandler hspRequestHandler =

new HspRequestHandler(socket);

new Thread(hspRequestHandler).start();

}

} catch (IOException e) {

e.printStackTrace();

}

}

//直接对两个容器进行初始化

public void init() {

//读取web.xml => dom4j =>

//得到web.xml文件的路径 => 拷贝一份.

String path = HspTomcatV3.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 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(),

(HspHttpServlet) 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);

}

}

改进HspRequestHandler

/**

* 1. HspRequestHandler 对象是一个线程对象

* 2. 处理一个http请求的

*/

public class HspRequestHandler implements Runnable {

//定义Socket

private Socket socket = null;

public HspRequestHandler(Socket socket) {

this.socket = socket;

}

@Override

public void run() {

//这里我们可以对客户端/浏览器进行IO编程/交互

try {

//这里我们先死后活

HspRequest hspRequest = new HspRequest(socket.getInputStream());

//这里我们可以同HspResponse对象,返回数据给浏览器/客户端

HspResponse hspResponse = new HspResponse(socket.getOutputStream());

//1. 得到 uri => 就是 servletUrlMapping 的 url-pattern

String uri = hspRequest.getUri();

String servletName = HspTomcatV3.servletUrlMapping.get(uri);

if (servletName == null) {

servletName = "";

}

//2. 通过uri->servletName->servlet的实例 , 真正的运行类型是其子类 HspCalServlet

HspHttpServlet hspHttpServlet =

HspTomcatV3.servletMapping.get(servletName);

//3. 调用service , 通过OOP的动态绑定机制,调用运行类型的 doGet/doPost

if (hspHttpServlet != null) {//得到

hspHttpServlet.service(hspRequest, hspResponse);

} else {

//没有这个servlet , 返回404的提示信息

String resp = HspResponse.respHeader + "

404 Not Found

";

OutputStream outputStream = hspResponse.getOutputStream();

outputStream.write(resp.getBytes());

outputStream.flush();

outputStream.close();

}

socket.close();

} catch (IOException e) {

e.printStackTrace();

} finally {

//最后一定确保socket要关闭

if (socket != null) {

try {

socket.close();

} catch (IOException e) {

e.printStackTrace();

}

}

}

}

}

改进web.xml

"-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"

"http://java.sun.com/dtd/web-app_2_3.dtd" >

Archetype Created Web Application

HspCalServlet

com.hspedu.tomcat.servlet.HspCalServlet

HspCalServlet

/hspCalServlet

Hsp6CalServlet

com.hspedu.tomcat.servlet.Hsp6CalServlet

Hsp6CalServlet

/hsp6CalServlet

增添Hsp6CalServlet

public class Hsp6CalServlet extends HspHttpServlet {

@Override

public void doGet(HspRequest request, HspResponse response) {

//java基础的 OOP 的动态绑定机制..

//写业务代码,完成计算任务

int num1 = WebUtils.parseInt(request.getParameter("num1"), 0);

int num2 = WebUtils.parseInt(request.getParameter("num2"), 0);

int sum = num1 * num2;

//返回计算结果给浏览器

//outputStream 和 当前的socket关联

OutputStream outputStream = response.getOutputStream();

String respMes = HspResponse.respHeader

+ "

" + num1 + " * " + num2 + " = " + sum + " HspTomcatV3 - 反射+xml创建

";

try {

outputStream.write(respMes.getBytes());

outputStream.flush();

outputStream.close();

} catch (IOException e) {

e.printStackTrace();

}

}

@Override

public void doPost(HspRequest request, HspResponse response) {

}

@Override

public void init() throws Exception {

}

@Override

public void destroy() {

}

}

2.4 小结

回顾 Tomcat 工作架构图 BIO线程模型

BIO(Blocking I/O)即同步阻塞 I/O,是 Java 中最早使用的一种 I/O 模型。它的特点是使用传统的套接字(Socket)进行通信,当服务端接收到客户端请求后,为每个客户端连接创建一个新的线程进行处理,因此这种模型也被称为“一请求一应答”模式。

BIO 线程模型的流程如下:

服务端启动后,通过 ServerSocket 等待客户端连接请求。客户端发起连接请求后,服务端接收到请求并创建一个新的线程(或使用线程池中的一个线程)处理客户端的请求。服务端将接收到的数据存放到缓冲区中,并处理客户端请求。服务端将处理结果返回给客户端,并关闭连接。

需要注意的是,BIO 模型中的线程数随着客户端连接数量的增加而增加,如果客户端连接数量过多,服务端可能会出现线程资源不足的情况。

NIO线程模型

NIO(Non-blocking I/O)即非阻塞 I/O,是 Java 中另一种 I/O 模型。相比于 BIO 模型,它使用单个线程或少量线程来处理多个客户端的请求,因此也被称为“多路复用”模式。

NIO 线程模型的流程如下:

服务端启动后,通过 ServerSocketChannel 等待客户端连接请求。客户端发起连接请求后,服务端接收到请求,并将其注册到 Selector 中,由 Selector 监听客户端的事件。Selector 在监听到事件后,将其分发给对应的线程进行处理,例如读取客户端发送的数据。完成对客户端请求的处理后,服务端将处理结果返回给客户端,并关闭连接。

需要注意的是,NIO 模型中使用单个线程或少量线程进行处理,因此可以避免频繁创建线程造成的开销,同时也能够提高系统的性能和吞吐量。但是,在处理客户端请求时需要使用较为复杂的事件轮询机制。

3. 课后作业

cal.html

计算器

计算器

num1:

num2:

web.xml

HspCalServlet

com.hspedu.tomcat.servlet.HspCalServlet

HspCalServlet

/hspCalServlet

WebUtils.java

public class WebUtils {

//将字符串转成数字方法

public static int parseInt(String strNum, int defaultVal) {

try {

return Integer.parseInt(strNum);

} catch (NumberFormatException e) {

System.out.println(strNum + " 不能转成数字");

}

return defaultVal;

}

//判断uri是不是html文件

public static boolean isHtml(String uri) {

return uri.endsWith(".html");

}

//根据文件名来读取该文件->String

public static String readHtml(String filename) {

String path = com.hspedu.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();

}

}

HspRequestHandler.java

/**

* 老师解读

* 1. HspRequestHandler 对象是一个线程对象

* 2. 处理一个http请求的

*/

public class HspRequestHandler implements Runnable {

//定义Socket

private Socket socket = null;

public HspRequestHandler(Socket socket) {

this.socket = socket;

}

@Override

public void run() {

//这里我们可以对客户端/浏览器进行IO编程/交互

try {

//这里我们先死后活

HspRequest hspRequest = new HspRequest(socket.getInputStream());

//这里我们可以同HspResponse对象,返回数据给浏览器/客户端

HspResponse hspResponse = new HspResponse(socket.getOutputStream());

//1. 得到 uri => 就是 servletUrlMapping 的 url-pattern

String uri = hspRequest.getUri();

//=====================新增业务逻辑==========

//(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 = HspResponse.respHeader + content;

//得到outputstream , 返回信息(静态页面)给浏览器

OutputStream outputStream = hspResponse.getOutputStream();

outputStream.write(content.getBytes());

outputStream.flush();

outputStream.close();

socket.close();

return;

}

String servletName = HspTomcatV3.servletUrlMapping.get(uri);

if (servletName == null) {

servletName = "";

}

//2. 通过uri->servletName->servlet的实例 , 真正的运行类型是其子类 HspCalServlet

HspHttpServlet hspHttpServlet =

HspTomcatV3.servletMapping.get(servletName);

//3. 调用service , 通过OOP的动态绑定机制,调用运行类型的 doGet/doPost

if (hspHttpServlet != null) {//得到

hspHttpServlet.service(hspRequest, hspResponse);

} else {

//没有这个servlet , 返回404的提示信息

String resp = HspResponse.respHeader + "

404 Not Found

";

OutputStream outputStream = hspResponse.getOutputStream();

outputStream.write(resp.getBytes());

outputStream.flush();

outputStream.close();

}

socket.close();

} catch (IOException e) {

e.printStackTrace();

} finally {

//最后一定确保socket要关闭

if (socket != null) {

try {

socket.close();

} catch (IOException e) {

e.printStackTrace();

}

}

}

}

}

/**

* @author 韩顺平

* @version 1.0

* 第3版的Tomcat, 实现通过xml+反射来初始化容器

*/

public class HspTomcatV3 {

//1. 存放容器 servletMapping

// -ConcurrentHashMap

// -HashMap

// key - value

// ServletName 对应的实例

public static final ConcurrentHashMap

servletMapping = new ConcurrentHashMap<>();

//2容器 servletUrlMapping

// -ConcurrentHashMap

// -HashMap

// key - value

// url-pattern ServletName

public static final ConcurrentHashMap

servletUrlMapping = new ConcurrentHashMap<>();

//你可以这里理解session, tomcat还维护一个容器

public static final ConcurrentHashMap

sessionMapping = new ConcurrentHashMap<>();

//你可以这里理解filter, tomcat还维护了filter的容器

public static final ConcurrentHashMap

filterUrlMapping = new ConcurrentHashMap<>();

public static final ConcurrentHashMap

filterMapping = new ConcurrentHashMap<>();

//变强..

public static void main(String[] args) {

HspTomcatV3 hspTomcatV3 = new HspTomcatV3();

hspTomcatV3.init();

//启动hsptomcat容器

hspTomcatV3.run();

}

//启动HspTomcatV3容器

public void run() {

try {

ServerSocket serverSocket = new ServerSocket(8080);

System.out.println("=====hsptomcatv3在8080监听======");

while (!serverSocket.isClosed()) {

Socket socket = serverSocket.accept();

HspRequestHandler hspRequestHandler =

new HspRequestHandler(socket);

new Thread(hspRequestHandler).start();

}

} catch (IOException e) {

e.printStackTrace();

}

}

//直接对两个容器进行初始化

public void init() {

//读取web.xml => dom4j =>

//得到web.xml文件的路径 => 拷贝一份.

String path = HspTomcatV3.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 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(),

(HspHttpServlet) 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);

}

}

相关文章

评论可见,请评论后查看内容,谢谢!!!
 您阅读本篇文章共花了: