【Tomcat】Tomcat工作原理及簡(jiǎn)單模擬實(shí)現(xiàn)

Tomcat應(yīng)該都不陌生,我們經(jīng)常會(huì)把寫(xiě)好的代碼打包放在Tomcat里并啟動(dòng),然后在瀏覽器里就能愉快的調(diào)用我們寫(xiě)的代碼來(lái)實(shí)現(xiàn)相應(yīng)的功能了,那么Tomcat是如何工作的?

一、Tomcat工作原理

我們啟動(dòng)Tomcat時(shí)雙擊的startup.bat文件的主要作用是找到catalina.bat,并且把參數(shù)傳遞給它,而catalina.bat中有這樣一段話(huà):


image.png

Bootstrap.class是整個(gè)Tomcat 的入口,我們?cè)赥omcat源碼里找到這個(gè)類(lèi),其中就有我們經(jīng)常使用的main方法:


image.png

這個(gè)類(lèi)有兩個(gè)作用 :1.初始化一個(gè)守護(hù)進(jìn)程變量、加載類(lèi)和相應(yīng)參數(shù)。2.解析命令,并執(zhí)行。

源碼不過(guò)多贅述,我們?cè)谶@里只需要把握整體架構(gòu),有興趣的同學(xué)可以自己研究下源碼。Tomcat的server.xml配置文件中可以對(duì)應(yīng)構(gòu)架圖中位置,多層的表示可以配置多個(gè):


image.png

即一個(gè)由 Server->Service->Engine->Host->Context 組成的結(jié)構(gòu),從里層向外層分別是:

Server:服務(wù)器Tomcat的頂級(jí)元素,它包含了所有東西。
Service:一組 Engine(引擎) 的集合,包括線(xiàn)程池 Executor 和連接器 Connector 的定義。
Engine(引擎):一個(gè) Engine代表一個(gè)完整的 Servlet 引擎,它接收來(lái)自Connector的請(qǐng)求,并決定傳給哪個(gè)Host來(lái)處理。
Container(容器):Host、Context、Engine和Wraper都繼承自Container接口,它們都是容器。
Connector(連接器):將Service和Container連接起來(lái),注冊(cè)到一個(gè)Service,把來(lái)自客戶(hù)端的請(qǐng)求轉(zhuǎn)發(fā)到Container。
Host:即虛擬主機(jī),所謂的”一個(gè)虛擬主機(jī)”可簡(jiǎn)單理解為”一個(gè)網(wǎng)站”。
Context(上下文 ): 即 Web 應(yīng)用程序,一個(gè) Context 即對(duì)于一個(gè) Web 應(yīng)用程序。Context容器直接管理Servlet的運(yùn)行,Servlet會(huì)被其給包裝成一個(gè)StandardWrapper類(lèi)去運(yùn)行。Wrapper負(fù)責(zé)管理一個(gè)Servlet的裝載、初始化、執(zhí)行以及資源回收,它是最底層容器。

比如現(xiàn)在有以下網(wǎng)址,根據(jù)“/”切割的鏈接就會(huì)定位到具體的處理邏輯上,且每個(gè)容器都有過(guò)濾功能。


image.png
二、Tomcat實(shí)現(xiàn)思路

下面只是簡(jiǎn)單實(shí)現(xiàn)效果,當(dāng)瀏覽器訪問(wèn)對(duì)應(yīng)地址時(shí):


image.png

實(shí)現(xiàn)以上效果整體思路如下:
1.ServerSocket占用8080端口,用while(true)循環(huán)等待用戶(hù)發(fā)請(qǐng)求。

2.拿到瀏覽器的請(qǐng)求,解析并返回URL地址,用I/O輸入流讀取本地磁盤(pán)上相應(yīng)文件。

3.讀取文件,不存在構(gòu)建響應(yīng)報(bào)文頭、HTML正文內(nèi)容,存在則寫(xiě)到瀏覽器端。

三、實(shí)現(xiàn)Tomcat

工程文件結(jié)構(gòu)和pom.xml文件:


image.png

1.HttpServer核心處理類(lèi),用于接受用戶(hù)請(qǐng)求,傳遞HTTP請(qǐng)求頭信息,關(guān)閉容器:

public class HttpServer {
  // 用于判斷是否需要關(guān)閉容器
  private boolean shutdown = false;
  
  public void acceptWait() {
    ServerSocket serverSocket = null;
    try {
        //端口號(hào),最大鏈接數(shù),ip地址
      serverSocket = new ServerSocket(8080, 1, InetAddress.getByName("127.0.0.1"));
    }
    catch (IOException e) {
        e.printStackTrace();
        System.exit(1); 
    }
    // 等待用戶(hù)發(fā)請(qǐng)求
    while (!shutdown) {
      try {
        Socket socket = serverSocket.accept();
        InputStream is = socket.getInputStream();
        OutputStream  os = socket.getOutputStream();
        // 接受請(qǐng)求參數(shù)
        Request request = new Request(is);
        request.parse();
        // 創(chuàng)建用于返回瀏覽器的對(duì)象
        Response response = new Response(os);
        response.setRequest(request);
        response.sendStaticResource();
        //關(guān)閉一次請(qǐng)求的socket,因?yàn)閔ttp請(qǐng)求就是采用短連接的方式
        socket.close();
        //如果請(qǐng)求地址是/shutdown  則關(guān)閉容器
        if(null != request){
             shutdown = request.getUrL().equals("/shutdown");
        }
      }
      catch (Exception e) {
          e.printStackTrace();
          continue;
      }
    }
  }
  public static void main(String[] args) {
        HttpServer server = new HttpServer();
        server.acceptWait();
  }
}

2.創(chuàng)建Request類(lèi),獲取HTTP的請(qǐng)求頭所有信息并截取URL地址返回:

public class Request {
  private InputStream is;
  private String url;

  public Request(InputStream input) {
    this.is = input;
  }
  public void parse() {
    //從socket中讀取一個(gè)2048長(zhǎng)度字符
    StringBuffer request = new StringBuffer(Response.BUFFER_SIZE);
    int i;
    byte[] buffer = new byte[Response.BUFFER_SIZE];
    try {
      i = is.read(buffer);
    }
    catch (IOException e) {
      e.printStackTrace();
      i = -1;
    }
    for (int j=0; j<i; j++) {
      request.append((char) buffer[j]);
    }
    //打印讀取的socket中的內(nèi)容
    System.out.print(request.toString());
    url = parseUrL(request.toString());
  }

  private String parseUrL(String requestString) {
    int index1, index2;
    index1 = requestString.indexOf(' ');//看socket獲取請(qǐng)求頭是否有值
    if (index1 != -1) {
      index2 = requestString.indexOf(' ', index1 + 1);
      if (index2 > index1)
        return requestString.substring(index1 + 1, index2);
    }
    return null;
  }

  public String getUrL() {
    return url;
  }

}

3.創(chuàng)建Response類(lèi),響應(yīng)請(qǐng)求讀取文件并寫(xiě)回到瀏覽器

public class Response {
  public static final int BUFFER_SIZE = 2048;
  //瀏覽器訪問(wèn)D盤(pán)的文件
  private static final String WEB_ROOT ="D:";
  private Request request;
  private 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 {
        //拼接本地目錄和瀏覽器端口號(hào)后面的目錄
      File file = new File(WEB_ROOT, request.getUrL());
      //如果文件存在,且不是個(gè)目錄
      if (file.exists() && !file.isDirectory()) {
        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 {
           //文件不存在,返回給瀏覽器響應(yīng)提示,這里可以拼接HTML任何元素
          String retMessage = "<h1>"+file.getName()+" file or directory not exists</h1>";
          String returnMessage ="HTTP/1.1 404 File Not Found\r\n" +
                  "Content-Type: text/html\r\n" +
                  "Content-Length: "+retMessage.length()+"\r\n" +
                  "\r\n" +
                  retMessage;
        output.write(returnMessage.getBytes());
      }
    }
    catch (Exception e) {
      System.out.println(e.toString() );
    }
    finally {
      if (fis!=null)
        fis.close();
    }
  }
}

四、擴(kuò)展點(diǎn)
1.在WEB_INF文件夾下讀取web.xml解析,通過(guò)請(qǐng)求名找到對(duì)應(yīng)的類(lèi)名,通過(guò)類(lèi)名創(chuàng)建對(duì)象,用反射來(lái)初始化配置信息,如welcome頁(yè)面,Servlet、servlet-mapping,filter,listener,啟動(dòng)加載級(jí)別等。

2.抽象Servlet類(lèi)來(lái)轉(zhuǎn)碼處理請(qǐng)求和響應(yīng)的業(yè)務(wù)。發(fā)過(guò)來(lái)的請(qǐng)求會(huì)有很多,也就意味著我們應(yīng)該會(huì)有很多的Servlet,例如:RegisterServlet、LoginServlet等等還有很多其他的訪問(wèn)??梢杂玫筋?lèi)似于工廠模式的方法處理,隨時(shí)產(chǎn)生很多的Servlet,來(lái)滿(mǎn)足不同的功能性的請(qǐng)求。

3.使用多線(xiàn)程。本文的代碼是死循環(huán),且只能有一個(gè)鏈接,而現(xiàn)實(shí)中的情況是往往會(huì)有很多很多的客戶(hù)端發(fā)請(qǐng)求,可以把每個(gè)瀏覽器的通信封裝到一個(gè)線(xiàn)程當(dāng)中。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • 在學(xué)習(xí)Servlet是找到一篇不錯(cuò)的文章,轉(zhuǎn)載一下。學(xué)習(xí)心得,Servlet其實(shí)只是個(gè)接口,相當(dāng)于是定義了一個(gè)標(biāo)準(zhǔn)...
    君子若蓮閱讀 1,264評(píng)論 1 16
  • 0 系列目錄# WEB請(qǐng)求處理 WEB請(qǐng)求處理一:瀏覽器請(qǐng)求發(fā)起處理 WEB請(qǐng)求處理二:Nginx請(qǐng)求反向代理 本...
    七寸知架構(gòu)閱讀 14,250評(píng)論 22 189
  • 這是我們自編譯源碼以來(lái)第一次總結(jié) tomcat, 雖然不知從何說(shuō)起, 但這筆不能停下來(lái), 看了很多的文章和源碼, ...
    莫那一魯?shù)?/span>閱讀 5,973評(píng)論 4 28
  • WEB服務(wù)器 只要Web上的Server都叫Web Server,但是大家分工不同,解決的問(wèn)題也不同,所以根據(jù)We...
    Rick617閱讀 15,708評(píng)論 1 13
  • 如果你看看地球的歷史,就會(huì)發(fā)現(xiàn)宗教就是迷信和狂熱崇拜,狂熱癡迷的地球人類(lèi)隨后又創(chuàng)造了宗教里的各個(gè)派系,由此導(dǎo)致了仇...
    心享繪閱讀 7,317評(píng)論 0 7

友情鏈接更多精彩內(nèi)容