web服務器是如何工作的
1989年的夏天,蒂姆.博納斯-李開發了世界上第一個web服務器和web客戶機。這個瀏覽器程序是一個簡單的電話號碼查詢軟件。最初的web服務器程序就是一個利用瀏覽器和web服務器軟件之間的聯系,將存儲在硬盤上的文件傳遞給遠程的讀者。
web服務器軟件主要是提供web服務的軟件,為瀏覽器提供http數據的支持。
它無非就是把硬盤上的文件 以http數據流 的形式提供給web服務器,這就是它的基本用途。這個基本用途就是作為web服務器軟件的發明人蒂姆.博納斯-李發明web服務器的初衷。
需要傳遞的硬盤上 的文件 的格式是html格式的標記性語言的文件。web服務器軟件在接受到瀏覽器的訪問請求的時候,將直接不加任何修改的將這個html文件傳遞到遠程瀏覽器端,傳輸協議是TCP的HTTP協議。
再看下圖,深入了解他的原理。
這是一個最初的web服務器軟件 的原理圖,也是一個支持html格式文件服務 的所以web服務器 的原理圖,即使是最著名 的Apache HTTP Server也是這個原理。
所謂的靜態頁面是指本地文件直接被web服務器取得的這種web頁面。而想Asp,jsp,php這樣的所謂動態頁面是怎么個原理呢?
支持jsp的web服務器 的原理
動態頁面 的web服務器和靜態頁面的web服務器之間僅有一點的區別,就是在本地端得到html格式信息的方法不是直接從文件中讀取,而是從程序電腦生成信息中獲取而已。
那么,支持jsp的動態 的web服務器的原理又是什么養的呢?其實就是多了一個將jsp文件轉換成java文件並且編譯 的過程,然后運行那個被編譯的Class文件,從而時期得到要返回給了瀏覽器的格式信息,然后將其返回給遠端的瀏覽器。
下圖玩他的原理圖
大家估計都要已經看出來了,與返回靜態頁面的區別是,返回的信息是由過程生成的。其實,原理很簡單,無非就是讀文件發出去而已。
常用的web服務器
前面介紹的就是web服務器的 工作原理,java程序猿應該對下面的這些軟件做到非常熟悉
1.Apache HTTP Server
Apache也許是時間最久也是最流的 http服務器軟件。快速、可靠,通過簡單的API擴展,Perl/Python解釋器可悲編譯到服務器當中,完全免費,源代碼開放。
官方的站點為http://httpd.apache.org/.
2.Tomact
是目前業界被最廣泛認可 的一個web服務器,他是Java Servlet2.2和Java Server Pages1.1技術的標准實現,是基於Apache許可證下的開發 是自由軟件,由Jakarta項目組開發,
官方站點是http://tomcat.apache.org/
研究一下web服務器的源程序
既然web服務器的原理如此簡單,那就手動自己開一個試試吧。
1.步驟一、確定用TCP作為服務傳輸協議
package yxh; import java.io.IOException; import java.net.ServerSocket; import java.net.Socket; /** * * @author Baron * @version 創建時間:2017年1月2日 * @Dsecription 確定用TCP 作為服務傳輸協議,首先做一個main函數,建立Socket ,並用一個“死循環” 的形式 * 監聽指定端口。 */ public class HttpServer { public static String ROOT = "./wwwroot"; ///默認root文件夾 public static String defaultPage = "index.html"; //默認文件的文件名 public static void main(String[] args) throws IOException { ServerSocket server = new ServerSocket(8000); while(true) { //阻塞,等待瀏覽器的連接 Socket sk = server.accept(); System.out.println("Accepting Connection ...\n"); //啟動服務線程 new HttpThread(sk).start(); } } }
首先做一個main函數,簡歷一個Socket並且用一個“死循環’”的形式監聽指定端口。程序如下
2.步驟2.實現一個多線程的 程序,用於處理每一個客戶的請求。程序如下。
package yxh; import java.io.*; import java.net.Socket; /** * * @author Baron * @version 創建時間:2017年1月2日 * @Dsecription 實現一個多線程,處理每個用戶的請求 */ public class HttpThread extends Thread { private Socket sock; HttpThread(Socket socktmp){ this.sock = socktmp; } public void run (){ InputStream ins = null; OutputStream outs = null; try{ ins = sock.getInputStream(); outs= sock.getOutputStream(); Receive rcv = new Receive(ins); String sURL = rcv.parse(); //用Receive 類取得瀏覽器發過來 的URL請求 if(sURL.equals("/")){ sURL = HttpServer.defaultPage;// 如果沒有指定文件,那么給傳過來的URL加上默認的文件名 } Answer ans = new Answer(outs); ans.Send(sURL);//再將URL執行的文件用Answer類的send方法放回給瀏覽器 }catch(IOException e){ System.out.println(e.toString()); } finally{ //最后調用那些類的close方法釋放資源 try{ if(ins != null) ins.close(); if(outs != null) outs.close(); if(sock != null) sock.close(); }catch(IOException e){} } } }
3.步驟3.如何取得瀏覽器傳來的URL
現在實現的這個類是Receive,用這個類u取得了來自瀏覽器的字符串。程序如下。
package yxh; import java.io.IOException; import java.io.InputStream; /** * * @author Baron * @version 創建時間:2017年1月2日 * @Dsecription 取得瀏覽器傳過來的URL 字符串 */ public class Receive { InputStream in = null; public Receive(InputStream input){ this.in = input; } //這個方法 的目的是將URL 請求的文件返回 public String parse(){ StringBuffer receiveStr = new StringBuffer(2048);//這個變量就是實際從瀏覽器取得的請求數據流 int i; byte[] buffer = new byte[2048]; try{ i = in.read(buffer);//從Socket讀出數據 }catch(IOException e){ i= -1; } for(int j=0;j<i;j++){ receiveStr.append((char)buffer[j]);//將請求到的信息循環追加到receiverStr變量中 } return getUri(requestStr.toString()); } private String getUri(String receiveStr){ int index1,index2; index1 = receiveStr.indexOf(' '); if(index1 != -1){ index2 = receiveStr.indexOf(' ', index1+1); if(index2>index1){ return receiveStr.substring(index1 +1,index2); } } return null; } }
下面來解釋getUri這個方法,大家一定覺得奇怪,這個方法從兩個空格之間取得了URL的文件名,這是怎么回事?
其實,只要把receiveStr打印出來就可以了。下面來看看瀏覽器發過來的 請求是什么字符串,請求字符串如下。
requestStr:GET/index.htm HTTP/1.1
Accept:*/*
Accept-Language:zh-cn
Accept-Encoding:gzip,deflate
User-Agent:Mozilla/4.0(compatiable;MSIE 6.0 ;Windows NT 5.1;SV1;
.NET CLR 1.1.4322;MAXTHON 2.0)
Host:localhost:8000
Connection:Keep-Alive
那么在瀏覽器里面請求的 URL是這樣的
http://localhost:8000/index.htm
這時候可以發現,出現在第一個“空格”和第二個“空格”之間包圍的字符串就是“、index.htm”,這就是要取得的字符串。好了,這下可以明白,為什么取兩個空格之間的東西了吧!
那么,打印出來的 這個receiveStr其實就是按照http協議發過來的請求數據流,現在需要做的就是格局http協議將瀏覽器希望得到的內容返回出去。
4.步驟4.返回http數據流
返回的數據流的要求是:按照http 的要求返回請求文件。程序如下。
package yxh; import java.io.*; /** * * @author Baron * @version 2017年1月2日 * @Dsecription 返回Http 數據流 */ public class Answer { OutputStream out = null; public Answer(OutputStream output){ this.out = output; } public void Send(String pagefile) throws IOException{ //這個方法是關鍵,其目的是返回HTTP數據流 byte[] bytes = new byte[2048]; FileInputStream fis = null; try{ //讀取指定的文件內容 File file = new File(HttpServer.ROOT,pagefile); if(file.exists()){ fis = new FileInputStream(file); int ch = fis.read(bytes, 0, 2048); @SuppressWarnings("deprecation") String sBody = new String(bytes,0); //返回 的信息是在文件的內容的前面加上Http協議的格式內容 String sendMessage = "HTTP/1.1 200 OK\r\n"+ "Content-Type:text/html\r\n"+ "Content-Length:"+ch+"\r\n"+ "\r\n"+sBody; //輸出文件 out.write(sendMessage.getBytes()); }else{ //文件不存在的話,返回一個Http協議的格式內容 @SuppressWarnings("unused") String errorMessage = "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>"; } }catch(Exception e){ System.out.println(e.toString()); }finally { if(fis != null){ fis.close(); } } } }
這回完全清楚了吧!web服務器正真返回給瀏覽器 的內容是:在http文件內容的前面加上協議內容格式信息。這個格式信息如下。
http/1.1 200 OK
Content-Type:text/html
Content-Length:[要發送的數據長度]
之所以在瀏覽器這邊可以看到不同的字體的顏色等信息,完全是瀏覽器自己對html標記性語言解析的結果。
有興趣的盆友如果想做一個更加強大更加完整的web服務器軟件的話,可以更深入的研究http協議。