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

介绍:

servlet:处理 http 请求

tomcat:服务器

Servlet

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

Tomcat

Tomcat 和 servlet 原理:
  1. 浏览器向服务器发送 http 请求
  2. socket 接收请求,发送给请求解析器
  3. 请求解析器再解析自己想要的信息
    1. 请求地址
    2. 请求方式
    3. 浏览器类型
    4. Cookie
    5. ··········
  4. 解析器解析到的信息发送给映射器
  5. 映射器中存放:
    1. Web 地址
    2. 内存地址
  6. 根据请求解析器中解析的信息,找到映射器中相对应的网络地址和内存地址,根据请求方式去访问对应的程序
Socket 交互以及解析阶段:
java 复制代码
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);
        
    }
}
Request 存储解析信息 ==> 继承 HttpservletRequest,为方便访问同一变量
java 复制代码
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 + '\'' +
                '}';
    }


}
扫包:
java 复制代码
/**
 * 扫描指定包,获取该包下所有的类的全路径信息
 */
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);
        }
    }
}
注解:设置文件的访问地址 ==> HashMap 中的 key 值
java 复制代码
@Retention(RetentionPolicy.RUNTIME)     //在运行期间保留
@Target(ElementType.TYPE)       //作用于类上面
public @interface Webservlet {
    String url() default "";
}
创建 Httpservlet 实现 Service 服务:
java 复制代码
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);
        }
    }
}
HttpservletRequast:为 Httpservlet 访问对象为统一对象,让 Request 实现这个接口
java 复制代码
public interface HttpservletRequast {

    String getMethod();

    void setMethod(String method);

    String getPath();

    void setPath(String path);
}
自己创建 servlet,继承 Httpservlet 实现 service 服务 ==> 实现相关的访问方式
java 复制代码
@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) {

    }
}
获取访问地址:HashMap 中的 key 值
java 复制代码
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();
    }
}
  1. 映射器:底层由 HashMap 容器存储

    java 复制代码
    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 返回数据:

设置返回头工具类:
java 复制代码
/**
 * 设置返回头
 */
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;
    }
}
读取文件:根据提供的地址转化为文件完整地址
java 复制代码
/**
 * 该类的主要作用是进行读取文件
 */
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;
    }

}
response 返回数据:
java 复制代码
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("静态资源不存在");
        }
    }
}
HttpServletResponse 接口:
java 复制代码
public interface HttpServletResponse {

    void write(String context) throws IOException;
}
输出资源:
java 复制代码
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:
java 复制代码
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. 浏览器发起请求
  2. Socket 解析输入流,获取请求头信息
  3. 分析请求的地址是动态资源还是静态资源
    1. 首先判断 HashMap 中有没有这个 Key 值
    2. 如果有就去访问动态资源,如果没有就去查看静态资源
    3. 如果也不是静态资源就返回 404
  4. Servlet 容器(HashMap):
    1. 将 @WebServlet 中的值作为 key 值,将对象作为 value 值,存入 HashMap 中
Servlet 容器加载时期:
  1. 在 Socket 启动之前启动 Servlet 容器
    1. 缺点:程序启动时间变长
    2. 优点:不易出现空指针
  2. 在 Socket 启动之后启动 Servlet 容器
  3. 在浏览器访问的同时启动 Servlet 容器
相关推荐
崔庆才丨静觅4 小时前
hCaptcha 验证码图像识别 API 对接教程
前端
passerby60614 小时前
完成前端时间处理的另一块版图
前端·github·web components
掘了5 小时前
「2025 年终总结」在所有失去的人中,我最怀念我自己
前端·后端·年终总结
崔庆才丨静觅5 小时前
实用免费的 Short URL 短链接 API 对接说明
前端
崔庆才丨静觅5 小时前
5分钟快速搭建 AI 平台并用它赚钱!
前端
七夜zippoe5 小时前
CANN Runtime任务描述序列化与持久化源码深度解码
大数据·运维·服务器·cann
盟接之桥5 小时前
盟接之桥说制造:引流品 × 利润品,全球电商平台高效产品组合策略(供讨论)
大数据·linux·服务器·网络·人工智能·制造
崔庆才丨静觅5 小时前
比官方便宜一半以上!Midjourney API 申请及使用
前端
Moment5 小时前
富文本编辑器在 AI 时代为什么这么受欢迎
前端·javascript·后端
崔庆才丨静觅6 小时前
刷屏全网的“nano-banana”API接入指南!0.1元/张量产高清创意图,开发者必藏
前端