網路程式設計(一)——淺析web伺服器與瀏覽器的實現原理
我們基本每天都在通過WEB瀏覽器,去瀏覽一些新聞,看看視訊之類的。
眾所周知,這就是所謂的B/S結構(Browser/Server,瀏覽器/伺服器模式),是WEB興起後的一種網路結構模式,WEB瀏覽器是客戶端最主要的應用軟體。
那順道就來簡單的看一下,所謂的Web伺服器(例如知名的Tomcat)與瀏覽器,基本的實現原理是什麼樣的呢?
首先可以明確的就是,例如我們所做的通過瀏覽器輸入一個地址,訪問一個網頁的操作。
實際對應的底層操作簡單來說就是:客戶端(瀏覽器)面向於WEB伺服器的網路通訊。
那麼,既然是網路通訊。對應於Java當中來說,就自然離不開Socket與IO流。其實這也正是Web伺服器與瀏覽器的基礎實現原理。
當然,想要開發一套完善的WEB伺服器或瀏覽器,需要做的工作是很複雜的。但這裡,我們想要了解的,只是其原理。
我們知道,將開發的web專案部署到tomcat伺服器之後,就可以通過瀏覽器對伺服器上的資源進行訪問。
但重要的一點是,存在多種不同廠商開發的不同瀏覽器。但各個型別的WEB瀏覽器,都可以正常的訪問tomcat伺服器上的資源。
對此,我們可以這樣理解:我開發了一個WEB伺服器,並且能夠保證其他人開發的客戶端都能夠與我的伺服器正常通訊。
能夠實現這樣的目的的前提自然就是,你要制定一個規範,並讓想要與你開發的伺服器正常進行通訊的客戶端都遵循這個規範來實現。
這個規範,也就是所謂的協議。
所以,正如在網路通訊中,資料的傳輸可以遵循TCP/IP或UDP協議一樣。
WEB伺服器與WEB瀏覽器之間,也通過一種雙方都熟悉的語言進行通訊。
這種協議即是:超文字傳輸協議,也就是HTTP協議。
不同的是,TCP/IP與UDP議是傳輸層當中的通訊協議,而HTTP協議是應用層當中的協議。
所以,當我們想要使用Java語言實現所謂的WEB通訊,自然也應當遵循HTTP協議。
Java中已經為我們提供了這樣的一種實現規範,也就是廣為人知的:Servlet介面。
而我們開發web專案時,最常用到的HttpServlet類,就是基於此介面實現的具體子類。
該類封裝和提供了,針對基於Http協議通訊的內容進行訪問和操作的常用方法。
說了這麼多,我們通過一些小的例項,方便進行更形象的理解。
首先,我們通過一段簡單的Servlet程式碼來看一下,基於HTTP協議進行WEB通訊的請求資訊:
- publicclass ServletTest extends HttpServlet {
- publicvoid doGet(HttpServletRequest request, HttpServletResponse response)
- throws ServletException, IOException {
- for (Enumeration e = request.getHeaderNames(); e.hasMoreElements();) {
- String header = (String) e.nextElement();
- if (header != null)
- System.out.println((new StringBuilder(String.valueOf(header)))
- .append(":").append(request.getHeader(header))
- .toString());
- }
- }
- publicvoid doPost(HttpServletRequest request, HttpServletResponse response)
- throws ServletException, IOException {
- }
- }
來列印web瀏覽器基於http協議發起的請求當中,封裝的HTTP請求詳情。程式輸出的結果如下:
一個HTTP協議的請求中,通常主要包含三個部分:
- 方法/統一資源標示符(URI)/協議/版本
- 請求標頭
- 實體主體
其中方法也就是所謂的get/post之類的請求方法,統一資源標示符也就是要訪問的目標資源的路徑,包括協議及協議版本,這些資訊被放在請求的第一行。
隨後,緊接著的便是請求標頭;請求標頭通常包含了與客戶端環境及請求實體主體相關的有用資訊。
最後,在標頭與實體主體之間是一個空行。它對於HTTP請求格式是很重要的,空行告訴HTTP伺服器,實體主體從這裡開始。
前面已經說過了,我們這裡想要研究的,是WEB伺服器的基本實現原理。
那麼我們自然想要自己來實現一下所謂的WEB伺服器,我們已經知道了:
所謂的B/S結構,實際上就是客戶端與伺服器之間基於HTTP協議的網路通訊。
那麼,肯定是離不開socket與io的,所以我們可以簡單的模擬一個最簡易功能的山寨瀏覽器:
- publicclass MyTomcat {
- publicstaticvoid main(String[] args) {
- try {
- ServerSocket tomcat = new ServerSocket(9090);
- System.out.println("伺服器啟動");
- //
- Socket s = tomcat.accept();
- //
- byte[] buf = newbyte[1024];
- InputStream in = s.getInputStream();
- //
- int length = in.read(buf);
- String request = new String(buf,0,length);
- //
- System.out.println(request);
- } catch (IOException e) {
- // TODO Auto-generated catch block
- e.printStackTrace();
- }
- }
- }
通過成果我們看到,我們已經成功的簡單山寨了一下tomcat。
不過這裡需要注意的是,我們自己山寨的tomcat伺服器當中,之所以也成功的輸出了Http協議的請求體,是因為:
我們是通過web瀏覽器進行訪問的,如果通過普通的socket進行對serversocket的連線訪問,是沒有這些請求資訊的。
因為我們前面已經說過了,web瀏覽器與伺服器之間的通訊必須遵循Http協議。
所以,我們日常生活中使用的web瀏覽器,會自動的為我們的請求進行基於http協議的包裝。
但是,因為我們已經瞭解了原理,所以我們也可以自己模擬一下瀏覽器過過癮:
- //山寨瀏覽器
- publicclass MyBrowser {
- publicstaticvoid main(String[] args) {
- try {
- Socket browser = new Socket("192.168.1.102", 9090);
- PrintWriter pw = new PrintWriter(browser.getOutputStream(),true);
- // 封裝請求第一行
- pw.println("GET/ HTTP/1.1");
- // 封裝請求頭
- pw.println("User-Agent: Java/1.6.0_13");
- pw.println("Host: 192.168.1.102:9090");
- pw
- .println("Accept: text/html, image/gif, image/jpeg, *; q=.2, */*; q=.2");
- pw.println("Connection: keep-alive");
- // 空行
- pw.println();
- // 封裝實體主體
- pw.println("UserName=zhangsan&Age=17");
- // 寫入完畢
- browser.shutdownOutput();
- // 接受伺服器返回資訊,
- InputStream in = browser.getInputStream();
- //
- int length = 0;
- StringBuffer request = new StringBuffer();
- byte[] buf = newbyte[1024];
- //
- while ((length = in.read(buf)) != -1) {
- String line = new String(buf, 0, length);
- request.append(line);
- }
- System.out.println(request);
- //browser.close();
- } catch (IOException e) {
- System.out.println("異常了,操!");
- }finally{
- }
- }
- }
- //修改後的山寨tomcat伺服器
- publicclass MyTomcat {
- publicstaticvoid main(String[] args) {
- try {
- ServerSocket tomcat = new ServerSocket(9090);
- System.out.println("伺服器啟動");
- //
- Socket s = tomcat.accept();
- //
- byte[] buf = newbyte[1024];
- InputStream in = s.getInputStream();
- //
- int length = 0;
- StringBuffer request = new StringBuffer();
- while ((length = in.read(buf)) != -1) {
- String line = new String(buf, 0, length);
- request.append(line);
- }
- //
- System.out.println("request:"+request);
- PrintWriter pw = new PrintWriter(s.getOutputStream(),true);
- pw.println("<html>");
- pw.println("<head>");
- pw.println("<title>LiveSession List</title>");
- pw.println("</head>");
- pw.println("<body>");
- pw.println("<p style=\"font-weight: bold;color: red;\">welcome to MyTomcat</p>");
- pw.println("</body>");
- s.close();
- tomcat.close();
- } catch (IOException e) {
- // TODO Auto-generated catch block
- e.printStackTrace();
- }
- }
- }
我們先啟動伺服器,然後執行瀏覽器模擬網頁瀏覽的過程,首先看到伺服器端收到的請求資訊:
緊接著,伺服器收到請求進行處理後,返回資源給瀏覽器,於是得到輸出資訊:
可以看到,我們在山寨瀏覽器當中得到的返回資訊,實際上就是一個HTML檔案的原始碼,
之所以我們的山寨瀏覽器中,這些資訊僅僅是以純文字形式顯示,是因為我們的山寨瀏覽器不具備解析HTML語言的能力。
所以說,瀏覽器另外一個重要的功能其實就是:可以對超文字標記語言進行解析。而實際上,這也是瀏覽器開發的難點和重點。
上面這樣的輸出結果看上去顯然不爽,所以說山寨貨畢竟還是坑爹!
我們還是通過正規的WEB瀏覽器,來試著訪問一下我們的山寨伺服器,結果發現,效果帥多了: