# 一、网络编程入门 访问网络(特别是互联网)正成为应用程序的一个重要且经常是必要的功能。应用程序经常需要访问和提供服务。随着**物联网**(**物联网**)连接越来越多的设备,了解如何接入网络变得至关重要。 是推动更多网络应用的重要因素,包括更快网络和更大带宽的可用性。这使得传输更大范围的数据成为可能,例如视频流。近年来,无论是针对新服务、更广泛的社交互动还是游戏,我们都看到了连通性的增加。了解如何开发网络应用程序是一项重要的开发技能。 在本章中,我们将介绍网络编程的基础知识: * 为什么网络很重要 * Java 提供的支持 * 解决基本网络操作的简单程序 * 基本网络术语 * 一个简单的服务器/客户端应用程序 * 使用线程支持服务器 在本书中,您将接触到许多使用旧 Java 技术和新 Java 技术的网络概念、想法、模式和实现策略。网络连接使用套接字在较低级别上进行,使用多种协议在更高级别上进行。通信可以是同步的,需要仔细协调请求和响应,也可以是异步的,在提交响应之前执行其他活动。 这些概念和其他概念通过一系列章节加以阐述,每个章节都侧重于一个特定的主题。各章通过尽可能详细阐述先前介绍的概念相互补充。尽可能使用大量代码示例来加深您对主题的理解。 访问服务的核心是知道或发现其地址。该地址可以是人类可读的,例如[www.packtpub.com](http://www.packtpub.com),或者以**IP**地址的形式,例如`83.166.169.231`。**互联网协议**(**IP**)是一种用于访问互联网上信息的低级寻址方案。寻址长期以来一直使用 IPv4 来访问资源。然而,这些地址几乎都不见了。较新的 IPv6 可提供更大范围的地址。[第 2 章](2.html "Chapter 2. Network Addressing")*网络寻址*的重点是网络寻址的基础知识以及如何在 Java 中进行管理。 网络通信的目的是在其他应用程序之间传输信息。这通过使用缓冲区和通道来实现。缓冲区暂时保存信息,直到应用程序可以处理它为止。通道是简化应用程序之间通信的抽象。NIO 和 NIO.2 包提供了对缓冲区和通道的大部分支持。我们将在[第 3 章](3.html "Chapter 3. NIO Support for Networking")*网络 NIO 支持*中探讨这些技术以及其他技术,如阻塞和非阻塞 IO。 服务由服务器提供。这方面的一个例子是简单的 echo 服务器,它重新传输发送的内容。更复杂的服务器,如 HTTP 服务器,可以支持广泛的服务以满足广泛的需求。客户端/服务器模型及其 Java 支持见[第 3 章](3.html "Chapter 3. NIO Support for Networking")、*网络 NIO 支持*。 另一种服务模式是**对等**(**P2P**模式)。在这种体系结构中,没有中央服务器,而是一个应用程序网络,通过通信提供服务。该模型由应用程序表示,如 BitTorrent、Skype 和 BBC 的 iPlayer。虽然开发这些类型的应用程序所需的许多支持超出了本书的范围,[第 4 章](4.html "Chapter 4. Client/Server Development")*客户端/服务器开发*探讨了 P2P 问题以及 Java 和 JXTA 提供的支持。 IP 在较低级别上用于通过网络发送和接收信息包。我们还将演示**用户数据报协议****UDP**和**传输控制协议****TCP**通信协议的使用。这些协议是在 IP 之上分层的。UDP 用于广播短数据包或消息,但不保证可靠传递。TCP 比 UDP 更常用,并提供更高级别的服务。我们将在[第 5 章](5.html "Chapter 5. Peer-to-Peer Networks")、*对等网络*中介绍这些相关技术的使用。 由于许多因素,服务通常会面临不同程度的需求。它的负载可能随一天中的时间而变化。随着它越来越受欢迎,其总体需求也将增加。服务器需要扩展以满足负载的增减。线程和线程池被用来支持这项工作。这些技术和其他技术是[第 6 章](6.html "Chapter 6. UDP and Multicasting")、*UDP 和多播*的重点。 应用程序越来越需要防止黑客的攻击。当它连接到网络时,这种威胁会增加。在[第 7 章](7.html "Chapter 7. Network Scalability")、*网络可扩展性*中,我们将探讨许多可用于支持安全 Java 应用程序的技术。其中包括**安全套接字级别**(**SSL**),以及 Java 如何支持它。 应用程序很少单独工作。因此,他们需要使用网络访问其他应用程序。然而,并非所有的应用程序都是用 Java 编写的。与这些应用程序联网可能会带来一些特殊问题,从数据类型的字节如何组织到应用程序支持的接口。使用专用协议(如 HTTP 和 WSDL)是很常见的。本书的最后一章从 Java 的角度研究了这些问题。 我们将演示旧的和新的 Java 技术。为了维护较旧的代码,了解较旧的技术可能是必要的,它可以让我们深入了解为什么要开发较新的技术。我们还将使用许多 Java8 函数编程技术来补充我们的示例。通过使用 Java8 示例以及 Java8 之前的实现,我们可以学习如何使用 Java8,并更好地了解何时可以使用 Java8 以及何时应该使用 Java8。 本文并不打算全面解释较新的 Java8 技术,例如 lambda 表达式和流。但是,使用 Java8 示例将深入了解如何使用它们来支持网络应用程序。 本章的其余部分涉及本书中探讨的许多网络技术。我们将向您介绍这些技术的基础知识,您会发现它们很容易理解。然而,在一些地方,时间不允许我们充分探索和解释这些概念。这些问题将在后续章节中讨论。因此,让我们从网络寻址开始探索。 # 使用 InetAddress 类进行网络寻址 IP 地址由`InetAddress`类表示。地址可以是单播,用于标识特定地址,也可以是多播,用于将消息发送到多个地址。 `InetAddress`类没有公共构造函数。要获取实例,请使用几个静态 get-type 方法之一。例如,`getByName`方法采用表示地址的字符串,如下所示。本例中的字符串为**统一资源定位器**(**URL**: ```java InetAddress address = InetAddress.getByName("www.packtpub.com"); System.out.println(address); ``` ### 提示 **下载示例代码** 您可以下载您在[账户购买的所有 Packt 书籍的示例代码文件 http://www.packtpub.com](http://www.packtpub.com) 。如果您在其他地方购买了本书,您可以访问[http://www.packtpub.com/support](http://www.packtpub.com/support) 并注册,将文件直接通过电子邮件发送给您。 这将显示以下结果: **www.packtpub.com/83.166.169.231** 附加在名称末尾的数字是 IP 地址。此地址唯一标识 Internet 上的实体。 如果我们需要关于地址的其他信息,我们可以使用以下几种方法之一,如下所示: ```java System.out.println("CanonicalHostName: " + address.getCanonicalHostName()); System.out.println("HostAddress: " + address.getHostAddress()); System.out.println("HostName: " + address.getHostName()); ``` 执行时,将生成以下输出: **规范主机名:83.166.169.231** **主机地址:83.166.169.231** **主机名:www.packtpub.com** 要测试此地址是否可访问,请使用`isReachable`方法,如下所示。它的参数指定在决定无法到达地址之前要等待多长时间。参数是等待的毫秒数: ```java address.isReachable(10000); ``` 还有分别支持 IPv4 和 IPv6 地址的`Inet4Address`和`Inet6Address`类。我们将在[第 2 章](2.html "Chapter 2. Network Addressing")、*网络寻址*中解释它们的用法。 一旦我们获得了一个地址,我们就可以使用它来支持网络访问,比如服务器。在本文中演示它的使用之前,让我们先看看如何从连接中获取和处理数据。 # NIO 支持 `java.io`、`java.nio`和`java.nio`子包为 IO 处理提供了大部分 Java 支持。我们将在[第 3 章](3.html "Chapter 3. NIO Support for Networking")*NIO 网络支持*中检查这些包对网络访问的支持。在这里,我们将重点介绍`java.nio`套餐的基本方面。 NIO 包中使用了三个关键概念: * **通道**:表示应用程序之间的数据流 * **缓冲区**:与一起工作,通过一个通道处理数据 * **选择器**:这是一种允许单个线程处理多个通道的技术 通道和缓冲区通常相互关联。数据可以从通道传输到缓冲器,也可以从缓冲器传输到通道。顾名思义,缓冲区是信息的临时存储库。选择器在支持应用程序可伸缩性方面很有用,这将在[第 7 章](7.html "Chapter 7. Network Scalability")、*网络可伸缩性*中讨论。 有四个主通道: * `FileChannel`:这对文件有效 * `DatagramChannel`:此支持 UDP 通信 * `SocketChannel`:用于 TCP 客户端 * `ServerSocketChannel`:这是与 TCP 服务器一起使用的 有几个缓冲区类支持基本数据类型,例如 character、integer 和 float。 ## 使用 URLConnection 类 访问服务器的一种简单方法是使用`URLConnection`类。此类表示应用程序和`URL`实例之间的连接。`URL`实例表示 Internet 上的一个资源。 在下一个示例中,将为 Google 网站创建一个 URL 实例。使用`URL`类的`openConnection`方法创建`URLConnection`实例。`BufferedReader`实例用于从连接中读取线路,然后显示: ```java try { URL url = new URL("http://www.google.com"); URLConnection urlConnection = url.openConnection(); BufferedReader br = new BufferedReader( new InputStreamReader( urlConnection.getInputStream())); String line; while ((line = br.readLine()) != null) { System.out.println(line); } br.close(); } catch (IOException ex) { // Handle exceptions } ``` 输出比较长,所以这里只显示了第一行的一部分: **<!doctype html> 0) { System.out.println(new String(buffer.array())); buffer.clear(); } channel.close(); } catch (IOException ex) { // Handle exceptions } ``` 下面显示输出的第一行。此产生与之前相同的输出,但限制为一次显示 64 字节: **<!doctype html> socketInput = () -> { try { return br.readLine(); } catch (IOException ex) { return null; } }; ``` `Supplier`实例生成无限流。下面的`map`方法从用户获取输入,然后将其发送到服务器。当输入`quit`时,流将终止。`allMatch`方法是一种短路方法,当其参数计算为`false`时,流终止: ```java Stream stream = Stream.generate(socketInput); stream .map(s -> { System.out.println("Client request: " + s); out.println(s); return s; }) .allMatch(s -> s != null); ``` 虽然此实现比传统实现更长,但它可以为更复杂的问题提供更简洁和简单的解决方案。 在客户端,我们可以用功能实现替换此处重复的 while 循环: ```java while (true) { System.out.print("Enter text: "); String inputLine = scanner.nextLine(); if ("quit".equalsIgnoreCase(inputLine)) { break; } out.println(inputLine); String response = br.readLine(); System.out.println("Server response: " + response); } ``` 功能解决方案还使用`Supplier`实例捕获控制台输入,如下所示: ```java Supplier scannerInput = () -> scanner.next(); ``` 生成无限流,如下图所示,使用`map`方法提供等效功能: ```java System.out.print("Enter text: "); Stream.generate(scannerInput) .map(s -> { out.println(s); System.out.println("Server response: " + s); System.out.print("Enter text: "); return s; }) .allMatch(s -> !"quit".equalsIgnoreCase(s)); ``` 功能方法通常是解决许多问题的更好方法。 请注意,在输入了`quit`命令后,客户端会显示一个额外的提示**输入文本:**。如果输入了`quit`命令,则不显示提示,这很容易纠正。此更正留作读者练习。 # UDP 和多播 如果您需要定期向组发送消息,则可以使用多播技术。它使用一个 UDP 服务器和一个或多个 UDP 客户端。为了演示此功能,我们将创建一个简单的时间服务器。服务器将每秒向客户端发送一个日期和时间字符串。 多播将向组中的每个成员发送相同的消息。组由多播地址标识。多播地址必须使用以下 IP 地址范围:`224.0.0.0`到`239.255.255.255`。服务器将发送带有此地址的消息标记。客户端必须加入组才能接收任何多播消息。 ## 创建多播服务器 接下来声明一个`MulticastServer`类,在这里创建一个`DatagramSocket`实例。try-catch 块将在异常发生时处理异常: ```java public class MulticastServer { public static void main(String args[]) { System.out.println("Multicast Time Server"); DatagramSocket serverSocket = null; try { serverSocket = new DatagramSocket(); ... } } catch (SocketException ex) { // Handle exception } catch (IOException ex) { // Handle exception } } } ``` try 块的主体使用无限循环创建一个字节数组来保存当前日期和时间。接下来,创建表示多播组的`InetAddress`实例。使用数组和组地址,实例化一个`DatagramPacket`并用作类的`send`方法的参数。然后显示发送的数据和时间。然后服务器暂停一秒钟: ```java while (true) { String dateText = new Date().toString(); byte[] buffer = new byte[256]; buffer = dateText.getBytes(); InetAddress group = InetAddress.getByName("224.0.0.0"); DatagramPacket packet; packet = new DatagramPacket(buffer, buffer.length, group, 8888); serverSocket.send(packet); System.out.println("Time sent: " + dateText); try { Thread.sleep(1000); } catch (InterruptedException ex) { // Handle exception } } ``` 此服务器仅广播消息。它从不从客户端接收消息。 ## 创建多播客户端 客户端是使用以下`MulticastClient`类创建的。为了接收消息,客户端必须使用相同的组地址和端口号。在接收消息之前,它必须使用`joinGroup`方法加入组。在这个实现中,它接收 5 条日期和时间消息,显示它们,然后终止。`trim`方法从字符串中删除前导空格和尾随空格。否则,将显示消息的所有 256 字节: ```java public class MulticastClient { public static void main(String args[]) { System.out.println("Multicast Time Client"); try (MulticastSocket socket = new MulticastSocket(8888)) { InetAddress group = InetAddress.getByName("224.0.0.0"); socket.joinGroup(group); System.out.println("Multicast Group Joined"); byte[] buffer = new byte[256]; DatagramPacket packet = new DatagramPacket(buffer, buffer.length); for (int i = 0; i < 5; i++) { socket.receive(packet); String received = new String(packet.getData()); System.out.println(received.trim()); } socket.leaveGroup(group); } catch (IOException ex) { // Handle exception } System.out.println("Multicast Time Client Terminated"); } } ``` 服务器启动时,发送的消息如下所示: **多播时间服务器** **发送时间:2015 年 7 月 9 日星期四 13:19:49 CDT** **发送时间:2015 年 7 月 9 日星期四 13:19:50 CDT** **发送时间:CDT 2015 年 7 月 9 日星期四 13:19:51** **发送时间:2015 年 7 月 9 日星期四 13:19:52 CDT** **发送时间:2015 年 7 月 9 日星期四 13:19:53 CDT** **发送时间:2015 年 7 月 9 日星期四 13:19:54 CDT** **发送时间:CDT 2015 年 7 月 9 日星期四 13:19:55** **。。。** 客户端输出将类似于以下内容: **多播时间客户端** **多播组加入** **周四 7 月 9 日 13:19:50 CDT 2015** **周四 7 月 9 日 13:19:51 CDT 2015** **周四 7 月 9 日 13:19:52 CDT 2015** **周四 7 月 9 日 13:19:53 CDT 2015** **周四 7 月 9 日 13:19:54 CDT 2015** **多播时间客户端终止** ### 注 如果该示例是在 Mac 上执行的,您可能会收到一个异常,表明它无法分配请求的地址。这可以通过使用 JVM 选项`-Djava.net.preferIPv4Stack=true`来修复。 还有许多其他多播功能,将在[第 6 章](6.html "Chapter 6. UDP and Multicasting")、*UDP 和多播*中探讨。 # 可伸缩性 当服务器上的需求增加或减少时,最好更改专用于服务器的资源。可用的选项范围从使用手动线程来允许并发行为到嵌入在专用类中以处理线程池和 NIO 通道。 ## 创建线程服务器 在本节中,我们将使用线程来扩充我们的简单 echo 服务器。`ThreadedEchoServer`类的定义如下。它实现了`Runnable`接口,为每个连接创建一个新线程。private`Socket`变量将保存特定线程的客户端套接字: ```java public class ThreadedEchoServer implements Runnable { private static Socket clientSocket; public ThreadedEchoServer(Socket clientSocket) { this.clientSocket = clientSocket; } ... } ``` ### 注 线程是与应用程序中的其他代码块并行执行的代码块。`Thread`类支持 Java 中的线程。虽然有几种创建线程的方法,但其中一种方法是将实现`Runnable`接口的对象传递给其构造函数。调用`Thread`类的`start`方法时,创建线程并执行`Runnable`接口的`run`方法。当`run`方法终止时,线程也会终止。 添加线程的另一种方法是为线程使用单独的类。它可以声明为独立于`ThreadedEchoServer`类,也可以声明为`ThreadedEchoServer`类的内部类。使用单独的类,可以更好地拆分应用程序的功能。 `main`方法像以前一样创建服务器套接字,但是当创建客户端套接字时,客户端套接字用于创建线程,如下所示: ```java public static void main(String[] args) { System.out.println("Threaded Echo Server"); try (ServerSocket serverSocket = new ServerSocket(6000)) { while (true) { System.out.println("Waiting for connection....."); clientSocket = serverSocket.accept(); ThreadedEchoServer tes = new ThreadedEchoServer(clientSocket); new Thread(tes).start(); } } catch (IOException ex) { // Handle exceptions } System.out.println("Threaded Echo Server Terminating"); } ``` 实际工作按`run`方法执行,如下所示。它本质上与原始 echo 服务器的实现相同,只是显示当前线程以澄清正在使用的线程: ```java @Override public void run() { System.out.println("Connected to client using [" + Thread.currentThread() + "]"); try (BufferedReader br = new BufferedReader( new InputStreamReader( clientSocket.getInputStream())); PrintWriter out = new PrintWriter( clientSocket.getOutputStream(), true)) { String inputLine; while ((inputLine = br.readLine()) != null) { System.out.println("Client request [" + Thread.currentThread() + "]: " + inputLine); out.println(inputLine); } System.out.println("Client [" + Thread.currentThread() + " connection terminated"); } catch (IOException ex) { // Handle exceptions } } ``` ## 使用线程服务器 以下输出显示了服务器和两个客户端之间的交互。最初的 echo 客户端被启动了两次。如您所见,每个客户端交互都是使用不同的线程执行的: **线程化回音服务器** **正在等待连接。。。。。** **正在等待连接。。。。。** 使用[Thread[Thread-0,5,main]]连接到客户端 **客户端请求[Thread[Thread-0,5,main]]:来自客户端 1 的你好** **客户端请求【线程[Thread-0,5,main】]:这边好** **正在等待连接。。。。。** 使用[Thread[Thread-1,5,main]]连接到客户端 **客户端请求【线程[Thread-1,5,main]]:来自客户端 2 的你好** **客户端请求[Thread[Thread-1,5,main]]:你好!** **客户端请求【线程【线程-1,5,主】】:退出** **客户端【线程【线程-1,5,主】连接终止** **客户端请求[线程[Thread-0,5,main]]:太长了** **客户端请求【线程【线程-0,5,主】】:退出** 以下交互是从第一个客户的角度进行的: **简单回音客户端** **正在等待连接。。。。。** **已连接到服务器** **输入文本:来自客户 1 的你好** **服务器响应:来自客户端 1**的你好 **输入文字:这边好** **服务器响应:这边不错** **输入文字:这么长** **服务器响应:这么长** **输入文本:退出** **服务器响应:退出** 从第二个客户的角度来看,以下互动是: **简单回音客户端** **正在等待连接。。。。。** **已连接到服务器** **输入文本:来自客户 2 的你好** **服务器响应:来自客户端 2**的你好 **输入文字:你好!** **服务器响应:你好!** **输入文本:退出** **服务器响应:退出** 此实现允许一次处理多个客户端。客户端未被阻止,因为另一个客户端正在使用服务器。但是,它也允许创建大量线程。如果存在太多线程,则服务器性能可能会降低。我们将在[第 7 章](7.html "Chapter 7. Network Scalability")、*网络可扩展性*中讨论这些问题。 # 安全 安全是一个复杂的话题。在本节中,我们将演示本主题与网络相关的几个简单方面。具体来说,我们将创建一个安全的 echo 服务器。创建安全的 echo 服务器与我们先前开发的非安全 echo 服务器没有太大区别。然而,在幕后还有很多事情要做。我们现在可以忽略这些细节,但我们将在[第 8 章](8.html "Chapter 8. Network Security")、*网络安全*中更深入地探讨。 我们将使用`SSLServerSocketFactory`类来实例化安全服务器套接字。此外,有必要创建底层 SSL 机制可用于加密通信的密钥。 ## 创建 SSL 服务器 在下面的示例中,`SSLServerSocket`类被声明作为 echo 服务器。由于它与之前的 echo 服务器类似,我们将不解释它的实现,除了它与使用`SSLServerSocketFactory`类的关系。它的静态`getDefault`方法返回一个`ServerSocketFactory`实例。其`createServerSocket`方法返回绑定到端口`8000`的`ServerSocket`实例,该端口能够支持安全通信。否则,它的组织和功能与以前的 echo 服务器类似: ```java public class SSLServerSocket { public static void main(String[] args) { try { SSLServerSocketFactory ssf = (SSLServerSocketFactory) SSLServerSocketFactory.getDefault(); ServerSocket serverSocket = ssf.createServerSocket(8000); System.out.println("SSLServerSocket Started"); try (Socket socket = serverSocket.accept(); PrintWriter out = new PrintWriter( socket.getOutputStream(), true); BufferedReader br = new BufferedReader( new InputStreamReader( socket.getInputStream()))) { System.out.println("Client socket created"); String line = null; while (((line = br.readLine()) != null)) { System.out.println(line); out.println(line); } br.close(); System.out.println("SSLServerSocket Terminated"); } catch (IOException ex) { // Handle exceptions } } catch (IOException ex) { // Handle exceptions } } } ``` ## 创建 SSL 客户端 安全 echo 客户端也类似于之前的非安全 echo 客户端。`SSLSocketFactory`类“`getDefault`返回一个`SSLSocketFactory`实例,该实例的`createSocket`创建了一个连接到安全 echo 服务器的套接字。申请如下: ```java public class SSLClientSocket { public static void main(String[] args) throws Exception { System.out.println("SSLClientSocket Started"); SSLSocketFactory sf = (SSLSocketFactory) SSLSocketFactory.getDefault(); try (Socket socket = sf.createSocket("localhost", 8000); PrintWriter out = new PrintWriter( socket.getOutputStream(), true); BufferedReader br = new BufferedReader( new InputStreamReader( socket.getInputStream()))) { Scanner scanner = new Scanner(System.in); while (true) { System.out.print("Enter text: "); String inputLine = scanner.nextLine(); if ("quit".equalsIgnoreCase(inputLine)) { break; } out.println(inputLine); System.out.println("Server response: " + br.readLine()); } System.out.println("SSLServerSocket Terminated"); } } } ``` 如果我们先执行此服务器,然后执行客户端,则它们将因连接错误而中止。这是因为我们没有提供应用程序可以共享和使用的一组密钥来保护它们之间传递的数据。 ## 生成安全密钥 为了提供必要的密钥,我们需要创建一个密钥库来保存密钥。当应用程序执行时,密钥库必须对应用程序可用。首先,我们将演示如何创建密钥库,然后我们将向您展示必须提供哪些运行时参数。 Java SE SDK 的`bin`目录中有一个名为`keytool`的程序。这是一个命令级程序,将生成必要的密钥并将其存储在密钥文件中。在 Windows 中,您需要打开一个命令窗口并导航到源文件的根目录。此目录将包含保存应用程序包的目录。 ### 注 在 Mac 上,您可能无法生成密钥对。有关在 Mac 上使用此工具的更多信息,请参见[https://developer.apple.com/library/mac/documentation/Darwin/Reference/ManPages/man1/keytool.1.html](https://developer.apple.com/library/mac/documentation/Darwin/Reference/ManPages/man1/keytool.1.html) 。 您还需要使用类似于以下命令的命令设置`bin`目录的路径。查找并执行`keytool`应用程序需要此命令: ```java set path= C:\Program Files\Java\jdk1.8.0_25\bin;%path% ``` 接下来,输入`keytool`命令。系统将提示您输入用于创建密钥的密码和其他信息。此处显示了此过程,其中使用了`123456`密码,但在输入时未显示该密码: ```java Enter keystore password: Re-enter new password: What is your first and last name? [Unknown]: First Last What is the name of your organizational unit? [Unknown]: packt What is the name of your organization? [Unknown]: publishing What is the name of your City or Locality? [Unknown]: home What is the name of your State or Province? [Unknown]: calm What is the two-letter country code for this unit? [Unknown]: me Is CN=First Last, OU=packt, O=publishing, L=home, ST=calm, C=me correct? [no]: y Enter key password for (RETURN if same as keystore password): ``` 创建密钥库后,您可以运行服务器和客户端应用程序。这些应用程序的启动方式取决于项目的创建方式。您可以从 IDE 执行它,或者您可能需要从命令窗口启动它们。 接下来是可以从命令窗口使用的命令。`java`命令的两个参数是密钥库的位置和密码。它们需要从包目录的根目录执行: ```java java -Djavax.net.ssl.keyStore=keystore.jks -Djavax.net.ssl.keyStorePassword=123456 packt.SSLServerSocket java -Djavax.net.ssl.trustStore=keystore.jks -Djavax.net.ssl.trustStorePassword=123456 packt.SSLClientSocket ``` 如果要使用 IDE,请对运行时命令参数使用等效设置。下面的示例说明了客户端和服务器之间的一种可能的交换。首先显示服务器窗口的输出,然后显示客户端的输出: **SSLServerSocket 启动** **客户端套接字已创建** **你好回声服务器** **安全可靠** **SSLServerSocket 端接** **SSLClientSocket 启动** **输入文本:Hello echo server** **服务器响应:Hello echo 服务器** **输入文本:安全可靠** **服务器响应:安全可靠** **输入文本:退出** **SSLServerSocket 端接** 关于 SSL,需要了解的内容比这里显示的更多。然而,这提供了流程的概述,更多细节见[第 8 章](8.html "Chapter 8. Network Security")、*网络安全*。 # 总结 网络应用在当今社会中扮演着越来越重要的角色。随着越来越多的设备连接到 Internet,了解如何构建能够与其他应用程序通信的应用程序非常重要。 我们简要地识别并解释了 Java 用于连接网络的几种技术。我们演示了`InetAddress`类如何表示 IP 地址,并在几个示例中使用了该类。使用 UDP、TCP 和 SSL 技术演示了客户端/服务器体系结构的基本元素。它们提供不同类型的支持。UDP 速度快,但不如 TCP 可靠或功能强大。TCP 是一种可靠且方便的通信方式,但除非与 SSL 一起使用,否则不安全。 说明了 NIO 对缓冲区和通道的支持。这些技术可以提高通信效率。应用程序的可伸缩性对于许多应用程序来说是至关重要的,特别是客户端/服务器模型。我们还了解了线程如何支持可伸缩性。 这些主题中的每一个都将在后面的章节中进行更详细的讨论。这包括 NIO 对可伸缩性的支持、P2P 应用程序的工作方式以及可用于 Java 的各种互操作性技术。 在下一章中,我们将首先详细检查网络,尤其是网络寻址。