博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
探秘Tomcat——从一个简陋的Web服务器开始
阅读量:6428 次
发布时间:2019-06-23

本文共 6561 字,大约阅读时间需要 21 分钟。

前言:

  无论是之前所在实习单位小到一个三五个人做的项目,还是如今一个在做的百人以上的产品,一直都能看到tomcat的身影。工作中经常遇到的操作就是启动和关闭tomcat服务,或者修改了摸个java文件,编译该文件,将生成的class文件塞到tomcat目录下相应的jar包中去,以使其生效,但是也可以热部署,不需要这么繁琐的操作。

  总之,一直以来都是习惯了tomcat的存在,没有深究tomcat的运行机制和原理,上一次对于tomcat源码的跃跃欲试还是去年的事儿了——《》。在这篇文章中,我下载了tomcat6版本的代码,并将其导入到eclipse中,时隔一年多,原来的项目还在,但是为显诚意,我还是重头做了一遍导入tomcat源码到eclipse的操作。

  对于tomcat的崇敬之情让我决定深入其中,一探究竟。于是我找到了网上大家首推的教材——《深入剖析tomcat》,书比较老,但是很权威,不影响原理性东西的理解。目前已经看到第二章。

 

  读过或者了解该书的应该都知道,这不是一本上来就直接告诉你tomcat的设计思想,用到的什么设计模式或者源码中某一行有什么匠心独运的地方。该书采用一个循序渐进的方式从一个简单的不能再简单的servlet容器开始,之后慢慢丰富,添加功能模块,最终形成我们想知道的tomcat的模样。

 

背景知识:

  • HTTP请求:

    请求方法——统一资源标识符URI——协议/版本

    请求头

    实体

比如这里我们可以看到请求的方法Request Method是GET, Request URL为http://tech.qq.com/a/20160604/007535.htm,并且分别有Request和下面要讲的Response的请求头信息,如Content-Type等。

 

  • HTTP共支持7中请求方法

    GET,POST,HEAD,OPTIONS,PUT,DELETE,TRACE

 

  • HTTP响应

    协议——状态码——描述

    响应头

    响应实体段

 

  • Socket

    socket在应用程序中用于从网络中读取数据,实现不同计算机之间的通讯,实现一个socket需要知道对应应用程序的ip地址和端口号。

    首先创建该socket类的实例,有了该实例后,就可以使用它实现发送或接收字节流。如果想要发送字节流,需要调用socket类的getOutputStream来获取一个java.io.OutputStream对象;要发送文本到远程应用程序,需要使用返回的OutputStream对象创建一个java.io.PrintWriter对象;要从连接的另一端接收字节流,需要调用Socket类的getInputStream方法,其会返回一个java.io.InputStream对象。

 

  • ServerSocket类

    有了客户端的socket可以发送请求,如果没有服务端来响应,发送的请求也是肉包子打狗,ServerSocket就是充当服务端的角色,serversocket出于随时待命的状态,一旦有客户端发出请求,serversocket就要给出响应,从而实现与客户端的通信。

 

请求响应模型

  有了以上的背景知识,我们就可以实现一个简单到爆的通讯模型,新建一个socket客户端通讯类,用于发送和接收数据,还需要创建一个服务端的ServerSocket用于监听和响应客户端的请求。

  主要包含以下三个类:

  HttpServer:模拟一个Web服务器

 

package myTest;import java.net.Socket;import java.net.ServerSocket;import java.net.InetAddress;import java.io.InputStream;import java.io.OutputStream;import java.io.IOException;import java.io.File;public class HttpServer {  /** WEB_ROOT is the directory where our HTML and other files reside.   *  For this package, WEB_ROOT is the "webroot" directory under the working   *  directory.   *  The working directory is the location in the file system   *  from where the java command was invoked.   */  public static final String WEB_ROOT =    System.getProperty("user.dir") + File.separator  + "webroot";  // shutdown command  private static final String SHUTDOWN_COMMAND = "/SHUTDOWN";  // the shutdown command received  private boolean shutdown = false;  public static void main(String[] args) {    HttpServer server = new HttpServer();    server.await();  }  public void await() {    ServerSocket serverSocket = null;    int port = 8080;    try {      serverSocket =  new ServerSocket(port, 1, InetAddress.getByName("127.0.0.1"));    }    catch (IOException e) {      e.printStackTrace();      System.exit(1);    }    // Loop waiting for a request    while (!shutdown) {      Socket socket = null;      InputStream input = null;      OutputStream output = null;      try {        socket = serverSocket.accept();        input = socket.getInputStream();        output = socket.getOutputStream();        // create Request object and parse        Request request = new Request(input);        request.parse();        // create Response object        Response response = new Response(output);        response.setRequest(request);        response.sendStaticResource();        // Close the socket        socket.close();        //check if the previous URI is a shutdown command        shutdown = request.getUri().equals(SHUTDOWN_COMMAND);      }      catch (Exception e) {        e.printStackTrace();        continue;      }    }  }}

  

从代码可以看出:

  1. 类中分别创建了socket和serversocket;
  2. 定义了一个WEB_ROOT目录,其中存放了对应请求的相应结果文件;
  3. 定义了一个关闭命令,通过在浏览器中输入类似.../SHUTDOWN来关闭Web服务器
  4. 创建了一个await方法,一直监听127.0.0.1的8080端口,如果有请求产生(比如在浏览器中输入一个请求地址),则会进入await方法并执行serverSocket的accept方法。

 

  Request类:

  模拟一个HTTP请求。

 

package myTest;import java.io.InputStream;import java.io.IOException;public class Request {  private InputStream input;  private String uri;  public Request(InputStream input) {    this.input = input;  }  public void parse() {    // Read a set of characters from the socket    StringBuffer request = new StringBuffer(2048);    int i;    byte[] buffer = new byte[2048];    try {      i = input.read(buffer);    }    catch (IOException e) {      e.printStackTrace();      i = -1;    }    for (int j=0; j
index1) return requestString.substring(index1 + 1, index2); } return null; } public String getUri() { return uri; }}

  

从代码中可以发现:

  1. 可以实现传递InputStream对象,在处理与客户端通讯的Socket对象中获取;
  2. 调用InputStream对象的read来获取HTTP请求的原始数据;
  3. parse方法用于解析HTTP请求中的原始数据(原始数据由上面的getInputStream中获得);
  4. parseUri作为一个私有方法被parse调用,用于解析HTTP请求的URI

 

  Response类:

  模拟HTTP的响应。

 

package myTest;import java.io.OutputStream;import java.io.IOException;import java.io.FileInputStream;import java.io.File;/*  HTTP Response = Status-Line    *(( general-header | response-header | entity-header ) CRLF)    CRLF    [ message-body ]    Status-Line = HTTP-Version SP Status-Code SP Reason-Phrase CRLF*/public class Response {  private static final int BUFFER_SIZE = 1024;  Request request;  OutputStream output;  public Response(OutputStream output) {    this.output = output;  }  public void setRequest(Request request) {    this.request = request;  }  public void sendStaticResource() throws IOException {    byte[] bytes = new byte[BUFFER_SIZE];    FileInputStream fis = null;    try {      File file = new File(HttpServer.WEB_ROOT, request.getUri());      if (file.exists()) {        fis = new FileInputStream(file);        int ch = fis.read(bytes, 0, BUFFER_SIZE);        while (ch!=-1) {          output.write(bytes, 0, ch);          ch = fis.read(bytes, 0, BUFFER_SIZE);        }      }      else {        // file not found        String errorMessage = "HTTP/1.1 404 File Not Found\r\n" +          "Content-Type: text/html\r\n" +          "Content-Length: 23\r\n" +          "\r\n" +          "

File Not Found

"; output.write(errorMessage.getBytes()); } } catch (Exception e) { // thrown if cannot instantiate a File object System.out.println(e.toString() ); } finally { if (fis!=null) fis.close(); } }}

  

从代码可以发现:

  1. Request类似,这里通过接受OutputStream构造了Response对象;
  2. setRequest方法用于接收Request对象,因为在Response中需要用到Request的getUri方法;
  3. sendStaticResource方法主要用于处理请求的响应,如这里发送一个静态资源html作为请求的结果

 

至此, 本篇主要提到:

  • 一些基本概念如http请求、http响应、socket等;
  • 对于一个超级简陋的web服务器有了基本的认识;
  • 明确了客户端和服务端各自的角色和职责。

 

如果您觉得阅读本文对您有帮助,请点一下“推荐”按钮,您的“推荐”将是我最大的写作动力!如果您想持续关注我的文章,请扫描二维码,关注JackieZheng的微信公众号,我会将我的文章推送给您,并和您一起分享我日常阅读过的优质文章。

 

  

友情赞助

如果你觉得博主的文章对你那么一点小帮助,恰巧你又有想打赏博主的小冲动,那么事不宜迟,赶紧扫一扫,小额地赞助下,攒个奶粉钱,也是让博主有动力继续努力,写出更好的文章^^。

    1. 支付宝                          2. 微信

                      

 

转载于:https://www.cnblogs.com/bigdataZJ/p/TomcatSourceZJ2.html

你可能感兴趣的文章
二分答案经典入门题:)
查看>>
为什么你需要将代码迁移到ASP.NET Core 2.0?
查看>>
思杰的雄心——软件定义的工作空间
查看>>
Servlet的多线程和线程安全
查看>>
存储树形的数据表转为Json
查看>>
CAN 总线通信控制芯片SJA1000 的读写
查看>>
oauth授权协议的原理
查看>>
OutputCache说明
查看>>
sdl2.0示例
查看>>
数学 --- 高斯消元 POJ 1830
查看>>
Ejabberd源码解析前奏--集群
查看>>
[ZHUAN]Flask学习记录之Flask-SQLAlchemy
查看>>
【转】Install SmartGit via PPA in Ubuntu 13.10/13.04/12.04/Linux Mint
查看>>
PNG怎么转换成32位的BMP保持透明
查看>>
经验分享:CSS浮动(float,clear)通俗讲解
查看>>
WTL中最简单的实现窗口拖动的方法(转)
查看>>
数据结构—队列
查看>>
C. Adidas vs Adivon
查看>>
BZOJ4241 : 历史研究
查看>>
(LeetCode)两个队列来实现一个栈
查看>>