Java 教程是为 JDK 8 编写的。本页中描述的示例和实践未利用在后续版本中引入的改进。
本节中的示例包含两个应用程序:客户端和服务器。服务器通过数据报套接字连续接收数据报包。服务器接收的每个数据报包指示客户端对引语的请求。当服务器收到数据报时,它通过向客户端发送包含一行“quote of the moment”的数据报包来回复。
此示例中的客户端应用程序非常简单。它向服务器发送单个数据报包,指示客户端希望接收当前的引语。然后,客户端等待服务器发送数据报包作为响应。
两个类实现服务器应用程序:QuoteServer 和 QuoteServerThread。单个类实现客户端应用程序:QuoteClient。
让我们从包含服务器应用程序的 main 方法的类开始研究这些类。Working With a Server-Side Application 包含 QuoteClient 类的 applet 版本。
此处完整显示的 QuoteServer 类包含一个方法:引语服务器应用程序的 main 方法。main 方法只是创建一个新的 QuoteServerThread 对象并启动它:
import java.io.*;
public class QuoteServer {
public static void main(String[] args) throws IOException {
new QuoteServerThread().start();
}
}
QuoteServerThread 类实现了引语服务器的主要逻辑。
QuoteServerThread 类创建时,QuoteServerThread 在端口 4445(任意选择)上创建 DatagramSocket。这是 DatagramSocket,服务器通过它与所有客户端进行通信。
public QuoteServerThread() throws IOException {
this("QuoteServer");
}
public QuoteServerThread(String name) throws IOException {
super(name);
socket = new DatagramSocket(4445);
try {
in = new BufferedReader(new FileReader("one-liners.txt"));
}
catch (FileNotFoundException e){
System.err.println("Couldn't open quote file. Serving time instead.");
}
}
请记住,某些端口专用于众所周知的服务,你无法使用它们。如果指定正在使用的端口,则 DatagramSocket 的创建将失败。
构造函数还在名为 one-liners.txt 的文件上打开 BufferedReader
其中包含引语列表。文件中的每个引语都是单独的。
现在,对于 QuoteServerThread 的有趣部分:它的 run 方法。run 方法覆盖 Thread 类中的 run,并提供该线程的实现。有关线程的信息,请参阅 Defining and Starting a Thread。
run 方法包含 while 循环,只要文件中有更多引语,该循环就会继续。在循环的每次迭代期间,线程等待 DatagramPacket 通过 DatagramSocket 到达。数据包表示来自客户端的请求。为了响应客户端的请求,QuoteServerThread 从文件中获取引语,将其放在 DatagramPacket 中,并通过 DatagramSocket 将其发送到请求它的客户。
让我们首先看一下接收客户请求的部分:
byte[] buf = new byte[256]; DatagramPacket packet = new DatagramPacket(buf, buf.length); socket.receive(packet);
第一个语句创建一个字节数组,然后用于创建 DatagramPacket。由于用于创建它的构造函数,DatagramPacket 将用于从套接字接收数据报。此构造函数只需要两个参数:包含特定于客户端的数据的字节数组和字节数组的长度。构造 DatagramPacket 以通过 DatagramSocket 发送时,还必须提供数据包目标的 Internet 地址和端口号。稍后当我们讨论服务器如何响应客户端请求时,你会看到这一点。
前一个代码段中的最后一个语句从套接字接收数据报(从客户端接收的信息被复制到数据包中)。receive 方法一直等待,直到收到数据包。如果没有收到数据包,则服务器不再继续进行,只是等待。
现在假设,服务器已收到来自客户端的引语请求。现在服务器必须响应。run 方法中的这部分代码构造响应:
String dString = null;
if (in == null)
dString = new Date().toString();
else
dString = getNextQuote();
buf = dString.getBytes();
如果由于某种原因导致文件没有打开,那么 in 等于 null。如果是这种情况,引语服务器会提供一天中的时间。否则,引语服务器从已打开的文件中获取下一个引语。最后,代码将字符串转换为字节数组。
现在,run 方法通过以下代码通过 DatagramSocket 将响应发送到客户端:
InetAddress address = packet.getAddress(); int port = packet.getPort(); packet = new DatagramPacket(buf, buf.length, address, port); socket.send(packet);
此代码段中的前两个语句分别从从客户端接收的数据报包中获取 Internet 地址和端口号。Internet 地址和端口号表示数据报包的来源。这是服务器必须发送其响应的位置。在此示例中,数据报包的字节数组不包含相关信息。数据包本身的到达表示来自客户端的请求,该请求可以在数据报包中指示的因特网地址和端口号处找到。
第三个语句创建一个新的 DatagramPacket 对象,用于通过数据报套接字发送数据报消息。你可以告诉新的 DatagramPacket 旨在通过套接字发送数据,因为用于创建它的构造函数。此构造函数需要四个参数。前两个参数与用于创建接收数据报的构造函数相同:一个字节数组,包含从发送方到接收方的消息以及该数组的长度。接下来的两个参数是不同的:Internet 地址和端口号。这两个参数是数据报包的目的地的完整地址,必须由数据报的发送者提供。最后一行代码在其路上发送 DatagramPacket。
当服务器读取了引语文件中的所有引语时,while 循环终止并且 run 方法清除:
socket.close();
QuoteClient 类实现 QuoteServer 的客户端应用程序。此应用程序向 QuoteServer 发送请求,等待响应,并在收到响应时将其显示到标准输出。让我们详细看一下代码。
QuoteClient 类包含一个方法,即客户端应用程序的 main 方法。main 方法的顶部声明了几个局部变量供其使用:
int port; InetAddress address; DatagramSocket socket = null; DatagramPacket packet; byte[] sendBuf = new byte[256];
首先,main 方法处理用于调用 QuoteClient 应用程序的命令行参数:
if (args.length != 1) {
System.out.println("Usage: java QuoteClient <hostname>");
return;
}
QuoteClient 应用程序需要一个命令行参数:运行 QuoteServer 的计算机的名称。
接下来,main 方法创建 DatagramSocket:
DatagramSocket socket = new DatagramSocket();
客户端使用不需要端口号的构造函数。此构造函数只是将 DatagramSocket 绑定到任何可用的本地端口。客户端绑定到哪个端口并不重要,因为 DatagramPacket 包含寻址信息。服务器从 DatagramPacket 获取端口号,并将其响应发送到该端口。
接下来,QuoteClient 程序向服务器发送请求:
byte[] buf = new byte[256];
InetAddress address = InetAddress.getByName(args[0]);
DatagramPacket packet = new DatagramPacket(buf, buf.length,
address, 4445);
socket.send(packet);
代码段获取命令行上命名的主机的 Internet 地址(可能是运行服务器的计算机的名称)。然后使用此 InetAddress 和端口号 4445(服务器用于创建其 DatagramSocket 的端口号)来为那个互联网地址和端口号创建 DatagramPacket 。因此,DatagramPacket 将被传递到引语服务器。
请注意,代码创建一个带有空字节数组的 DatagramPacket。字节数组为空,因为该数据报包只是向服务器请求信息的请求。服务器需要知道发送响应的内容 - 响应的地址和端口号 - 自动成为数据包的一部分。
接下来,客户端从服务器获取响应并显示它:
packet = new DatagramPacket(buf, buf.length);
socket.receive(packet);
String received = new String(packet.getData(), 0, packet.getLength());
System.out.println("Quote of the Moment: " + received);
为了从服务器获得响应,客户端创建“receive”数据包并使用 DatagramSocket 接收方法从服务器接收回复。receive 方法等待,直到发往客户端的数据报包通过套接字到来。请注意,如果服务器的回复以某种方式丢失,则客户端将永远等待,因为数据报模型的无保证策略。通常,客户端设置一个计时器,以便它不会永远等待回复;如果没有回复,则计时器关闭,客户端重新发送。
当客户端从服务器收到回复时,客户端使用 getData 方法从数据包中获取该数据。然后,客户端将数据转换为字符串并显示它。
成功编译服务器和客户端程序后,运行它们。你必须先运行服务器程序。只需使用 Java 解释器并指定 QuoteServer 类名。
服务器启动后,你可以运行客户端程序。请记住使用一个命令行参数运行客户端程序:运行 QuoteServer 的主机的名称。
客户端发送请求并从服务器收到响应后,你应该看到与此类似的输出:
Quote of the Moment: Good programming is 99% sweat and 1% coffee.