關(guān)于這篇文章也確實(shí)籌劃了很久,今天決定開篇寫第一篇,說起 tomcat 首先很容易聯(lián)想到 IIS ,因?yàn)槲易铋_始使用的就是 .net 技術(shù),我第一次使用 asp 寫學(xué)生成績(jī)管理系統(tǒng)后,很茫然如何讓別人都能看到或者說使用這個(gè)系統(tǒng)呢?由此認(rèn)識(shí)了 IIS ,它是一個(gè) web 容器,天生的多線程,及時(shí)響應(yīng)用戶提交的請(qǐng)求返回 html 頁面,這就是我了解的最初的 web 容器的功能,由此我們來認(rèn)識(shí) tomcat 也并不困難,可以的話,在了解完 tomcat 后我們可以繼續(xù)了解 jboss 、 jetty 等,好我們進(jìn)入主題。
我們?cè)谄綍r(shí)開發(fā)的過程中是在使用 eclipse 時(shí)候才啟動(dòng) tomcat ,對(duì)于一個(gè) web 容器而言,簡(jiǎn)而言之,它是系統(tǒng)的一個(gè)守護(hù)進(jìn)程,守護(hù)著對(duì)這臺(tái)服務(wù)器某個(gè)端口發(fā)起的請(qǐng)求,基于這一點(diǎn),它就需要一個(gè)監(jiān)聽程序,這個(gè)監(jiān)聽程序來獲取來自這個(gè)端口的特定請(qǐng)求的數(shù)據(jù), ok ,直接點(diǎn)講,我們這里使用 Socket 來獲取某個(gè)端口,通常是 80 端口的 http 請(qǐng)求,通過簡(jiǎn)單的 Java
程序的死循環(huán)(粗糙的做法,后面逐步優(yōu)化)來實(shí)現(xiàn)不斷的獲取 80 端口 http 請(qǐng)求,來達(dá)到監(jiān)聽 80 端口 http 請(qǐng)求的目的。j ava.net包 下面的 Socket 和 ServerSocket 兩個(gè)類就能實(shí)現(xiàn)我們對(duì) 8080 端口的監(jiān)聽,去除中間的邏輯代碼,我們只看這個(gè)兩個(gè)類的演繹的話如下:
?
1 ServerSocket serverSocket = new ServerSocket(8080, 1, InetAddress.getByName("10.10.10.106"));
?
對(duì)本機(jī)的 8080 端口進(jìn)行監(jiān)聽
?
1 socket = serverSocket.accept(); 2 input = socket.getInputStream(); 3 output = socket.getOutputStream();
?
以上代碼就是獲取監(jiān)聽結(jié)果。
這是最簡(jiǎn)單和最精簡(jiǎn)的Socket 通訊原理,基于這個(gè)核心點(diǎn)我們來開發(fā)一個(gè)簡(jiǎn)易的,可以提供靜態(tài)頁面訪問的? custom?tomcat ,準(zhǔn)備一個(gè) index.html 文件放到 /home/webroot 目錄下,那么除去拓展上面代碼外,我們還需要一個(gè) Response 和一個(gè) Request 。
類設(shè)計(jì)如下:
HttpServer :?主函數(shù)所在類,負(fù)載啟動(dòng) ServerSocket 和?操作整合 Socket 監(jiān)聽到的數(shù)據(jù),以及返回結(jié)果,即操作 Response 和 Request 。
Request:? 封裝 Socket 監(jiān)聽到的用戶端請(qǐng)求,包括請(qǐng)求的 http?uri 信息。
Response :?封裝需要推送到客戶端的結(jié)果數(shù)據(jù),即我們需要根據(jù) http?uri? 去本機(jī)尋找相應(yīng)的資源,寫給客戶端。
言簡(jiǎn)意賅,進(jìn)入代碼,首先? 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 }
?
代碼解釋:類包括一個(gè)屬性和兩個(gè)方法, input 屬性即是從 Socket 監(jiān)聽到的信息, Socket 會(huì)將監(jiān)聽到的信息放入一個(gè) InputStream 中,我們使用 Reqeust 類的 Input 屬性來接受。接收到輸入流后,在 parse 中對(duì)這個(gè)輸入流進(jìn)行解析成字符串,即對(duì) Http 請(qǐng)求進(jìn)行拆解,得到完整的 Http?URL ,所以這個(gè)方法是私有的,是類存在意義的核心所在,而提供的對(duì)外方法 parseUri 是負(fù)載將 parse 解析的 url 結(jié)果提供給外界,即,客戶端發(fā)來請(qǐng)求那個(gè)文件,具體的是最終提供給 Response 類, Response 類得到這個(gè)文件名稱后,去本地制定目錄讀取文件。 Tomcat 中通常就是 webapps 目錄啦,很熟悉了吧,哈哈。
Response 類如何實(shí)現(xiàn)這個(gè)讀取文件的歷史使命呢,代碼如下 :
?
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 一共三個(gè)屬性,一個(gè)方法。三個(gè)屬性,一個(gè)是設(shè)置屬性, BUFFER_SIZE 設(shè)置讀寫字節(jié)流大小,關(guān)于讀寫文件,我個(gè)人覺得和服務(wù)器的性能和程序性能息息相關(guān),不宜設(shè)定過大或過小(此處有不同見解的同仁歡迎來噴,我對(duì)這塊理解目前限于此)。 Reqeust 屬性,對(duì)照前文呼應(yīng), Response 需要獲取 Request 類的 uri 結(jié)果信息,所以這里放了一個(gè) Request 屬性,獲取 uri 。 Output ,就不用說了,也是這個(gè)類存在的核心意義,依照 Request 類提供的 uri 信息,在本地讀寫文件后,形成一個(gè)輸出來,存放到 output 中,那么這項(xiàng)工作就由 sendStaticResource 這個(gè)共有方法完成啦。
好,代碼到這個(gè),可以說我們大家已經(jīng)看到一個(gè) tomcat 模型了,有點(diǎn)萬事俱備,只欠東風(fēng)的感覺,客戶端發(fā)起請(qǐng)求, Response 和 Reqeust 有了,那么繼續(xù)往上游考慮, Reqeust 依賴于客戶端的請(qǐng)求,自然以來于 Socket 數(shù)據(jù)。我們?cè)谶@里做得簡(jiǎn)便一點(diǎn),將 ServerSocket 和 Socket 封裝到一個(gè) 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 }
?
代碼解釋:我們知道啟動(dòng) tomcat 之后,只要服務(wù)正常,客戶端任意時(shí)候發(fā)起一個(gè) http 請(qǐng)求, tomcat 就會(huì)響應(yīng),那么這里我們肯定需要一個(gè) while 循環(huán)來模擬不間斷的監(jiān)聽,類 await 方法就是負(fù)責(zé)不斷的獲取 socket 監(jiān)聽到的結(jié)果,有立刻調(diào)動(dòng) Reqeust 和 Response 進(jìn)行響應(yīng),加入主函數(shù),為的是我們這個(gè)是模擬的控制臺(tái)程序,需要一個(gè)程序入口, main 函數(shù)就是程序入口。此外, HttpServer 類包括一個(gè)靜態(tài)屬性 SHUTDOWN_COMMAND ,輸入為 true 則停止這個(gè) main 函數(shù),變量初始值為 false ,當(dāng)客戶端也就是 Request 響應(yīng)得到客戶端輸入? http://10.10.10.108:8080/SHUTDOWN 時(shí)候,則變量在 while 中會(huì)置成 true ,緊接著停止 main ,結(jié)束應(yīng)用程序進(jìn)程。
在 eclipse 中或者在命令行中啟動(dòng)這個(gè) main 函數(shù),命令行則是輸入? java?HttpServer.java 。 eclipse 則是在 main 函數(shù)中右鍵? run?as?application 啟動(dòng)。 我們打開瀏覽器,輸入? http://10.10.10.108:8080/index.html, 回車結(jié)果如下:
本地文件:
好了,夜深啦,就此擱筆了,拋磚引玉,歡迎提議和討論,這個(gè)系列會(huì)繼續(xù)下去,直到一個(gè)完整的可以響應(yīng)一個(gè)java action請(qǐng)求的custom tomcat產(chǎn)品出來。
最后附上我的源代碼:http://files.cnblogs.com/aspnetdream/Project.zip
參考:《How tomcat works》?作者:Budi Kurniawan & Paul Deck
?
更多文章、技術(shù)交流、商務(wù)合作、聯(lián)系博主
微信掃碼或搜索:z360901061

微信掃一掃加我為好友
QQ號(hào)聯(lián)系: 360901061
您的支持是博主寫作最大的動(dòng)力,如果您喜歡我的文章,感覺我的文章對(duì)您有幫助,請(qǐng)用微信掃描下面二維碼支持博主2元、5元、10元、20元等您想捐的金額吧,狠狠點(diǎn)擊下面給點(diǎn)支持吧,站長(zhǎng)非常感激您!手機(jī)微信長(zhǎng)按不能支付解決辦法:請(qǐng)將微信支付二維碼保存到相冊(cè),切換到微信,然后點(diǎn)擊微信右上角掃一掃功能,選擇支付二維碼完成支付。
【本文對(duì)您有幫助就好】元
