深入核心:一步步手撕Tomcat搭建自己的Web服务器

devtools/2025/2/6 19:45:45/

介绍:

        servlet:处理 http 请求

        tomcat服务器

Servlet

  1.  servlet 接口:
    1. 定义 Servlet 声明周期
    2. 初始化:init
    3. 服务:service
    4. 销毁:destory
  2. 继承链:

Tomcat

  1. Tomcat 和 servlet 原理:
    1. 浏览器向服务器发送 http 请求
    2. socket 接收请求,发送给请求解析器
    3. 请求解析器再解析自己想要的信息
      1. 请求地址
      2. 请求方式
      3. 浏览器类型
      4. Cookie
      5. ··········
    4. 解析器解析到的信息发送给映射器
    5. 映射器中存放:
      1. Web 地址
      2. 内存地址
    6. 根据请求解析器中解析的信息,找到映射器中相对应的网络地址和内存地址,根据请求方式去访问对应的程序
  2. Socket 交互以及解析阶段:
    package com.Tomcat;import com.sun.corba.se.spi.activation.Server;import java.io.IOException;
    import java.io.InputStream;
    import java.net.ServerSocket;
    import java.net.Socket;public class myTomcat {Request request = new Request();    //创建解析请求的对象public void startUP() throws IOException {//监听端口号ServerSocket serverSocket = new ServerSocket(7421);while(true){Socket socket = serverSocket.accept();      //阻塞监听System.out.println("有请求!!!!!!!");//将每个请求开启一个线程new Thread(new Runnable() {@Overridepublic void run() {try {handler(socket);    //调用处理信息方法}catch (Exception e){e.printStackTrace();}}}).start();}}//处理信息public void handler(Socket socket) throws IOException {InputStream inputStream = socket.getInputStream();//将bit流转换为文字信息int count = 0;while(count == 0){count = inputStream.available();        //统计输入流的长度}//打印数据byte[] bytes = new byte[count];inputStream.read(bytes);    //将bit信息写入到byte数组String Context = new String(bytes);     //将 byte 数组转换为字符串System.out.println(Context);    //输出信息//拆解字符串,获取想要的信息String[] list = Context.split("\\n");   //根据换行切割字符串String Methed = list[0].split(" ")[0];  //在拆分的第一行中以空格再次拆分,获取第一个数据String Path = list[0].split(" ")[1];    //在拆分的第一行中以空格再次拆分,获取第二个数据//把截取的数据传给 Request 类request.setMethod(Methed);request.setPath(Path);}
    }
  3. Request 存储解析信息 ==> 继承 HttpservletRequest,为方便访问同一变量
    public class Request implements HttpservletRequast {private String Method;private String Path;public String getMethod() {return Method;}public void setMethod(String method) {Method = method;}public String getPath() {return Path;}public void setPath(String path) {Path = path;}@Overridepublic String toString() {return "Request{" +"Method='" + Method + '\'' +", Path='" + Path + '\'' +'}';}}
  4. 扫包:
    /*** 扫描指定包,获取该包下所有的类的全路径信息*/
    public class SearchClassUtil {//存放文件的绝对路径public static  List<String> classPaths = new ArrayList<String>();/*** 扫描固定包下面的类* @return*/public static List<String> searchClass(){//需要扫描的包名String basePack = "com.servlet";      //写需要获取包名的路径//将获取到的包名转换为路径//getResource():是获取类对象的方法, "/" :表示在根目录开始//getPath():是将对象的路径转为字符串String classPath = SearchClassUtil.class.getResource("/").getPath();//将包名转换为文件系统路径  --->  把 "." 替换成 系统的路径分隔符(系统不一样,分隔符也不一样)basePack =  basePack.replace(".", File.separator);//把两个路径合并为文件的 绝对路径String searchPath = classPath + basePack;//调用doPath()方法,把路径写入路径数组中doPath(new File(searchPath),classPath);//这个时候我们已经得到了指定包下所有的类的绝对路径了。我们现在利用这些绝对路径和java的反射机制得到他们的类对象return classPaths;}/*** 该方法会得到所有的类,将类的绝对路径写入到classPaths中* @param file*/private static void doPath(File file,String classpath) {if (file.isDirectory()) {//当前为文件夹//文件夹我们就递归  --->  筛出文件夹File[] files = file.listFiles();for (File f1 : files) {doPath(f1,classpath);}} else {//标准文件//标准文件我们就判断是否是class文件if (file.getName().endsWith(".class")) {//各级拆解字符串,替换分隔符String path = file.getPath().replace(classpath.replace("/","\\").replaceFirst("\\\\",""),"").replace("\\",".").replace(".class","");//如果是class文件我们就放入我们的集合中。classPaths.add(path);}}}public static void main(String[] args) {List<String> classes = SearchClassUtil.searchClass();for (String s: classes) {//System.out.println(s);}}
    }
  5. 注解:设置文件的访问地址 ==> HashMap 中的 key 值
    @Retention(RetentionPolicy.RUNTIME)     //在运行期间保留
    @Target(ElementType.TYPE)       //作用于类上面
    public @interface Webservlet {String url() default "";
    }
  6. 创建 Httpservlet 实现 Service 服务:
    public abstract class HttpServlet {   //HttpServerlet只实现了父类的service服务,其他方法没有实现,此时该类为抽象类//子类需要使用doGet或doPost方法,在这里直接让子类去实现两个发给发//这里需要获取Request中被解析出来的数据,//要想访问的是同一个Request对象,这里用到接口,让Request实现这个接口,传参时就会向上转型,此时request对象为同一个对象public abstract void doGet(HttpServletRequast requast, HttpServletResponse response) throws Exception;public abstract void doPost(HttpServletRequast requast,HttpServletResponse response);//在服务中判断用户的请求方式,让子类实现向对应的方法public void service(HttpServletRequast requast,HttpServletResponse response) throws Exception {if(requast.getMethod().equals("GET")){doGet(requast,response);}else if(requast.getMethod().equals("POST")){doPost(requast,response);}}
    }
  7. HttpservletRequast:为 Httpservlet 访问对象为统一对象,让 Request 实现这个接口
    public interface HttpservletRequast {String getMethod();void setMethod(String method);String getPath();void setPath(String path);
    }
  8. 自己创建 servlet,继承 Httpservlet 实现 service 服务  ==>  实现相关的访问方式
    @WebServerlet(url = "OneServerlet")
    public class FirstServlet extends HttpServlet {@Overridepublic void doGet(HttpServletRequast requast, HttpServletResponse response) throws Exception {}@Overridepublic void doPost(HttpServletRequast requast, HttpServletResponse response) {}
    }
  9. 获取访问地址:HashMap 中的 key 值
    public class getMessageUtil {public static String fund(String path) throws Exception {//创建类对象Class clazz = Class.forName(path);//根据类对象调用 getDeclaredAnnotation() 方法找到该类的访问地址(@Webservlet()中的内容)Webservlet webservlet = (Webservlet) clazz.getDeclaredAnnotation(Webservlet.class);return webservlet.url();}public static void main(String[] args) throws Exception {//fund();}
    }
  10. 映射器:底层由 HashMap 容器存储
    public class ServletConfigMapping {//将执行逻辑写入static代码块中,以便更好加载//定义Servlet容器public static Map<String,Class<HttpServlet>> classMap = new HashMap<>();//该静态代码块应放在启动tomcat前运行static {List<String> classPaths = SearchClassUtil.searchClass();for (String classPath : classPaths){try {InitClassMap(classPath);}catch (Exception e){e.printStackTrace();}}}//将key val 值插入到HashMap中public static void InitClassMap(String classPath) throws Exception {//获取类对象Class clazz = Class.forName(classPath);//获取访问地址String url = getMessageUtil.fundUrl(classPath);//将值插入HashMap树当中classMap.put(url,clazz);}
    }

Response 返回数据:

  1. 设置返回头工具类:
    /*** 设置返回头*/
    public class ResponseUtil {public  static  final String responseHeader200 = "HTTP/1.1 200 \r\n"+"Content-Type:text/html \r\n"+"\r\n";public  static  final String responseHeader200JSON = "HTTP/1.1 200 \r\n"+"Content-Type:application/json \r\n"+"\r\n";public static String getResponseHeader404(){return "HTTP/1.1 404 \r\n"+"Content-Type:text/html \r\n"+"\r\n" + "404";}public static String getResponseHeader200(String context){return "HTTP/1.1 200 \r\n"+"Content-Type:text/html \r\n"+"\r\n" + context;}
    }
  2. 读取文件:根据提供的地址转化为文件完整地址
    /*** 该类的主要作用是进行读取文件*/
    public class FileUtil {public  static  boolean witeFile(InputStream inputStream, OutputStream outputStream){boolean success = false ;BufferedInputStream bufferedInputStream ;BufferedOutputStream bufferedOutputStream;try {bufferedInputStream = new BufferedInputStream(inputStream);bufferedOutputStream = new BufferedOutputStream(outputStream);bufferedOutputStream.write(ResponseUtil.responseHeader200.getBytes());int count = 0;while (count == 0){count = inputStream.available();}int fileSize = inputStream.available();long written = 0;int beteSize = 1024;byte[] bytes = new byte[beteSize];while (written < fileSize){if(written + beteSize > fileSize){beteSize = (int)(fileSize - written);bytes = new byte[beteSize];}bufferedInputStream.read(bytes);bufferedOutputStream.write(bytes);bufferedOutputStream.flush();written += beteSize;}success = true;} catch (IOException e) {e.printStackTrace();}return success;}public static boolean writeFile(File file,OutputStream outputStream) throws Exception{return witeFile(new FileInputStream(file),outputStream);}/*** 根据提供的地址转换为文件完整地址* @param path* @return*/public static String getResoucePath(String path){String resource = FileUtil.class.getResource("/").getPath();return resource + "\\" + path;}}
  3. response 返回数据:
    public class Response implements HttpServletResponse {//输出流private OutputStream outputStream;public Response(OutputStream outputStream){this.outputStream = outputStream;}/**** 返回动态文字信息* @param context* @throws IOException*/public void write(String context) throws IOException {outputStream.write(context.getBytes());     //将文字信息转换为 byte流 形式}public void WriteHtml(String Path) throws Exception {//得到文件全路径String resoucePath = FileUtil.getResoucePath(Path);//创建文件File file = new File(resoucePath);if(file.exists()){System.out.println("静态资源存在");//输出静态资源FileUtil.writeFile(file,outputStream);}else {System.out.println("静态资源不存在");}}
    }
  4. HttpServletResponse 接口:
    public interface HttpServletResponse {void write(String context) throws IOException;
    }
  5. 输出资源:
    Response response = new Response(socket.getOutputStream());if(request.getPath().equals("") || request.getPath().equals("/")){      //空访问response.WriteHtml("404.html");     //抛出404页面response.write(ResponseUtil.getResponseHeader404());    //抛出404文字信息} else if (ServerletConfigMapping.classMap.get(request.getPath()) == null) {        //静态资源response.WriteHtml(request.getPath());}else {     //动态资源Class<HttpServlet> httpServletClass = ServerletConfigMapping.classMap.get(request.getPath());   //获取类对象if(httpServletClass != null){       //有类对象HttpServlet httpServlet = httpServletClass.newInstance();   //多态创建对象httpServlet.service(request,response);      //启动service服务}else{      //没有动态资源response.WriteHtml("404.html");     //抛出 404页面}
    }
  6. 整合后 Tomcat:
    public class myTomcat {Request request = new Request();    //创建解析请求的对象//提前加载容器(HashMap)static {List<String> classPaths = SearchClassUtil.searchClass();for (String classPath : classPaths){try {ServerletConfigMapping.InitClassMap(classPath);}catch (Exception e){e.printStackTrace();}}}public static void main(String[] args) throws IOException {myTomcat myTomcat = new myTomcat();myTomcat.startUP();}public void startUP() throws IOException {//监听端口号ServerSocket serverSocket = new ServerSocket(8080 );while(true){Socket socket = serverSocket.accept();      //阻塞监听System.out.println("有请求!!!!!!!");//将每个请求开启一个线程new Thread(new Runnable() {@Overridepublic void run() {try {handler(socket);    //调用处理信息方法}catch (Exception e){e.printStackTrace();}}}).start();}}//处理信息public void handler(Socket socket) throws Exception {InputStream inputStream = socket.getInputStream();//将bit流转换为文字信息int count = 0;while(count == 0){count = inputStream.available();        //统计输入流的长度}//打印数据byte[] bytes = new byte[count];inputStream.read(bytes);    //将bit信息写入到byte数组String Context = new String(bytes);     //将 byte 数组转换为字符串System.out.println(Context);    //输出信息//拆解字符串,获取想要的信息String[] list = Context.split("\\n");   //根据换行切割字符串String Methed = list[0].split(" ")[0];  //在拆分的第一行中以空格再次拆分,获取第一个数据String Path = list[0].split(" ")[1];    //在拆分的第一行中以空格再次拆分,获取第二个数据//把截取的数据传给 Request 类request.setMethod(Methed);request.setPath(Path);//判断资源类型Response response = new Response(socket.getOutputStream());if(request.getPath().equals("") || request.getPath().equals("/")){      //空访问response.WriteHtml("404.html");     //抛出404页面response.write(ResponseUtil.getResponseHeader404());    //抛出404文字信息} else if (ServerletConfigMapping.classMap.get(request.getPath()) == null) {        //静态资源response.WriteHtml(request.getPath());}else {     //动态资源Class<HttpServlet> httpServletClass = ServerletConfigMapping.classMap.get(request.getPath());   //获取类对象if(httpServletClass != null){       //有类对象HttpServlet httpServlet = httpServletClass.newInstance();   //多态创建对象httpServlet.service(request,response);      //启动service服务}else{      //没有动态资源response.WriteHtml("404.html");     //抛出 404页面}}}
    }

Tomcat 运行原理:

  1. 原理:
    1. 浏览器发起请求
    2. Socket 解析输入流,获取请求头信息
    3. 分析请求的地址是动态资源还是静态资源
      1. 首先判断 HashMap 中有没有这个 Key 值
      2. 如果有就去访问动态资源,如果没有就去查看静态资源
      3. 如果也不是静态资源就返回 404
    4. Servlet 容器(HashMap):
      1. 将 @WebServlet 中的值作为 key 值,将对象作为 value 值,存入 HashMap 中
  2. Servlet 容器加载时期:
    1. 在 Socket 启动之前启动 Servlet 容器
      1. 缺点:程序启动时间变长
      2. 优点:不易出现空指针
    2. 在 Socket 启动之后启动 Servlet 容器
    3. 在浏览器访问的同时启动 Servlet 容器

http://www.ppmy.cn/devtools/156602.html

相关文章

C++的 I/O 流

本文把复杂的基类和派生类的作用和关系捋出来&#xff0c;具体的接口请参考相关文档 C的 I/O 流相关的类&#xff0c;继承关系如下图所示 https://zh.cppreference.com/w/cpp/io I / O 的概念&#xff1a;内存和外设进行数据交互称为 I / O &#xff0c;例如&#xff1a;把数…

设计模式 - 行为模式_Template Method Pattern模板方法模式在数据处理中的应用

文章目录 概述1. 核心思想2. 结构3. 示例代码4. 优点5. 缺点6. 适用场景7. 案例&#xff1a;模板方法模式在数据处理中的应用案例背景UML搭建抽象基类 - 数据处理的 “总指挥”子类定制 - 适配不同供应商供应商 A 的数据处理器供应商 B 的数据处理器 在业务代码中整合运用 8. 总…

Tomcat启动流程与前端请求处理详解

Tomcat启动流程与前端请求处理详解 一、Tomcat启动流程 Tomcat是一个Servlet容器&#xff0c;它负责将Web应用程序中的Servlet与外部HTTP请求进行交互。Tomcat启动时会加载所有的Web应用和Servlet&#xff0c;并通过扫描和反射机制将其映射到适当的Servlet类上。下面是Tomcat启…

gitlab云服务器配置

目录 1、关闭防火墙 2、安装gitlab 3、修改配置 4、查看版本 GitLab终端常用命令 5、访问 1、关闭防火墙 firewall-cmd --state 检查防火墙状态 systemctl stop firewalld.service 停止防火墙 2、安装gitlab xftp中导入安装包 [rootgitlab ~]#mkdir -p /service/tool…

MySQL知识点总结(十七)

在从属服务器上执行RESET SLAVE命令时&#xff0c;会发生哪些操作&#xff1f; RESET SLAVE命令会断开从属服务器与主服务器的连接&#xff0c;以重置从属服务器&#xff0c;具体效果如下&#xff1a;清除 master.info和relay.log资料档案库删除所有中继日志启动新的中继日志文…

pytorch实现门控循环单元 (GRU)

人工智能例子汇总&#xff1a;AI常见的算法和例子-CSDN博客 特性GRULSTM计算效率更快&#xff0c;参数更少相对较慢&#xff0c;参数更多结构复杂度只有两个门&#xff08;更新门和重置门&#xff09;三个门&#xff08;输入门、遗忘门、输出门&#xff09;处理长时依赖一般适…

Java 大视界 -- 深度洞察 Java 大数据安全多方计算的前沿趋势与应用革新(52)

&#x1f496;&#x1f496;&#x1f496;亲爱的朋友们&#xff0c;热烈欢迎你们来到 青云交的博客&#xff01;能与你们在此邂逅&#xff0c;我满心欢喜&#xff0c;深感无比荣幸。在这个瞬息万变的时代&#xff0c;我们每个人都在苦苦追寻一处能让心灵安然栖息的港湾。而 我的…

vue vscode插件推荐安装

在 VSCode 中开发 Vue&#xff0c;推荐安装以下插件&#xff1a; 核心插件 1. Volar&#xff08;Vue Language Features&#xff09; - Vue 3 官方推荐的开发工具&#xff0c;替代 Vetur。 This extension is deprecated. Use the Vue - Official extension instead. 1.Vue …