乐趣区

关于读书:HowTomcatWork笔记总结一

《HowTomcatWork》笔记总结(一)

前言

​ 这一篇是 howTomcatWork 的书籍笔记内容。上面是依据书中的内容做的一部分集体笔记。

书籍地址:

链接:https://pan.baidu.com/s/1jazo…
提取码:lp96
– 来自百度网盘超级会员 V6 的分享

集体评估

​ 没啥好说的,tomcat 作者写的书,看了下海淘竟然要 500 多的确吓到了。尽管代码应用的是 tomcat5 的版本,然而能够根本了解 tomcat 的外部工作机制。也能够看到作者是如何用一个十多行的肉鸡服务器代码一直降级成为当初的 tomcat 的模样。

​ 本文是集体依据看书记录的一些笔记,两头逻辑不肯定连贯,因为有些内容过于根底没有记录的价值,所以挑了一些集体关注的点。

一个最简略的 Servlet 是如何工作的

  1. 创立 Request 对象,并且解析 HTTP 的申请信息,通过 Request 对象封装了这些信息的具体细节

具体接口:

javax.servlet.ServletRequest javax.servlet.http.ServletRequest

  1. 创立 Response 对象, 封装了客户需须要的真正数据,封装了响应体的相干信息

javax.servlet.ServletResponsejavax.servlet.http.ServletResponse

  1. servlet 的service 办法,依据此办法对于申请头进行解析,同时创立 response 将数据回传给客户端

tomcat 的根本构造

​ Tomcat 把服务器在大体上能够拆分为两局部,一部分叫做 容器 ,另一部分叫做 连接器

连接器

作用:接管到每一个 HTTP 申请结构一个 ==request== 和 ==response== 对象

容器

作用:承受连接器的申请依据 service 办法进行响应给对应的客户端

Tomcat 4 和 和 5 的次要区别

  • Tomcat 5 反对 Servlet 2.4 和 JSP 2.0 标准,而 Tomcat 4 反对 Servlet 2.3 和 JSP 1.2。
  • 比起 Tomcat 4,Tomcat 5 有一些更有效率的默认连接器。
  • Tomcat 5 共享一个后盾解决线程,而 Tomcat 4 的组件都有属于本人的后盾解决线程。
    因而,就这一点而言,Tomcat 5 耗费较少的资源。
  • Tomcat 5 并不需要一个映射组件 (mapper component) 用于查找子组件,因而简化了代码。

构建一个最简略的 web 程序

构建对象

​ 下方的代码简略浏览即可,无需本人入手试验

HttpServer

用于构建一个服务器,同时建设 serverSocket 套接字期待链接

  • 调用 httprequest.parse() 办法

代码如下:

public class HttpServer {

    /**
     * 敞开容器的申请门路
     */
    private static final String SHUTDOWN = "/SHUTDOWN";

    /**
     * 动态资源根门路
     */
    public static final String WEBROOT = System.getProperty("user.dir") + File.separator + "webroot";

    /**
     * 是否敞开标识
     */
    private boolean SHUTDOWN_FLAG = false;

    public static void main(String[] args) {new HttpServer().await();}

    /**
     * 具体的 server 办法,期待 socket 申请
     */
    public void await() {
        // 默认为 8080 端口
        int port = 8080;
        String host = "127.0.0.1";
        ServerSocket serverSocket = null;
        try {
            // 创立套接字
            serverSocket = new ServerSocket(port, 1, InetAddress.getByName("127.0.0.1"));
        } catch (IOException e) {e.printStackTrace();
            System.exit(-1);
        }

        while (!SHUTDOWN_FLAG) {
            try {
                // 期待套接字
                Socket accept = serverSocket.accept();
                HttpRequest httpRequest = new HttpRequest(accept.getInputStream());
                // 解决申请数据
                httpRequest.parse();
                // 创立响应对象,解决响应信息
                HttpResponse httpResponse = new HttpResponse(accept.getOutputStream());
                // 设置动态资源
                httpResponse.setRequest(httpRequest);
                httpResponse.setResource();
                // 敞开的套接字
                accept.close();
                // 判断申请 Url 是否为 /shutdown
                SHUTDOWN_FLAG = httpRequest.getUri().equalsIgnoreCase(SHUTDOWN);
            } catch (IOException e) {e.printStackTrace();
                continue;
            }
        }

    }
}

HttpRequest

​ 以 httpserver 的申请 inputstream, 解析申请内容,合成申请 uri

​ 应用 parse() 办法解析申请信息,设置到 stringbuffer 外面

​ 应用 parseUri(str) 截取申请信息的申请 uri, 设置到属性外面

public class HttpRequest {

    /**
     * 缓冲区的大小为 1M
     */
    private static final int BUFFER_COUNT = 1024;

    /**
     * 申请门路
     */
    private String uri;

    /**
     * 申请流
     */
    private InputStream inputStream;

    public HttpRequest(InputStream inputStream) {this.inputStream = inputStream;}

    /**
     * 解析 inputstream 对于内容进行解析
     */
    public void parse() {
        // 字符串缓冲池
        StringBuffer stringBuffer = new StringBuffer(BUFFER_COUNT);

        byte[] byteBuffer = new byte[BUFFER_COUNT];

        if (inputStream == null) {System.err.println("未找到套接字");
            return;
        }

        int read = 0;
        try {
            // 读取数据到 byte 数组
            read = inputStream.read(byteBuffer);
        } catch (IOException e) {e.printStackTrace();
            System.exit(-1);
        }
        // 读取 byte 数组的数据进入到 stringbuffer
        for (int i = 0; i < read; i++) {stringBuffer.append((char)byteBuffer[i]);
        }
        // 打印 stringbuffer
        System.err.println(stringBuffer.toString());
        // 获取 uri
        uri = parseUri(stringBuffer.toString());
    }

    /**
     * 解析申请,获取申请 Uri
     * @param requestString 须要解决的 uri
     */
    public String parseUri(String requestString){
        // 建设 index1 和 2
        int index1, index2;
        // 获取到第一个空行
        index1 = requestString.indexOf(' ');
        if(index1 != -1){
            // 从 index1 开始找
            index2 = requestString.indexOf(' ', index1 + 1);
            if(index2 > index1){
                // 获取申请门路
                return requestString.substring(index1 + 1, index2);
            }
        }
        return null;

    }


    public String getUri() {return uri;}
}

HttpResonse

​ 以 httpserver 的申请 outputstream,获取输出流,将数据返回给客户端

​ 要害办法为setResouces,获取申请 Uri,同时应用 file 读取文件

public class HttpResponse {

    /**
     * 组合 httprequest
     * 依据 request 返回对应到信息
     */
    private HttpRequest request;

    /**
     * 输入流
     */
    private OutputStream outputStream;

    /**
     * 缓冲区大小
     */
    private static final int BUFFER_COUNT = 1024;


    public HttpResponse(OutputStream outputStream) {this.outputStream = outputStream;}

    /**
     * 设置动态资源
     */
    public void setResource() throws IOException {
        String errMsg = "404 msg";
        // 字节缓存区
        byte[] bytes = new byte[BUFFER_COUNT];
        // 读取动态资源
        File file = new File(HttpServer.WEBROOT, request.getUri());
        if (file.exists()) {
            // 文件流
            try {FileInputStream fileInputStream = new FileInputStream(file);
                // 读取字节
                int ch = fileInputStream.read(bytes, 0, BUFFER_COUNT);
                // 输入
                while (ch != -1) {
                    // 写入流
                    outputStream.write(bytes, 0, ch);
                    // 反复读取数据到缓冲区
                    ch = fileInputStream.read(bytes, 0, BUFFER_COUNT);
                }

            } catch (IOException e) {e.printStackTrace();
            } finally {if (outputStream != null) {outputStream.close();
                }
            }
        } else {
            try {outputStream.write(errMsg.getBytes());
            } catch (IOException e) {e.printStackTrace();
            } finally {if (outputStream != null) {outputStream.close();
                }
            }
        }
    }

    /**
     * 设置 request
     *
     * @param httpRequest
     */
    public void setRequest(HttpRequest httpRequest) {this.request = httpRequest;}
}

根本步骤:

上面是代码的根本交互步骤:

  1. 创立 httpserver 对象
  2. 绑定端口和主机,建设套接字连贯
  3. accept()办法期待申请,阻塞以后线程
  4. 创立 reuqest 申请对象
  5. 获取 inputstream 解析申请信息
  6. 获取申请到 uri, 设置到 Request 对象
  7. 创立 response 响应对象
  8. 设置响应对象的 request,拿到 uri, 同时应用 Io,读取申请对应到文件
  9. outputstream 解析文件流数据,应用 write 返回到客户端
  10. 无论胜利还是失败,敞开流(重要)

补充内容:

​ 这里重点关注对于套接字的一些常识

ServerSocket

​ 服务器套接字的另一个重要的属性是 backlog,这是服务器套接字开始 == 回绝 == 传入的申请之前,传入的连贯申请的 == 最大队列长度 ==。

parseUri() 解决逻辑

GET /index.html HTTP/1.1。parse 办法从传递给 Requst 对象的套接字的 InputStream 中读取整个字节流并在一个缓冲区中存储字节数组。而后它应用缓冲区字节数据的字节来填入一个 StringBuffer 对象,并且把代表 StringBuffer 的字符串传递给 parseUri 办法。

Http 申请对于 servlet 的操作

​ 当第一次调用 servlet 的时候,加载该 servlet 类并调用 servlet 的 init 办法(仅仅一次)。

  • 对每次申请,结构一个 javax.servlet.ServletRequest 实例和一个
    javax.servlet.ServletResponse 实例。
  • 调用 servlet 的 service 办法,同时传递 ServletRequest 和 ServletResponse 对象。
  • 当 servlet 类被敞开的时候,调用 servlet 的 destroy 办法并卸载 servlet 类。
    本章的第一个 servlet 容器不是全功能的。因而,她不能运行什么除了非常简单的 servlet,
    而且也不调用 servlet 的 init 办法和 destroy 办法。相同它做了上面的事件:
  • 期待 HTTP 申请。
  • 结构一个 ServletRequest 对象和一个 ServletResponse 对象。
  • 如果该申请须要一个动态资源的话,调用 StaticResourceProcessor 实例的 process 方
    法,同时传递 ServletRequest 和 ServletResponse 对象。
  • 如果该申请须要一个 servlet 的话,加载 servlet 类并调用 servlet 的 service 办法,
    同时传递 ServletRequest 和 ServletResponse 对象

StringManager

特点:

  1. 应用单例模式
  2. 每个实例会读取包对应的一个属性文件
  3. StringManager 类被设计成一个 StringManager 实例能够被包里边的所有类共享
  4. getManager() 办法被 同步润饰,并且应用 hashtable 对于 manager 进行治理(tomcat4)

外围办法解释

SocketInputStream:套接字读取流,次要用于解决 Http 申请中的各种参数,为了提高效率,应用懒加载个性读取

  1. 回收查看流数据
  2. 查看空行,如果呈现 - 1 抛出结尾异样
  3. 获取 servlet 办法名称

    1. 如果缓冲区曾经满了,则进行扩大

      1. 咱们在外部缓冲区的止境
      2. 如果到了缓冲区结尾,将指针归位
    2. 这里有一个关键点:System.arraycopy 用来扩大缓冲区
  4. 浏览协定

模块解释

​ 对于局部模块的相干解释。

解析头部

  • 你能够通过应用类的无参数构造方法结构一个 HttpHeader 实例。
  • 一旦你领有一个 HttpHeader 实例,你能够把它传递给 SocketInputStream 的 readHeader
    办法。如果这里有头部须要读取,readHeader 办法将会相应的填充 HttpHeader 对象。
    如果再也没有头部须要读取了,HttpHeader 实例的 nameEnd 和 valueEnd 字段将会置零。
  • 为了获取头部的名称和值,应用上面的办法:
  • String name = new String(header.name, 0, header.nameEnd);
  • String value = new String(header.value, 0, header.valueEnd);

启动器

  • 启动应用程序
  • 连接器
  • 创立一个 HttpRequest 对象
  • 创立一个 HttpResponse 对象
  • 动态资源处理器和 servlet 处理器
  • 运行应用程序

startup 模块只有一个类,Bootstrap,用来启动利用的。connector 模块的类能够分为五组:

  • 连接器和它的撑持类(HttpConnector 和 HttpProcessor)。
  • 指代 HTTP 申请的类 (HttpRequest) 和它的辅助类。
  • 指代 HTTP 响应的类 (HttpResponse) 和它的辅助类。
  • Facade 类(HttpRequestFacade 和 HttpResponseFacade)。
  • Constant 类

问题以及解决

​ 如何防止在 servlet 调用连接器的时候,不须要申请参数能够防止掉 getParamMap,getAttribute 等低廉开销的操作?

​ Tomcat 的默认连接器 (和本章应用程序的连接器) 试图不解析参数直到 servlet 真正须要它的时候,通过这样来取得更高效率

小常识补充

  1. System 在打印的时候 print 办法不会刷新输入。
  2. 在一个 servlet 容器里边,一个类加载器能够找到 servlet 的中央被称为资源库(repository)。
  3. 通过外观模式将 Request 对象的细节暗藏,setvlet 调用外部无奈晓得,然而在解析的时候仍然能够互相通信,只须要应用 faced 将接口进行一层包裹,即可保障 getUri()办法安全性

总结

​ 书中第一个章节内容比较简单,后续章节代码的难度会逐步回升,同时应用了不少的设计模式也是须要多加浏览了解和消化的

写在最初

​ 这篇笔记目标是让更多人理解这本书,这本书算是一本神书,毕竟开发的原作者本人写的货色毫无疑问是一手常识了。

退出移动版