Java 网络通信协议实现与测试
1. 网络通信基础与协议概述
在网络通信中,连接建立后,会显示欢迎消息。此时,用户输入的任何内容都会发送到服务器,然后服务器将其回显到用户屏幕。若按下
?
字符,服务器会关闭连接。回声服务器会持续运行,直到在命令提示符中按下
CTRL+C
。不过,通常会使用其他机制来停止服务器,例如一些基于服务器的软件会在另一个套接字上单独监听,等待授权用户或机器连接并发送特殊的关闭消息。
套接字是开发两个独立应用程序之间通信语言(即协议)的基础。TCP 套接字提供输入和输出流,但除非接收端理解数据的含义,否则发送的任何数据对接收端来说只是字节。在之前的回声服务器示例中,服务器并不理解发送给它的数据,只是读取并将其传回客户端。实际上,这类应用仅适用于测试网络连接性,并无其他用途。要实现有意义的通信,客户端和服务器必须使用相同的语言或协议。
实现协议是一项艰巨的任务。虽然 Java 中的套接字编程并不复杂,只是从输入流读取和向输出流写入的另一种方式,但与套接字编程相关的许多难题与读取特定类型文件的难题类似。文件具有某种有意义的结构,例如 HTML 文件是通过一系列标签结构化的文本文件,用于指示内容的显示方式。Web 浏览器能够读取和显示 HTML 文件,是因为它们知道如何解析文件格式。编写比简单文本命令更复杂的解析器是一项艰巨的任务,超出了本文的范围。实现协议需要客户端和服务器就某种形式的契约(或文件/数据格式)达成一致。一旦开发出协议,客户端和服务器就可以实现它以进行相互通信。协议必须明确无误,才能使两个独立的实现正确协作。
2. HTTP 协议基础
HTTP 遵循简单的请求/响应模式。客户端向 HTTP 服务器发送请求并发出特定命令,服务器根据发送的命令返回响应。HTTP 是无状态协议,这意味着 HTTP 服务器无需在不同请求之间保留特定客户端的信息,每个请求都被视为相同,无论客户端之前发出过哪些请求。
不过,通过使用会话标识符和 cookie,Web 应用程序可以模拟 HTTP 上的状态,从而在多个请求之间保留特定客户端的信息。这就是像 amazon.com 这样的网站能够识别特定用户并为电子商务提供必要基础的方式。
HTTP 最初是为了成为一种简单且易于实现的协议而开发的,旨在作为在网络上传输 HTML 页面的机制。后来,诸如有状态会话支持等功能才被构建在 HTTP 之上。在 HTTP 中,客户端只需连接到远程机器上的端口(通常是 80)并发出 HTTP 命令。主要的 HTTP 命令包括:
-
GET
:检索指定 URL 处的内容。
-
POST
:向 HTTP 服务器发送数据并检索指定 URL 处的内容。通常,HTTP 服务器返回的内容基于 POST 命令发送的数据(即传递给服务器的表单数据)。
-
PUT
:要求 HTTP 服务器将请求中发送的数据存储到指定的 URL。
-
HEAD
:仅检索请求的 HTTP 标头,不检索实际内容。
-
DELETE
:要求 HTTP 服务器删除指定 URL 处的内容。
HTTP 服务器在收到 HTTP 命令后会返回响应,并返回一个响应代码以指示响应的情况。常见的 HTTP 响应代码包括:
-
200
:响应正常,请求已完成。
-
404
:请求的 URL 未找到。
-
403
:对该 URL 的请求被禁止。
-
500
:服务器遇到内部错误,无法完成请求。
3. HTTP GET 请求的简单实现
HTTP GET 是最常用的 HTTP 请求操作。当用户在浏览器地址栏中输入 URL 并导航到该 URL 时,就会使用 GET 请求。GET 请求只是要求服务器检索特定文件,服务器返回一个响应代码以指示是否成功,如果成功则返回文件。
一个示例 HTTP GET 请求如下:
GET / HTTP/1.1
Accept: */*
Accept-Language: en-nz
Accept-Encoding: gzip, deflate
User-Agent: Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.0; .NET CLR 1.1.4322)
Host: www.cnn.com
Connection: Keep-Alive
请求的格式如下:首先是 HTTP 命令行,其中
GET
表示 HTTP GET 命令,
/
表示服务器上的文件(在此例中为根文件,也可以是
/index.html
等),
HTTP/1.1
表示使用的 HTTP 版本。HTTP 命令行之后是 HTTP 标头,标头遵循
Key: Value
的格式。在 HTTP 1.0 中,标头是可选的,但在 1.1 中,某些标头是必需的,不过大多数 HTTP 服务器对此要求并不严格。许多 HTTP 的可选功能都是基于标头构建的,例如压缩响应或设置 cookie。请求以两个换行符结束,通知服务器不再发送 HTTP 标头,服务器可以开始发送响应。
HTTP 响应的结构与请求类似。响应的第一行包含 HTTP 响应状态代码,随后是标头,最后是请求的文件内容(在成功的 HTTP GET 请求中)。上述请求的响应示例如下:
HTTP/1.1 200 OK
Date: Sun, 06 Aug 2006 03:40:21 GMT
Server: Apache
Vary: Host
Content-Type: text/html; charset=ISO-8859-1
X-Cache: MISS from www.java.net
Connection: close
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"><html lang="en">
... (more html follows)
4. HttpGetter 实现步骤
我们将实现一个简单的命令行 Java 应用程序
HttpGetter
,用于将用户指定的远程 HTML 文件保存到本地文件。该应用程序按以下顺序执行四个主要任务:
1. 从命令行参数解析 URL 和保存远程文件的文件位置。
2. 设置与从命令行解析的 URL 对应的 Socket 和 InetSocketAddress,并连接到远程主机。
3. 将 HTTP GET 请求写入 Socket 的 OutputStream。
4. 从 Socket 的 InputStream 读取服务器的 HTTP GET 响应,并将远程文件写入命令行指定的文件位置。
以下是实现代码:
package book;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.net.InetSocketAddress;
import java.net.MalformedURLException;
import java.net.Socket;
import java.net.URL;
import java.util.StringTokenizer;
public class HttpGetter {
public static void main(String[] args) {
try {
if (args.length < 2) {
System.out.println("Usage");
System.out.println("\tHttpGetter <Http URL> <file to save>");
System.out.println("\tExample: HttpGetter http://www.google.com/ google.html");
System.exit(1);
}
URL url = new URL(args[0]);
File outFile = new File(args[1]);
Socket socket = new Socket();
int port = url.getPort();
if (port == -1)
port = url.getDefaultPort();
InetSocketAddress remoteAddress = new InetSocketAddress(url.getHost(), port);
socket.connect(remoteAddress);
PrintWriter out = new PrintWriter(socket.getOutputStream());
// write our client’s request
out.println("GET " + url.getFile() + " HTTP/1.0");
out.println("User-Agent: HttpGetter");
out.println("Host: " + url.getHost());
out.println();
out.flush();
// read remote server’s response
InputStream in = socket.getInputStream();
boolean responseOK = true;
BufferedReader br = new BufferedReader(new InputStreamReader(in));
String currLine = null;
// get http response code from first line of result
currLine = br.readLine();
if (currLine != null) {
System.out.println(currLine);
StringTokenizer st = new StringTokenizer(currLine, " \t");
st.nextToken();
String responseCode = st.nextToken();
int httpResponseCode = Integer.parseInt(responseCode.trim());
if (httpResponseCode != 200) {
// response not OK
responseOK = false;
}
} else {
System.err.println("Server returned no response!");
System.exit(1);
}
// read headers
while ((currLine = br.readLine()) != null) {
System.out.println(currLine);
// done reading headers, so break out of loop
if (currLine.trim().equals(""))
break;
}
if (responseOK) {
FileOutputStream fout = new FileOutputStream(outFile);
int currByte;
while ((currByte = br.read()) != -1)
fout.write(currByte);
fout.close();
System.out.println("** Wrote result to " + args[1]);
} else {
System.out.println("HTTP response code not OK -- file not written");
}
socket.close();
} catch (MalformedURLException me) {
me.printStackTrace();
} catch (IOException ioe) {
ioe.printStackTrace();
}
}
}
需要注意的是,这个简单的实现存在一些局限性。首先,它只能读取文本,不能处理二进制数据,因此不够健壮,因为 HTTP 服务器经常提供图像和其他二进制文件。其次,它不能优雅地处理错误,实际上需要比现有的
java.io
使用更完善的解析器。此实现是实现 HTTP GET 的最少代码和逻辑。
5. 测试与调试协议实现
测试和调试协议实现比测试和调试独立应用程序要困难和繁琐得多。为确保正在开发的协议实现正确,查看与远程服务器之间通过网络发送和接收的内容非常有帮助。有一些实用工具可以查看 TCP/IP 套接字连接上发送和接收的内容。对于
HttpGetter
,可以使用 Apache 工具 TCPMon 来监控与远程 Web 服务器的 TCP/IP 连接。
5.1 获取和运行 TCPMon
TCPMon 最初是 Apache AXIS 的一部分,现在作为一个独立项目存在。它最初用于调试 Web 服务请求和响应,也可用于套接字开发,特别是在实现协议时。可以从以下 URL 下载 TCPMon:
http://ws.apache.org/commons/tcpmon/
通过运行发行版构建目录中的
tcpmon.bat
文件即可启动 TCPMon。
5.2 使用 TCPMon
要让 TCPMon 能够打印出请求和服务器的响应,需要将其设置为本地机器和远程服务器之间的中间人。测试程序时,程序需要连接到 TCPMon,而 TCPMon 再将其连接到远程服务器。TCPMon 会将发送给它的任何内容转发到远程服务器,并将远程服务器发送给它的内容转发回应用程序。
配置 TCPMon 时,需要将其设置为监听器并指定本地机器上的端口号。例如,将 TCPMon 设置为在端口 8079 上监听,并将连接转发到
www.google.com
的端口 80(默认 HTTP 端口)。点击“Add”按钮后,TCPMon 会设置转发。
启动转发后,可以通过以下命令测试
HttpGetter
:
java book.HttpGetter http://localhost:8079/ tester.html
HttpGetter
连接到 TCPMon,而 TCPMon 再将其连接到
java.net
。在 TCPMon 的“Port 8079”选项卡上,可以查看此会话中对
java.net
的所有连接尝试的详细请求和响应信息。
使用像 Apache TCPMon 这样的工具可以让开发者像查看本地文件一样查看服务器的响应,从而更轻松地调试协议实现。
6. 专有协议与逆向工程
有些协议是不公开的,例如 AOL 的 Instant Messenger 和 Microsoft 的 Messenger 客户端的即时通讯协议内部细节属于专有信息,目前并未公开(尽管 FCC 正在努力推动开放即时通讯标准以实现不同客户端的互操作性)。如果软件必须与使用未知或专有协议的服务器进行通信,选择并不多。一种选择是尝试逆向工程协议。例如,对于 AOL 的 Instant Messenger,现在除了 AOL 自己的客户端外,还有许多即时通讯客户端实现了其大部分专有协议 OSCAR。逆向工程通常通过监控专有客户端和服务器之间的 TCP 连接和发送的数据来完成,有时可以识别出协议的部分内容。
在设计专有协议时,考虑其逆向工程的难易程度非常重要,特别是在安全是首要任务的情况下。为了提高安全性,可能需要对协议进行某种加密以防止逆向工程。大多数情况下,协议应该是开放的,因为其规范通常更容易被大家实现,并且经过了许多不同视角的审查。例如,HTTP 从 1.0 版本到 1.1 版本经历了许多性能改进的修订。通常,经过一段时间使用的免费开放协议会产生最健壮和稳定的实现。像 HTTP、TCP/IP 和 X-Windows 等协议都有高质量的参考实现,正是因为这些协议是开放的。
7. 利用现有协议和实现
开发者应尽可能避免设计和编写自己的协议。通常,现有的某个协议能够满足几乎任何应用程序的需求,重新发明轮子是没有意义的,而且使用开放协议通常是简化与外部世界互操作性的好方法。如果应用程序需要与其他应用程序进行接口,编写和设计自定义协议会带来更多成本,因为任何希望与该应用程序进行接口的其他应用程序都必须实现自定义协议。让协议的两个不同实现稳健地协同工作本身就是一项艰巨的任务,更不用说还要进行正常的应用程序开发了。
许多协议已经有了可供 Java 开发者免费使用的高质量实现。Apache 的 Jakarta 项目托管了许多开源项目,例如 Jakarta Commons Net 包提供了实现 FTP、NNTP、SMTP、POP3、Telnet、TFTP 等的 API。可以在以下 URL 找到更多相关信息:
http://jakarta.apache.org/commons/net/
虽然在
HttpGetter
示例中,实现 HTTP 的一小部分相对简单,但实现包含所有可选组件的整个协议要困难得多。已经有优化的 HTTP 实现可供使用,在需要 HTTP 客户端支持的任何应用程序中,使用现有的实现是更好的设计选择。JDK 通过
java.net.URL
类提供了有限的 HTTP 支持,适用于简单的 HTTP 操作,但有时需要对 HTTP 的使用进行更多控制。例如,要查看和设置 HTTP 标头,需要一个比 JDK 中的
java.net.URL
类更能暴露 HTTP 细节的 HTTP 客户端库。Jakarta 项目中的 HTTP Client 项目提供了高质量的 HTTP 实现,可以在以下 URL 找到更多信息:
http://jakarta.apache.org/commons/httpclient/
除了客户端库,还有许多免费的服务器库。Jakarta 项目通过其 Servlet 容器 Tomcat 提供了 HTTP 服务器实现,也有 POP3 邮件服务器的实现。几乎在所有情况下,在应用程序中使用现有协议来实现 Java 组件与其他平台组件之间的通信都是合理的选择,而且通常不需要自己实现协议,因为对于当今使用的几乎所有主要开放协议,都有高质量的健壮开源实现可供使用。
以下是一些寻找和集成开源 Java 项目到应用程序的优秀资源:
| 资源 | URL |
| ---- | ---- |
| The Jakarta Project | http://jakarta.apache.org |
| OpenSymphony Quality Components | www.opensymphony.com |
| JBoss: Professional Open Source | www.jboss.org |
| The Apache XML Project | http://xml.apache.org |
| The Eclipse Project | www.eclipse.org |
综上所述,在进行网络通信和协议实现时,应充分利用现有的资源和工具,避免不必要的重复工作,以提高开发效率和系统的稳定性。
Java 网络通信协议实现与测试(续)
8. 现有协议资源的深入分析
在前面提到了许多可利用的现有协议资源,下面对这些资源进行更深入的分析。
| 资源项目 | 功能特点 | 适用场景 |
|---|---|---|
| Jakarta Commons Net | 提供了 FTP、NNTP、SMTP、POP3、Telnet、TFTP 等多种协议的 API 实现。功能全面,涵盖了常见的网络协议操作。 | 适用于需要与多种不同类型服务器进行通信的应用,如文件传输、邮件收发、新闻组访问等。 |
| Jakarta Commons HttpClient | 提供高质量的 HTTP 实现,能够暴露更多 HTTP 细节,方便对 HTTP 操作进行更精细的控制。 | 适用于需要对 HTTP 请求和响应进行详细处理的应用,如设置和查看 HTTP 标头、处理复杂的 HTTP 交互等。 |
| Tomcat | Jakarta 项目的 Servlet 容器,提供 HTTP 服务器实现。 | 适用于开发基于 Java 的 Web 应用,作为 Web 服务器来处理 HTTP 请求。 |
| JDK 的 java.net.URL 类 | 提供有限的 HTTP 支持,适用于简单的 HTTP 操作。 | 适用于对 HTTP 操作要求不高,只需要进行基本的文件获取等操作的简单应用。 |
从这些资源可以看出,不同的协议实现库具有不同的特点和适用场景。开发者在选择时,需要根据具体的应用需求来决定使用哪个库。例如,如果只是进行简单的 HTTP 文件下载,使用 JDK 的
java.net.URL
类可能就足够了;但如果需要对 HTTP 标头进行详细设置和处理,那么 Jakarta Commons HttpClient 会是更好的选择。
9. 协议实现的优化建议
在实现协议时,除了利用现有资源,还可以采取一些优化措施来提高性能和稳定性。
- 连接复用 :在进行多次请求时,尽量复用已经建立的连接,避免频繁地创建和销毁连接。例如,在使用 HTTP 协议时,可以使用支持连接池的 HTTP 客户端库,如 Jakarta Commons HttpClient,它可以管理连接池,提高连接的复用率,减少连接建立的开销。
-
错误处理优化
:在协议实现中,要对各种可能的错误情况进行充分的考虑和处理。例如,在
HttpGetter示例中,虽然有基本的错误处理,但可以进一步细化,如对不同的 HTTP 响应代码进行不同的处理,给出更详细的错误信息,方便调试和维护。 - 数据缓存 :对于一些经常访问的数据,可以考虑使用缓存机制。例如,在进行文件下载时,如果文件内容不经常变化,可以将下载的文件缓存到本地,下次请求时先检查缓存,如果缓存有效则直接使用缓存数据,减少对服务器的请求。
10. 协议实现的未来发展趋势
随着互联网技术的不断发展,协议实现也呈现出一些新的发展趋势。
- 安全性增强 :随着网络攻击的日益增多,协议的安全性变得越来越重要。未来的协议实现将更加注重数据的加密和身份验证,以保护用户的隐私和数据安全。例如,HTTP/3 协议在传输层使用了 QUIC 协议,提供了更好的安全性和性能。
- 性能优化 :随着用户对应用响应速度的要求越来越高,协议实现将不断进行性能优化。例如,采用更高效的编码方式、减少数据传输量、优化连接建立和断开的过程等。
- 跨平台和互操作性 :随着不同平台和设备的多样性增加,协议实现需要更好地支持跨平台和互操作性。例如,Web 服务协议需要能够在不同的操作系统和编程语言之间进行无缝交互。
11. 总结与实践建议
通过对 Java 网络通信协议实现与测试的学习,我们可以得出以下总结和实践建议。
-
总结
- 网络通信协议的实现是一个复杂的过程,需要考虑协议的规范、数据的解析和处理、错误的处理等多个方面。
- 利用现有的协议实现资源可以大大减少开发工作量,提高开发效率和系统的稳定性。
- 测试和调试协议实现是确保协议正确性的重要环节,使用合适的工具如 Apache TCPMon 可以帮助我们更好地进行调试。
-
实践建议
- 在开发应用时,优先考虑使用现有的协议和实现库,避免自己设计和实现协议,除非有特殊的需求。
- 在实现协议时,要充分考虑协议的安全性、性能和稳定性,采取相应的优化措施。
- 在测试协议实现时,要进行全面的测试,包括正常情况和异常情况的测试,确保协议在各种情况下都能正常工作。
以下是一个简单的 mermaid 流程图,展示了一个典型的 HTTP 请求处理流程:
graph LR
A[客户端] --> B[创建 HTTP 请求]
B --> C[建立 TCP 连接]
C --> D[发送 HTTP 请求]
D --> E[服务器接收请求]
E --> F[处理请求]
F --> G[生成 HTTP 响应]
G --> H[发送 HTTP 响应]
H --> I[客户端接收响应]
I --> J[解析响应]
J --> K[处理响应数据]
这个流程图展示了从客户端发起 HTTP 请求到最终处理响应数据的整个过程,帮助我们更好地理解 HTTP 协议的工作原理。通过遵循这些建议和了解未来的发展趋势,开发者可以更好地进行协议实现和网络通信开发,为用户提供更高效、稳定和安全的应用。
超级会员免费看

被折叠的 条评论
为什么被折叠?



