Java学习之仿写Tomcat

一、原理讲解

OSI七层模型

在这里插入图片描述

如何调动后四层协议

在这里插入图片描述

先仿写一个socket服务端和客户端接发数据的demo

/**
 * 服务器端
 */
public class ServerA {
    //main方法是主线程要对数据进行处理
    public static void main(String[] args) throws IOException {
        //监听端口号
        ServerSocket serverSocket = new ServerSocket(8080);
        System.out.println("ServerA started...");

        while(true){
            Socket socket = serverSocket.accept();  //阻塞监听
            //子线程,去网卡拿数据
            new Thread(new Runnable() {
                @Override
                public void run() {
                    try {
                        handler(socket);
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                }
            }).start();
        }
    }

    public static void handler(Socket socket) throws IOException {
        //读取数据
        InputStream inputStream = socket.getInputStream();
        //将 01010这样的bit信息 转换为 字符数据
        BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream));
        String str = reader.readLine();
        System.out.println(str);
    }
/**
 * 客户端
 */
public class ServerB {
    public static void main(String[] args) throws IOException {
        Socket socket = new Socket("127.0.0.1",8080);
        System.out.println("ServerB started...");

        Scanner scanner = new Scanner(System.in);
        String input = scanner.next();

        //获取输出流对象
        OutputStream outputStream = socket.getOutputStream();
        //将字符数据转化为bit信息
        PrintWriter writer = new PrintWriter(outputStream);
        writer.println(input);
        writer.flush();
        socket.close();
    }

}

这样的设计有一个问题就是:服务器每次处理来自客户端的数据都必须新建一个线程,如果客户端B一直连接但不是发送数据,那么相应的子线程就会一直处于阻塞状态。
在这里插入图片描述
因为这是一个BIO,所以需要在服务端用while来防止单个客户端阻塞线程

在这里插入图片描述

我们应该以byte[]数组的方式收发数据而不是用bit数据流是因为byte[]数组有规整的长度(8bit)分割便于解析,而bit是无法分割的

二、仿写Tomcat

我们在仿写Tomcat的时候需要从Tomcat的运行思路看,总共分分为两个阶段:启动阶段和访问阶段

在这里插入图片描述

启动阶段

  • 首先我们需要扫描servlet文件获取文件全路径名
  • 再利用反射的方式获取@WebServlet()当中的地址信息

tips:

  • Tomcat本身就是servlet容器
  • 以下是servlet的概念
    • key值是地址信息,value值是通过全路径名获取类对象,把以上放入到HashMap集合当中
      在这里插入图片描述
      主类HttpServlet中的主要方法包含doGet、doPost剩下不重要的还有doDelete,doHead,doOption,doPut,doTrace等等

访问阶段

  • 利用socket获取http请求信息(请求信息就比如headers之类的)
  • 创建HttpServletRequest接口,将http请求信息进行拆分,将请求信息和请求方式放入到实现类当中
  • 通过请求地址判断有没有这个servlet对象
    • 有的话,获取类对象,生成servlet对象——>判断请求方式,分为doGet()和doPost()请求,分别返回各自的方法数据
    • 没有的话,按照路径查询有无静态资源——>如果有,返回页面;如果没有,返回404

三、主要代码

HttpServlet主类和Request、Response接口类

public abstract class HttpServlet {

    public abstract void doGet(HttpServletRequest request,HttpServletResponse response) throws IOException;

    public abstract void doPost(HttpServletRequest request,HttpServletResponse response) throws IOException;

    public void service(HttpServletRequest request,HttpServletResponse response) throws IOException {
        if("GET".equals(request.getMethod())){
            doGet(request,response);
        }else if("POST".equals(request.getMethod())){
            doPost(request,response);
        }
    }
}
public interface HttpServletRequest {

    public String getMethod();

    public void setMethod(String method);
}
public interface HttpServletResponse {

    public void GetWriter(String context) throws IOException;
}

配置类:

public class ServletConfig {

    private String urlMapping; //url
    private String classPath; //类的全路径名


    public ServletConfig(String urlMapping,String classPath){
        this.urlMapping = urlMapping;
        this.classPath = classPath;
    }

    public String getUrlMapping() {
        return urlMapping;
    }

    public void setUrlMapping(String urlMapping) {
        this.urlMapping = urlMapping;
    }

    public String getClassPath() {
        return classPath;
    }

    public void setClassPath(String classPath) {
        this.classPath = classPath;
    }

    @Override
    public String toString() {
        return "ServletConfig{" +
                "urlMapping='" + urlMapping + '\'' +
                ", classPath='" + classPath + '\'' +
                '}';
    }
}
public class ServletConfigMapping {

    //定义servlet容器
    public static Map<String,Class<HttpServlet>> classMap = new HashMap<>();

    //将每一个自定义servlet类的信息放入ServletConfig中
    private static List<ServletConfig> configs = new ArrayList<>();


    static {
        //获取用户自定义的servlet全路径名
        List<String> classPaths = SearchClassUtil.searchClass();
        for (String classPath:classPaths) {
            try {
                getMessage(classPath);
            } catch (Exception e) {
                e.printStackTrace();
            }
        }

    }

    //使用反射获取注解信息
    public static void getMessage(String classPath) throws Exception {
        Class clazz = Class.forName(classPath);
        //获取注解信息
        WebServlet webServlet = (WebServlet) clazz.getDeclaredAnnotation(WebServlet.class);

        //需要将解析的信息加载到configs集合当中(保证url和类对象是相同的)
        configs.add(new ServletConfig(webServlet.urlMapping(), classPath));
    }


    //将url和类对象放入到servlet容器
    public static void initServlet() throws ClassNotFoundException {
        for (ServletConfig config:configs) {
            classMap.put(config.getUrlMapping(), (Class<HttpServlet>) Class.forName(config.getClassPath()));
        }
    }


}

MyTomcat

public class MyTomcat {

    Request request = new Request();

    //获取类对象生成servlet对象
    public void dispatch(Request request,Response response) throws Exception {
        Class<HttpServlet> servletClass = ServletConfigMapping.classMap.get(request.getUrl());
        if (servletClass != null){
            HttpServlet servlet = servletClass.newInstance();   //生成当前servlet对象
            servlet.service(request,response);
        }
    }

    //启动tomcat的主方法
    public void startUp() throws Exception {
        //加载servlet信息
        ServletConfigMapping.initServlet(); //启动阶段

        //定义ServerSocket8080端口
        ServerSocket serverSocket = new ServerSocket(8080);
        while (true){
            Socket socket = serverSocket.accept();
            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();
        getInput(inputStream);//解析Http请求

        //获取输出通道、获取输出流
        Response response = new Response(socket.getOutputStream());

        if (request.getUrl().equals("")){
            //返回404
            response.GetWriter(ResponseUtil.getResponseHeader404());
        }else if (ServletConfigMapping.classMap.get(request.getUrl()) == null){
            //访问的是静态资源
            response.GetWriterHtml(request.getUrl());
        }else {
            //访问的是动态资源
            dispatch(request,response);
        }

    }

    //解析Http请求
    public void getInput(InputStream inputStream) throws IOException {
        int count = 0;
        while(count == 0){
            count = inputStream.available();
        }
        byte[] bytes = new byte[count];
        inputStream.read(bytes);
        String content = new String(bytes);
        System.out.println(content);

        if(content.equals("")){
            System.out.println("你有一个空请求");
        }else{
            String firstLine = content.split("\\n")[0];
            request.setMethod(firstLine.split("\\s")[0]);
            request.setUrl(firstLine.split("\\s")[1]);
        }
    }


    public static void main(String[] args) throws Exception {
        MyTomcat myTomcat = new MyTomcat();
        myTomcat.startUp();

    }


}
  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值