一、引入案例
1 小案例
引出对 Tomcat 底层实现思考
1.1 完成小案例
1.1.1 运行效果
1.2 maven简要介绍
我们准备使用
Maven
来
创建一个
WEB
项目
,
先
简单给小伙伴介绍一下
Maven
是
什
么
,
更加详细的使用,我们还会细讲
,
现在先使用一把
1.3 创建maven的web项目
完成后,如果目录没有出现src,不要慌,自行百度搜索解决方案
1.4 配置阿里 maven 镜像
1.5 修改 pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.hspedu</groupId>
<artifactId>tomcat02</artifactId>
<version>1.0-SNAPSHOT</version>
<packaging>war</packaging>
<name>tomcat02 Maven Webapp</name>
<!-- FIXME change it to the project's website -->
<url>http://www.example.com</url>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<maven.compiler.source>1.8</maven.compiler.source>
<maven.compiler.target>1.8</maven.compiler.target>
</properties>
<dependencies>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.11</version>
<scope>test</scope>
</dependency>
<!-- 引入servlet的依赖
1. dependency 表示依赖, 也就是我们这个项目需要依赖的 jar 包
2. groupId 和 artifactId 被统称为坐标, 是为了去定位这个项目/jar
3. groupId: 一般是公司 比如 com.baidu , 这里是 avax.servlet
4. artifactId 一般是项目名, 这里是 javax.servlet-api
5. 这样的化就可以定位一个 jar 包
6. version 表示你引入到我们项目的 jar 包的版本是 3.1.0
7. scope: 表示作用域,也就是你引入的 jar 包的作用范围
8. provided 表示在 tomcat 本身是有这个 jar 的,因此在编译,测试使用,但是在打包
发布就不用要带上
9. 在默认情况下, 引入的 jar 会到 中央仓库去下载 https://mvnrepository.com/
10. 会下载到哪里到你指定的目录 C:\Users\Administrator\.m2\repository
11. 有时为了下载更快, 往往配置镜像,
12. 在 默 认 的 路 径 下 拷 贝 一 份 setting.xml 到
C:\Users\Administrator\.m2\settings.xml
13. 指定默认的阿里云镜像
-->
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
<version>3.1.0</version>
<scope>provided</scope>
</dependency>
</dependencies>
<build>
<finalName>tomcat02</finalName>
<pluginManagement><!-- lock down plugins versions to avoid using Maven defaults (may be moved to parent pom) -->
<plugins>
<plugin>
<artifactId>maven-clean-plugin</artifactId>
<version>3.1.0</version>
</plugin>
<!-- see http://maven.apache.org/ref/current/maven-core/default-bindings.html#Plugin_bindings_for_war_packaging -->
<plugin>
<artifactId>maven-resources-plugin</artifactId>
<version>3.0.2</version>
</plugin>
<plugin>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.8.0</version>
</plugin>
<plugin>
<artifactId>maven-surefire-plugin</artifactId>
<version>2.22.1</version>
</plugin>
<plugin>
<artifactId>maven-war-plugin</artifactId>
<version>3.2.2</version>
</plugin>
<plugin>
<artifactId>maven-install-plugin</artifactId>
<version>2.5.2</version>
</plugin>
<plugin>
<artifactId>maven-deploy-plugin</artifactId>
<version>2.8.2</version>
</plugin>
</plugins>
</pluginManagement>
</build>
</project>
1.7 cal.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>计算器</title>
</head>
<body>
<h1>计算器</h1>
<form action="/calServlet" method="get">
num1:<input type="text" name="num1"><br/>
num2:<input type="text" name="num2"><br/>
<input type="submit" value="提交">
</form>
</body>
</html>
1.8 创建 java 目录,存放 java 源文件
1.9 创建 CalServlet.java
package com.hspedu.servlet;
import com.hspedu.utils.WebUtils;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.PrintWriter;
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");
//把strnum1和strnum2转成int
int num1= WebUtils.parseIt(strnum1,0);
int num2=WebUtils.parseIt(strnum2,0);
int result=num1+num2;
response.setContentType("text/html;charset=utf-8");
PrintWriter writer = response.getWriter();
writer.print("<h1>"+num1+" + "+num2+" = "+result+"<h1>");
writer.flush();
writer.close();
}
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
doPost(request,response);
}
}
1.10 修改 web.xml , 配置 Servlet
1.11 创 建 工 具类
package com.hspedu.utils;
public class WebUtils {
public static int parseIt (String strNum,int defaultVal){
try {
return Integer.parseInt(strNum);
} catch (NumberFormatException e) {
System.out.println(strNum+" 格式不对,转换失败");
return defaultVal;
}
}
}
没有配置tomcat记得配置哦,
我们的目标
:
不用
Tomcat,
不用系统提供的
Servlet,
模拟
Tomcat
底层实现并能调用我
们自己设计的
Servle,
也能完成相同的功能
二、 Tomcat 整体架构分析
1 一图胜千言
- 说明: Tomcat 有三种运行模式(BIO, NIO, APR), 因为核心讲解的是 Tomcat 如何接收客户端请求,解析请求, 调用 Servlet , 并返回结果的机制流程, 采用 BIO 线程模型来模拟.[绘图]
三、实现
1 实现任务阶段 1
编写自己 Tomcat, 能给浏览器返回 Hi, Hspedu
1.1 基于 socket 开发服务端-流程
1.2 需求分析/图解
需求分析如图, 浏览器请求 http://localhost:8080/??, 服务端返回 hi , hspedu
1.3 分析+代码实现
1.3.1 分析示意图
1.3.2 代码实现
package com.hspedu.tomcat;
import java.io.*;
import java.net.ServerSocket;
import java.net.Socket;
/*
这是第一个版本的tomcat,可以完成接收浏览器的请求,并返回信息
*/
public class LinranTomcatV1 {
public static void main(String[] args) throws IOException {
//1.创建
ServerSocket serverSocket=new ServerSocket(8080);
System.out.println("服务器正在8080端口监听");
while (!serverSocket.isClosed()){
//等待浏览器/客户端的链接
//如果有连接,就创建一个socket
//这个socket就是服务器和浏览器的连接通道
Socket socket=serverSocket.accept();
//先接收浏览器发送的数据
//inputStream是字节流=》BufferedReader(字符流)
InputStream inputStream = socket.getInputStream();
BufferedReader bufferedReader=new BufferedReader(new InputStreamReader(inputStream,"utf-8"));
//接收到浏览器发送的数据
System.out.println("==============接收到浏览器发送的数据==============");
String mes=null;
//循环读取
while ((mes=bufferedReader.readLine())!=null){
if(mes.length()==0){
break;//退出
}
System.out.println(mes);
}
//我们的tomcat回送数据
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();
}
}
}
问题分析:没有使用 BIO 线程模型,没有实现多线程,性能差
2 实现任务阶段 2
使用
BIO
线程模型,支持多线程
2.1 BIO 线程模型介绍
2.2 需求分析/图解
浏览器请求
http://localhost:8080,
服务端返回
hi , hspedu,
后台
hsptomcat
使用
BIO
线程模型
,
支持多线程
=>
对前面的开发模式进行改造
2.3 分析+代码实现
2.3.1 分析示意图
2.3.2 代码实现
新建一个线程类
package com.hspedu.tomcat.handler;
/*
HspRequestHandler对象是一个线程对象
处理http请求的
*/
import java.io.*;
import java.net.Socket;
public class HspRequestHandler implements Runnable {
//定义一个socket
private Socket socket=null;
public HspRequestHandler(Socket socket) {
this.socket = socket;
}
@Override
public void run() {
//这里我们可以对客户端/浏览器进行通信
try {
InputStream inputStream = socket.getInputStream();
//把inputStream->bufferedReader
BufferedReader bufferedReader =
new BufferedReader(new InputStreamReader(inputStream, "utf-8"));
String mes;
System.out.println("=============HspRequestHandler接收的信息如下=================");
while ((mes=bufferedReader.readLine())!=null){
if(mes.length()==0)
{
break;
}
System.out.println(mes);
}
//返回数据给我们的浏览器->封装成http响应
OutputStream outputStream = socket.getOutputStream();
//构建http响应
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();
} catch (IOException e) {
e.printStackTrace();
} finally {
//最后一定确保socket要关闭
if(socket!=null){
try {
socket.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}
主程序
package com.hspedu.tomcat;
import com.hspedu.tomcat.handler.HspRequestHandler;
import java.io.IOException;
import java.net.ServerSocket;
import java.net.Socket;
public class LinranTomcatV2 {
public static void main(String[] args) throws IOException {
ServerSocket serverSocket=new ServerSocket(8080);
System.out.println("============LinranTomcatV2在8080监听===============");
while (!serverSocket.isClosed()){
//只要serverSocket没有关闭,就一直等待连接
Socket socket = serverSocket.accept();
//这个socket就是一个通信
//创建一个线程对象,并把socket给该线程
new Thread( new HspRequestHandler(socket)).start();
}
}
}
3 实现任务阶段
处理
Servlet
3.1 Servlet 生命周期-回顾
3.2 需求分析/图解
3.3 分析
代码实现参考视频和项目源码
很心塞的一点,在跟着学习的过程中,最终 报错了,很烦