Web服務器與客戶端的通信使用HTTP協議(超文本傳輸協議),所以也叫做HTTP服務器。用Java構造Web服務器主要用二個類,java.net.Socket和java.net.ServerSocket,來實現HTTP通信。因此,本文首先要討論的是HTTP協議和這兩個類,在此基礎上實現一個簡單但完整的Web服務器。
一、超文本傳輸協議
Web服務器和瀏覽器通過HTTP協議在Internet上發送和接收消息。HTTP協議是一種請求-應答式的協議——客戶端發送一個請求,服務器返回該請求的應答。HTTP協議使用可靠的TCP連接,默認端口是80。HTTP的第一個版本是HTTP/0.9,后來發展到了HTTP/1.0,現在最新的版本是HTTP/1.1。HTTP/1.1由 RFC 2616 定義(pdf格式)。
本文只簡要介紹HTTP 1.1的相關知識,但應該足以讓你理解Web服務器和瀏覽器發送的消息。如果你要了解更多的細節,請參考RFC 2616。
在HTTP中,客戶端/服務器之間的會話總是由客戶端通過建立連接和發送HTTP請求的方式初始化,服務器不會主動聯系客戶端或要求與客戶端建立連接。瀏覽器和服務器都可以隨時中斷連接,例如,在瀏覽網頁時你可以隨時點擊“停止”按鈕中斷當前的文件下載過程,關閉與Web服務器的HTTP連接。
1.1 HTTP請求
HTTP請求由三個部分構成,分別是:方法-URI-協議/版本,請求頭,請求正文。下面是一個HTTP請求的例子:
GET /servlet/default.jsp HTTP/1.1
Accept: text/plain; text/html
Accept-Language: en-gb
Connection: Keep-Alive
Host: localhost
Referer: http://localhost/ch8/SendDetails.htm
User-Agent: Mozilla/4.0 (compatible; MSIE 4.01; Windows 98)
Content-Length: 33
Content-Type: application/x-www-form-urlencoded
Accept-Encoding: gzip, deflate
userName=JavaJava&userID=javaID
請求的第一行是“方法-URI-協議/版本”,其中GET就是請求方法,/servlet/default.jsp表示URI,HTTP/1.1是協議和協議的版本。根據HTTP標準,HTTP請求可以使用多種請求方法。例如,HTTP 1.1支持七種請求方法:GET,POST,HEAD,OPTIONS,PUT,DELETE,和TRACE。在Internet應用中,最常用的請求方法是GET和POST。
URI完整地指定了要訪問的網絡資源,通常認為它相對于服務器的根目錄而言,因此總是以“/”開頭。URL實際上是URI 一種類型。最后,協議版本聲明了通信過程中使用的HTTP協議的版本。
請求頭包含許多有關客戶端環境和請求正文的有用信息。例如,請求頭可以聲明瀏覽器所用的語言,請求正文的長度,等等,它們之間用一個回車換行符號(CRLF)分隔。
請求頭和請求正文之間是一個空行(只有CRLF符號的行),這個行非常重要,它表示請求頭已經結束,接下來的是請求的正文。一些介紹Internet編程的書籍把這個CRLF視為HTTP請求的第四個組成部分。
在前面的HTTP請求中,請求的正文只有一行內容。當然,在實際應用中,HTTP請求正文可以包含更多的內容。
1.2 HTTP應答
和HTTP請求相似,HTTP應答也由三個部分構成,分別是:協議-狀態代碼-描述,應答頭,應答正文。下面是一個HTTP應答的例子:
HTTP/1.1 200 OK
Date: Tue, 06 Mar 2012 12:32:58 GMT
Server: Apache/2.2.22 (Win32)
Last-Modified: Tue, 06 Mar 2012 11:46:06 GMT
ETag: “b000000008d9e-57-4ba9196947acd”
Accept-Ranges: bytes
Content-Length: 87
Content-Type: text/html
經過測試,可以使用滴
//實例一:
package com.abin.lii.han.socket;
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.net.InetAddress;
import java.net.Socket;
import java.net.URLEncoder;
public class SocketGetServletTest {
public static void main(String[] args) {
BufferedWriter httpGetWriter = null;
BufferedReader httpResponse = null;
try {
String hostname = "localhost";// 主機,可以是域名,也可以是ip地址
int port = 1443;// 端口
InetAddress addr = InetAddress.getByName(hostname);
// 建立連接
Socket socket = new Socket(addr, port);
// 創建數據提交數據流
httpGetWriter = new BufferedWriter(new OutputStreamWriter(socket.getOutputStream(), "GBK"));
// 相對主機的請求地址
StringBuffer httpSubmitPath = new StringBuffer("/abin/ImediaRegister?");
// StringBuffer httpSubmitPath = new StringBuffer("http://localhost:7200/abin/ImediaRegister?");
httpSubmitPath.append(URLEncoder.encode("app", "GBK"));
httpSubmitPath.append("=");
httpSubmitPath.append(URLEncoder.encode("longcodeimedia", "GBK"));
httpSubmitPath.append("&");
httpSubmitPath.append(URLEncoder.encode("udid", "GBK"));
httpSubmitPath.append("=");
httpSubmitPath.append(URLEncoder.encode("123456789", "GBK"));
httpSubmitPath.append("&");
httpSubmitPath.append(URLEncoder.encode("source", "GBK"));
httpSubmitPath.append("=");
httpSubmitPath.append(URLEncoder.encode("limei", "GBK"));
httpSubmitPath.append("&");
httpSubmitPath.append(URLEncoder.encode("returnFormat", "GBK"));
httpSubmitPath.append("=");
httpSubmitPath.append(URLEncoder.encode("2", "GBK"));
httpGetWriter.write("GET " + httpSubmitPath.toString() + " HTTP/1.1\r\n");
httpGetWriter.write("Host: localhost:7200\r\n");
httpGetWriter.write("UserAgent: IE8.0\r\n");
httpGetWriter.write("Connection: Keep-Alive\r\n");
httpGetWriter.write("\r\n");
httpGetWriter.flush();
// 創建web服務器響應的數據流
httpResponse = new BufferedReader(new InputStreamReader(socket.getInputStream(), "GBK"));
// 讀取每一行的數據.注意大部分端口操作都需要交互數據。
String lineStr = "";
while ((lineStr = httpResponse.readLine()) != null) {
System.out.println(lineStr);
}
} catch (Exception e) {
e.printStackTrace();
} finally {
try {
if (httpGetWriter != null) {
httpGetWriter.close();
}
if (httpResponse != null) {
httpResponse.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
//實例二
package com.abin.lii.han.socket;
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.net.InetAddress;
import java.net.Socket;
public class SocketGetServletTest1 {
public static void main(String[] args) {
try {
Socket socket = new Socket(InetAddress.getLocalHost(), 7200);
BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(socket.getOutputStream()));
StringBuffer buffer = new StringBuffer();
buffer.append("GET http://localhost:7200/abin/ImediaRegister?app=2 HTTP/1.1\r\n");
buffer.append("Host: localhost:7200\r\n");
buffer.append("UserAgent: IE8.0\r\n");
buffer.append("Connection: Keep-Alive\r\n");
// 注,這是關鍵的關鍵,忘了這里讓我搞了半個小時。這里一定要一個回車換行,表示消息頭完,不然服務器會等待
buffer.append("\r\n");
writer.write(buffer.toString());
writer.flush();
// --輸出服務器傳回的消息的頭信息
BufferedReader reader=new BufferedReader(new InputStreamReader(socket.getInputStream()));
String line = null;
StringBuilder builder=new StringBuilder();
while((line=reader.readLine())!=null){
builder.append(line);
}
String result=builder.toString();
System.out.println("result="+result);
} catch (Exception e) {
e.printStackTrace();
}
}
}