關於這篇文章也確實籌划了很久,今天決定開篇寫第一篇,說起tomcat首先很容易聯想到IIS,因為我最開始使用的就是.net技術,我第一次使用asp寫學生成績管理系統后,很茫然如何讓別人都能看到或者說使用這個系統呢?由此認識了IIS,它是一個web容器,天生的多線程,及時響應用戶提交的請求返回html頁面,這就是我了解的最初的web容器的功能,由此我們來認識tomcat也並不困難,可以的話,在了解完tomcat后我們可以繼續了解jboss、jetty等,好我們進入主題。
我們在平時開發的過程中是在使用eclipse時候才啟動tomcat,對於一個web容器而言,簡而言之,它是系統的一個守護進程,守護着對這台服務器某個端口發起的請求,基於這一點,它就需要一個監聽程序,這個監聽程序來獲取來自這個端口的特定請求的數據,ok,直接點講,我們這里使用Socket來獲取某個端口,通常是80端口的http請求,通過簡單的Java
程序的死循環(粗糙的做法,后面逐步優化)來實現不斷的獲取80端口http請求,來達到監聽80端口http請求的目的。java.net包下面的Socket和ServerSocket兩個類就能實現我們對8080端口的監聽,去除中間的邏輯代碼,我們只看這個兩個類的演繹的話如下:
1 ServerSocket serverSocket = new ServerSocket(8080, 1, InetAddress.getByName("10.10.10.106"));
對本機的8080端口進行監聽
1 socket = serverSocket.accept(); 2 input = socket.getInputStream(); 3 output = socket.getOutputStream();
以上代碼就是獲取監聽結果。
這是最簡單和最精簡的Socket通訊原理,基於這個核心點我們來開發一個簡易的,可以提供靜態頁面訪問的 custom tomcat,准備一個index.html文件放到/home/webroot目錄下,那么除去拓展上面代碼外,我們還需要一個Response和一個Request。
類設計如下:
HttpServer: 主函數所在類,負載啟動ServerSocket和 操作整合Socket監聽到的數據,以及返回結果,即操作Response和Request。
Request: 封裝Socket監聽到的用戶端請求,包括請求的http uri信息。
Response: 封裝需要推送到客戶端的結果數據,即我們需要根據http uri 去本機尋找相應的資源,寫給客戶端。
言簡意賅,進入代碼,首先 Request類代碼:
1 public class Request 2 { 3 private InputStream input; 4 private String uri; 5 6 public Request(InputStream input) { 7 this.input = input; 8 } 9 10 public void parse() 11 { 12 StringBuffer request = new StringBuffer(2048); 13 int i; 14 byte[] buffer = new byte[2048]; 15 try 16 { 17 i = input.read(buffer); 18 } 19 catch(IOException e) 20 { 21 e.printStackTrace(); 22 i = -1; 23 } 24 25 for (int j=0; j<i; j++) 26 { 27 request.append((char) buffer[j]); 28 } 29 System.out.print(request.toString()); 30 uri = parseUri(request.toString()); 31 } 32 33 private String parseUri(String requestString) 34 { 35 int index1, index2; 36 index1 = requestString.indexOf(' '); 37 if (index1 != -1) { 38 index2 = requestString.indexOf(' ', index1 + 1); 39 if (index2 > index1) 40 return requestString.substring(index1 + 1, index2); 41 } 42 return null; 43 } 44 45 public String getUri() 46 { 47 return uri; 48 } 49 }
代碼解釋:類包括一個屬性和兩個方法,input屬性即是從Socket監聽到的信息,Socket會將監聽到的信息放入一個InputStream中,我們使用Reqeust類的Input屬性來接受。接收到輸入流后,在parse中對這個輸入流進行解析成字符串,即對Http請求進行拆解,得到完整的Http URL,所以這個方法是私有的,是類存在意義的核心所在,而提供的對外方法parseUri是負載將parse解析的url結果提供給外界,即,客戶端發來請求那個文件,具體的是最終提供給Response類,Response類得到這個文件名稱后,去本地制定目錄讀取文件。Tomcat中通常就是webapps目錄啦,很熟悉了吧,哈哈。
Response類如何實現這個讀取文件的歷史使命呢,代碼如下:
1 public class Response { 2 3 private static final int BUFFER_SIZE = 1024; 4 Request request; 5 OutputStream output; 6 7 public Response(OutputStream output) 8 { 9 this.output = output; 10 } 11 12 public void setRequest(Request request) 13 { 14 this.request = request; 15 } 16 17 public void sendStaticResource() throws IOException 18 { 19 byte[] bytes = new byte[BUFFER_SIZE]; 20 FileInputStream fis = null; 21 try 22 { 23 File file = new File(HttpServer.WEB_ROOT, request.getUri()); 24 if (file.exists()) 25 { 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 } 33 else 34 { 35 String errorMessage = "HTTP/1.1 404 File Not Found\r\n" + 36 "Content-Type: text/html\r\n" + 37 "Content-Length: 23\r\n" + 38 "\r\n" + 39 "<h1>File Not Found</h1>"; 40 output.write(errorMessage.getBytes()); 41 } 42 } 43 catch(Exception e) 44 { 45 System.out.println(e.toString()); 46 } 47 finally{ 48 fis.close(); 49 } 50 51 } 52 }
代碼解釋:Response一共三個屬性,一個方法。三個屬性,一個是設置屬性,BUFFER_SIZE設置讀寫字節流大小,關於讀寫文件,我個人覺得和服務器的性能和程序性能息息相關,不宜設定過大或過小(此處有不同見解的同仁歡迎來噴,我對這塊理解目前限於此)。Reqeust屬性,對照前文呼應,Response需要獲取Request類的uri結果信息,所以這里放了一個Request屬性,獲取uri。Output,就不用說了,也是這個類存在的核心意義,依照Request類提供的uri信息,在本地讀寫文件后,形成一個輸出來,存放到output中,那么這項工作就由sendStaticResource這個共有方法完成啦。
好,代碼到這個,可以說我們大家已經看到一個tomcat模型了,有點萬事俱備,只欠東風的感覺,客戶端發起請求,Response和Reqeust有了,那么繼續往上游考慮,Reqeust依賴於客戶端的請求,自然以來於Socket數據。我們在這里做得簡便一點,將ServerSocket和Socket封裝到一個HttpServer類中來,代碼如下:
1 public class HttpServer { 2 3 public static final String WEB_ROOT = System.getProperty("user.dir") + File.separator + "webroot"; 4 private static final String SHUTDOWN_COMMAND = "/SHUTDOWN"; 5 private boolean shutdown = false; 6 public static void main(String[] args) 7 { 8 HttpServer httpServer = new HttpServer(); 9 httpServer.await(); 10 } 11 12 public void await() 13 { 14 ServerSocket serverSocket = null; 15 Integer port = 8080; 16 try 17 { 18 serverSocket = new ServerSocket(port, 1, InetAddress.getByName("10.10.10.106")); 19 } 20 catch(IOException e) 21 { 22 e.printStackTrace(); 23 System.exit(1); 24 } 25 26 while(!shutdown) 27 { 28 Socket socket = null; 29 InputStream input = null; 30 OutputStream output = null; 31 try 32 { 33 socket = serverSocket.accept(); 34 35 input = socket.getInputStream(); 36 output = socket.getOutputStream(); 37 Request request = new Request(input); 38 request.parse(); 39 Response response = new Response(output); 40 response.setRequest(request); response.sendStaticResource(); socket.close(); 41 shutdown = request.getUri().equals(SHUTDOWN_COMMAND); 42 } 43 catch(Exception e) 44 { 45 e.printStackTrace();continue; 46 } 47 } 48 } 49 }
代碼解釋:我們知道啟動tomcat之后,只要服務正常,客戶端任意時候發起一個http請求,tomcat就會響應,那么這里我們肯定需要一個while循環來模擬不間斷的監聽,類await方法就是負責不斷的獲取socket監聽到的結果,有立刻調動Reqeust和Response進行響應,加入主函數,為的是我們這個是模擬的控制台程序,需要一個程序入口,main函數就是程序入口。此外,HttpServer類包括一個靜態屬性SHUTDOWN_COMMAND,輸入為true則停止這個main函數,變量初始值為false,當客戶端也就是Request響應得到客戶端輸入 http://10.10.10.108:8080/SHUTDOWN時候,則變量在while中會置成true,緊接着停止main,結束應用程序進程。
在eclipse中或者在命令行中啟動這個main函數,命令行則是輸入 java HttpServer.java。eclipse則是在main函數中右鍵 run as application啟動。我們打開瀏覽器,輸入 http://10.10.10.108:8080/index.html,回車結果如下:
本地文件:
好了,夜深啦,就此擱筆了,拋磚引玉,歡迎提議和討論,這個系列會繼續下去,直到一個完整的可以響應一個java action請求的custom tomcat產品出來。
最后附上我的源代碼:http://files.cnblogs.com/aspnetdream/Project.zip
參考:《How tomcat works》 作者:Budi Kurniawan & Paul Deck