Tomcat Web服务器

提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档


前言

提示:这里可以添加本文要记录的大概内容:

tomcat的简单介绍,自己实现一个简易的tomcat。


Tomcat架构

Tomcat是⼀个Http服务器,当用户使用浏览器对某个网站发起请求时,浏览器生成HTTP格式的数据包,浏览器通过TCP协议与服务器传输数据。
在这里插入图片描述
HTTP 服务器接收到请求之后把请求交给Servlet容器来处理,Servlet 容器通过Servlet接⼝调⽤业务
类。Servlet接⼝和Servlet容器这⼀整套内容叫作Servlet规范。
Tomcat的两个重要身份
1)http服务器:和客户端浏览器进⾏交互,进⾏socket通信,将字节流和Request/Response等对象进⾏转换
2)Tomcat是⼀个Servlet容器:Servlet容器处理业务逻辑。

在这里插入图片描述
针对上述两个角色,tomcat内部实现有两个组件: 连接器组件 Coyote和容器组件Catalina
在这里插入图片描述

Tomcat Coyote

Tomcat Coyote ⽀持的 IO模型与协议协议
Coyote 是Tomcat 中连接器的组件名称 , 是对外的接⼝。客户端通过Coyote与服务器建⽴连接、发送请求并接受响应 。
(1)Coyote 封装了底层的⽹络通信(Socket 请求及响应处理)
(2)Coyote 使Catalina 容器(容器组件)与具体的请求协议及IO操作⽅式完全解耦
(3)Coyote 将Socket 输⼊转换封装为 Request 对象,进⼀步封装后交由Catalina 容器进⾏处理,处
理请求完成后, Catalina 通过Coyote 提供的Response 对象将结果写⼊输出流
(4)Coyote 负责的是具体协议(应⽤层)和IO(传输层)相关内容

在这里插入图片描述

Tomcat Catalina

可以认为整个Tomcat就是⼀个Catalina实例,Tomcat 启动的时候会初始化这个实例,Catalina
实例通过加载server.xml完成其他实例的创建,创建并管理⼀个Server,Server创建并管理多个服务,
每个服务⼜可以有多个Connector和⼀个Container。
在这里插入图片描述

  • Catalina
    负责解析Tomcat的配置⽂件(server.xml) , 以此来创建服务器Server组件并进⾏管理
  • Server
    服务器表示整个Catalina Servlet容器以及其它组件,负责组装并启动Servlaet引擎,Tomcat连接
    器。Server通过实现Lifecycle接⼝,提供了⼀种优雅的启动和关闭整个系统的⽅式
  • Service
    服务是Server内部的组件,⼀个Server包含多个Service。它将若⼲个Connector组件绑定到⼀个
  • Container
    Container容器,负责处理⽤户的servlet请求,并返回对象给web⽤户的模块

手写实现简易tomcat

V1版:返回⼀个固定的字符串

浏览器请求http://localhost:8080,返回⼀个固定的字符串到⻚⾯"Hello Minicat!

    private void miniCatV1(ServerSocket serverSocket) throws IOException {
        while (true) {
            Socket socket = serverSocket.accept();
            OutputStream outputStream = socket.getOutputStream();
            String data = "Hello MiniCat";
//            String responseText = HttpProtocolUtil.getHttpHeader200(data.getBytes().length) + data;
            String res404 = HttpProtocolUtil.getHttpHeader404() + data;

            outputStream.write(res404.getBytes());
            socket.close();
        }
    }

V2版:封装Request和Response对象,返回html静态资源⽂件

    /**
     * 需求:封装Request和Response对象,返回html静态资源⽂件
     *
     * @param serverSocket
     * @throws IOException
     */
    private void miniCatV2(ServerSocket serverSocket) throws IOException {
        while (true) {
            Socket socket = serverSocket.accept();
            InputStream inputStream = socket.getInputStream();
            Request request = new Request(inputStream);
            OutputStream outputStream = socket.getOutputStream();
            Response response = new Response(outputStream);
            response.outputHtml(request.getUrl());
            socket.close();
        }
    }

V3版:可以请求动态资源(Servlet)

    /**
     * 需求:可以请求动态资源(Servlet)
     *
     * @param serverSocket
     * @throws IOException
     */
    private void miniCatV3(ServerSocket serverSocket) throws Exception {
        while (true) {
            Socket socket = serverSocket.accept();
            InputStream inputStream = socket.getInputStream();
            Request request = new Request(inputStream);

            OutputStream outputStream = socket.getOutputStream();
            Response response = new Response(outputStream);

            if (servletMap.get(request.getUrl()) == null) {
                response.outputHtml(request.getUrl());
            } else {
                HttpServlet httpServlet = servletMap.get(request.getUrl());
                httpServlet.service(request, response);
            }
            socket.close();
        }
    }
    /**
     * 加载web.xml,初始化servlet
     */
    private void loadServlet() {
        InputStream resourceAsStream = this.getClass().getClassLoader().getResourceAsStream("web.xml");
        SAXReader saxReader = new SAXReader();
        try {
            Document read = saxReader.read(resourceAsStream);
            Element rootElement = read.getRootElement();
            List<Element> list = rootElement.selectNodes("//servlet");
            for (int i = 0; i < list.size(); i++) {
                Element element = list.get(i);
                Element servletNameElement = (Element) element.selectSingleNode("servlet-name");
                String servletName = servletNameElement.getStringValue();
                Element servletClassElement = (Element) element.selectSingleNode("servlet-class");
                String servletClass = servletClassElement.getStringValue();
                // 根据servlet-name的值找到url-pattern
                Element servletMapping = (Element) rootElement.selectSingleNode("//servlet-mapping[servlet-name='" + servletName + "']");
                String urlPattern = servletMapping.selectSingleNode("servlet-pattern").getStringValue();
                servletMap.put(urlPattern, (HttpServlet) Class.forName(servletClass).newInstance());
            }
        } catch (DocumentException e) {
            e.printStackTrace();
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        } catch (InstantiationException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        }

    }

优化-引入线程池

    public void start() throws Exception {
        loadServlet();

        //定义线程池改造
        int coolPoolSize = 10;
        int maximumPoolSize = 50;
        long keepAliveTime = 100L;
        BlockingQueue<Runnable> workDequeue = new ArrayBlockingQueue<>(50);
        ThreadFactory threadFactory = Executors.defaultThreadFactory();
        RejectedExecutionHandler handler = new ThreadPoolExecutor.AbortPolicy();
        ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(coolPoolSize, maximumPoolSize, keepAliveTime, TimeUnit.SECONDS, workDequeue, threadFactory, handler);

        ServerSocket serverSocket = new ServerSocket(port);
        System.out.println("MiniCat started with port:" + port);
//        miniCatV3(serverSocket);
        while (true) {
            Socket socket = serverSocket.accept();
            RequestProcessor requestProcessor = new RequestProcessor(socket, servletMap);
            //多线程,不使用线程池
//            requestProcessor.start();
            //使用线程池
            threadPoolExecutor.execute(requestProcessor);
        }
    }

完整代码


工作中遇到的问题分析

1、 service中定义了静态成员变量localdate,期望每次调用service时重新new一个对象,结果只实例化了一次。联想到单例模式也有用静态关键词修饰的,顺便探究一下service类的声明周期。
解答:
如果Servlet还没有被加载,就⽤反射机制创建这个Servlet,并调⽤Servlet的init⽅法来完成初始化。

总结

可扩展的思路:springboot集成的tomcat、Spring管理的bean声明周期。


版权声明:本文为Tianweilan123原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接和本声明。