深入學習Tomcat----自己動手寫服務器(附服務器源碼)

轉自:http://blog.csdn.net/beijiguangyong/article/details/7439352#comments


相信大多Web開發者對Tomcat是非常熟悉的,衆所周知Tomcat是一款非常好用的開源Servlet容器,您一定對這個最流行的Servlet容器充滿好奇,雖然它並不像一個黑盒子那樣讓人無法觸摸但是Tomcat的源碼的確讓人看起來頭疼。筆者就在這裏和大家共同分析一個簡單的Web服務器是如何工作的源碼下載地址

Web服務器

Web服務器是一個複雜的系統,一個Web服務器要爲一個Servlet的請求提供服務,需要做三件事:

1、創建一個request對象並填充那些有可能被所引用的Servlet使用的信息,如參數、頭部、cookies、查詢字符串等等。一個request對象是javax.servlet.ServletRequest或javax.servlet.http.ServletRequest接口的一個實例

2、創建一個response對象,所引用的servlet使用它來給客戶端發送響應。一個response對象是javax.servlet.ServletRequest或javax.servlet.http.ServletRequest接口的一個實例。

3、調用servlet的service方法,並傳入request和response對象。這裏servlet會從request對象取值,給response寫值。

在正式展示代碼之前還需要了解一些必須額HTTP的知識(如果您對此非常熟悉您可以直接看下面分析代碼)

HTTP

HTTP的定義不知道的童鞋可以自己去度娘,這裏主要要說的就是HTTP協議的格式

HTTP請求包括三部分

1、方法、統一資源標識符(URI)、協議/版本

2、請求的頭部

3、主題內容

下面是一個HTTP請求的例子

  1. POST /examples/default.jsp HTTP/1.1   
  2. Accept: text/plain; text/html   
  3. Accept-Language: en-gb   
  4. Connection: Keep-Alive   
  5. Host: localhost   
  6. User-Agent: Mozilla/4.0 (compatible; MSIE 4.01; Windows 98)   
  7. Content-Length: 33   
  8. Content-Type: application/x-www-form-urlencoded   
  9. Accept-Encoding: gzip, deflate   
  10.    
  11. lastName=Franks&firstName=Michael    

第一行表明這是POST請求方法,/examples/default.jsp是URI,HTTP/1.1是協議以及版本。其中URI指明瞭一個互聯網資源,這裏通常是相對服務器根目錄解釋的,也就是說這個HTTP請求就是告訴服務器我需要這個文件目錄如下:根目錄/ examples/default.jsp。

最後一行是HTTP的主題內容,Servlet會處理請求的主題內容,然後返回給客戶端HTTP響應。

類似於HTTP請求,一個HTTP響應也包括上面三個部分。

1、方法、統一資源標識符(URI)、協議/版本

2、響應的頭部

3、主題內容

下面是一個HTTP響應的例子

  1. HTTP/1.1 200 OK   
  2. Server: Microsoft-IIS/4.0   
  3. Date: Mon, 5 Jan 2004 13:13:33 GMT   
  4. Content-Type: text/html   
  5. Last-Modified: Mon, 5 Jan 2004 13:13:12 GMT   
  6. Content-Length: 112   
  7.    
  8. <html>   
  9. <head>   
  10. <title>HTTP Response Example</title>   
  11. </head>   
  12. <body>   
  13. Welcome to Brainy Software   
  14. </body>   
  15. </html>  

第一行告訴協議版本,以及請求成功(200表示成功)

響應頭部和請求頭部一樣,一些有用的信息。響應的主體就是響應本身HTML內容。

好了基本知識介紹完畢,下面開始解釋代碼

部分相關代碼

  1. import java.net.Socket;  
  2. import java.net.ServerSocket;  
  3. import java.net.InetAddress;  
  4. import java.io.InputStream;  
  5. import java.io.OutputStream;  
  6. import java.io.IOException;  
  7. import java.io.File;  
  8.   
  9. public class HttpServer {  
  10.   
  11.     public static final String WEB_ROOT = System.getProperty("user.dir")  
  12.             + File.separator + "webroot";  
  13.   
  14.     private static final String SHUTDOWN_COMMAND = "/SHUTDOWN";  
  15.   
  16.     private boolean shutdown = false;  
  17.   
  18.     public static void main(String[] args) {  
  19.         HttpServer server = new HttpServer();  
  20.         server.await();  
  21.     }  
  22.   
  23.     public void await() {  
  24.         ServerSocket serverSocket = null;  
  25.         int port = 8080;  
  26.         try {  
  27.             serverSocket = new ServerSocket(port, 1,  
  28.                     InetAddress.getByName("127.0.0.1"));  
  29.         } catch (IOException e) {  
  30.             e.printStackTrace();  
  31.             System.exit(1);  
  32.         }  
  33.   
  34.         while (!shutdown) {  
  35.             Socket socket = null;  
  36.             InputStream input = null;  
  37.             OutputStream output = null;  
  38.             try {  
  39.                 socket = serverSocket.accept();  
  40.                 input = socket.getInputStream();  
  41.                 output = socket.getOutputStream();  
  42.   
  43.                 Request request = new Request(input);  
  44.                 request.parse();  
  45.   
  46.                 Response response = new Response(output);  
  47.                 response.setRequest(request);  
  48.                 response.sendStaticResource();  
  49.   
  50.                 socket.close();  
  51.   
  52.                 shutdown = request.getUri().equals(SHUTDOWN_COMMAND);  
  53.             } catch (Exception e) {  
  54.                 e.printStackTrace();  
  55.                 continue;  
  56.             }  
  57.         }  
  58.     }  
  59. }  

HttpServer類代表一個web服務器。首先提供一個WEB_ROOT所在的目錄和它下面所有的子目錄下靜態資源。其次定義了一箇中止服務的命令,也就是說當得到的請求後面跟/shutdown的時候停止服務,默認是把服務設置爲開啓。下面就是進入main函數了,首先實例化一個HttpServer類,然後就是通過await方法等待客戶端發來的請求。如果客戶端輸入的URL不是http://localhost:8080/SHUTDOWN則表示不停止服務器,然後就是繼續執行await方法中的內容,在await方法中最重要的就是定義兩個對象,一個是request一個是response,下面就來說說Request和Response類。

  1. import java.io.InputStream;  
  2. import java.io.IOException;  
  3.   
  4. public class Request {  
  5.   
  6.     private InputStream input;  
  7.     private String uri;  
  8.   
  9.     public Request(InputStream input) {  
  10.         this.input = input;  
  11.     }  
  12.   
  13.     public void parse() {  
  14.   
  15.         StringBuffer request = new StringBuffer(2048);  
  16.         int i;  
  17.         byte[] buffer = new byte[2048];  
  18.         try {  
  19.             i = input.read(buffer);  
  20.         } catch (IOException e) {  
  21.             e.printStackTrace();  
  22.             i = -1;  
  23.         }  
  24.         for (int j = 0; j < i; j++) {  
  25.             request.append((char) buffer[j]);  
  26.         }  
  27.         System.out.print(request.toString());  
  28.         uri = parseUri(request.toString());  
  29.     }  
  30.   
  31.     private String parseUri(String requestString) {  
  32.         int index1, index2;  
  33.         index1 = requestString.indexOf(' ');  
  34.         if (index1 != -1) {  
  35.             index2 = requestString.indexOf(' ', index1 + 1);  
  36.             if (index2 > index1)  
  37.                 return requestString.substring(index1 + 1, index2);  
  38.         }  
  39.         return null;  
  40.     }  
  41.   
  42.     public String getUri() {  
  43.         return uri;  
  44.     }  
  45.   
  46. }  

首先調用InputStream對象中的read方法獲取HTTP請求的原始數據,然後在parseUri方法中獲得uri也就是要請求的靜態資源。說白了Request類的主要作用就是告訴服務器用戶要的是什麼也就是在http://localhost:8080後面出現的東西。

  1. import java.io.OutputStream;  
  2. import java.io.IOException;  
  3. import java.io.FileInputStream;  
  4. import java.io.File;  
  5.   
  6. public class Response {  
  7.   
  8.     private static final int BUFFER_SIZE = 1024;  
  9.     Request request;  
  10.     OutputStream output;  
  11.   
  12.     public Response(OutputStream output) {  
  13.         this.output = output;  
  14.     }  
  15.   
  16.     public void setRequest(Request request) {  
  17.         this.request = request;  
  18.     }  
  19.   
  20.     public void sendStaticResource() throws IOException {  
  21.         byte[] bytes = new byte[BUFFER_SIZE];  
  22.         FileInputStream fis = null;  
  23.         try {  
  24.             File file = new File(HttpServer.WEB_ROOT, request.getUri());  
  25.             if (file.exists()) {  
  26.                 fis = new FileInputStream(file);  
  27.                 int ch = fis.read(bytes, 0, BUFFER_SIZE);  
  28.                 while (ch != -1) {  
  29.                     output.write(bytes, 0, ch);  
  30.                     ch = fis.read(bytes, 0, BUFFER_SIZE);  
  31.                 }  
  32.             } else {  
  33.                   
  34.                 String errorMessage = "HTTP/1.1 404 File Not Found\r\n"  
  35.                         + "Content-Type: text/html\r\n"  
  36.                         + "Content-Length: 23\r\n" + "\r\n"  
  37.                         + "<h1>File Not Found</h1>";  
  38.                 output.write(errorMessage.getBytes());  
  39.             }  
  40.         } catch (Exception e) {  
  41.               
  42.             System.out.println(e.toString());  
  43.         } finally {  
  44.             if (fis != null)  
  45.                 fis.close();  
  46.         }  
  47.     }  
  48. }  

Response類代表一個HTTP響應。首先Response接收一個OutputStream對象,然後通過sendStaticResource方法對接收的Request進行處理,整個處理過程就是根據請求在服務器端進行尋找對應靜態資源的過程。找到所需要的資源後發送給客戶端然後讓客戶端顯示出來。

運行程序

運行上面的HttpServer類,然後在瀏覽器的地址欄中鍵入下面的地址:http:localhost:8080/index.jsp,然後你會在瀏覽器中看到index.jsp頁面。

在控制檯可以看到類似於下面的HTTP請求

  1. GET /index.jsp HTTP/1.1  
  2. Host: localhost:8080  
  3. Connection: keep-alive  
  4. Cache-Control: max-age=0  
  5. User-Agent: Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/535.7 (KHTML, like Gecko) Chrome/16.0.912.75 Safari/535.7 360EE  
  6. Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8  
  7. Accept-Encoding: gzip,deflate,sdch  
  8. Accept-Language: zh-CN,zh;q=0.8  
  9. Accept-Charset: GBK,utf-8;q=0.7,*;q=0.3  


小結

上面自己動手寫的這個所謂的服務器僅僅有三個類組成,從功能上來說他只能顯示一些靜態的資源,並不是全部功能。一個優秀的服務器還有很多細節要做,但是出於學習的目的大家現在有這些瞭解就足夠了,後面還會有對服務器的詳細介紹,敬請期待。



發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章