Web服務(wù)器與客戶端的通信使用HTTP協(xié)議(超文本傳輸協(xié)議),所以也叫做HTTP服務(wù)器。用Java構(gòu)造Web服務(wù)器主要用二個(gè)類,java.net.Socket和java.net.ServerSocket,來(lái)實(shí)現(xiàn)HTTP通信。因此,本文首先要討論的是HTTP協(xié)議和這兩個(gè)類,在此基礎(chǔ)上實(shí)現(xiàn)一個(gè)簡(jiǎn)單但完整的Web服務(wù)器。
一、超文本傳輸協(xié)議
Web服務(wù)器和瀏覽器通過(guò)HTTP協(xié)議在Internet上發(fā)送和接收消息。HTTP協(xié)議是一種請(qǐng)求-應(yīng)答式的協(xié)議——客戶端發(fā)送一個(gè)請(qǐng)求,服務(wù)器返回該請(qǐng)求的應(yīng)答。HTTP協(xié)議使用可靠的TCP連接,默認(rèn)端口是80。HTTP的第一個(gè)版本是HTTP/0.9,后來(lái)發(fā)展到了HTTP/1.0,現(xiàn)在最新的版本是HTTP/1.1。HTTP/1.1由 RFC 2616 定義(pdf格式)。
本文只簡(jiǎn)要介紹HTTP 1.1的相關(guān)知識(shí),但應(yīng)該足以讓你理解Web服務(wù)器和瀏覽器發(fā)送的消息。如果你要了解更多的細(xì)節(jié),請(qǐng)參考RFC 2616。
在HTTP中,客戶端/服務(wù)器之間的會(huì)話總是由客戶端通過(guò)建立連接和發(fā)送HTTP請(qǐng)求的方式初始化,服務(wù)器不會(huì)主動(dòng)聯(lián)系客戶端或要求與客戶端建立連接。瀏覽器和服務(wù)器都可以隨時(shí)中斷連接,例如,在瀏覽網(wǎng)頁(yè)時(shí)你可以隨時(shí)點(diǎn)擊“停止”按鈕中斷當(dāng)前的文件下載過(guò)程,關(guān)閉與Web服務(wù)器的HTTP連接。
1.1 HTTP請(qǐng)求
HTTP請(qǐng)求由三個(gè)部分構(gòu)成,分別是:方法-URI-協(xié)議/版本,請(qǐng)求頭,請(qǐng)求正文。下面是一個(gè)HTTP請(qǐng)求的例子:
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
請(qǐng)求的第一行是“方法-URI-協(xié)議/版本”,其中GET就是請(qǐng)求方法,/servlet/default.jsp表示URI,HTTP/1.1是協(xié)議和協(xié)議的版本。根據(jù)HTTP標(biāo)準(zhǔn),HTTP請(qǐng)求可以使用多種請(qǐng)求方法。例如,HTTP 1.1支持七種請(qǐng)求方法:GET,POST,HEAD,OPTIONS,PUT,DELETE,和TRACE。在Internet應(yīng)用中,最常用的請(qǐng)求方法是GET和POST。
URI完整地指定了要訪問(wèn)的網(wǎng)絡(luò)資源,通常認(rèn)為它相對(duì)于服務(wù)器的根目錄而言,因此總是以“/”開頭。URL實(shí)際上是URI 一種類型。最后,協(xié)議版本聲明了通信過(guò)程中使用的HTTP協(xié)議的版本。
請(qǐng)求頭包含許多有關(guān)客戶端環(huán)境和請(qǐng)求正文的有用信息。例如,請(qǐng)求頭可以聲明瀏覽器所用的語(yǔ)言,請(qǐng)求正文的長(zhǎng)度,等等,它們之間用一個(gè)回車換行符號(hào)(CRLF)分隔。
請(qǐng)求頭和請(qǐng)求正文之間是一個(gè)空行(只有CRLF符號(hào)的行),這個(gè)行非常重要,它表示請(qǐng)求頭已經(jīng)結(jié)束,接下來(lái)的是請(qǐng)求的正文。一些介紹Internet編程的書籍把這個(gè)CRLF視為HTTP請(qǐng)求的第四個(gè)組成部分。
在前面的HTTP請(qǐng)求中,請(qǐng)求的正文只有一行內(nèi)容。當(dāng)然,在實(shí)際應(yīng)用中,HTTP請(qǐng)求正文可以包含更多的內(nèi)容。
1.2 HTTP應(yīng)答
和HTTP請(qǐng)求相似,HTTP應(yīng)答也由三個(gè)部分構(gòu)成,分別是:協(xié)議-狀態(tài)代碼-描述,應(yīng)答頭,應(yīng)答正文。下面是一個(gè)HTTP應(yīng)答的例子:
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
經(jīng)過(guò)測(cè)試,可以使用滴
//實(shí)例一:
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";// 主機(jī),可以是域名,也可以是ip地址
int port = 1443;// 端口
InetAddress addr = InetAddress.getByName(hostname);
// 建立連接
Socket socket = new Socket(addr, port);
// 創(chuàng)建數(shù)據(jù)提交數(shù)據(jù)流
httpGetWriter = new BufferedWriter(new OutputStreamWriter(socket.getOutputStream(), "GBK"));
// 相對(duì)主機(jī)的請(qǐng)求地址
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();
// 創(chuàng)建web服務(wù)器響應(yīng)的數(shù)據(jù)流
httpResponse = new BufferedReader(new InputStreamReader(socket.getInputStream(), "GBK"));
// 讀取每一行的數(shù)據(jù).注意大部分端口操作都需要交互數(shù)據(jù)。
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();
}
}
}
}
//實(shí)例二
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");
// 注,這是關(guān)鍵的關(guān)鍵,忘了這里讓我搞了半個(gè)小時(shí)。這里一定要一個(gè)回車換行,表示消息頭完,不然服務(wù)器會(huì)等待
buffer.append("\r\n");
writer.write(buffer.toString());
writer.flush();
// --輸出服務(wù)器傳回的消息的頭信息
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();
}
}
}