TOMCAT核心之旅--一個簡單的WEB伺服器--學習心得(一)
阿新 • • 發佈:2019-01-24
TOMCAT核心之旅–一個簡單的WEB伺服器–學習心得(一)
標籤(空格分隔): web伺服器
一、學習背景
本人是一名大三學生,開始以java學習為主,後來學習了javaWEB,瞭解到了TOMCAT伺服器,很好奇其內部是如何實現的,其與瀏覽器是如何聯絡起來的,帶著這一系列問題,我開始了TOMCAT的核心之旅。
二、知識支撐
本次學習藉助HOW TOMCAT WORKS一書,跟隨其思路,實現程式碼。這一節我們主要是瞭解一個WEB伺服器是如何工作的?
三、基礎知識
現在,我們就正式開始我們的學習。首先,我們知道,瀏覽器伺服器這種B/S架構,其通訊主要玩的就是協議!!! 什麼是協議?我們舉個簡單的例子, 如果沒有漢字,語法,文化這些限制,生活中我們還能正常溝通嗎? 這些規範就是協議!而瀏覽器,伺服器之間要通訊,就必須有自己的協議。 Web伺服器也成為超文字傳輸協議(HTTP)伺服器,因為它使用HTTP來跟客戶端進行通訊的,這通常是個web瀏覽器。一個基於java的web伺服器使用兩個重要的類:java.net.Socket和java.net.ServerSocket,並通過HTTP訊息進行通訊。
3.1 超文字傳輸協議(HTTP)
HTTP是一種協議,允許web伺服器和瀏覽器通過網際網路進行來發送和接受資料。它是一種請求和響應協議。客戶端請求一個檔案而伺服器響應請求。HTTP使用可靠的TCP連線--TCP預設使用80埠。第一個HTTP版是HTTP/0.9,然後被HTTP/1.0所替代。正在取代HTTP/1.0的是當前版本HTTP/1.1,它定義於徵求意見文件(RFC) 2616,可以從http://www.w3.org/Protocols/HTTP/1.1/rfc2616.pdf下載。 在HTTP中,始終都是客戶端通過建立連線和傳送一個HTTP請求從而開啟一個事務。web伺服器不需要聯絡客戶端或者對客戶端做一個回撥連線。無論是客戶端或者伺服器都可以提前終止連線。舉例來說,當你正在使用一個web瀏覽器的時候,可以通過點選瀏覽器上的停止按鈕來停止一個檔案的下載程序,從而有效的關閉與web伺服器的HTTP連線。
3.2 HTTP請求
一個HTTP請求包括三個組成部分:
- 方法-統一資源識別符號(URI)-協議/版本
- 請求的頭部
主題內容
下面我們看一個例子: POST /examples/default.jsp HTTP/1.1 Accept: text/plain; text/html Accept-Language: en-gb Connection: Keep-Alive Host: localhost User-Agent: Mozilla/4.0 (compatible; MSIE 4.01; Windows98) Content-Length: 33 Content-Type: application/x-www-form-urlencoded Accept-Encoding: gzip, deflate lastName=Franks&firstName=Michael 這裡POST是請求方法,/examples/default.jsp是URI,而HTTP/1.1是協議/版本部分。每個HTTP請求可以使用HTTP標準裡邊提到的多種方法之一。HTTP 1.1支援7種類型的請求:GET, POST,HEAD,OPTIONS, PUT,DELETE和TRACE。GET和POST在網際網路應用裡邊最普遍使用的。URI完全指明瞭一個網際網路資源。URI通常是相對伺服器的根目錄解釋的。因此,始終一斜線/開頭。統一資源定位器(URL)其實是一種URI(檢視http://www.ietf.org/rfc/rfc2396.txt)來的。該協議版本代表了正在使用的HTTP協議的版本。 請求的頭部包含了關於客戶端環境和請求的主體內容的有用資訊。例如它可能包括瀏覽器設定的語言,主體內容的長度等等。每個頭部通過一個回車換行符(CRLF)來分隔的。 對於HTTP請求格式來說,頭部和主體內容之間有一個回車換行符(CRLF)是相當重要的。CRLF告訴HTTP伺服器主體內容是在什麼地方開始的。在一些網際網路程式設計書籍中,CRLF還被認為是HTTP請求的第四部分。
3.3 HTTP響應
類似於HTTP請求,一個HTTP響應也包括三個組成部分:
- 方法—統一資源識別符號(URI)—協議/版本
- 響應的頭部
主體內容
下面我們看一個例子: HTTP/1.1 200 OK Server: Microsoft-IIS/4.0 Date: Mon, 5 Jan 2017 13:13:33 GMT Content-Type: text/html Last-Modified: Mon, 5 Jan 2004 13:13:12 GMT Content-Length: 112 <html> <head> <title>HTTP Response Example</title> </head> <body> Welcome to My CSDN </body> </html> 響應頭部的第一行類似於請求頭部的第一行。第一行告訴你該協議使用HTTP 1.1,請求成功(200=成功),表示一切都執行良好。 響應頭部和請求頭部類似,也包括很多有用的資訊。響應的主體內容是響應本身的HTML內容。頭部和主體內容通過CRLF分隔開來。 現在你是不是對協議有了瀏覽器與伺服器之間的通訊有了一定的瞭解?那麼我們繼續我們的學習。
3.4 Socket類
套接字是網路連線的一個端點。套接字使得一個應用可以從網路中讀取和寫入資料。放在兩個不同計算機上的兩個應用可以通過連線傳送和接受位元組流。為了從你的應用傳送一條資訊到另一個應用,你需要知道另一個應用的IP地址和套接字埠。在Java裡邊,套接字指的是java.net.Socket類。
一旦你成功建立了一個Socket類的例項,你可以使用它來發送和接受位元組流。要傳送位元組流,你首先必須呼叫Socket類的getOutputStream方法來獲取一個java.io.OutputStream物件。要傳送文字到一個遠端應用,你經常要從返回的OutputStream物件中構造一個java.io.PrintWriter物件。要從連線的另一端接受位元組流,你可以呼叫Socket類的getInputStream方法用來返回一個java.io.InputStream物件。
3.4 ServerSocket類
Socket類代表一個客戶端套接字,即任何時候你想連線到一個遠端伺服器應用的時候你構造的套接字,現在,假如你想實施一個伺服器應用,例如一個HTTP伺服器或者FTP伺服器,你需要一種不同的做法。這是因為你的伺服器必須隨時待命,因為它不知道一個客戶端應用什麼時候會嘗試去連線它。為了讓你的應用能隨時待命,你需要使用java.net.ServerSocket類。這是伺服器套接字的實現。
ServerSocket和Socket不同,伺服器套接字的角色是等待來自客戶端的連線請求。一旦伺服器套接字獲得一個連線請求,它建立一個Socket例項來與客戶端進行通訊。
要建立一個伺服器套接字,你需要使用ServerSocket類提供的四個構造方法中的一個。你需要指定IP地址和伺服器套接字將要進行監聽的埠號。通常,IP地址將會是127.0.0.1,也就是說,伺服器套接字將會監聽本地機器。伺服器套接字正在監聽的IP地址被稱為是繫結地址。伺服器套接字的另一個重要的屬性是backlog,這是伺服器套接字開始拒絕傳入的請求之前,傳入的連線請求的最大佇列長度。
有一定JAVA基礎的同學,我相信,這些基礎知識我們已經耳熟能詳。接下來,我們就藉助這些知識,完成一個最最最最最基礎的WEB伺服器。
四、“活尿泥版”的WEB伺服器
4.1 原始碼
我們的web伺服器應用程式有三個類組成:
HttpServer類
package com.liu.tomcat.simpleTomcat; import java.io.File; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.net.InetAddress; import java.net.ServerSocket; import java.net.Socket; import com.liu.tomcat.simpleTomcat.request.Request; import com.liu.tomcat.simpleTomcat.response.Response; 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 httpServer = new HttpServer(); System.out.println(httpServer.WEB_ROOT); httpServer.await(); } public void await() { ServerSocket serverSocket = null; int port = 8088; 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 inputStream = null; OutputStream outputStream = null; try { //這裡會產生阻塞,等待客戶端的請求 socket = serverSocket.accept(); inputStream = socket.getInputStream(); outputStream = socket.getOutputStream(); // create Request object and parse Request request = new Request(inputStream); request.parse(); // create Response object Response response = new Response(outputStream); 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 (IOException e) { e.printStackTrace(); continue; } } } }
Request類
package com.liu.tomcat.simpleTomcat.request; import java.io.IOException; import java.io.InputStream; public class Request { private InputStream inputStream; private String uri; public String getUri() { return uri; } public Request(InputStream inputStream) { this.inputStream = inputStream; } 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 = inputStream.read(buffer); } catch (IOException e) { e.printStackTrace(); i = -1; } for(int j = 0; j < i; j++) { request.append((char)buffer[j]); } System.out.println("request:\n" + request.toString()); uri = parseUri(request.toString()); } private String parseUri(String requestString) { int index1, index2; index1 = requestString.indexOf(' '); if(index1 != -1) { index2 = requestString.indexOf(' ', index1 + 1); if(index2 > index1) { System.out.println("resquest檔案:" + requestString.substring(index1+1, index2)); return requestString.substring(index1+1, index2); } } return null; } }
Response類
package com.liu.tomcat.simpleTomcat.response; import java.io.File; import java.io.FileInputStream; import java.io.IOException; import java.io.OutputStream; import com.liu.tomcat.simpleTomcat.HttpServer; import com.liu.tomcat.simpleTomcat.request.Request; /* 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 outputStream; public Response(OutputStream outputStream) { super(); this.outputStream = outputStream; } 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()) { String successMessage = "HTTP/1.1 200 OK\r\n" + "Content-Type: text/html\r\n" + "\r\n"; fis = new FileInputStream(file); //把檔案裡的東西讀出來放到bytes字元數組裡 int ch = fis.read(bytes, 0, BUFFER_SIZE); outputStream.write(successMessage.getBytes()); //把bytes數組裡的東西放到要給客戶端回覆的流裡面 while(ch != -1) { outputStream.write(bytes, 0, ch); ch = fis.read(bytes, 0, BUFFER_SIZE); } } else { //file not find String errorMassage = "HTTP/1.1 404 File Not Found\r\n" + "Content-Type: text/html\r\n" + "Content-Length: 23\r\n" + "\r\n" + "<h1>File Not Found</h1>"; outputStream.write(errorMassage.getBytes()); } } catch (Exception e){ // thrown if cannot instantiate a File object System.out.println(e.toString() ); } finally { if(fis != null) { fis.close(); } } } }
4.2 原始碼分析
這個應用程式的入口點(靜態main方法)可以在HttpServer類裡邊找到。main方法建立了一個HttpServer的例項並呼叫了它的await方法。await方法,顧名思義就是在一個指定的埠上等待HTTP請求,處理它們併發送響應返回客戶端。它一直等待直至接收到shutdown命令。
應用程式不能做什麼,除了傳送靜態資源,例如放在一個特定目錄的HTML檔案和影象檔案。它也在控制檯上顯示傳入的HTTP請求的位元組流。不過,它不給瀏覽器傳送任何的頭部例如日期或者cookies。
4.3 執行結果
首先我們必須將我們的WEB伺服器開啟,使其執行起來,這樣子我們在瀏覽器中訪問我們的WEB伺服器,我們來看結果:
至此,本小結結束,累死了。目前已經凌晨1:25,不說了,去睡覺了;有問題請留言哈哈哈!!!