当前位置: 首页 > article >正文

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

介绍:

        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() {
                    @Override
                    public 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;
        }
    
        @Override
        public 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 {
    
        @Override
        public void doGet(HttpServletRequast requast, HttpServletResponse response) throws Exception {
    
        }
    
        @Override
        public 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() {
                    @Override
                    public 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.kler.cn/a/530481.html

相关文章:

  • Windows程序设计10:文件指针及目录的创建与删除
  • 使用MATLAB进行雷达数据采集可视化
  • Vue 与 Electron 结合开发桌面应用
  • 【大模型LLM面试合集】大语言模型架构_MHA_MQA_GQA
  • 手写call函数、手写apply函数、手写bind函数
  • 分布式事务组件Seata简介与使用,搭配Nacos统一管理服务端和客户端配置
  • Ubuntu 下 nginx-1.24.0 源码分析 ngx_debug_init();
  • 构建一个文档助手Agent:提升知识管理效率的实践
  • CUDA内存模型
  • 力扣经典题目之3无重复字符的最长子串
  • STL之初识string
  • 浅谈 JSON 对象和 FormData 相互转换,打通前端与后端的通信血脉_json转formdata
  • Baklib推动内容中台与人工智能技术的智能化升级与行业变革
  • Qt 5.14.2 学习记录 —— 이십삼 绘图API
  • MATLAB基础应用精讲-【数模应用】梯度直方图(HOG)(附C++和python代码实现)(二)
  • 攻防世界 php2
  • 物业综合管理系统助力社区服务创新提升管理效率与住户体验
  • Hive 整合 Spark 全教程 (Hive on Spark)
  • [SAP ABAP] Debug Skill
  • JavaScript面向对象编程:Prototype与Class的对比详解
  • 【最后203篇系列】004 -Smarklink
  • 蓝桥杯C语言程序设计赛备赛指南
  • 2025年2月2日(tcp3次握手4次挥手)
  • 【UE】 APlayerState
  • elasticsearch8.15 高可用集群搭建(含认证Kibana)
  • 代码讲解系列-CV(一)——CV基础框架