本教程是针对 JDK 8 编写的。本页面中描述的示例和实践不利用后续版本中引入的改进,并且可能使用不再可用的技术。
请查看Java 语言变更以了解 Java SE 9 及后续版本中更新的语言特性的摘要。
请查看JDK 发行说明以获取有关所有 JDK 发行版的新功能、增强功能以及已删除或已弃用选项的信息。
本节中的示例包括两个应用程序:客户端和服务器。服务器持续通过数据报套接字接收数据报包。服务器接收到的每个数据报包都表示客户端请求报价。服务器接收到数据报时,会通过发送一个包含一行“此刻报价”的数据报包回复客户端。
本示例中的客户端应用程序非常简单。它向服务器发送一个单独的数据报包,指示客户端希望接收此刻报价。然后,客户端等待服务器发送响应的数据报包。
两个类实现了服务器应用程序:QuoteServer
和QuoteServerThread
。一个类实现了客户端应用程序:QuoteClient
。
让我们来研究这些类,从包含服务器应用程序main
方法的类开始。在使用服务器端应用程序中,包含了QuoteClient
类的小程序版本。
QuoteServer
类如下所示,它包含一个方法:用于报价服务器应用程序的main
方法。main
方法只是创建一个新的QuoteServerThread
对象并启动它:
import java.io.*; public class QuoteServer { public static void main(String[] args) throws IOException { new QuoteServerThread().start(); } }
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("无法打开报价文件。将返回时间。"); } }
请记住,某些端口专门用于众所周知的服务,您不能使用它们。如果指定了一个已被使用的端口,将无法创建DatagramSocket
。
构造方法还在一个名为one-liners.txt
的文件上打开了一个BufferedReader
。该文件包含了一系列的引用语。文件中的每个引用语都占据一行。
现在来看一下QuoteServerThread
的有趣部分:它的run
方法。该方法覆盖了Thread
类中的run
方法,并为线程提供了实现。关于线程的信息,请参阅定义和启动线程。
run
方法包含一个while
循环,只要文件中还有更多的引用语,就会继续执行。在每次循环迭代期间,线程会等待DatagramSocket
上的DatagramPacket
到达。该数据包表示来自客户端的请求。作为对客户端请求的响应,QuoteServerThread
从文件中获取一个引用语,将其放入DatagramPacket
中,并通过DatagramSocket
将其发送给请求的客户端。
首先,让我们看一下接收来自客户端请求的部分:
byte[] buf = new byte[256]; DatagramPacket packet = new DatagramPacket(buf, buf.length); socket.receive(packet);
第一条语句创建了一个字节数组,然后用它创建了一个DatagramPacket
。由于使用的是构造方法,该DatagramPacket
将用于从套接字接收数据报。该构造方法仅需要两个参数:包含客户端特定数据的字节数组和字节数组的长度。在构造要通过DatagramSocket
发送的DatagramPacket
时,还必须提供数据包目的地的互联网地址和端口号。当我们讨论服务器如何响应客户端请求时,您将会看到这一点。
上述代码片段中的最后一条语句从套接字接收了一个数据报(从客户端接收到的信息被复制到数据包中)。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地址和端口号指示了数据报包的来源。这是服务器必须发送响应的地方。在这个示例中,数据报包的字节数组不包含任何相关信息。数据报包本身的到达表明了来自Internet地址和端口号指示的客户端的请求。
第三个语句创建一个新的DatagramPacket
对象,用于通过数据报套接字发送数据报消息。你可以通过用于创建它的构造函数知道新的DatagramPacket
用于通过套接字发送数据,因为这个构造函数需要四个参数。前两个参数与用于创建接收数据报的构造函数所需的参数相同:包含从发送方到接收方的消息的字节数组和该数组的长度。下面两个参数不同:一个是Internet地址,另一个是端口号。这两个参数是数据报包目标的完整地址,必须由数据报的发送方提供。代码的最后一行将DatagramPacket
发送出去。
当服务器从报价文件中读取完所有报价时,while
循环终止,run
方法进行清理:
socket.close();
QuoteClient
类实现了QuoteServer
的客户端应用程序。该应用程序向QuoteServer
发送请求,等待响应,并在接收到响应时将其显示在标准输出中。让我们详细看一下代码。
QuoteClient
类包含一个方法,即用于客户端应用程序的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
,目标是该Internet地址和端口号。因此,DatagramPacket
将被传递到报价服务器。
请注意,代码创建了一个带有空字节数组的DatagramPacket
。字节数组为空,因为此数据报包仅仅是向服务器发送的请求。服务器发送响应所需的所有信息(回复的地址和端口号)都自动包含在数据包中。
接下来,客户端从服务器获取响应并显示它:
packet = new DatagramPacket(buf, buf.length); socket.receive(packet); String received = new String(packet.getData(), 0, packet.getLength()); System.out.println("此刻的名言:" + received);
为了从服务器获取响应,客户端创建一个“接收”数据包,并使用DatagramSocket
的receive方法从服务器接收回复。receive方法会等待,直到有一个目标为客户端的数据报包通过套接字传输过来。请注意,如果服务器的回复被某种方式丢失,客户端将永远等待,因为数据报模型没有任何保证。通常,客户端设置一个定时器,以便它不会永远等待回复;如果没有收到回复,定时器将触发,客户端将重新发送。
当客户端从服务器接收到回复时,客户端使用getData方法从数据包中检索数据。然后,客户端将数据转换为字符串并显示出来。
在成功编译服务器和客户端程序之后,您可以运行它们。首先运行服务器程序。只需使用Java解释器并指定QuoteServer
类名即可。
一旦服务器启动,您可以运行客户端程序。请记住使用一个命令行参数来运行客户端程序:即运行QuoteServer
的主机名。
在客户端发送请求并从服务器接收响应后,您应该看到类似于以下输出:
此刻的名言: 好的编程是99%的汗水和1%的咖啡。