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

news/2025/2/6 1:58:22/

介绍:

        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/news/1569671.html

相关文章

6. k8s二进制集群之各节点部署

获取kubernetes源码安装主节点&#xff08;分别执行以下各节点命令&#xff09;安装工作节点&#xff08;同步kebelet和kube-proxy到各工作节点&#xff09;总结 继续上一篇文章《k8s二进制集群之ETCD集群部署》下面介绍一下各节点的部署与配置。 获取kubernetes源码 https:/…

Mac上有哪些好用的开源粘贴板app

在Mac上&#xff0c;有几款开源且好用的粘贴板管理工具值得推荐&#xff1a; Maccy 特点&#xff1a;Maccy是一款开源、轻量级的剪贴板管理工具&#xff0c;支持多种功能&#xff0c;包括搜索、Pin单条记录、忽略格式粘贴等。它采用键盘优先设计&#xff0c;操作组合键可减少鼠…

Google Chrome-便携增强版[解压即用]

Google Chrome-便携增强版 链接&#xff1a;https://pan.xunlei.com/s/VOI0OyrhUx3biEbFgJyLl-Z8A1?pwdf5qa# a 特点描述 √ 无升级、便携式、绿色免安装&#xff0c;即可以覆盖更新又能解压使用&#xff01; √ 此增强版&#xff0c;支持右键解压使用 √ 加入Chrome增强…

鸿蒙Harmony–状态管理器–@State详解

鸿蒙Harmony–状态管理器–State详解 1.1 定义 State装饰的变量,或者称为状态变量,一旦变量拥有了状态属性,就可以触发其直接绑定UI组件的刷新。当状态改变时,UI会发生对应的渲染变化 ,State装饰的变量,与声明式范式中的其他被装饰变量一样,是私有的,只能从组件内部访问。在声…

Cassandra的下载与安装

1.下载Cassandra安装包 Apache Cassandra | Apache Cassandra Documentation G: cd G:\Cassandra\apache-cassandra-5.0.3\bin cassandra -f

deep generative model stanford lecture note2 --- autoregressive

1 Introduction 在note1 已经明确了生成模型&#xff0c;是通过概率分布来拟合数据&#xff0c;这个部分采用自回归的模型结构来拟合数据。主要任务包括&#xff1a;选择什么样的自回归模型结构和设计什么样的loss函数来让模型收敛。 自回归模型结构的理论基础还是贝叶斯概率结…

Vue 2 项目中 Mock.js 的完整集成与使用教程

Vue 2 实现 Mock.js 使用教程 1. 背景与问题 前端开发常常会遇到与后端开发的时间同步问题&#xff0c;尤其是在后端接口未完成或不稳定的情况下&#xff0c;前端开发无法继续下去&#xff0c;这会极大地影响项目进度。为了有效地解决这一问题&#xff0c;Mock.js 提供了一个极…

Netty中用了哪些设计模式?

大家好&#xff0c;我是锋哥。今天分享关于【Netty中用了哪些设计模式&#xff1f;】面试题。希望对大家有帮助&#xff1b; Netty中用了哪些设计模式&#xff1f; 1000道 互联网大厂Java工程师 精选面试题-Java资源分享网 Netty 是一个基于 Java 的高性能网络应用框架&#…