Java網路編程,java網路編程pdf
我們基本每天都在通過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),也就是HTTP協議。
不同的是,TCP/IP與UDP議是傳輸層當中的通訊協定,而HTTP協議是應用程式層當中的協議。
所以,當我們想要使用Java語言實現所謂的WEB通訊,自然也應當遵循HTTP協議。
Java中已經為我們提供了這樣的一種實現規範,也就是廣為人知的:Servlet介面。
而我們開發web項目時,最常用到的HttpServlet類,就是基於此介面實現的具體子類。
該類封裝和提供了,針對基於Http協議通訊的內容進行訪問和操作的常用方法。
說了這麼多,我們通過一些小的執行個體,方便進行更形象的理解。
首先,我們通過一段簡單的Servlet代碼來看一下,基於HTTP協議進行WEB通訊的請求資訊:
public class ServletTest extends HttpServlet {public void 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());}}public void doPost(HttpServletRequest request, HttpServletResponse response)throws ServletException, IOException {}}上面的代碼中,我們的目的是通過HttpSerlvetRequest當中的方法,
來列印web瀏覽器基於http協議發起的請求當中,封裝的HTTP請求詳情。程式輸出的結果如下:
一個HTTP協議的請求中,通常主要包含三個部分:
- 方法/統一資源標示符(URI)/協議/版本
- 請求標題
- 實體主體
其中方法也就是所謂的get/post之類的要求方法,統一資源標示符也就是要訪問的目標資源的路徑,包括協議及協議版本,這些資訊被放在請求的第一行。
隨後,緊接著的便是請求標題;請求標題通常包含了與用戶端環境及請求實體主體相關的有用資訊。
最後,在標題與實體主體之間是一個空行。它對於HTTP請求格式是很重要的,空行告訴HTTP伺服器,實體主體從這裡開始。
前面已經說過了,我們這裡想要研究的,是WEB伺服器的基本實現原理。
那麼我們自然想要自己來實現一下所謂的WEB伺服器,我們已經知道了:
所謂的B/S結構,實際上就是用戶端與伺服器之間基於HTTP協議的網路通訊。
那麼,肯定是離不開socket與io的,所以我們可以簡單的類比一個最簡易功能的山寨瀏覽器:
public class MyTomcat {public static void main(String[] args) {try {ServerSocket tomcat = new ServerSocket(9090);System.out.println("伺服器啟動");//Socket s = tomcat.accept();//byte[] buf = new byte[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 blocke.printStackTrace();}}}這次我們在通過對應的URL在瀏覽器中對我們的山寨伺服器進行訪問,得到的輸出結果是:
通過成果我們看到,我們已經成功的簡單山寨了一下tomcat。
不過這裡需要注意的是,我們自己山寨的tomcat伺服器當中,之所以也成功的輸出了Http協議的請求體,是因為:
我們是通過web瀏覽器進行訪問的,如果通過普通的socket進行對serversocket的串連訪問,是沒有這些請求資訊的。
因為我們前面已經說過了,web瀏覽器與伺服器之間的通訊必須遵循Http協議。
所以,我們日常生活中使用的web瀏覽器,會自動的為我們的請求進行基於http協議的封裝。
但是,因為我們已經瞭解了原理,所以我們也可以自己類比一下瀏覽器過過癮:
//山寨瀏覽器public class MyBrowser { public static void 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 = new byte[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伺服器public class MyTomcat { public static void main(String[] args) { try { ServerSocket tomcat = new ServerSocket(9090); System.out.println("伺服器啟動"); // Socket s = tomcat.accept(); // byte[] buf = new byte[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語言的能力。
所以說,瀏覽器另外一個重要的功能其實就是:可以對超文字標記語言 (HTML)進行解析。而實際上,這也是瀏覽器開發的痛點和重點。
上面這樣的輸出結果看上去顯然不爽,所以說山寨貨畢竟還是坑爹!
我們還是通過正規的WEB瀏覽器,來試著訪問一下我們的山寨伺服器,結果發現,效果帥多了:
而順帶一提的是,既然當瀏覽器向WEB伺服器發起訪問請求時,會封裝有對應的HTTP請求體。
那麼,對應的,當WEB伺服器處理完瀏覽器請求,返回資料時,也會有對應的封裝,就是所謂的HTTP響應體。
舉例來說,假如我們將我們的山寨瀏覽器的代碼進行修改,去串連真正的tomcat伺服器:
public class MyBrowser {public static void main(String[] args) {try {Socket browser = new Socket("192.168.1.102", 8080);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:8080");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 = new byte[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{}}}運行程式,你將會發下如下的輸出資訊:
與HTTP請求類似,通常一個HTTP響應也包含三個部分:
- 協議/響應碼/狀態原因:協議也就是指HTTP協議的資訊,響應碼是指代表該次請求的處理結果的碼(例如常見的200、404、500),其實就是該次請求處理的響應描述
- 響應標題:響應標題也包含與HTTP請求中的標題類似的有用資訊。
- 響應實體:通常也就是指響應本身的HTML內容。
與HTTP請求一樣,響應表頭與響應實體之間,也會使用一個空行進行分割,方便解讀。
同時我們也可以發現,其實真正被解析顯示在瀏覽器網頁上的內容,其實只是響應實體的部分。
響應行和響應標題當中,實際上是負責將相關的一些有用資訊返回給我們,但這部分是不需要在瀏覽器中所展示的。
也就是說,我們的瀏覽器除了應當具備擷取一個完整的HTTP響應的能力之外,還應該具備解析HTTP協議響應的能力。
事實上,Java也為我們提供了這樣的對象,那就是URL及URLConnection對象。
如果我們在我們的山寨瀏覽器中,植入這樣的對象,來進行與伺服器之間的HTTP通訊,那麼:
public class MyBrowser2 {public static void main(String[] args) {try {URL url = new URL("http://192.168.1.102:8080");HttpURLConnection conn = (HttpURLConnection) url.openConnection();InputStream in = conn.getInputStream();byte[] buf = new byte[1024];int length = 0;StringBuffer text = new StringBuffer();String line = null;while ((length = in.read(buf)) != -1) {line = new String(buf, 0, length);text.append(line);}System.out.println(text);} catch (IOException e) {// TODO Auto-generated catch blocke.printStackTrace();}}}這次當我們再運行程式,查看輸出資訊,發現我們從URLConnection對象擷取到的輸入資料流當中,
讀取的響應資訊,就如我們所願的,只剩下了需要被解析顯示在頁面的響應實體的內容。
實際上這也就是Java為我們提供的對象,將對HTTP協議內容的解析功能進行了封裝。
而究其根本來說,我們基本可以想象到,URLConnection = Socket + HTTP協議解析器。
也就是說,該對象的底層除了通過Socket串連到WEB伺服器之外,還封裝了對HTTP協議內容的解析功能。
於是到此,我們已經簡單的瞭解了關於WEB伺服器與瀏覽器的基本實現原理。