java基础教程-网络应用

来源:互联网 发布:淘宝服务器时间校准 编辑:程序博客网 时间:2024/05/08 12:20
 
第13章 网 
本章讨论支持网络应用的java.net包。它的创建者称Java为“网络编程”。尽管它只占Java的一小部分,与C++或FORTRAN相比,这种编程语言更有利于编写网络程序。使Java成为好的网络语言正是java.net包定义的类。
这些类包装了由加州大学的伯克利分校的BSD引入的“套接字(socket)”范型。如果不简单介绍UNIX和BSD套接字的历史对Internet网络库的讨论就是不完整的。
13.1 
1969年,Ken Thompson和Dennis Ritchie在Murray Hill,New Jersey的贝尔电话实验室开发了与C语言一致的UNIX。很多年来,UNIX的发展停留在贝尔实验室和一些大学及研究机构,用特意设计的DEC PDP机器运行。到了1978 年,Bill Joy在Cal Berkeley领导了一个项目,给UNIX增添新的特性,例如虚拟内存和全屏显示功能。到了1984年早期,当Bill正准备建立Sun Microsystems,它发明了4.2BSD,即众所周知的Berkeley UNIX。
4.2BSD带有快速文件系统、可靠信号处理、进程间通信以及最重要的网络功能。最先在4.2中发现的网络支持后来成为了实际的Internet标准。Berkeley的TCP/IP实现保留了在Internet内通信的最初的标准。进程间和网络通信的套接字范型被Berkeley以外的系统广泛采用。甚至Window和Macintosh在20世纪80年代晚期也开始和“Berkeley 套接字”谈话。
13.1.1 套接字概述
网络套接字(network socket)有一点像电源插座。网络周围的各式插头有一个标准方法传输它们的有效负载。理解标准协议的任何东西都能够插入套接字并进行通信。对于电源插座,不论你插入一个电灯或是烤箱,只要它们使用60HZ、115伏电压,设备将会工作。思考一下你的用电账单是怎样生成的。在你的房子和电网支架间可能有1米的距离,经过这1米的每千瓦电都将列入账单,账单到达你的“地址”。所以,虽然电流在电源插座周围是自由流动的,你房子的所有插头都是有特定的地址的。
除了我们谈论的是TCP/IP包括IP地址而不是电器和街道地址外,同样的思想被应用到网络套接字。Internet Protocol(IP)是一种低级路由协议。该协议将数据分解成小包然后通过网络传到一个地址,但它并不确保传输的信息包一定到达目的。传输控制协议(TCP)是一种较高级的协议,它把这些信息包有力的捆绑在一起,在必要的时候,排序和重传这些信息包以获得可靠的数据传输。第三种协议,用户数据报协议(UDP)几乎与TCP协议相当,并能够直接用来支持快速的、无连接的、不可靠的信息包传输。
 
13.1.2 客户/服务器模式
你经常在与网络有关的话题中听说客户/服务器(client/server)这个术语。在一些产品说明中,这个概念似乎非常复杂,其实它的含义很简单。 服务器(server)就是能够提供共享资源的任何东西。如计算服务器,提供计算功能;打印服务器,管理多个打印机;磁盘服务器,提供联网的磁盘空间;以及Web服务器,用来存储网页。客户(client)是简单的任何有权访问特定服务器的实体。客户和服务器之间的连接就像电灯和电源插头的连接。房间的电源插座是服务器,电灯是客户。服务器是永久的资源,在访问过服务器之后,客户可以自由的“拔去插头”。
在Berkeley套接字中,套接字的概念允许单个计算机同时服务于很多不同的客户,并能够提供不同类型信息的服务。该种技术由引入的端口(port)处理,此端口是一个特定机器上的被编号的套接字。服务器进程是在“监听”端口直到客户连到它。尽管每个客户部分是独特的,一个服务器允许在同样端口接受多个客户。为管理多个客户连接,服务器进程必须是多线程的,或者有同步输入/输出处理多路复用技术的其他方法。
13.1.3 保留套接字
一旦连接成功,一个高级的协议跟着生效,该协议与所使用的端口有关。TCP/IP 为特定协议保留了低端的1024 个端口。如果你在网络上冲浪有一些时间了,那么这中间的很多端口你已经很熟悉了。端口21是FTP的,23是Telne的t,25是e-mail的,79是finger的,80是HTTP的,119是网络新闻的——等等。下面依次讲述每个协议客户与端口是如何交互的。
举例来说,HTTP是网络浏览器及服务器用来传输超文本网页和图像的协议。它是基本网页浏览服务器的一个非常简单的协议。当一个客户向一个HTTP服务器请求一个文件时,即一个点击动作,它仅仅以一种特定格式向预先指定的端口打印文件名然后读回文件的内容。服务器同样对状态代码编号反应,告诉客户请求是否被执行以及原因。下面以一个例子说明它的工作原理。客户请求单个文件/index.html,服务器回应它已成功找到该文件并且把文件传输到客户:
服务器
客户
监听80端口
与端口80连接
接受连接
写“GET /index.html HTTP/1.0/n/n
读取数据直到遇到第二个换行符(/n
 
知道GET是一个命令,HTTP/1.0是有效的协议
 
读取名为/ index.html的本地文件
 
写“HTTP/1.0 200 OK/n/n
200”意味着“文件来了”
向套接字复制文件内容
读取文件内容并显示
挂起
挂起
很明显,HTTP协议比该例显示的要复杂的多,但这是一个实际的和附近Web服务器的
 
传输。
13.1.4 代理服务器
一个代理服务器(proxy server)以客户端协议与其他服务器通信。这在客户与服务器连接受到某些限制的情况下经常是必需的。这样,客户可以连接代理服务器,代理服务器没有这些限制并且代理服务器也会依次和客户通信。代理服务器具有过滤某些请求或缓存一些这样的请求的结果以备后用的额外功能。一个缓冲代理HTTP 服务器可用来减小局域网连向Internet的带宽要求。若一个流行网站的网址被成百上千个用户点击,代理服务器可以一次获得该网络服务器的流行网页,节省昂贵的Internet网络传输,同时为用户快速提供对这些网页的访问。
本章的后面部分,我们将实际建立一个完整的缓冲代理HTTP服务器。这个程序有趣的是它既是客户又是服务器。为服务于某些网页,它必须像客户那样向其他服务器获取被请求内容的一个拷贝。
13.1.5 Internet 编址
Internet上的每一台计算机都有一个地址。Internet地址是网络上标识每台计算机的惟一定义的数。IP地址有32位,我们通常把它们分成4个从0到255的,用点号(.)隔开的序列。这使它们易于记忆,因为它们不是随机指派的——它们是按层次结构指派的。最开始的字节定义了网络属于A、B、C、D或E哪个等级。多数Internet用户使用C级,因为有多于两百万的网络是在C类。C类网的开始字节从192到224,最后字节实际上标识了256之中可以上单个C类网的独立计算机。这种安排允许在C类网中可以有5亿的设备。
域名服务(DNS)
如果每台机器必须用数字作为它们的地址,Internet不是一个漫游的友好场所。例如,设想在广告的底部看见“http://192.9.9.1/”,这一定使人头昏脑胀。感谢上帝,存在一个与所有这些数字相伴的一个平行层次的名称的交换所,叫做域名服务(DNS)。就像IP地址中从左到右描绘网络层次的四个数字一样,Internet 地址的名称,域名,在名称空间从右到左描述了机器的地址。例如,www.osborne.com是在COM域(为美国商业企业保留)中的,叫做osborne(公司名称)的,www是Osborne的Web服务器的特定计算机的名称。www在意义上相应于IP地址的最右边的数字。
13.2 JAVA和网络
现在准备工作已经完成,让我们看看Java怎样和所有的这些网络概念相联系。Java 通过扩展第17章介绍的已有的流式输入/输出接口和增加在网络上建立输入/输出对象特性这两个方法支持TCP/IP。Java支持TCP和UDP协议族。TCP用于网络的可靠的流式输入/输出。UDP支持更简单的、快速的、点对点的数据报模式。
 
13.2.1 网络类和接口
java.net 包所包含的类如下:
Authenticator (Java 2)
JarURLConnection (Java 2)
SocketPermission
ContentHandler
MulticastSocket
URL
DatagramPacket
NetPermission
URLClassLoader (Java 2)
DatagramSocket
PasswordAuthentication (Java 2)
URLConnection
DatagramSocketImpl
ServerSocket
URLDecoder (Java 2)
HttpURLConnection
Socket
URLEncoder
InetAddress
SocketImpl
URLStreamHandler
java.net包的接口如下:
ContentHandlerFactory
SocketImplFactory
URLStreamHandlerFactory
FileNameMap
SocketOptions
DatagramSocketImplFactory (Java 2, v1.3中添加的)
下面我们将讨论主要的网络类并且一些应用例子。
13.3 InetAddress
无论你是在打电话、发送邮件或建立与Internet的连接,地址是基础。InetAddress 类用来封装我们前面讨论的数字式的IP地址和该地址的域名。你通过一个IP主机名与这个类发生作用,IP主机名比它的IP地址用起来更简便更容易理解。InetAddress 类内部隐藏了地址数字。
13.3.1 工厂方法
InetAddress 类没有明显的构造函数。为生成一个InetAddress对象,必须运用一个可用的工厂方法。工厂方法(factory method)仅是一个类中静态方法返回一个该类实例的约定。这是在一个带有各种参数列表的重载构造函数完成的,当持有惟一方法名时可使结果更清晰。对于InetAddress,三个方法 getLocalHost( )、getByName( )以及getAllByName( )可以用来创建InetAddress的实例。三个方法显示如下:
static InetAddress getLocalHost( )
   throws UnknownHostException
static InetAddress getByName(String hostName)
   throws UnknownHostException
static InetAddress[ ] getAllByName(String hostName)
   throws UnknownHostException
getLocalHost( )仅返回象征本地主机的InetAddress 对象。getByName( )方法返回一个传给它的主机名的InetAddress。如果这些方法不能解决主机名,它们引发一个
 
UnknownHostException异常。
在Internet上,用一个名称来代表多个机器是很常有的事。Web服务器中,也有方法提供一定程度的缩放。getAllByName( )工厂方法返回代表由一个特殊名称分解的所有地址的InetAddresses类数组。在不能把名称分解成至少一个地址时,它将引发一个Unknown HostException异常。
下面的例子打印了本地机的地址和名称以及两个著名的Internet网址:
// Demonstrate InetAddress.
import java.net.*;

class InetAddressTest
{
 public static void main(String args[]) throws UnknownHostException {
    InetAddress Address = InetAddress.getLocalHost();
    System.out.println(Address);
    Address = InetAddress.getByName("osborne.com");
    System.out.println(Address);
    InetAddress SW[] = InetAddress.getAllByName("www.nba.com");
    for (int i=0; i<SW.length; i++)
      System.out.println(SW[i]);
 }
}
下面是该程序的输出(当然,你所看到的结果可能有一些不同):
default/206.148.209.138
osborne.com/198.45.24.130
www.nba.com/204.202.130.223
13.3.2 实例方法
InetAddress 类也有一些非静态的方法,列于下面,它们可以用于讨论过的方法返回的对象:
boolean equals(Object other)
如果对象具有和other相同的Internet地址则返回true
byte[ ] getAddress( )
返回代表对象的Internert地址的以网络字节为顺序的有四个元素的字节数组。
String getHostAddress( )
返回代表与InetAddress对象相关的主机地址的字符串。
String getHostName( )
返回代表与InetAddress对象相关的主机名的字符串。
int hashCode( )
返回调用对象的散列码。
boolean isMulticastAddress( )
如果Internet地址是一个多播地址返回true;否则返回false
String toString( )
返回主机名字符串和IP地址。
Internet地址在分层的缓存服务器系列中被找到。这意味着你的本地机可能像知道它自己和附近的服务器一样知道一个名称-IP地址的自动映射。对于其他名称,它可能向一个本地DNS服务器询问IP地址信息。如果那个服务器不含一个指定的地址,它可以到一个远程
 
的站点去询问。这可以一路通到名为InterNIC(internic.net)的根服务器。该过程可能需要比较长的时间,所以结构化你的代码以使你在本地存储IP地址信息而不是重复向上查找信息是一个明智之举。
13.4 TCP/IP客户套接字
TCP/IP 套接字用于在主机和Internet之间建立可靠的、双向的、持续的、点对点的流式连接。一个套接字可以用来建立Java 的输入/输出系统到其他的驻留在本地机或Internet上的任何机器的程序的连接。
注意:小应用程序只建立回到下载它的主机的套接字连接。存在这个限制的原因是:穿过防火墙的小应用程序有权使用任何机器是很危险的事情。
Java中有两类TCP套接字。一种是服务器端的,另一种是客户端的。ServerSocket类设计成在等待客户建立连接之前不做任何事的“监听器”。Socket类为建立连向服务器套接字以及启动协议交换而设计。
一个Socket对象的创建隐式建立了一个客户和服务器的连接。没有显式的说明建立连接细节的方法或构造函数。下面是用来生成客户套接字的两个构造函数:
Socket(String hostName, int port)
创建一个本地主机与给定名称的主机和端口的套接字连接,可以引发一个UnknownHostException异常或IOException异常。
Socket(InetAddress ipAddress, int port)
用一个预先存在的InetAddress对象和端口创建一个套接字,可以引发IOException异常。
使用下面的方法,可以在任何时候检查套接字的地址和与之有关的端口信息:
InetAddress getInetAddress( )
返回和Socket对象相关的InetAddress
Int getPort( )
返回与该Socket对象连接的远程端口。
Int getLocalPort( )
返回与该Socket连接的本地端口。
一旦Socket对象被创建,同样可以检查它获得访问与之相连的输入和输出流的权力。如果套接字因为网络的连接中断而失效,这些方法都能够引发一个IOException异常。
InputStream getInputStream( )
返回与调用套接字有关的InputStream类。
OutputStream getOutputStream( )
返回与调用套接字有关的OutputStream类。
void close( )
关闭InputStreamOutputStream
13.4.1 Whois
下面的例子打开了一个InterNIC服务器上“whois”端口的连接,传输命令行语句到套接字,然后打印返回的数据。InterNIC将努力寻找作为已注册的Internet域名的参数,然后
 
传输回IP地址和该地点的联系信息。
//Demonstrate Sockets.
import java.net.*;
import java.io.*;

class Whois {
 public static void main(String args[]) throws Exception {
    int c;
    Socket s = new Socket("internic.net", 43);
    InputStream in = s.getInputStream();
    OutputStream out = s.getOutputStream();
    String str = (args.length == 0 ? "osborne.com" : args[0]) + "/n";
    byte buf[] = str.getBytes();
    out.write(buf);
    while ((c = in.read()) != -1) {
     System.out.print((char) c);
    }
    s.close();
 }
}
例如,如果你在命令行键入osborne.com,你将会获得下面相似的结果:
Whois Server Version 1.3

Domain names in the .com, .net, and .org domains can now be registered
with many different competing registrars. Go to http://www.internic.net
for detailed information.

   Domain Name: OSBORNE.COM
   Registrar: NETWORK SOLUTIONS, INC.
   Whois Server: whois.networksolutions.com
   Referral URL: www.networksolutions.com
   Name Server: NS1.EPPG.COM
   Name Server: NS2.EPPG.COM
   Updated Date: 07-apr-2000

>>> Last update of whois database: Fri, 6 Oct 2000 10:03:36 EDT <<<

The Registry database contains ONLY .COM, .NET, .ORG, .EDU domains and
Registrars.
13.5 URL
最后的例子有一点含糊,因为现代Internet不是围绕老的协议,如whois、finger及FTP的,而是关于WWW(万维网)的。Web是一个由Web浏览器统一的高级协议和文件格式的松散集合。Web中最重要的一个方面是Tim Berners-Lee设计了一个定位所有网络资源的弹性方法。一旦你能够可靠的命名一个事物,它将成为一个功能强大的范型,统一资源定位
 
(URL)就做这些。
URL提供了一个相当容易理解的形式来惟一确定或对Internet上的信息进行编址。URL是无所不在的;每一个浏览器用它们来识别Web上的信息。实际上,Web是用URL 和HTML 为所有资源编制同样陈旧的Internet 。在Java的网络类库中,URL 类为用URL在Internet 上获取信息提供了一个简单的、简洁的用户编程接口(API)。
13.5.1 格式化(Format
http://www.osborne.com/ 和 http://www.osborne.com:80/index.htm是URL 的两个例子。一个URL规范以四个元素为基础。第一个是所用到的协议,用冒号(:)来将它与定位符的其他部分相隔离。尽管现在所有的事情都通过HTTP(实际上,如果你在URL规范中不用“http://”,大多数浏览器都能正确执行)完成,但它不是惟一的协议,常见的协议有http、ftp、gopher和文件。第二个元素是主机名或所用主机的IP地址,这由左边的双斜线(//)和右边的单斜线(/)或可选冒号(:)限制。第三个成分,端口号,是可选的参数,由主机名左边的冒号(:)和右边的斜线(/)限制(它的默认端口为80,它是预定义的HTTP 端口;所以“:80是多余的)。第四部分是实际的文件路径。多数HTTP服务器将给URL附加一个与目录资源相关的index.html或 index.htm 文件。所以,http://www.osborne.com/ 与http://www.osborne.com/index.htm 是相同的。
Java的URL类有多个构造函数,每个都能引发一个MalformedURLException异常。一个常见形式是用与你在浏览器中看到的相同的字符串指定URL:
URL(String urlSpecifier)
下面的两个构造函数形式允许你把URL分裂它的组成部分:
URL(String protocolName, String hostName, int port, String path)
URL(String protocolName, String hostName, String path)
另一个经常用到的构造函数允许你用一个已经存在的URL作为引用上下文,然后从该上下文中创建一个新的URL。尽管这听起来有些别扭,它实际上是很简单而有用的。
URL(URL urlObj, String urlSpecifier)
下面的例子中,我们为Osborne的下载页面创建一个URL,然后检查它的属性:
// Demonstrate URL.
import java.net.*;
class URLDemo {
 public static void main(String args[]) throws MalformedURLException {
    URL hp = new URL("http://www.osborne.com/download");

    System.out.println("Protocol: " + hp.getProtocol());
    System.out.println("Port: " + hp.getPort());
    System.out.println("Host: " + hp.getHost());
    System.out.println("File: " + hp.getFile());
    System.out.println("Ext:" + hp.toExternalForm());
 }
}
运行该程序,你将获得下面输出:
Protocol: http
Port: -1
Host: www.osborne.com
File: /download
Ext:http://www.osborne.com/download
注意端口是-1,这意味着该端口没有被明确设置。现在我们已经创建了一个URL对象,我们希望找回与之相连的数据。为获得URL的实际内容信息,用它的openConnection( )方法创建一个URLConnection对象,如下:
url.openConnection()
openConnection( ) 有下面的常用形式:
URLConnection openConnection( )
与调用URL对象相关,它返回一个URLConnection对象。它可能引发IOException异常。
13.6 URLConnection
URLConnection是访问远程资源属性的一般用途的类。如果你建立了与远程服务器之间的连接,你可以在传输它到本地之前用URLConnection来检查远程对象的属性。这些属性由HTTP协议规范定义并且仅对用HTTP协议的URL对象有意义。我们这里将验证URLConnection最有用的原理。
下面的例子中我们用一个URL对象的openConnection( )方法创建了一个URLConnection类,然后用它来检查文件的属性和内容:
// Demonstrate URLConnection.
import java.net.*;
import java.io.*;
import java.util.Date;

class UCDemo
{
 public static void main(String args[]) throws Exception {
    int c;
    URL hp = new URL("http://www.osborne.com");
    URLConnection hpCon = hp.openConnection();

    System.out.println("Date: " + new Date(hpCon.getDate()));
    System.out.println("Content-Type: " + hpCon.getContentType());
    System.out.println("Expires: " + hpCon.getExpiration());
    System.out.println("Last-Modified: " +
                       new Date(hpCon.getLastModified()));
    int len = hpCon.getContentLength();
    System.out.println("Content-Length: " + len);
    if (len > 0) {
     
System.out.println("=== Content ===");
      InputStream input = hpCon.getInputStream();
      int i = len;
      while (((c = input.read()) != -1) && (--i > 0)) {
        System.out.print((char) c);
      }
      input.close();
    } else {
      System.out.println("No Content Available");
    }
 }
}
该程序建立了一个经过端口80通向www.osborne.com 的HTTP 连接。然后列出了标头值并检索内容。下面是输出的前几行:
Date: Fri Oct 06 14:17:10 CDT 2000
Content-Type: text/html
Expires: 0
Last-Modified: Tue Oct 26 01:36:57 CDT 1999
Content-Length: 529
=== Content ===
<html>
<head>
<title>Osborne/McGraw-Hill: Required Reading for the Information Age</title>
</head>
URL和URLConnection类对于希望建立与HTTP服务器的连接来获取信息的简单程序来说是非常好的。对于更复杂的应用程序,你会发现学习HTTP协议规范,实现你自己的包装程序是比较好的。
13.7 TCP/IP服务器套接字
如我们在前面提到的,Java具有用来创建服务器应用程序的不同的套接字类。ServerSocket类用来创建服务器,服务器监听本地或远程客户程序通过公共端口的连接。既然Web驱动着Internet中的大部分活动,本节就开发一个可运行的Web服务器(http)。
ServerSocket与通常的Sockets类完全不同。当创建一个ServerSocket类,它在系统注册自己并对客户连接感兴趣。ServerSocket的构造函数反映了希望接受连接的端口号及你希望排队等待上述端口的时间(该项可选)。队列长度告诉系统多少与之连接的客户在系统拒绝连接之前可以挂起。队列的默认长度是50。构造函数在不利情况下可以引发IOException异常。下面是构造函数:
ServerSocket(int port)
在指定端口创建队列长度为50的服务器套接字。
ServerSocket(int port, int maxQueue)
在指定端口创建一个最大队列长度为maxQueue的服务器套接字。
ServerSocket(int port, int maxQueue,
                     InetAddress localAddress)
在指定端口创建一个最大队列长度为maxQueue的服务器套接字。在一个多地址主机上,localAddress指定该套接字约束的IP地址。
ServerSocket有一个额外的accept()方法,该方法是一个等待客户开始通信的模块化调用,然后以一个用来与客户通信的常规Socket返回。
13.8 缓存代理HTTP服务器
本节的剩余部分,我们讲述一个简单的缓存代理HTTP服务器,名为http,来演示客户与服务器套接字。http只支持GET操作及硬编码的MIME类型的一小部分(MIME类型是多媒体内容的类型描述符)。代理HTTP服务器是单线程的,该线程中每一个请求依次被处理,其他请求等待。这是缓存的相当天真的策略——它在RAM永久保存所有信息。http作为一个代理服务器时,它还拷贝每一个它获取的文件到本地缓存中。对于本地缓存,它没有用于刷新和无用单元回收的策略。除此之外,http代表了客户和服务器套接字的一个多产的例子,它是值得探究和容易扩展的。
13.8.1 源代码
HTTP服务器是通过5个类和一个接口实现的。更完善的实现方案可能在主类httpd外分裂很多的方法,以使组成结构更抽象。考虑本书的容量,多数功能是在单个类中实现的,小的支持类仅作为数据结构。我们仔细学习每一个类和方法来了解该服务器怎样工作,由支持类开始,终止于主程序。
MimeHeader.java
MIME是通过电子邮件系统传达多媒体内容的一个Internet标准。该标准是由Nat Borenstein在1992年创建的。HTTP协议运用并扩展了MIME标头的概念,在HTTP客户和服务器之间传输常规的属性/值对。
构造函数 该类是Hashtable 的一个子类,所以它能方便的存储和检索与MIME标头有关的关键字/值对。它有两个构造函数。一个创建一个不含关键字的空的MimeHeader。另一个以一个格式化的字符串作为MIME标头,然后把它解析为对象的初始内容。参看下面的parse( )。
parse( ) parse( )方法用来获取一个原始MIME格式的字符串,并使它的关键字/值对进入一个给定的MimeHeader实例。它用StringTokenizer 把输入数据分解成独立的由CRLF(/r/n)序列标记的行。然后用规范的while... hasMoreTokens( ) ... nextToken( ) 序列遍历每一行。
对于MIME标头的每一行,parse( )通过冒号(:)把该行分解成两个字符串。两个变量key和val由substring( )方法设置,用来提取冒号前后的字符及后面的空隔。当两个字符串被提取后,使用put( )方法存储Hashtable中的关键字和值之间的关联。
toString( ) toString( )方法(用于String 串联操作,+)只是parse( )的反方法。它获取当前存储在MimeHeader 中的关键字/值对,返回一个MIME格式的字符串描述,然后打印
 
关键字,跟着是冒号和空隔,然后是值,最后是CRLF。
put( ), get( ), AND fix( )  如果不是特殊的任务,Hashtable中的put( )和get( )方法将运行良好。MIME规范定义了几个重要的关键字例如Content-Type和Content-Length。一些早期的MIME 系统设备,特别是网络浏览器,对这些成员的大小写是自由的。一些用Content-type,另一些用content-type。为避免灾祸,我们的HTTP服务器努力将所有的输入和输出的MimeHeader关键字转换成规范形式Content-Type。因此,我们在它们进入Hashtable和寻找给定关键字之前用fix()方法重载put( )和get( ),转变值的大小写。
代码 下面是MimeHeader的源代码:
import java.util.*;

class MimeHeader extends Hashtable {
 void parse(String data) {
    StringTokenizer st = new StringTokenizer(data, "/r/n");

    while (st.hasMoreTokens()) {
      String s = st.nextToken();
      int colon = s.indexOf(':');
      String key = s.substring(0, colon);
      String val = s.substring(colon + 2); // skip ": "
      put(key, val);
    }
 }

 MimeHeader() {}

 MimeHeader(String d) {
    parse(d);
 }

 public String toString() {
    String ret = "";
    Enumeration e = keys();

    while(e.hasMoreElements()) {
      String key = (String) e.nextElement();
      String val = (String) get(key);
      ret += key + ": " + val + "/r/n";
    }
    return ret;
 }

 // This simple function converts a mime string from
 // any variant of capitalization to a canonical form.
 // For example: CONTENT-TYPE or content-type to Content-Type,
 // or Content-length or CoNTeNT-LENgth to Content-Length.
 private String fix(String ms) {
    char chars[] = ms.toLowerCase().toCharArray();
    boolean upcaseNext = true;

   
for (int i = 0; i < chars.length - 1; i++) {
      char ch = chars[i];
      if (upcaseNext && 'a' <= ch && ch <= 'z') {
        chars[i] = (char) (ch - ('a' - 'A'));
      }
      upcaseNext = ch == '-';
    }
    return new String(chars);
 }

 public String get(String key) {
    return (String) super.get(fix(key));
 }

 public void put(String key, String val) {
    super.put(fix(key), val);
 }
}
HttpResponse.java
HttpResponse类是所有与HTTP服务器应答有关的事物的包装程序。它被httpd类的代理部分使用。当你向一个HTTP服务器发送一个请求时,它以一个存储在statusCode中的整数形式的代码以及一个存储在reasonPhrase中的文本应答(这些变量名在正式的HTTP规范中规定)。这个单行的响应后面跟随着一个包含进一步应答信息的MIME头。我们用以前解释过的MimeHeader对象来解析这个字符串。MimeHeader对象存储在HttpResponse类的mh变量中。这些变量不是私有的,所以httpd可以直接使用它们。
构造函数 如果用一个字符串参数创建一个HttpResponse类对象,它被用来作为一个HTTP服务器的原始响应,并传向下面描述的parse( )来初始化对象。你还可以传入一个预计算的状态码,原因语句以及MIME标头。
parse( )  parse( )方法获得从HTTP服务器上读取的原始数据,从第一行解析出statusCode 和reasonPhrase ,然后在剩下的行外部创建一个MimeHeader 。
toString( ) toString( )方法是parse( )的逆方法。它获取HttpResponse对象的当前值并返回一个字符串,HTTP客户希望从服务器读回该字符串。
代码 下面是HttpResponse 的源代码:
import java.io.*;
/*
 * HttpResponse
 * Parse a return message and MIME header from a server.
 * HTTP/1.0 302 Found = redirection, check Location for where.
 * HTTP/1.0 200 OK = file data comes after mime header.
 */

class HttpResponse
{
 int statusCode;      // Status-Code in spec
 String reasonPhrase; // Reason-Phrase in spec
 MimeHeader mh;
 
static String CRLF = "/r/n";

 void parse(String request) {
    int fsp = request.indexOf(' ');
    int nsp = request.indexOf(' ', fsp+1);
    int eol = request.indexOf('/n');
    String protocol = request.substring(0, fsp);
    statusCode = Integer.parseInt(request.substring(fsp+1, nsp));
    reasonPhrase = request.substring(nsp+1, eol);
    String raw_mime_header = request.substring(eol + 1);
    mh = new MimeHeader(raw_mime_header);
 }

 HttpResponse(String request) {
    parse(request);
 }

 HttpResponse(int code, String reason, MimeHeader m) {
    statusCode = code;
    reasonPhrase = reason;
    mh = m;
 }

 public String toString() {
    return "HTTP/1.0 " + statusCode + " " + reasonPhrase + CRLF +
      mh + CRLF;
 }
}
UrlCacheEntry.java
为在服务器上保存文档的内容,必须在用于找回文档的URL和文档自身描述之间建立联系。一个文档由它的MimeHeader和原始数据描述。例如一副图像可以被一个Content-Type: image/gif样式的MimeHeader描述,而原始图像数据就是一个字节数组。同样,一个网页在它的MimeHeader中有Content-Type:text/html关键字/值对,而原始数据就是HTML页的内容。再次申明,实例变量不是私有的,所以httpd可以自由的访问它们。
构造函数 UrlCacheEntry 对象的构造函数需要用URL作为关键字以及一个与之相关的MimeHeader。如果MimeHeader内部有一个名为Content-Length成员(大多数情况下如此),数据区域被预先分配足够大的空间来保存它的内容。
append( ) append( ) 方法用来给UrlCacheEntry对象增添数据的。它不是一个简单的setData( )方法,原因是数据可能流经网络且需要在一定时间被存储成块。append( )方法处理三种情形。第一种,数据缓冲区根本没有分配。第二种情形,数据缓冲区对于引入的数据来说太小,所以它被重新分配。最后一种情况,引入的数据正好可以插入缓冲区。在任何时候,length成员变量保存数据缓冲区当前的有效大小值。
代码 下面是UrlCacheEntry的源代码:
class UrlCacheEntry
{
 String url;
 
MimeHeader mh;
 byte data[];
 int length = 0;

 public UrlCacheEntry(String u, MimeHeader m) {
    url = u;
    mh = m;
    String cl = mh.get("Content-Length");
    if (cl != null) {
      data = new byte[Integer.parseInt(cl)];
    }
 }

 void append(byte d[], int n) {
    if (data == null) {
      data = new byte[n];
      System.arraycopy(d, 0, data, 0, n);
      length = n;
    } else if (length + n > data.length) {
      byte old[] = data;
      data = new byte[old.length + n];
      System.arraycopy(old, 0, data, 0, old.length);
      System.arraycopy(d, 0, data, old.length, n);
    } else {
      System.arraycopy(d, 0, data, length, n);
      length += n;
    }
 }
}
LogMessage.java
LogMessage是一个简单的接口,它只定义了一个方法log( ),该方法只有一个String型参数。它用来抽象从httpd获得消息的输出。在应用程序条件下,该方法用来打印标准应用程序起始处控制台的输出。在小应用程序情况下,数据被送到一个视窗文本缓冲区。
代码 下面是LogMessage的源程序:
interface LogMessage {
 public void log(String msg);
}
httpd.java
这真是一个具有很多功能的大类。我们将一个方法一个方法的讲解它。
构造函数  存在5个主要的实例变量:port, docRoot,log,cache和stopFlag,它们都是私有的。其中的三个可以由httpd的独立构造函数设置,显示如下:
httpd(int p, String dr, LogMessage lm)
它初始化监听端口,初始化检索文件的目录以及初始化发送消息的接口。
 
第四个实例变量cache,是在RAM中保存所有文件的Hashtable,它是在对象创建时被初始化的。stopFlag 控制程序的执行。
静态部分 该类中有几个重要的静态变量。MIME标头中的“Server”域报告的版本在变量version中被发现。接着定义了一些常量:HTML文件的MIME类型,mime_text_html;MIME的结束顺序,CRLF;代替原始目录请求返回的HTML文件名,indexfile以及在输入/输出中用到的数据缓冲区的大小, buffer_size。
然后mt 定义了一系列文件扩展名和这些文件相应的MIME 类型。types Hashtable在下一个块中被静态初始化,以用来包含作为可选关键字和值的数组mt。接着可以用fnameToMimeType( )方法来返回传入的每个filename的合适的MIME类型。如果filename 不含mt 表中的任何一个扩展名,该方法返回defaultExt或“text/plain.”。
统计计算器 下面,我们声明另外5个实例变量。它们是没有private 修饰符,所以一个外部监控器可以检查这些值并以图形形式显示它们(我们将在后面演示)。这些变量表示了我们的Web服务器所用的统计资料。点击数和提供的字节的原始数目被存储在hits_served 和bytes_served中。通常存储在高速缓存中的文件和字节数被存放在files_in_cache 和bytes_in_cache中。最后,我们把成功在高速缓存外部提供服务的点击数目存放在hits_to_cache中。
toBytes( ) 接着,我们有一个方便的程序,toBytes( )。该程序把它的字符串转变成一个字节数组。这是十分必要的,因为Java的String 对象是以统一编码的字符形式存储的,而Internet 协议中的混合语例如HTTP是老式的8位ASCII码。
makeMimeHeader( ) MakeMimeHeader()方法是另一个方便的方法,它用来创建由一些关键字值填充的MimeHeader对象。该方法返回的MimeHeader在Date成员中含有当前时间和时期,在Server成员中有服务器的名称和版本,Content-Type成员中有type参数,Content-Length成员中有length参数。
error( ) error( )方法用来格式化HTML页并返回提出不能完成请求的Web客户。第一个参数code,是返回的出错代码。一般它在400到499之间。我们的服务器返回404和405错误。它用HttpResponse 类和适当的MimeHeader来封装返回的代码。该方法返回字符串表示是与HTML页有关的响应。该页包括易于人读的错误代码信息msg 和导致错误的url请求。
getRawRequest( ) GetRawRequest( )方法是很简单的。它从流读取数据直到它获得两个连续的换行符。它忽略回车符号并且只寻找换行符。一旦它已经发现了连续的两个换行符,它使字节数组转向一个String对象并返回该对象。如果输入流在结束之前没有生成两个连续的换行符,它将返回null。这说明了HTTP服务器和客户的消息是怎样被格式化的。它们以状态的一行开始然后立即跟着一个MIME头。MIME头的结尾被两个换行符从剩余的内容中分离。
logEntry( ) logEntry( )方法用来报告标准格式下的HTTP服务器的每个点击数。该方法生成的格式也许看起来有一点奇怪,但是它和HTTP日志文件的当前标准相匹配。该方法有若干个用来格式化每个日志项的日期戳的辅助变量和方法。months数组用来把月份转换成字符串。当host变量接受一个给定主机的连接时它由主HTTP循环设置。fmt02d( )方法把0到9的整数格式化成两位的,第一位为零的数,然后结果字符串通过LogMessage接口变量log
 
传输。
writeString( ) 另一个方便的方法writeString( ),用来隐藏字符到字节数组的转变,以使它可以被写入流。
writeUCE( ) writeUCE( )方法占取一个OutputStream和一个UrlCacheEntry。它从高速缓存项提取信息,以便给网络客户传送消息。消息中包含适当的响应代码,MIME标头和内容。
serveFromCache( ) 这个布尔方法试图在高速缓存中发现一个特殊的URL。如果成功,缓存项的内容被写给客户,hits_to_cache变量递增,调用者被返回true。否则,它只返回false。
loadFile( ) 该方法占用了一个InputStream、与之相应的url以及该URL的MimeHeader。用存储在MimeHeader的信息创建一个新的UrlCacheEntry。输入流在buffer_size字节块中被读取并传入UrlCacheEntry。结果的UrlCacheEntry 存储在高速缓存中。files_in_cache和bytes_in_cache变量更新,UrlCacheEntry返回调用者。
readFile( ) readFile( )方法就loadFile( )方法来说看起来有些多余,实际不然。该方法严格的从本地文件系统读取文件。在本地文件系统中,loadFile( )方法用来与各种类型的流交流。如果File对象f存在,将会为它创建一个InputStream类。文件的大小是决定了的,MIME类型来自文件名。当loadFile( )被调用来做实际的读取和缓存高速工作时,有两个变量用来创建合适的MimeHeader。
writeDiskCache( ) writeDiskCache( )方法占用一个UrlCacheEntry 对象并且把它持久的写入本地磁盘。它从URL中建立一个目录名,确保用与系统有关的separatorChar代替斜线(/)字符。然后它调用mkdirs( )方法来保证这个URL的本地磁盘路径存在。最后,它打开一个FileOutputStream,向它写入所有的数据然后关闭它。
handleProxy( ) HandleProxy()程序是该服务器的两个主要模式之一。基础思想是:如果你把你的浏览器设置成把该服务器当成代理服务器,则要传给它的请求将包括完整的URL,URL中常规GET方法可删除“http://”和主机名部分。我们仅把完整的URL拆碎,寻找“://”序列,其次是斜线(/),然后是使用非标准端口号的服务器的另一个冒号(:)。一旦我们发现了这些字符,我们就可以知道已经所需要的主机和端口号以及我们需要获取的URL。然后我们可以试图从RAM高速缓存中转载一个先前保存过的文档版本。如果失败,我们可以试图从文件系统装载它到RAM高速缓存并且再尝试从RAM高速缓存装载它。如果此举失败,那么事情变得很有趣,因为我们必须从远程站点读取该文件。
为此,我们打开一个远程站点和端口的套接字。我们发送一个GET请求,要求传给我们的URL。无论我们从远程站点获得什么响应标头信息,我们把它传给客户。如果代码是200,对于成功的文件传输,我们还把确认数据流读到一个新的UrlCacheEntry类并且把它写入客户套接字。然后,我们调用writeDiskCache( )来保存传输结果到本地磁盘。我们记录传输日志,关闭套接字,然后返回。
handleGet( ) 当http后台进程像一个普通的Web服务器一样工作时,handleGet( )被调用。它有一个服务于文件的本地磁盘文件根目录。handleGet( )的参数告诉它向何处写结果,何处访问URL以及何处请求网络浏览器的MimeHeader。这个MIME标头将包括用户代理字
 
符串和其他有用的属性。开始,我们试图在RAM高速缓存外为URL提供服务。如果此举失败,为寻找URL,我们顺序访问文件系统。如果文件不存在或不可读,我们向Web客户报告一个错误。否则,我们就用readFile( )方法获得文件的内容并把它们输入到高速缓存。然后调用writeUCE( )方法以用来传输文件内容到客户套接字。
doRequest( ) 每次连接服务器时都会调用一次 doRequest( )方法。它解析请求字符串和引入的MIME标头。它在请求字符串中是否存在“://”的基础上判定是调用handleProxy( ) 还是 handleGet( )方法。如果不用GET,而使用任何其他的方法例如HEAD或POST,该程序向客户返回一个405错误。注意如果stopFlag 是true时HTTP请求被忽略。
run( ) run( )方法在服务器线程启动时被调用。它在指定端口生成一个新的ServerSocket,在服务器套接字处进入一个调用accept( )的无限循环,把结果Socket传给doRequest( )作检查。
start( ) AND stop( ) 存在两个用来启动和终止服务器过程的方法。这些方法设置stopFlag的值。
main( )  你可以在命令行用main( )方法来运行应用程序。它为服务器自身设置LogMessage参数,然后为log( )提供简单的控制台输出执行。
代码 下面是httpd的源代码:
import java.net.*;
import java.io.*;
import java.text.*;
import java.util.*;

class httpd implements Runnable, LogMessage {
 private int port;
 private String docRoot;
 private LogMessage log;
 private Hashtable cache = new Hashtable();
 private boolean stopFlag;

 private static String version = "1.0";
 private static String mime_text_html = "text/html";
 private static String CRLF = "/r/n";
 private static String indexfile = "index.html";
 private static int buffer_size = 8192;
 static String mt[] = { // mapping from file ext to Mime-Type
    "txt", "text/plain",
    "html", mime_text_html,
    "htm", "text/html",
    "gif", "image/gif",
    "jpg", "image/jpg",
    "jpeg", "image/jpg",
    "class", "application/octet-stream"
 };
 static String defaultExt = "txt";
 static Hashtable types = new Hashtable();
 static {
    for (int i=0; i<mt.length;i+=2)
     
types.put(mt[i], mt[i+1]);
 }

 static String fnameToMimeType(String filename) {
    if (filename.endsWith("/"))      // special for index files.
      return mime_text_html;
    int dot = filename.lastIndexOf('.');
    String ext = (dot > 0) ? filename.substring(dot + 1) : defaultExt;
    String ret = (String) types.get(ext);
    return ret != null ? ret : (String)types.get(defaultExt);
  }

 int hits_served = 0;
 int bytes_served = 0;
 int files_in_cache = 0;
 int bytes_in_cache = 0;
 int hits_to_cache = 0;
 
 private final byte toBytes(String s)[] {
    byte b[] = s.getBytes();
    return b;
 }

 private MimeHeader makeMimeHeader(String type, int length) {
    MimeHeader mh = new MimeHeader();
    Date curDate = new Date();
    TimeZone gmtTz = TimeZone.getTimeZone("GMT");
    SimpleDateFormat sdf =
      new SimpleDateFormat("dd MMM yyyy hh:mm:ss zzz");
    sdf.setTimeZone(gmtTz);
    mh.put("Date", sdf.format(curDate));
    mh.put("Server", "JavaCompleteReference/" + version);
    mh.put("Content-Type", type);
    if (length >= 0)
      mh.put("Content-Length", String.valueOf(length));
    return mh;
 }

 private String error(int code, String msg, String url) {
    String html_page = "<body>" + CRLF +
                 "<h1>" + code + " " + msg + "</h1>" + CRLF;
    if (url != null)
      html_page += "Error when fetching URL: " + url + CRLF;
    html_page += "</body>" + CRLF;
    MimeHeader mh = makeMimeHeader(mime_text_html, html_page.length());
    HttpResponse hr = new HttpResponse(code, msg, mh);

    logEntry("GET", url, code, 0);
    return hr + html_page;
 }

 // Read 'in' until you get two /n's in a row.
 // Return up to that point as a String.
 // Discard all /r's.
 
private String getRawRequest(InputStream in)
    throws IOException {
    byte buf[] = new byte[buffer_size];
    int pos=0;
    int c;
    while ((c = in.read()) != -1) {
      switch (c) {
        case '/r':
        break;
        case '/n':
          if (buf[pos-1] == c) {
            return new String(buf,0,pos);
          }
        default:
          buf[pos++] = (byte) c;
      }
    }
    return null;
 }

 static String months[] = {
    "Jan", "Feb", "Mar", "Apr", "May", "Jun",
    "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"
 };
 private String host;
 // fmt02d is the same as C's printf("%02d", i)
 private final String fmt02d(int i) {
    if(i < 0) {
      i = -i;
      return ((i < 9) ? "-0" : "-") + i;
    }
    else {
      return ((i < 9) ? "0" : "") + i;
    }
 }
 private void logEntry(String cmd, String url, int code, int size) {
    Calendar calendar = Calendar.getInstance();
    int tzmin = calendar.get(Calendar.ZONE_OFFSET)/(60*1000); 
    int tzhour = tzmin / 60;
    tzmin -= tzhour * 60;
    log.log(host + " - - [" +
      fmt02d(calendar.get(Calendar.DATE) ) + "/" +
      months[calendar.get(Calendar.MONTH)] + "/" +
      calendar.get(Calendar.YEAR) + ":" +
      fmt02d(calendar.get(Calendar.HOUR) ) + ":" +
      fmt02d(calendar.get(Calendar.MINUTE) ) + ":" +
      fmt02d(calendar.get(Calendar.SECOND)) + " " +
      fmt02d(tzhour) + fmt02d(tzmin) +
      "] /"" +
      cmd + " " +
      url + " HTTP/1.0/" " +
      code + " " +
      size + "/n");
    hits_served++;
   
bytes_served += size;
 }

 private void writeString(OutputStream out, String s)
    throws IOException {
    out.write(toBytes(s));
 }

 private void writeUCE(OutputStream out, UrlCacheEntry uce)
    throws IOException {
      HttpResponse hr = new HttpResponse(200, "OK", uce.mh);
      writeString(out, hr.toString());
      out.write(uce.data, 0, uce.length);
      logEntry("GET", uce.url, 200, uce.length);
 }

 private boolean serveFromCache(OutputStream out, String url)
    throws IOException {
    UrlCacheEntry uce;
    if ((uce = (UrlCacheEntry)cache.get(url)) != null) {
      writeUCE(out, uce);
      hits_to_cache++;
      return true;
    }
    return false;
 }

 private UrlCacheEntry loadFile(InputStream in, String url,
                                 MimeHeader mh)
    throws IOException {

    UrlCacheEntry uce;
    byte file_buf[] = new byte[buffer_size];
    uce = new UrlCacheEntry(url, mh);
    int size = 0;
    int n;
    while ((n = in.read(file_buf)) >= 0) {
      uce.append(file_buf, n);
      size += n;
    }
    in.close();
    cache.put(url, uce);
    files_in_cache++;
    bytes_in_cache += uce.length;
    return uce;
 }

 private UrlCacheEntry readFile(File f, String url)
    throws IOException {

    if (!f.exists())
      return null;
    InputStream in = new FileInputStream(f);
    int file_length = in.available();
   
String mime_type = fnameToMimeType(url);
    MimeHeader mh = makeMimeHeader(mime_type, file_length);
    UrlCacheEntry uce = loadFile(in, url, mh);
    return uce;
 }

 private void writeDiskCache(UrlCacheEntry uce)
    throws IOException {

    String path = docRoot + uce.url;
    String dir = path.substring(0, path.lastIndexOf("/"));
    dir.replace('/', File.separatorChar);
    new File(dir).mkdirs();
    FileOutputStream out = new FileOutputStream(path);
    out.write(uce.data, 0, uce.length);
    out.close();
 }

 // A client asks us for a url that looks like this:
 // http://the.internet.site/the/url
 // we go get it from the site and return it...
 private void handleProxy(OutputStream out, String url,
                           MimeHeader inmh) {
    try {
      int start = url.indexOf("://") + 3;
      int path = url.indexOf('/', start);
      String site = url.substring(start, path).toLowerCase();
      int port = 80;
      String server_url = url.substring(path);
      int colon = site.indexOf(':');
      if (colon > 0) {
        port = Integer.parseInt(site.substring(colon + 1));
        site = site.substring(0, colon);
      }
      url = "/cache/" + site + ((port != 80) ? (":" + port) : "") +
            server_url;
      if (url.endsWith("/"))
        url += indexfile;

      if (!serveFromCache(out, url)) {
        if (readFile(new File(docRoot + url), url) != null) {
          serveFromCache(out, url);
          return;
        }

        // If we haven't already cached this page, open a socket
        // to the site's port and send a GET command to it.
        // We modify the user-agent to add ourselves... "via".

        Socket server = new Socket(site, port);
        InputStream server_in = server.getInputStream();
        OutputStream server_out = server.getOutputStream();
        inmh.put("User-Agent", inmh.get("User-Agent") +
                 " via JavaCompleteReferenceProxy/" + version);
       
String req = "GET " + server_url + " HTTP/1.0" + CRLF +
                     inmh + CRLF;
        writeString(server_out, req);
        String raw_request = getRawRequest(server_in);
        HttpResponse server_response =
                       new HttpResponse(raw_request);
        writeString(out, server_response.toString());
        if (server_response.statusCode == 200) {
          UrlCacheEntry uce = loadFile(server_in, url,
                                       server_response.mh);
          out.write(uce.data, 0, uce.length);
          writeDiskCache(uce);
          logEntry("GET", site + server_url, 200, uce.length);
        }
        server_out.close();
        server.close();
      }
    } catch (IOException e) {
      log.log("Exception: " + e);
    }
 }

 private void handleGet(OutputStream out, String url,
                         MimeHeader inmh) {
    byte file_buf[] = new byte[buffer_size];
    String filename = docRoot + url +
                      (url.endsWith("/") ? indexfile : "");
    try {
      if (!serveFromCache(out, url)) {
        File f = new File(filename);
        if (! f.exists()) {
          writeString(out, error(404, "Not Found", filename));
          return;
        }
        if (! f.canRead()) {
          writeString(out, error(404, "Permission Denied", filename));
          return;
        }
        UrlCacheEntry uce = readFile(f, url);
        writeUCE(out, uce);
      }
    } catch (IOException e) {
      log.log("Exception: " + e);
    }
 }

 private void doRequest(Socket s) throws IOException {
    if(stopFlag)
      return;
    InputStream in = s.getInputStream();
    OutputStream out = s.getOutputStream();
    String request = getRawRequest(in);
    int fsp = request.indexOf(' ');
    int nsp = request.indexOf(' ', fsp+1);
   
int eol = request.indexOf('/n');
    String method = request.substring(0, fsp);
    String url = request.substring(fsp+1, nsp);
    String raw_mime_header = request.substring(eol + 1);

    MimeHeader inmh = new MimeHeader(raw_mime_header);

    request = request.substring(0, eol);

    if (method.equalsIgnoreCase("get")) {
      if (url.indexOf("://") >= 0) {
        handleProxy(out, url, inmh);
      } else {
        handleGet(out, url, inmh);
      }
    } else {
      writeString(out, error(405, "Method Not Allowed", method));
    }
    in.close();
    out.close();
 }

 public void run() {
    try {
      ServerSocket acceptSocket;
      acceptSocket = new ServerSocket(port);
      while (true) {
        Socket s = acceptSocket.accept();
        host = s.getInetAddress().getHostName();
        doRequest(s);
        s.close();
      }
    } catch (IOException e) {
      log.log("accept loop IOException: " + e + "/n");
    } catch (Exception e) {
      log.log("Exception: " + e);
    }
 }

 private Thread t;
 public synchronized void start() {
    stopFlag = false;
    if (t == null) {
      t = new Thread(this);
      t.start();
    }
 }

 public synchronized void stop() {
    stopFlag = true;
    log.log("Stopped at " + new Date() + "/n");
 }
 
 public httpd(int p, String dr, LogMessage lm) {
  
 port = p;
    docRoot = dr;
    log = lm;
 }

 // This main and log method allow httpd to be run from the console.
 public static void main(String args[]) {
    httpd h = new httpd(80, "c://www", null);
    h.log = h;
    h.start();
    try {
      Thread.currentThread().join();
    } catch (InterruptedException e) {};
 }

 public void log(String m) {
    System.out.print(m);
 }
}
HTTP.java
这里是一个给HTTP服务器一个“前面板”功能的applet类。该applet有两个可以用来配置服务器的参数:port和docroot。这是一个很简单的小应用程序。它创建了一个httpd实例,把它作为LogMessage 接口传入自己。然后创建了两个面板。一个面板在顶部有一个简单的标签,在中部是一个TextArea 文本区以显示LogMessage;另一个面板在底部有两个按钮及一个标签。小应用程序的start( )和stop( )方法调用httpd相应的方法。标注有“Start”和“Stop”的按钮调用httpd中它们相应的方法。在任何时候只要一条消息被记录,右下角的Label对象就更新,这样它包含httpd最新的统计数据。
import java.util.*;
import java.applet.*;
import java.awt.*;
import java.awt.event.*;

public class HTTP extends Applet implements LogMessage,
                                            ActionListener
{
 private int m_port = 80;
 private String m_docroot = "c://www";
 private httpd m_httpd;
 private TextArea m_log;
 private Label status;

 private final String PARAM_port = "port";
 private final String PARAM_docroot = "docroot";
 public HTTP() {
 }

 public void init() {
    setBackground(Color.white);
    String param;

    // port: Port number to listen on
    param = getParameter(PARAM_port);
    if (param != null)
      m_port = Integer.parseInt(param);

    // docroot: web document root
    param = getParameter(PARAM_docroot);
    if (param != null)
      m_docroot = param;

    setLayout(new BorderLayout());

    Label lab = new Label("Java HTTPD");

    lab.setFont(new Font("SansSerif", Font.BOLD, 18));
    add("North", lab);
    m_log = new TextArea("", 24, 80);
    add("Center", m_log);
    Panel p = new Panel();
    p.setLayout(new FlowLayout(FlowLayout.CENTER,1,1));
    add("South", p);
    Button bstart = new Button("Start");
    bstart.addActionListener(this);
    p.add(bstart);
    Button bstop = new Button("Stop");
    bstop.addActionListener(this);
    p.add(bstop);
    status = new Label("raw");
    status.setForeground(Color.green);
    status.setFont(new Font("SansSerif", Font.BOLD, 10));
    p.add(status);
    m_httpd = new httpd(m_port, m_docroot, this);
 }

 public void destroy() {
    stop();
 }

 public void paint(Graphics g) {
 }

 public void start() {
    m_httpd.start();
    status.setText("Running ");
    clear_log("Log started on " + new Date() + "/n");
 }

 public void stop() {
    m_httpd.stop();
    status.setText("Stopped ");
 }

 public void actionPerformed(ActionEvent ae) {
    String label = ae.getActionCommand();
   
if(label.equals("Start")) {
      start();
    }
    else {
      stop();
    }
 }

 public void clear_log(String msg) {
    m_log.setText(msg + "/n");
 }

 public void log(String msg) {
    m_log.append(msg);
    status.setText(m_httpd.hits_served + " hits (" +
            (m_httpd.bytes_served / 1024) + "K), " +
            m_httpd.files_in_cache + " cached files (" +
            (m_httpd.bytes_in_cache / 1024) + "K), " +
            m_httpd.hits_to_cache + " cached hits");
    status.setSize(status.getPreferredSize());
 }
}
注意:httpd.java 和HTTP.java文件中,代码是在假定文件根部是“c:/www”的基础上建立的。你可能需要改变配置的值。因为这个小应用程序写入一个日志文件,它只能在被信任的条件下工作。例如,一个小应用程序在它可以获得用户CLASSPATH时被信任。
13.9 
对于你的大多数网络需求,你会对TCP/IP型网络很满意。它提供了有序的、可预测和可靠的信息包数据流。但是,这样做的代价也很大。TCP包含很多在拥挤的网络中处理拥塞控制的复杂算法以及信息丢失的悲观的预测。这导致了一个效率很差的传输数据方式。数据报是一种可选的替换方法。
数据报(Datagrams)是在机器间传递的信息包,它有些像从一个训练有素但是很盲目的捕手投出一记有力的传球给三垒。一旦数据报被释放给它们预定的目标,不保证它们一定到达目的地,甚至不保证一定存在数据的接收者。同样,数据报被接受时,不保证它在传输过程不受损坏,不保证发送它的机器仍在那儿等待响应。
Java通过两个类实现UDP协议顶层的数据报:DatagramPacket对象是数据容器,DatagramSocket是用来发送和接受DatagramPackets的机制。
13.9.1 DatagramPacket
DatagramPackets 可以用四个构造函数中的一个创建。第一个构造函数指定了一个接收数据的缓冲区和信息包的容量大小。它通过DatagramSocket接收数据。第二种形式允许你在存储数据的缓冲区中指定一个偏移量。第三种形式指定了一个用于DatagramSocket决定
 
信息包将被送往何处的目标地址和端口。第四种形式从数据中指定的偏移量位置开始传输数据包。想象前两种形式是建立在“盒内”的,后两种形式形成了填塞物,并为一个信封注明了地址。下面是四个构造函数:
DatagramPacket(byte data[ ], int size)
DatagramPacket(byte data[ ], int offset, int size)
DatagramPacket(byte data[ ], int size, InetAddress ipAddress, int port)
DatagramPacket(byte data[ ], int offset, int size, InetAddress ipAddress, int port)
存在几种方法可获取DatagramPacket内部状态。它们对信息包的目标地址和端口号以及原始数据和数据长度有完全的使用权,下面是它们的概述:
InetAddress getAddress( )
返回目标文件InetAddress,一般用于发送。
Int getPort( )
返回端口号。
byte[ ] getData( )
返回包含在数据包中的字节数组数据。多用于在接收数据之后从数据包来检索数据。
Int getLength( )
返回包含在将从getData( )方法返回的字节数组中有效数据长度。通常它与整个字节数组长度不等。
13.9.2 数据报服务器和客户
下面的例子实现了一个非常简单的联网通信的客户和服务器。消息被写入服务器的窗口并通过网络写入客户端,在此它们被显示。
// Demonstrate Datagrams.
import java.net.*;

class WriteServer {
 public static int serverPort = 666;
 public static int clientPort = 999;
 public static int buffer_size = 1024;
 public static DatagramSocket ds;
 public static byte buffer[] = new byte[buffer_size];

 public static void TheServer() throws Exception {
    int pos=0;
    while (true) {
      int c = System.in.read();
      switch (c) {
        case -1:
          System.out.println("Server Quits.");
          return;
        case '/r':
          break;
        case '/n':
          ds.send(new DatagramPacket(buffer,pos,
             InetAddress.getLocalHost(),clientPort));
          pos=0;
         
break;
        default:
          buffer[pos++] = (byte) c;
      }
    }
 }

 public static void TheClient() throws Exception {
    while(true) {
      DatagramPacket p = new DatagramPacket(buffer, buffer.length);
      ds.receive(p);
      System.out.println(new String(p.getData(), 0, p.getLength()));
    }
 }

 public static void main(String args[]) throws Exception {
    if(args.length == 1) {
      ds = new DatagramSocket(serverPort);
      TheServer();
    } else {
      ds = new DatagramSocket(clientPort);
      TheClient();
    }
 }
}
该程序被DatagramSocket 构造函数限制在本地机的两个端口间运行。试着运行该程序,在一个窗口中键入
java WriteServer
这是在客户端的。然后运行
java WriteServer 1
这是在服务器端的。在服务器窗口中打印的任何东西在接受回车之后将被送到客户窗口。
注意:该例题需要你的计算机与Internet相连。
13.10 
给你5个类InetAddress,Socket,ServerSocket,DatagramSocket和DatagramPacket,你可以编写现有的任何Internet协议。它们同样为Internet连接提供功能强大的低级控制。Java 的网络包也完美地反映了Internet的状态。
 
 
原创粉丝点击