HTTP協(xié)議簡(jiǎn)介
超文本傳輸協(xié)議(HTTP)是位于TCP/IP 協(xié)議的應(yīng)用層,是最廣為人知的協(xié)議,也是互連網(wǎng)中最核心的協(xié)議之一,同樣,HTTP 也是基于 C/S 或 B/S 模型實(shí)現(xiàn)的。事實(shí)上,我們使用的瀏覽器如Netscape 或IE 是實(shí)現(xiàn)HTTP 協(xié)議中的客戶端,而一些常用的Web 服務(wù)器軟件如Apache、IIS 和iPlanet Web Server 等是實(shí)現(xiàn)HTTP 協(xié)議中的服務(wù)器端。Web 頁(yè)由服務(wù)端資源定位,傳輸?shù)綖g覽器,經(jīng)過(guò)瀏覽器的解釋后,被客戶所看到。
Web 的工作基于客戶機(jī)/服務(wù)器計(jì)算模型,由Web 瀏覽器(客戶機(jī))和Web服務(wù)器(服務(wù)器)構(gòu)成,兩者之間采用超文本傳送協(xié)議(HTTP)進(jìn)行通信。HTTP協(xié)議是Web瀏覽器和Web服務(wù)器之間的應(yīng)用層協(xié)議,是通用的、無(wú)狀態(tài)的、面向?qū)ο蟮膮f(xié)議。
一個(gè)完整的HTTP協(xié)議會(huì)話過(guò)程包括四個(gè)步驟:
◆ 連接,Web瀏覽器與Web服務(wù)器建立連接,打開(kāi)一個(gè)稱為Socket(套接字)的虛擬文件,此文件的建立標(biāo)志著連接建立成功;
◆ 請(qǐng)求,Web瀏覽器通過(guò)Socket向Web服務(wù)器提交請(qǐng)求。HTTP的請(qǐng)求一般是GET或POST命令(POST用于FORM參數(shù)的傳遞);
◆ 應(yīng)答,Web瀏覽器提交請(qǐng)求后,通過(guò)HTTP協(xié)議傳送給Web服務(wù)器。Web服務(wù)器接到后,進(jìn)行事務(wù)處理,處理結(jié)果又通過(guò)HTTP傳回給Web瀏覽器,從而在Web瀏覽器上顯示出所請(qǐng)求的頁(yè)面;
◆ 關(guān)閉連接,應(yīng)答結(jié)束后Web瀏覽器與Web服務(wù)器必須斷開(kāi),以保證其它Web瀏覽器能夠與Web服務(wù)器建立連接。 編程思路
根據(jù)上述HTTP協(xié)議的會(huì)話過(guò)程,本實(shí)例中實(shí)現(xiàn)了GET請(qǐng)求的Web服務(wù)器程序的方法,方法如下:
通過(guò)創(chuàng)建ServerSocket 類對(duì)象,偵聽(tīng)用戶指定的端口(為8080),等待并接受客戶機(jī)請(qǐng)求到端口。創(chuàng)建與Socket相關(guān)聯(lián)的輸入流和輸出流,然后讀取客戶機(jī)的請(qǐng)求信息。若請(qǐng)求類型是GET,則從請(qǐng)求信息中獲取所訪問(wèn)的HTML 文件名;如果HTML 文件存在,則打開(kāi)HTML 文件,把HTTP 頭信息和HTML 文件內(nèi)容通過(guò)Socket 傳回給Web瀏覽器,然后關(guān)閉文件,否則發(fā)送錯(cuò)誤信息給Web 瀏覽器。最后關(guān)閉與相應(yīng)Web 瀏覽器連接的Socket。
用Java編寫Web服務(wù)器httpServer.java文件的源代碼如下:
//httpServer.java import java.net.*; import java.io.*; import java.util.*; import java.lang.*; public class httpServer{ public static void main(String args[]) { int port; ServerSocket server_socket; //讀取服務(wù)器端口號(hào) try { port = Integer.parseInt(args[0]); } catch (Exception e) { port = 8080; } try { //監(jiān)聽(tīng)服務(wù)器端口,等待連接請(qǐng)求 server_socket = new ServerSocket(port); System.out.println("httpServer running on port " + server_socket.getLocalPort()); //顯示啟動(dòng)信息 while(true) { Socket socket = server_socket.accept(); System.out.println("New connection accepted " + socket.getInetAddress() + ":" + socket.getPort()); //創(chuàng)建分線程 try { httpRequestHandler request = new httpRequestHandler(socket); Thread thread = new Thread(request); //啟動(dòng)線程 thread.start(); } catch(Exception e) { System.out.println(e); } } } catch (IOException e) { System.out.println(e); } } } class httpRequestHandler implements Runnable { final static String CRLF = "\r\n"; Socket socket; InputStream input; OutputStream output; BufferedReader br; // 構(gòu)造方法 public httpRequestHandler(Socket socket) throws Exception { this.socket = socket; this.input = socket.getInputStream(); this.output = socket.getOutputStream(); this.br = new BufferedReader(new InputStreamReader(socket.getInputStream())); } // 實(shí)現(xiàn)Runnable 接口的run()方法 public void run() { try { processRequest(); } catch(Exception e) { System.out.println(e); } } private void processRequest() throws Exception { while(true) { //讀取并顯示W(wǎng)eb 瀏覽器提交的請(qǐng)求信息 String headerLine = br.readLine(); System.out.println("The client request is "+headerLine); if(headerLine.equals(CRLF) || headerLine.equals("")) break; StringTokenizer s = new StringTokenizer(headerLine); String temp = s.nextToken(); if(temp.equals("GET")) { String fileName = s.nextToken(); fileName = "." + fileName ; // 打開(kāi)所請(qǐng)求的文件 FileInputStream fis = null ; boolean fileExists = true ; try { fis = new FileInputStream( fileName ) ; } catch ( FileNotFoundException e ) { fileExists = false ; } // 完成回應(yīng)消息 String serverLine = "Server: a simple java httpServer"; String statusLine = null; String contentTypeLine = null; String entityBody = null; String contentLengthLine = "error"; if ( fileExists ) { statusLine = "HTTP/1.0 200 OK" + CRLF ; contentTypeLine = "Content-type: " + contentType( fileName ) + CRLF ; contentLengthLine = "Content-Length: " + (new Integer(fis.available())).toString() + CRLF; } else { statusLine = "HTTP/1.0 404 Not Found" + CRLF ; contentTypeLine = "text/html" ; entityBody = "<HTML>" + "<HEAD><TITLE>404 Not Found</TITLE></HEAD>" + "<BODY>404 Not Found" +"<br>usage:http://yourHostName:port/" +"fileName.html</BODY></HTML>" ; } // 發(fā)送到服務(wù)器信息 output.write(statusLine.getBytes()); output.write(serverLine.getBytes()); output.write(contentTypeLine.getBytes()); output.write(contentLengthLine.getBytes()); output.write(CRLF.getBytes()); // 發(fā)送信息內(nèi)容 if (fileExists) { sendBytes(fis, output) ; fis.close(); } else { output.write(entityBody.getBytes()); } } } //關(guān)閉套接字和流 try { output.close(); br.close(); socket.close(); } catch(Exception e) {} } private static void sendBytes(FileInputStream fis, OutputStream os) throws Exception { // 創(chuàng)建一個(gè) 1K buffer byte[] buffer = new byte[1024] ; int bytes = 0 ; // 將文件輸出到套接字輸出流中 while ((bytes = fis.read(buffer)) != -1 ) { os.write(buffer, 0, bytes); } } private static String contentType(String fileName) { if (fileName.endsWith(".htm") || fileName.endsWith(".html")) { return "text/html"; }
return "fileName"; } } |
|
編程技巧說(shuō)明
◆ 主線程設(shè)計(jì)
主線程的設(shè)計(jì)就是在主線程httpServer 類中實(shí)現(xiàn)了服務(wù)器端口的偵聽(tīng),服務(wù)器接受一個(gè)客戶端請(qǐng)求之后創(chuàng)建一個(gè)線程實(shí)例處理請(qǐng)求,代碼如下:
import java.net.*; import java.io.*; import java.util.*; import java.lang.*; public class httpServer{ public static void main(String args[]) { port; ServerSocket server_socket; //讀取服務(wù)器端口號(hào) try { port = Integer.parseInt(args[0]); } catch (Exception e) { port = 8080; } try { //監(jiān)聽(tīng)服務(wù)器端口,等待連接請(qǐng)求 server_socket = new ServerSocket(port); System.out.println("httpServer running on port " +server_socket.getLocalPort()); .......... .......... | ◆ 連接處理分線程設(shè)計(jì)
在分線程httpRequestHandler 類中實(shí)現(xiàn)了HTTP 協(xié)議的處理,這個(gè)類實(shí)現(xiàn)了Runnable 接口,代碼如下:
class httpRequestHandler implements Runnable { final static String CRLF = "\r\n"; Socket socket; InputStream input; OutputStream output; BufferedReader br; // 構(gòu)造方法 public httpRequestHandler(Socket socket) throws Exception { this.socket = socket; //得到輸入輸出流 this.input = socket.getInputStream(); this.output = socket.getOutputStream(); this.br = new BufferedReader(new InputStreamReader(socket.getInputStream())); }
// 實(shí)現(xiàn)Runnable 接口的run()方法 public void run() { try { processRequest(); } catch(Exception e) { System.out.println(e); } } | ◆ 構(gòu)建processRequest()方法來(lái)處理信息的接收和發(fā)送
作為實(shí)現(xiàn)Runnable 接口的主要內(nèi)容,在run()方法中調(diào)用processRequest()方法來(lái)處理客戶請(qǐng)求內(nèi)容的接收和服務(wù)器返回信息的發(fā)送,代碼如下:
private void processRequest() throws Exception { while(true) { //讀取并顯示W(wǎng)eb 瀏覽器提交的請(qǐng)求信息 String headerLine = br.readLine(); System.out.println("The client request is "+ headerLine); if(headerLine.equals(CRLF) || headerLine.equals("")) break; //根據(jù)請(qǐng)求字符串中的空格拆分客戶請(qǐng)求 StringTokenizer s = new StringTokenizer(headerLine); String temp = s.nextToken(); if(temp.equals("GET")) { String fileName = s.nextToken(); fileName = "." + fileName ; ............. ............. | 在processRequest()方法中得到客戶端請(qǐng)求后,利用一個(gè)StringTokenizer 類完成了字符串的拆分,這個(gè)類可以實(shí)現(xiàn)根據(jù)字符串中指定的分隔符(缺省為空格)將字符串拆分成為字串的功能。利用nextToken()方法依次得到這些字串;sendBytes()方法完成信息內(nèi)容的發(fā)送,contentType()方法用于判斷文件的類型。
顯示W(wǎng)eb頁(yè)面
顯示 Web 頁(yè)面的index.html 文件代碼如下:
<html> <head> <meta http-equiv="Content-Language" content="zh-cn"> <meta name="GENERATOR" content="Microsoft FrontPage 5.0"> <meta http-equiv="Content-Type" content="text/html; charset=gb2312"> <title>Java Web 服務(wù)器</title> </head> <body> <p>********* <font color="#FF0000">歡迎你的到來(lái)!</font>*********</p> <p>這是一個(gè)用 Java 語(yǔ)言實(shí)現(xiàn)的 Web 服務(wù)器</p> <hr> </body> </html> | 運(yùn)行實(shí)例
為了測(cè)試上述程序的正確性,將編譯后的httpServer.class、httpRequestHandler.class和上面的index.html文件置于網(wǎng)絡(luò)的某臺(tái)主機(jī)的同一目錄中。
首先運(yùn)行服務(wù)器程序 java httpServer 8080,服務(wù)器程序運(yùn)行后顯示端口信息“httpServer runing on port 8080”, 然后在瀏覽器的地址欄中輸入http://localhost:8080/index.html,就可以正確顯示網(wǎng)頁(yè),同時(shí)在顯示“httpServer runing on port 8080 ”窗口中服務(wù)器會(huì)出現(xiàn)一些信息。
| |