本节是《Java与Internet编程》的第三部分,也是最后一部分。在前面两节中,我们介绍了网络编程的基础知识,如协议、端口、套接字、UDP等,并给出了一些客户程序和服务程序的实现实例。本节我们介绍两个更高级的协议:POP3和HTTP,并给出一个POP3客户程序和一个HTTP服务器的实现。
㈠ POP3协议简介
POP3是一种高级网络协议,它的全称是Post Office Protocol Version 3。使用该协议,客户程序能够动态地、有效地访问服务器上的邮件。简单地说,POP3是一种能够让客户程序提取驻留于服务器的邮件的协议。有关POP3的操作可以概括为:
服务器在端口110监听客户请求。
客户程序发出连接请求并通过身份验证。
客户程序发送命令;服务器处理命令并将结果发送给客户程序;重复这个过程直至客户程序结束或中止连接为止。
POP3命令都是单行的,它由一个关键字开头,后面加上一个或多个参数,最后为一个回车符加一个换行符(CRLF)。服务器的应答可以由一行或多行组成,开头内容总是命令处理结果(+OK或-ERR),紧接着是其他附加信息,最后是一个CRLF。对于多行应答,最后一行是英文句点(“.")加一个CRLF。
下表是部分POP3命令的说明:
命令 说 明
STAT 获得邮箱的状态信息,即邮件数量及大小。
RETR msg 下载指定的邮件。
DELE msg 将指定的邮件标记为删除。
NOOP 空操作。
RSET 取消所有的删除标记。
QUIT 结束会话。
TOP msg n 下载指定邮件的头信息及前面的n行。
UIDL [msg] 获得所有邮件或指定邮件的唯一标识符。
USER name 标示将要访问的邮箱(即用户名字)。
PASS pw 发送由USER命令指定的用户的密码(以明文发送)。
㈡ POP3客户程序实例
下面的MailStat.java程序演示了POP3协议的基本用法。该程序的功能是检查指定服务器上的邮件状态。
【MailStat.java】
public class MailStat{
private static final int POP3_PORT = 110;
public static void main(String[] args) {
String host;
InetAddress hostAddress;
String username;
String password;
Socket mailSocket;
BufferedReader socketInput;
DataOutputStream socketOutput;
// 检查参数
if (args.length < 3) {
System.out.println("用法: MailStat [服务器] [用户名字] [密码]");
}
else {
host = args[0];
username = args[1];
password = args[2];
try {
hostAddress = InetAddress.getByName(host);
System.out.println("正在连接服务器" + hostAddress + "...");
mailSocket = new Socket(host, POP3_PORT);
try {
socketInput = new BufferedReader(
new InputStreamReader(mailSocket.getInputStream()) );
socketOutput = new DataOutputStream(mailSocket.getOutputStream());
// 从服务器读入初始应答
readReply(socketInput);
// 验证身份
sendCommand(socketOutput, "USER " + username);
readReply(socketInput);
sendCommand(socketOutput, "PASS " + password);
readReply(socketInput);
// 获得状态信息
sendCommand(socketOutput, "STAT");
readReply(socketInput);
// 结束会话
sendCommand(socketOutput, "QUIT");
readReply(socketInput);
} finally {
mailSocket.close();
}
}
catch(Exception theException) {
System.out.println(theException);
}
}
System.exit(0);
}
/**
* sendCommand() 发送一个POP3命令
*/
private static void sendCommand(DataOutputStream out, String command)
throws IOException {
…略…
}
/**
* readReply() 读取并显示POP3服务器的应答
*/
private static String readReply(BufferedReader reader)
throws IOException, Exception {
…略…
}
}
下面是其算法说明:
获得命令行参数,包括邮件主机、用户名称、密码。如果没有指定这些参数,则输出提示信息并退出。
获得邮件服务器的IP地址。
打开与邮件服务器通讯的Socket。
引用Socket的输入、输出流。
读取服务器的初始应答信息。
发送用户名字并读取应答。
发送密码并读取应答。
读取状态信息(邮件总数,邮箱大小)。
结束会话。
关闭Socket并退出。
为简单计,我们没有为MailStat加上任何“特色”功能。您可以自己修改它使之更为实用,比如增加每隔几分钟检查一次的功能,或同时检查多个邮箱的功能,或一个图形用户界面,等等。
㈢ HTTP协议简介
HTTP协议也是一种高级网络协议,是浏览器与Web服务器通信的标准协议。HTTP 1.1规范可以在RFC 2616找到,HTTP 1.0 规范可以在RFC 1945找到。
有关HTTP的基本操作为:
服务器在端口80监听。
客户程序(如浏览器)连接到服务器并发送请求信息。
服务器发送应答信息。
由客户程序或服务器关闭连接。
客户请求的一般格式为:
< command> /< url> < HTTP-version>CRLF
[< keyword>: < value>CRLF]
...
[< keyword>: < value>CRLF]
其中:
< command> = 请求服务器处理的命令,如
GET —— 提取文件
HEAD —— 提取文件头
POST —— 发送表单数据
PUT —— 上载文件
< url> = 要求提取文件的URL
< HTTP-version> = 客户程序能够理解的HTTP版本,如
HTTP/1.0、HTTP/1.1等等
< keyword> = 提供给服务器的附加信息关键词。
常见的关键词如:
Accept —— 可以接受的数据类型
User-Agent —— 用来标识浏览器
下面是客户请求的几个示例:
●GET /foo.html HTTP/1.1
●GET /foo.html HTTP/1.1
Accept: text/html
Accept: text/plain
Accept: image/gif
Accept: image/jpg
User-Agent: Netscape/4.5
服务器应答的基本格式如下:
< HTTP-version> < response-code>CRLF
Server: < server-identity>CRLF
MIME-version: < MIME-version>CRLF
Content-type: < content-type>CRLF
Content-length: 11160
CRLF
< data>
其中:
< HTTP-version> = 服务器所使用的HTTP版本号。
< response-code> = 应答类型。它由两部分组成,即一个编号及文本说明。最常见的应答是“200 OK”和“404 Not Found”。编号为200-299的应答表示成功,300-399表示重定向,400-499表示客户错误,500-599表示服务器错误。
< server-identity> = 服务器标识。
< MIME-version> = 服务器所使用的MIME版本号。
< content-type> = 所发送内容的MIME类型:text/html,image/gif等。
< content-length> = 以字节计的发送内容长度。
< data> = 内容。
下面是服务器应答的一个实例:
HTTP/1.1 200 OK
Server: NCSA/1.4.2
MIME-version: 1.0
Content-type: text/html
Content-length: 37756
< html>
< head>< title>Foo< /title>< /head>
< body>Foo< /body>
< /html>
㈣ 一个多线程HTTP服务器的实现
下面的HttpServer.java给出了一个简单的Web服务器。它仅由两个类构成,只支持HTML、TEXT、GIF和JPEG文件的GET命令。
【HttpServer.java】
public class HttpServer {
private static int DEFAULT_PORT = 80;
private int serverPort;
public static void main(String[] args) {
int port = DEFAULT_PORT;
// 获取命令行参数
if (args.length >= 1) {
try {
port = Integer.parseInt(args[0]);
} catch(NumberFormatException ex) {
System.out.println("Usage: HttpServer [端口]");
System.exit(0);
}
}
(new HttpServer(port)).go();
}
public HttpServer() {
this(DEFAULT_PORT);
}
public HttpServer(int port) {
super();
this.serverPort = port;
}
/**
* 启动服务器
*/
public void go() {
ServerSocket httpSocket;
Socket clientSocket;
HttpRequestThread requestThread;
try {
httpSocket = new ServerSocket(serverPort);
System.out.println("HttpServer在端口" + serverPort + "监听.");
try {
while (true) {
clientSocket = httpSocket.accept();
requestThread = new HttpRequestThread(clientSocket);
requestThread.start();
}
} finally {
httpSocket.close();
}
} catch(Exception ex) {
System.out.println(ex.toString());
}
System.exit(0);
}
}
下面是它的算法说明:
从命令行获取端口参数。若没有指定,则默认为80。
在指定的端口打开一个服务器Socket。
开始循环:
等待客户程序的连接请求,当请求到达时获得客户Socket的引用。
创建一个新的请求服务线程,并以客户Socket为参数启动该线程。
结束循环。
关闭服务器Socket并退出。
客户请求的服务线程类实现如下:
【HttpRequestThread.java】
public class HttpRequestThread extends Thread {
private Socket clientSocket;
public HttpRequestThread(Socket clientSocket) {
super();
this.clientSocket = clientSocket;
}
/**
* 启动服务线程
*/
public void run() {
OutputStream out;
BufferedReader in;
String line;
StringTokenizer tokenizer;
String method;
String url;
String httpVersion;
try {
try {
// 引用输入、输出流
out = clientSocket.getOutputStream();
in = new BufferedReader(new InputStreamReader(clientSocket.getInputStream()));
// 从客户请求读入一行
line = in.readLine();
tokenizer = new StringTokenizer(line);
if (tokenizer.countTokens() == 3) {
// 获得命令类型、URL、HTTP版本号
…略…
if (method.equalsIgnoreCase("get")) {
sendResponse(out, url);
}
}
} finally {
clientSocket.close();
}
} catch (Exception ex) {
System.out.println("RequestThread: " + ex.toString());
}
}
/**
* 将服务器应答发送给浏览器
*/
public void sendResponse(OutputStream out, String url) throws IOException {
…略…
}
}
服务线程的算法说明如下:
引用Socket的输入、输出流。
打印客户请求内容。
如果客户请求命令为GET,则发送应答:若所请求的文件存在,则将该文件作为应答的内容发送;否则,发送“404 Not Found”。
编译这两个Java类,执行“java HttpServer”之后,就可以用浏览器打开class文件所在目录下的页面文件了。