ex01: 一个简单的 WEB 服务器
runningwater đã chỉnh sửa trang này 2 năm trước cách đây

本章介绍 Java Web 服务器是如何运行的。Web 服务器也称为超文本传输协议(HyperText Transfer Protocol, HTTP) 服务器,因为它使用 HTTP 与其它客户端(通常是 Web 浏览器)进行通信。基于 Java 的 Web 服务器会使用两个重要的类:java.net.Socket 类和 java.net.ServerSocket 类,并通过发送 HTTP 消息进行通信。


HTTP

HTTP 允许 Web 服务服务器和浏览器通过 Internet 发送并接收数据,是一种基于“请求--响应”的协议。HTTP 使用 TCP 连接,TCP 协议默认使用 TCP 80 端口。

一个 HTTP 请求包含以下三部分:

  • 请求方法--统一资源标识符(Uniform Resource Identifer, URI) -- 协议/版本
  • 请求头
  • 实体

请求示例如下示:

POST /examples/default.jsp HTTP/1.1
Accept: text/plain; text/html 
Accept-Language: en-gb 
Connection: Keep-Alive
Host: localhost
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

lastName=Franks&firstName=Michael

第一行是请求方法--URI-- 协议/版本,其中 POST 是请求方法,/examples/default.jsp表示 URI,HTTP/1.1 表明请求使用的协议及其版本。

HTTP/1.1 支持 7 种请求方法: GET POST HEAD OPTIONS PUT DELETE TRACE,其中 GET 和 POST 是 Internet 应用中最常用的两种请求方法。

URI 指定 Internet 资源的完整路径。 URI 通常被解释为相对于服务器根目录的相对路径。它总是以 “/”开头。 统一资源定准符(Uniform Resource Locator, URL)实际上是 URI 的一种类型(参见http://www.ietf.org/rfc/rfc2396.txt)。

请求头包含客户端环境和请求实体正文的相关信息。例如,请求头可能会包含浏览器使用的语言,请求实体正文的长度等信息。各个请求头之间用回车/换行(Carriage Return/LineFeed,CRLF) 符间隔开。

在请求头和请求实体正文之间有一个空行,该空行只有 CRLF 符。这个空行对 HTTP 请求格式非常重要。CRLF 告诉 HTTP 服务器请求实体正文从哪里开始。

与 HTTP 请求类似,HTTP 响应也包含三部分:

  • 协议--状态码--描述
  • 响应头
  • 响应实体段

示例如下:

HTTP/1.1 200 OK
Server: Microsoft-IIS/4.0
Date: Mon, 5 Jan 2004 13:13:33 GMT 
Content-Type: text/html
Last-Modified: Mon, 5 Jan 2004 13:13:12 GMT 
Content-Length: 112

<html>
<head>
<title>HTTP Response Example</title> </head>
<body>
Welcome to Brainy Software
</body>
</html>

HTTP 响应头的第一行与 HTTP 请求头第一行类似。

Socket 类

套接字是网络连接的端点。套接字应用程序可以从网络中读取数据,可以向网络中写入数据。

要创建一个套接字,可以使用 Socket 类中众多构造函数中的一个。

public Socket(java.lang.String host, int port)

其中参数 host 是远程主机的名称或 IP 地址,参数 port 是连接远程应用程序的端口号。例如,想要通过 80 端口连接 yahoo.com,可以使用下面的语句创建Socket 对象:

new Socket("yahoo.com",80);

一旦成功地创建了 Socket 类的实例,就可以使用该实例发送或接收字节流。要发送字节流,需要调用 Socket 类的 getOutputStream()方法获取一个java.io.OutputStream对象。要发送文本到远程应用程序,通常要使用返回的 OutputSteam 对象创建一个java.io.PrintWriter对象。若想要从连接的另一端接收字节流,需要调用 Socket 类的getInputSteam()方法,该方法会返回一个java.io.InputSteam 对象。

下面的代码创建了一个套接字,用于与本地 HTTP 服务器进行通信(127.0.0.1 表示一个本地主机),发送 HTTP 请求,接收服务器的响应信息。创建了一个 StringBuffer 对象来保存响应信息,并将其输出到控制台上。

Socket socket = new Socket("127.0.0.1", "8080"); 
OutputStream os = socket.getOutputStream(); 
boolean autoflush = true;
PrintWriter out = new PrintWriter(
socket.getOutputStream(), autoflush); 
BufferedReader in = new BufferedReader(
new InputStreamReader( socket.getInputstream() ));

// send an HTTP request to the web server 
out.println("GET /index.jsp HTTP/1.1"); 
out.println("Host: localhost:8080");
out.println("Connection: Close");
out.println();

// read the response
boolean loop = true;
StringBuffer sb = new StringBuffer(8096);
while (loop) {
    if ( in.ready() ) { 
        int i=0;

    while (i!=-1) {
    i = in.read(); 
        sb.append((char) i);
    }
    loop = false; 
    }
    Thread.currentThread().sleep(50); 
}
// display the response to the out console 
System.out.println(sb.toString()); 
socket.close();

`Socket 类表示一个客户端套接字。 ServerSocket 类与 Socket类并不相同。服务器套接字要等待来自客户端的连接请求。当服务器套拉字接收到了连接请求后,它会创建一个 Socket 实例来处理与客户端的通信。

要创建一个服务器套接字,可以使用 ServerSocket 类提供的 4 个构造函数中的任意一个。

public ServerSocket(int port, int backLog, InetAddress bindingAddress);

值得注意的是,在这个构造函数中,参数绑定地址必须是 java.net.InetAddress类的实例。例如侦听本地 8080 端口:

new ServerSocket(8080, 1, InetAddress.getByName("127.0.0.1"));

创建了 ServerSocket 实例后,调用 accept方法可以使其等待传入的连接请求,该连接请求会通过服务器套接字侦听的端口上绑定地址传入。