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();
}
}
}