這篇文章用一個簡單的示例回味一下Web程序的運行原理。css
在開始Web框架設計以前,有必要先講一講Web軟件的運行原理。html
Web軟件和通常軟件最大不一樣,就是Web軟件是運行在一臺服務器上,你們經過瀏覽器訪問服務器來工做學習。而通常軟件則是運行在用戶手中的計算機設備裏。java
從部署角度來說,Web軟件是一處部署服務各方。而通常軟件則須要一臺一臺的去安裝部署。json
照此分析,現在流行的App其實說白了也算是通常軟件嘍?其實從運行本質上來看是這樣的。不一樣的是App運行在端上,這個端通常指的是Pad、手機或者智能設備。瀏覽器
現現在一些智能路由器也支持App了,搞很差那天咱們的插座上也能跑兩個App。可是不管如何一對一的部署方式和Web一次部署服務各方這種方式。是有着本質的區別的,好了言歸正傳回到咱們的話題上來。服務器
所謂Web開發其實就是編寫一個程序運行在一臺機器上,而後經過瀏覽器訪問這個應用。下面就展現一個很是簡單的Web應用程序:網絡
import java.io.BufferedReader; import java.io.IOException; import java.io.InputStreamReader; import java.io.PrintWriter; import java.io.Reader; import java.net.ServerSocket; import java.net.Socket; public class SocketServer { public static void main(String[] args) throws IOException { int messageID = 0; ServerSocket server = new ServerSocket(80); // 服務器端Socket對象 while (true) { try { dialog(server.accept(), messageID++); } catch (Throwable e) { System.out.println("Error. " + e); } } } public static void dialog(Socket socket, int messageID) throws Throwable { // //Request Reader reader = new InputStreamReader(socket.getInputStream()); BufferedReader ioRead = new BufferedReader(reader); String readLine = null; while ((readLine = ioRead.readLine()) != null) { System.out.println("[Message " + messageID + "] - " + readLine); if ("".equals(readLine.trim())) { break; } } // //Response String body = "Hello ..."; // PrintWriter outWriter = new PrintWriter(socket.getOutputStream()); outWriter.println("HTTP/1.1 200 OK"); outWriter.println("Content-Type:text/html; charset=utf-8"); outWriter.println("\n"); outWriter.println(body); // outWriter.close(); ioRead.close(); socket.close(); } }
運行上面這個程序,它會在你的計算機上打開「80」端口,而後等待瀏覽器發來的請求。框架
接着你只須要打開瀏覽器輸入你本身的機器IP地址或者輸入「localhost」敲一下回車就能夠看到頁面上打印出「Hello...」socket
如今咱們來仔細分析一下這個Web程序都作了哪些事?學習
首先,咱們使用JDK自帶的類「ServerSocket」建立了一個網絡套接字並打開了80端口進行偵聽。這件事若是不是很理解,能夠這樣比喻一下。
假設你在一個大房子裏,而這個房子有好多好多的窗戶,外面的人和你溝通的惟一方式就是向窗戶裏仍紙條。那麼你要作的就是和某我的約定好,用具體的窗戶。
爲何要約定窗戶呢?
由於考慮到這個房子裏不必定就只有你一我的,假定另一我的也想和外界溝通,那他也要經過窗戶傳小紙條。爲了不你和他爭搶同一個窗戶,因此你們要分開各自用各自的窗戶。而且要高速和你傳紙條的人,你用的是左邊第一個窗戶,另一我的用的是右邊第一個窗戶。
結合到咱們的例子裏,80端口就是你在使用的「左邊第一個窗戶」而和你傳紙條的那個同窗就是瀏覽器。
如今咱們經過「ServerSocket server = new ServerSocket(80);」這段代碼打開了一扇窗準備和瀏覽器傳小紙條。下面就是等着對方來跟你對暗號了。
當瀏覽器發來第一個「小紙條」時「server.accept()」方法就獲得了返回值。接着咱們就能夠經過「server.accept()」獲得的「Socket」對象在「dialog」方法中作咱們想作的事情。記得在最後別忘了在寫一個小紙條回遞給瀏覽器。
瀏覽器就比如是一我的,當和瀏覽器進行交流的時候Web程序必需要「說」瀏覽器能聽懂的語言。因此這就須要用到「HTML」了。
可是你們不要忘記,瀏覽器是能聽懂不少語言的。像「xml、json、png、gif、css」這些東西瀏覽器都能聽懂。因此當咱們跟瀏覽器交流的時候,必定要先告訴瀏覽器你說的是哪國語言,否則瀏覽器也會像人同樣傻傻的呆在那裏不知所云。
這就比如潛水員在水下經過手勢,交流一些基本信息。
好了如今咱們知道了,要想讓瀏覽器聽懂咱們程序說的話,就要必須告訴瀏覽器Web程序說的是哪國語言,而這個信息就要經過HTTP協議來告訴瀏覽器。
當Web程序說「HTML」國的語言時,瀏覽器好作好準備,用「HTML」國家的思惟方式來理解咱們告訴它的內容。
這個溝通和協定說什麼語言的過程有個專有名詞叫作「HTTP」協議,這個就是Web程序的社交禮儀。
下面在講一講瀏覽器和Web程序之間是怎樣交流的?
首先第一個地方是,交談的方式是「一問一答」式。率先發問題的是瀏覽器,回答問題的是服務器。一般一次交談,只有一個問題和一個答案。
如今回頭看看咱們剛剛寫的Web程序,雖然很粗糙可是已經能夠和瀏覽器進行對話了。
咱們的例子程序很簡單,以至於無論瀏覽器問什麼問題回答的答案都同樣,所以在下面這個代碼中咱們只是將瀏覽器的問題打印到控制檯不作處理。
在代碼中,瀏覽器當問完問題等待服務器回答時,會額外送出一個「空行 + 回車」表示發問結束等待回答。而這在咱們的程序看來就是多了一個換行而已。
以下:服務器一旦讀到這裏就表示瀏覽器的問題問完了,接着程序跳出循環不在處理瀏覽器的問題,轉而進行回答。
Reader reader = new InputStreamReader(socket.getInputStream()); BufferedReader ioRead = new BufferedReader(reader); String readLine = null; while ((readLine = ioRead.readLine()) != null) { System.out.println("[Message " + messageID + "] - " + readLine); if ("".equals(readLine.trim())) { break; } }
那麼咱們的Web程序怎麼回答的呢?
下面這段代碼就是咱們的服務器Web程序回答問題的代碼。
首先它會先告訴瀏覽器你的問題我都聽到了「HTTP/1.1 200 OK」。而後緊接着又告訴瀏覽器,接下來我說的是HTML語言,而且用了一個方言是「UTF-8」。隨後瀏覽器沉了口氣「輸出一個空行」。開始說咱們事先準備好的事「一段HTML代碼」。
outWriter.println("HTTP/1.1 200 OK"); outWriter.println("Content-Type:text/html; charset=utf-8"); outWriter.println("\n"); outWriter.println(body);
當服務器都說完了以後,就隨手把小紙條丟出去。最後還不忘記把用過的「筆墨紙硯」都收拾了一遍。
outWriter.flush(); outWriter.close(); ioRead.close(); socket.close();
最後小紙條回到了瀏覽器的手中,剩下的事就是瀏覽器本身去理解並展現給咱們了。
如今咱們已經掌握了Web程序和瀏覽器之間的外交禮儀,那麼下面咱們在深刻一下,讓兩個世界的人民作一些貿易往來吧。
咱們來假設一個場景,中國打算進口一批商品。因而打開了國門準備進行貿易。
聞訊趕來的美國正好手裏有一批多餘的石油,想出售給中國。因而美國的商人們向中國運送了這批石油,中國收下了。並給了一個交易回執。後來歐洲的國家也過來,不過賣的倒是羊毛。
假定Web程序就是上面例子中咱們的國家中國,不一樣的瀏覽器分別表明美國和歐洲。下面改造一下咱們的程序,讓外國朋友們能夠吧貨物送過來。
咱們吧上面代碼中,下面這個代碼換成另一個
//Response String body = "Hello ...";
換成
//Response String body = "<form action='/send.do' method='get'>貨物:<input type='text' name='name'/><input type='submit' value='發貨'/></form>";
從新啓動咱們的Web程序,你會發現瀏覽器上出現了一個讓咱們填寫的表單。咱們只要在瀏覽器上輸入貨物的名稱,點擊「發貨」就能夠了。
瀏覽器會以這種方式把咱們的貨物 -> 「表單內容」,發送給服務器。剩下的就是服務器如何接受這批貨物了。
接收貨物,下面咱們吧Request的部分改爲下面這個代碼用來收貨:
Reader reader = new InputStreamReader(socket.getInputStream()); BufferedReader ioRead = new BufferedReader(reader); String readLine = ioRead.readLine(); // String requestURI = readLine.split(" ")[1]; String params = null; if (requestURI.split("\\?").length == 2) { params = requestURI.split("\\?")[1]; System.out.println("param:" + params); } // while ((readLine = ioRead.readLine()) != null) { System.out.println("[Message " + messageID + "] - " + readLine); if ("".equals(readLine.trim())) { break; } }
首先上面這段代碼中,咱們加入了「ioRead.readLine();」這麼一行,這行代碼的意義是讀取瀏覽器送來的第一行內容。當瀏覽器遞交表單的時候,會把表單內容放在這裏。而表單的內容就是咱們瀏覽器送來的貨物。
由於這個內容是有特殊格式的,咱們須要將它進行拆解,這樣才能真正拿到瀏覽器送來的貨物。這就像遠洋運輸必定是把貨物放在集裝箱裏經過船舶運輸過來同樣。
後面的if判斷和字符串拆解目的就是把集裝箱裏的貨物拿出來。最後經過「System.out.println("param:" + params);」的方式把這個貨物打印出來。完成咱們例子中提到的商貿合做。
下面這個是最終的Web程序代碼。
public class WebServer { public static void main(String[] args) throws IOException { int messageID = 0; ServerSocket server = new ServerSocket(80); while (true) { try { dialog(server.accept(), messageID++); } catch (Throwable e) { System.out.println("Error. " + e); } } } public static void dialog(Socket socket, int messageID) throws Throwable { // //Request Reader reader = new InputStreamReader(socket.getInputStream()); BufferedReader ioRead = new BufferedReader(reader); String readLine = ioRead.readLine(); // String requestURI = readLine.split(" ")[1]; String params = null; if (requestURI.split("\\?").length == 2) { params = requestURI.split("\\?")[1]; System.out.println("param:" + params); } // while ((readLine = ioRead.readLine()) != null) { System.out.println("[Message " + messageID + "] - " + readLine); if ("".equals(readLine.trim())) { break; } } // //Response String body = "<form action='/addUser.do'><input type='text' name='userName'/><input type='submit' value='遞交'/></form>"; // PrintWriter outWriter = new PrintWriter(socket.getOutputStream()); outWriter.println("HTTP/1.1 200 OK"); outWriter.println("Content-Type:text/html; charset=utf-8"); outWriter.println("\n"); outWriter.println(body); // outWriter.flush(); outWriter.close(); ioRead.close(); socket.close(); } }