一个程序要与另一个程序通信,它必须有一个地址。在本章中,将检查地址的使用,包括 Internet 地址。我们将在本章的第一部分介绍许多基本概念。这包括网络架构和用于节点之间通信的协议。
我们将讨论几个主题,包括:
这将为您提供更深入地追求网络的基础。
网络基础
网络是一个广泛而复杂的话题。特别是,一个子主题,如寻址,是相当复杂的。我们将从 Java 的角度介绍经常遇到和有用的术语和概念。
大部分讨论将集中在 Java 对 Internet 的支持上。一个统一资源定位符(URL)被大多数网民的认可。但是,术语统一资源标识符( URI ) 和统一资源名称( URN ) 不像 URL 那样被识别或理解。我们将区分这些术语并检查 Java 支持类。
浏览器用户通常会输入他们想要访问的站点的 URL。此 URL 需要映射到 IP 地址。IP 地址是标识站点的唯一编号。URL使用域名系统( DNS ) 服务器映射到 IP地址。这避免了用户必须记住每个站点的编号。Java 使用该类访问 IP 地址和资源。InetAddress
UDP 和 TCP 被许多应用程序使用。IP 支持这两种协议。IP 协议在网络上的节点之间传输信息包。Java 支持 IPv4 和 IPv6 协议版本。
UDP 和 TCP 都位于 IP 之上。其他几种协议位于 TCP 之上,例如 HTTP。这些关系如下图所示:
当使用不同机器和操作系统的不同网络之间发生通信时,可能会由于硬件或软件级别的差异而出现问题。这些问题之一是 URL 中使用的字符。该URLEncoder和URLDecoder类可以帮助解决这个问题,他们在讨论第9章,网络互操作性。
分配给一个设备的IP地址可以是静态的或动态的。如果它是静态的,则每次设备重新启动时它都不会改变。对于动态地址,每次重新启动设备或重置网络连接时,地址可能会更改。
静态地址通常由管理员手动分配。动态地址经常使用从 DHCP 服务器运行的动态主机配置协议( DHCP ) 分配。对于 IPv6,由于 IPv6 地址空间较大,DHCP 的用处不大。但是,DHCP 对任务很有用,例如支持随机地址的生成,当从网络外部查看时,这会在网络内引入更多隐私。
在互联网编号分配 机构(IANA)负责IP地址空间分配的分配。五个区域互联网注册管理机构( RIR ) 将IP 地址块分配给通常称为互联网服务提供商( ISP ) 的本地互联网实体。
有几个出版物详细介绍了 IP 协议:
此处介绍的许多概念将尽可能用 Java 代码进行说明。因此,让我们从了解网络开始。
了解网络基础知识
网络由节点和链接组成,这些节点和链接组合在一起以创建网络架构。连接到 Internet 的设备称为节点。计算机节点称为主机。节点之间的通信是使用 HTTP 或 UDP 等协议沿着这些链路进行的。
链接可以是有线的,例如同轴电缆、双绞线和光纤,也可以是无线的,例如微波、蜂窝、Wi-Fi 或卫星通信。这些不同的链路支持不同的带宽和吞吐量,以满足特定的通信需求。
节点包括设备,例如网络接口控制器( NIC )、网桥、交换机、集线器和路由器。它们都涉及在计算机之间传输各种形式的数据。
NIC 具有 IP地址并且是计算机的一部分。网桥连接两个网段,允许将较大的网络分解为较小的网络。中继器和集线器主要用于重新传输增强其强度的信号。
集线器、交换机和路由器彼此相似,但其复杂性不同。集线器处理多个端口并简单地将数据转发到所有连接的端口。交换机将根据其流量了解将数据发送到何处。可以对路由器进行编程以操纵和路由消息。路由器在许多网络中更有用,大多数家庭网络都使用路由器。
当从家用计算机通过 Internet 发送消息时,会发生多种情况。计算机的地址不是全局唯一的。这要求发送到计算机和从计算机发送的任何消息都由网络地址转换( NAT ) 设备处理,该设备将地址更改为可在 Internet 上使用的地址。它允许将单个 IP 地址用于网络上的多个设备,例如家庭 LAN。
计算机还可以使用代理服务器,作为通往其他网络的网关。Java 为使用Proxy和ProxySelector类的代理提供支持。我们将在第 9 章,网络互操作性中检查它们的使用。
消息通常通过防火墙路由。防火墙保护计算机免受恶意企图。
网络架构和协议
常见的网络架构包括总线型、星型和树型网络。这些物理网络通常用于支持覆盖网络,即虚拟网络。这样的网络抽象了底层网络以创建支持应用程序的网络架构,例如对等应用程序。
当两台计算机通信时,它们使用一种协议。网络的各个层使用了许多不同的协议。我们将主要关注 HTTP、TCP、UDP 和 IP。
有几种模型描述了网络如何分层以支持不同的任务和协议。一种常见模型是开放系统互连( OSI ) 模型,它定义了七层。网络模型的每一层都可以支持一个或多个协议。各种协议的关系如下表所示:
层 |
示例协议 |
目的 |
应用 |
HTTP、FTP、SNMP |
支持专业操作的高级协议 |
介绍 |
传输层安全 |
支持应用层数据的交付和处理 |
会议 |
网络文件系统 |
管理会话 |
运输 |
TCP、UDP |
管理数据包 |
网络 |
知识产权 |
传输数据包 |
数据链接 |
以太网、帧中继 |
在网段之间传输数据 |
身体的 |
DSL, 蓝牙 |
处理原始数据 |
可以在https://en.wikipedia.org/wiki/List_of_network_protocols_(OSI_model)找到更完整的 OSI 层协议列表。我们无法解决所有这些协议,而是将重点放在 Java SDK 支持的更重要的协议上。
考虑将网页从服务器传输到客户端。当它被发送到客户端时,数据将被封装在一个 HTTP 消息中,该消息被进一步封装在 TCP、IP 和链路级协议消息中,每个消息经常包含一个页眉和页脚。这组封装的标头通过 Internet 发送到目标客户端,在那里为每个封装标头提取数据,直到显示原始 HTML 文件。
幸运的是,我们不需要熟悉这个过程的细节。许多类隐藏了这是如何发生的,使我们能够专注于数据。
的协议传输层是我们感兴趣的是TCP和UDP。TCP 提供了比 UDP 更可靠的通信协议。但是,UDP 更适合用于不需要可靠传递的短消息。流数据通常使用 UDP。
下表概述了 UDP 和 TCP 之间的差异:
特征 |
TCP |
UDP |
联系 |
面向连接 |
无连接 |
可靠性 |
更高 |
降低 |
数据包顺序 |
订单恢复 |
订单可能丢失 |
数据边界 |
数据包被合并 |
数据包是不同的 |
传输时间 |
比UDP慢 |
比 TCP 快 |
错误检查 |
是的 |
是的,但没有恢复选项 |
致谢 |
是的 |
不 |
重量 |
需要更多支撑的重物 |
重量轻,需要较少的支撑 |
TCP 用于许多协议,例如 HTTP、简单邮件传输 协议( SMTP ) 和文件传输协议( FTP )。DNS 使用 UDP 来流式传输媒体(例如电影)和IP语音( VOIP )。
使用 NetworkInterface 类
的NetworkInterface类提供访问充当网络上的节点的设备的装置。此类还提供了一种获取低级设备地址的方法。许多系统同时连接到多个网络。这些可以是有线的,例如网卡,也可以是无线的,例如无线 LAN 或蓝牙连接。
本NetworkInterface类代表一个IP地址,并提供有关该IP地址信息。甲网络接口是一台计算机和网络之间的连接点。这经常使用某种类型的 NIC。它不必具有物理表现形式,但可以像环回连接一样在软件中执行(对于 IPv4 和IPv6)。 127.0.0.1::1
本NetworkInterface类不具有任何公共构造函数。提供了三个静态方法来返回NetworkInterface类的实例:
以下代码说明了如何使用该getNetworkInterfaces方法获取并显示当前计算机的网络接口枚举:
try { EnumerationinterfaceEnum = NetworkInterface.getNetworkInterfaces(); System.out.printf("Name Display name\n"); for(NetworkInterface element : Collections.list(interfaceEnum)) { System.out.printf("%-8s %-32s\n", element.getName(), element.getDisplayName()); } catch (SocketException ex) { // Handle exceptions }
一种可能的输出如下,但它已被截断以节省空间:
Name Display name
lo Software Loopback Interface 1
eth0 Microsoft Kernel Debug Network Adapter
eth1 Realtek PCIe FE Family Controller
wlan0 Realtek RTL8188EE 802.11 b/g/n Wi-Fi Adapter
wlan1 Microsoft Wi-Fi Direct Virtual Adapter
net0 Microsoft 6to4 Adapter
net1 Teredo Tunneling Pseudo-Interface
...
甲getSubInterfaces方法如果存在任何将返回子接口的枚举,如下所示。当单个物理网络接口被划分为用于路由目的的逻辑接口时,就会出现子接口:
EnumerationinterfaceEnumeration = element.getSubInterfaces();
每个网络接口都有一个或多个与之关联的 IP 地址。该getInetAddresses方法将返回Enumeration这些地址中的一个。如下所示,网络接口的初始列表已被扩充以显示与它们关联的 IP 地址:
EnumerationinterfaceEnum = NetworkInterface.getNetworkInterfaces(); System.out.printf("Name Display name\n"); for (NetworkInterface element : Collections.list(interfaceEnum)) { System.out.printf("%-8s %-32s\n", element.getName(), element.getDisplayName()); Enumeration addresses = element.getInetAddresses(); for (InetAddress inetAddress : Collections.list(addresses)) { System.out.printf(" InetAddress: %s\n", inetAddress); }
一种可能的输出如下:
Name Display name
lo Software Loopback Interface 1
InetAddress: /127.0.0.1
InetAddress: /0:0:0:0:0:0:0:1
eth0 Microsoft Kernel Debug Network Adapter
eth1 Realtek PCIe FE Family Controller
InetAddress: /fe80:0:0:0:91d0:8e19:31f1:cb2d%eth1
wlan0 Realtek RTL8188EE 802.11 b/g/n Wi-Fi Adapter
InetAddress: /192.168.1.5
InetAddress: /2002:6028:2252:0:0:0:0:1000
InetAddress: /fe80:0:0:0:9cdb:371f:d3e9:4e2e%wlan0
wlan1 Microsoft Wi-Fi Direct Virtual Adapter
InetAddress: /fe80:0:0:0:f8f6:9c75:d86d:8a22%wlan1
net0 Microsoft 6to4 Adapter
net1 Teredo Tunneling Pseudo-Interface
InetAddress: /2001:0:9d38:6abd:6a:37:3f57:fefa
...
我们还可以使用以下 Java 8 技术。流和 lambda 表达式用于显示 IP 地址以生成相同的输出:
addresses = element.getInetAddresses(); Collections .list(addresses) .stream() .forEach((inetAddress) -> { System.out.printf(" InetAddress: %s\n", inetAddress); });
有许多InetworkAddress方法可以揭示有关网络连接的更多详细信息。我们将在遇到它们时讨论它们。
获取 MAC 地址
甲媒体访问控制(MAC)地址被用来识别一个NIC。MAC 地址通常由NIC的制造商分配,并且是其硬件的一部分。节点上的每个 NIC 都必须具有唯一的 MAC 地址。理论上,所有 NIC,无论其位置如何,都将具有唯一的 MAC 地址。MAC 地址由 48 位组成,通常以六对十六进制数字为一组写入。这些组由破折号或冒号分隔。
获取特定的 MAC 地址
通常,普通 Java 程序员不需要MAC 地址。但是,可以在需要时检索它们。以下方法返回一个字符串,其中包含NetworkInterface实例的 IP 地址及其 MAC 地址。该getHardwareAddress方法返回一个包含数字的字节数组。然后该阵列显示为 MAC 地址。大多数代码段逻辑专门用于格式化输出,其中第三个运算符确定是否应显示破折号:
public String getMACIdentifier(NetworkInterface network) { StringBuilder identifier = new StringBuilder(); try { byte[] macBuffer = network.getHardwareAddress(); if (macBuffer != null) { for (int i = 0; i < macBuffer.length; i++) { identifier.append( String.format("%02X%s",macBuffer[i], (i < macBuffer.length - 1) ? "-" : "")); } } else { return "---"; } } catch (SocketException ex) { ex.printStackTrace(); } return identifier.toString(); }
该方法在以下我们使用 localhost 的示例中演示:
InetAddress address = InetAddress.getLocalHost(); System.out.println("IP address: " + address.getHostAddress()); NetworkInterface network = NetworkInterface.getByInetAddress(address); System.out.println("MAC address: " + getMACIdentifier(network));
输出将根据所使用的计算机而有所不同。一种可能的输出如下:
IP address: 192.168.1.5
MAC address: EC-0E-C4-37-BB-72
笔记
该getHardwareAddress方法将只允许您访问本地主机 MAC 地址。您不能使用它来访问远程 MAC 地址。
获取多个 MAC 地址
并非所有网络接口都有 MAC 地址。此处演示了这一点,其中使用该方法创建了一个枚举,然后显示了每个网络接口:getNetworkInterfaces
EnumerationinterfaceEnum = NetworkInterface.getNetworkInterfaces(); System.out.println("Name MAC Address"); for (NetworkInterface element : Collections.list(interfaceEnum)) { System.out.printf("%-6s %s\n", element.getName(), getMACIdentifier(element));
一种可能的输出如下。输出被截断以节省空间:
Name MAC Address
lo ---
eth0 ---
eth1 8C-DC-D4-86-B1-05
wlan0 EC-0E-C4-37-BB-72
wlan1 EC-0E-C4-37-BB-72
net0 ---
net1 00-00-00-00-00-00-00-E0
net2 00-00-00-00-00-00-00-E0
...
或者,我们可以使用以下 Java 实现。它将枚举转换为流,然后处理流中的每个元素:
interfaceEnum = NetworkInterface.getNetworkInterfaces(); Collections .list(interfaceEnum) .stream() .forEach((inetAddress) -> { System.out.printf("%-6s %s\n", inetAddress.getName(), getMACIdentifier(inetAddress)); });
当我们需要执行额外的处理时,流的力量就来了,例如过滤掉某些接口,或者将接口转换为不同的数据类型。
网络寻址概念
有不同类型的网络地址。地址用于标识网络中的节点。例如,Internetwork Packet Exchange ( IPX ) 协议是较早的协议,用于访问网络上的节点。X.25 是用于广域网( WAN ) 数据包交换的协议套件。MAC 地址为物理网络级别的网络接口提供唯一标识符。但是,我们的主要兴趣是 IP 地址。
网址/URI/URN
这些术语用于指 Internet 资源的名称和位置。URI 标识资源的名称,例如网站或 Internet 上的文件。它可能包含资源的名称及其位置。
URL 指定资源所在的位置以及如何检索它。甲协议形成的URL的第一部分,和指定检索数据的方式。URL 始终包含协议,例如 HTTP 或 FTP。例如,以下两个 URL 使用不同的协议。在第一个使用HTTPS协议,而第二个使用FTP协议:
https://www.packtpub.com/
ftp://speedtest.tele2.net/
Java 提供了支持 URI 和 URL 的类。这些类的讨论从下一节开始。在这里,我们将更深入地讨论 URN。
URN 标识资源但不标识其位置。URN 类似于城市的名称,而 URL 类似于城市的经纬度。移动资源(例如网页或文件)时,资源的 URL不再正确。无论在何处使用该 URL,都需要更新该 URL。URN 指定资源的名称,但不指定其位置。某些其他实体在提供 URN 时将返回其位置。URN 没有被广泛使用。
URN 的语法如下所示。该
例如,以下 URN 指定为 SOAP 消息的一部分以限定其名称空间:
<肥皂:信封
xmlns:SOAP='urn:schemas-xmlsoap-org:soap.v1'>
<肥皂:身体>
...
xmlns:i='urn:gargantuan-com:IShop'>
...
肥皂:身体>
信封>
它用于其他地方,例如使用 ISBN 识别书籍。在浏览器中输入以下 URL 将显示对 EJB 书籍的引用:
https://books.google.com/books?isbn=9781849682381
URN 的语法取决于命名空间。IANA 负责分配许多互联网资源,包括URN 命名空间。URN 仍然是一个活跃的研究领域。URL 和 URN 都是 URI。
使用 URI 类
URI的一般语法由方案和方案特定部分组成:
[方案:] 方案特定部分
有许多与 URI一起使用的方案,包括:
特定于方案的部分因使用的方案而异。URI 可以分为绝对的或相对的,或者不透明的或分层的。尽管 Java 提供了确定 URI 是否属于这些类别之一的方法,但我们在这里并不直接对这些区别感兴趣。
创建 URI 实例
可以使用多个构造函数变体为不同的方案创建URI 。创建 URI 的最简单方法是使用指定 URI 的字符串参数,如下所示:
URI uri = 新
URI("https://www.packtpub.com/books/content/support");
下一个 URI 使用一个片段来访问处理 URL 规范化的维基百科文章的一个小节:
uri = 新 URI("https://en.wikipedia.org/wiki/"
+ "URL_normalization#Normalization_process");
我们还可以使用以下版本的构造函数来指定 URI 的方案、主机、路径和片段:
uri = 新
URI("https","en.wikipedia.org","/wiki/URL_normalization",
"Normalization_process");
后两个 URI 是相同的。
拆分 URI
Java 使用URI该类来表示 URI,并且它拥有多种方法来提取 URI 的一部分。下表列出了更有用的方法:
方法 |
目的 |
getAuthority |
这是负责解析 URI的实体 |
getScheme |
该方案使用 |
getSchemeSpecificPart |
URI的方案特定部分 |
getHost |
该主机 |
getPath |
该URI路径 |
getQuery |
该查询,如果有的话 |
getFragment |
正在访问的子元素(如果使用) |
getUserInfo |
用户信息(如果有) |
normalize |
删除不必要的“.” 和“..”来自路径 |
还有几个“原始”方法,例如getRawPath, or getRawFragment,它们分别返回路径或片段的版本。这包括特殊字符,例如问号,或以星号开头的字符序列。有几个字符类别定义了这些字符及其用途,如http://docs.oracle.com/javase/8/docs/api/java/net/URI.html 中所述。
我们开发了以下用于显示 URI 特征的辅助方法:
私有静态无效显示URI(URI uri){
System.out.println("getAuthority:" + uri.getAuthority());
System.out.println("getScheme:" + uri.getScheme());
System.out.println("getSchemeSpecificPart:"
+ uri.getSchemeSpecificPart());
System.out.println("getHost:" + uri.getHost());
System.out.println("获取路径:" + uri.getPath());
System.out.println("getQuery:" + uri.getQuery());
System.out.println("getFragment:" + uri.getFragment());
System.out.println("getUserInfo:" + uri.getUserInfo());
System.out.println("标准化:" + uri.normalize());
}
下一个代码序列URI为 Packtpub 网站创建一个实例,然后调用该displayURI方法:
尝试 {
URI uri = 新
URI("https://www.packtpub.com/books/content/support");
displayURI(uri);
} catch (URISyntaxException ex) {
// 处理异常
}
该序列的输出如下:
getAuthority:www.packtpub.com
获取方案:https
getSchemeSpecificPart://www.packtpub.com/books/content/support
getHost:www.packtpub.com
获取路径:/books/content/support
获取查询:空
获取片段:空
获取用户信息:空
规范化:https://www.packtpub.com/books/content/support
http://www.packtpub.com
更常见的是,这些方法用于提取相关信息以进行额外处理。
使用 URL 类
连接到站点并检索数据的最简单方法之一是通过URL类。您需要提供的只是站点的 URL 和协议的详细信息。InetAddress该类的一个实例将保存一个 IP 地址,可能还有该地址的主机名。
该URLConnection班在介绍第1章,入门网络编程。它还可用于提供对由 URL 表示的 Internet 资源的访问。我们将讨论这个类和它在使用第4章,客户/服务器开发。
创建 URL 实例
有多种方法可以创建 URL 实例。最简单的方法是简单地提供站点的 URL 作为类的构造函数的参数。此处说明了URL创建 Packtpub 网站实例的位置:
URL url = new URL("http://www.packtpub.com");
URL 需要指定协议。例如,以下尝试创建 URL 将导致java.net.MalformedURLException: no protocol: www.packtpub.com错误消息:
url = new URL("www.packtpub.com");
有几种构造函数变体。以下两个变体将创建相同的 URL。第二个使用协议、主机、端口号和文件的参数:
url = new URL("http://pluto.jhuapl.edu/");
url = new URL("http", "pluto.jhuapl.edu", 80,
"新闻中心/index.php");
拆分 URL
了解有关 URL 的更多信息会很有用。如果用户输入了我们需要处理的 URL,我们甚至可能不知道我们使用的是哪个 URL。有多种方法支持将 URL 拆分为其组件,如下表所示:
方法 |
目的 |
getProtocol |
这是协议的名称。 |
getHost |
这是主机名。 |
getPort |
这是端口号。 |
getDefaultPort |
这是协议的默认端口号。 |
getFile |
这将返回与 的结果getPath连接的结果getQuery。 |
getPath |
这将检索URL 的路径(如果有)。 |
getRef |
这是URL 引用的返回名称。 |
getQuery |
如果存在,这将返回URL的查询部分。 |
getUserInfo |
这将返回与 URL 关联的任何用户信息。 |
getAuthority |
权限通常由服务器主机名或 IP 地址组成。它可能包括端口号。 |
我们将使用以下方法来说明上表中的方法:
私有静态无效显示URL(URL url){
System.out.println("网址:" + url);
System.out.printf(" 协议:%-32s 主机:%-32s\n",
url.getProtocol(),url.getHost());
System.out.printf(" 端口:%-32d 路径:%-32s\n",
url.getPort(),url.getPath());
System.out.printf(" 参考:%-32s 文件:%-32s\n",
url.getRef(),url.getFile());
System.out.printf("权限:%-32s 查询:%-32s\n",
url.getAuthority(),url.getQuery());
System.out.println(" 用户信息:" + url.getUserInfo());
}
以下输出演示了使用多个 URL 作为此方法的参数时的输出。
网址:http://www.packpub.com
协议:http 主机:www.packpub.com
端口:-1 路径:
参考:空文件:
权限:www.packpub.com 查询:null
用户信息:空
网址:http://pluto.jhuapl.edu/
协议:http 主机:pluto.jhuapl.edu
端口:-1 路径:/
参考:空文件:/
权限:pluto.jhuapl.edu 查询:null
用户信息:空
网址:http://pluto.jhuapl.edu:80News-Center/index.php
协议:http 主机:pluto.jhuapl.edu
端口:80 路径:News-Center/index.php
参考:null 文件:News-Center/index.php
权限:pluto.jhuapl.edu:80 查询:null
用户信息:空
网址:https://en.wikipedia.org/wiki/Uniform_resource_locator#Syntax
协议:https 主机:en.wikipedia.org
端口:-1 路径:/wiki/Uniform_resource_locator
参考:语法文件:/wiki/Uniform_resource_locator
权威:en.wikipedia.org 查询:空
用户信息:空
网址:https://www.google.com/webhp?sourceid=chrome-instant&ion=1&espv=2&ie=UTF-8#q=url+syntax
协议:https 主机:www.google.com
端口:-1 路径:/webhp
参考:q=url+syntax 文件:/webhp?sourceid=chrome-instant&ion=1&espv=2&ie=UTF-8
权限:www.google.com 查询:sourceid=chrome-instant&ion=1&espv=2&ie=UTF-8
用户信息:空
网址:https://www.packtpub.com/books/content/support
协议:https 主机:www.packtpub.com
端口:-1 路径:/books/content/support
参考:空文件:/books/content/support
权限:www.packtpub.com 查询:null
用户信息:空
URL 类还支持打开连接和 IO 流。我们证明了openConnection在法第1章,入门网络编程。该getContent方法返回 URL 引用的数据。例如,以下对 Packtpub URL 应用该方法:
url = new URL("http://www.packtpub.com");
System.out.println("getContent:" + url.getContent());
输出如下:
sun.net.www.protocol.http.HttpURLConnection$HttpInputStream@5c647e05
这表明我们需要使用输入流来处理资源。数据类型取决于 URL。本主题探讨与URLConnection正在讨论中的类第4章,客户/服务器开发。
IP 地址和 InetAddress 类
IP 地址是用于标识节点(例如计算机、打印机、扫描仪或类似设备)的数值。它用于网络接口寻址和位置寻址。地址在其上下文中是唯一的,用于标识设备。同时,它构成了网络中的一个位置。名称指定实体,例如www.packtpub.com。它的地址83.166.169.231告诉我们它的位置。如果我们想从站点发送或接收消息,消息通常会通过一个或多个节点进行路由。
获取有关地址的信息
本InetAddress类代表一个IP地址。IP 协议是UDP 和TCP 协议使用的低级协议。IP 地址是分配给设备的 32 位或 128 位无符号数字。
IP 地址历史悠久,使用两个主要版本:IPv4 和 IPv6。数字 5 被分配给Internet 流协议。这是一个实验性协议,但实际上从未将其称为 IPv5 版本,也不打算用于一般用途。
在InetAddress类的getAllByName方法对于给定的URL返回的IP地址。在以下示例中,显示了与www.google.com关联的地址:
Inet地址名称[] =
InetAddress.getAllByName("www.google.com");
for(InetAddress 元素:名称){
System.out.println(元素);
}
一种可能的输出如下。输出将因位置和时间而异,因为许多网站都分配有多个 IP 地址。在这种情况下,它同时使用 IPv4 和 IPv6 地址:
www.google.com/74.125.21.105
www.google.com/74.125.21.103
www.google.com/74.125.21.147
www.google.com/74.125.21.104
www.google.com/74.125.21.99
www.google.com/74.125.21.106
www.google.com/2607:f8b0:4002:c06:0:0:0:69
本InetAddress类具有几种方法来提供访问的IP地址。我们将在它们变得相关时介绍它们。我们从返回其规范主机名、主机名和主机地址的方法开始。它们用于以下辅助方法:
私有静态无效 displayInetAddressInformation(
InetAddress 地址) {
System.out.println(地址);
System.out.println("规范主机名:" +
address.getCanonicalHostName());
System.out.println("主机名:" + address.getHostName());
System.out.println("主机地址:" +
address.getHostAddress());
}
规范主机名是完全限定域名( FQDN )。顾名思义,它是主机的全名,包括顶级域。这些方法返回的值取决于几个因素,包括 DNS 服务器。系统提供关于网络上实体的信息。
以下序列使用 Packtpub 网站的显示方法:
InetAddress 地址 =
InetAddress.getByName("www.packtpub.com");
displayInetAddressInformation(地址);
您将获得与以下类似的输出:
www.packtpub.com/83.166.169.231
规范主机名:83.166.169.231
主机地址:83.166.169.231
主机名:www.packtpub.com
在InetAddress类的toString方法返回的主机名,其次是正斜杠,然后主机地址。getCanonicalHostName在这种情况下,该方法返回主机地址,而不是 FQDN。该方法将尽最大努力返回名称,但可能无法返回,具体取决于机器的配置。
解决范围界定问题
IP地址的范围是指IP地址的唯一性。在本地网络中,例如在许多家庭和办公室中使用的网络,地址可能是该网络的本地地址。存在三种类型的范围:
IPv4中的私有地址和IPv6 中的私有地址部分还讨论了私有地址。的InetAddress类支持几种方法来识别正在使用的地址的类型。这些方法中的大多数都是不言自明的,如下表所示,其中 MC 是多播的缩写:
方法 |
范围 |
描述 |
isAnyLocalAddress |
任何 |
这是一个匹配任何本地地址的地址。它是一个通配符地址。 |
isLoopbackAddress |
环回 |
这是一个环回地址。对于 IPv4,它是127.0.0.1,对于 IPv6,它是0:0:0:0:0:0:0:1。 |
isLinkLocalAddress |
本地链接 |
这是链接本地地址。 |
isSiteLocalAddress |
站点本地 |
这是站点的本地。它们可以被不同网络上但在同一站点内的其他节点访问。 |
isMulticastAddress |
MC |
这是一个多播地址。 |
isMCLinkLocal |
MC 链路本地 |
这是链路本地多播地址。 |
isMCNodeLocal |
MC节点本地 |
这是节点本地多播地址。 |
isMCSiteLocal |
MC 站点本地 |
这是站点本地多播地址。 |
isMCOrgLocal |
MC 组织本地 |
这是一个组织本地多播地址。 |
isMCGlobal |
MC全球 |
这是一个全局多播地址。 |
下表总结了 IPv4 和 IPv6 使用的地址类型和范围:
地址类型 |
IPv4 |
IPv6 |
组播 |
224.0.0.0 到 239.255.255.25 |
以字节开头 FF |
MC全球 |
224.0.1.0 到 238.255.255.255 |
FF0E 或者 FF1E |
组织MC |
239.192.0.0/14 |
FF08 或者 FF18 |
MC 站点本地 |
不适用 |
FF05 或者 FF15 |
MC 链路本地 |
224.0.0.0 |
FF02 或者 FF12 |
MC节点本地 |
127.0.0.0 |
FF01 或者 FF11 |
私人的 |
10.0.0.0 到 10.255.255.255 172.16.0.0 到 172.31.255.255 192.168.0.0 到 192.168.255.255 |
fd00::/8 |
测试可达性
在InetAddress类的isReachable方法将尝试确定地址是否可以找到。如果可以,该方法返回true。以下示例演示了此方法。该getAllByName方法返回InetAddress可用于 URL的实例数组。该isReachable方法使用一个整数参数来指定在决定地址不可达之前最多等待多长时间(以毫秒为单位):
String URLAddress = "www.packtpub.com";
InetAddress[] 地址 =
InetAddress.getAllByName(URLAddress);
for (InetAddress inetAddress : 地址) {
尝试 {
如果(inetAddress.isReachable(10000)){
System.out.println(inetAddress + " 可达");
} 别的 {
System.out.println(inetAddress +
"无法访问");
}
} catch (IOException ex) {
// 处理异常
}
}
URL www.packtpub.com是可访问的,如下所示:
www.packtpub.com/83.166.169.231 可达
但是,www.google.com不是:
www.google.com/173.194.121.52 无法访问
www.google.com/173.194.121.51 无法访问
www.google.com/2607:f8b0:4004:809:0:0:0:1014 无法访问
您的结果可能会有所不同。该isReachable方法将尽最大努力确定地址是否可达。然而,它的成功不仅仅取决于地址是否存在。失败的原因可能包括:服务器可能已关闭、网络响应时间过长或防火墙可能阻止了某个站点。操作系统和 JVM 设置也会影响该方法的工作情况。
此方法的替代方法是使用RunTime类的exec方法ping针对 URL执行命令。然而,这不是可移植的,并且可能仍然受到影响该方法成功的一些相同因素的影响isReachable。
介绍 Inet4Address
该地址由32 位组成,最多允许 4,294,967,296 (232) 个地址。地址的人类可读形式由四个十进制数字(8 位)组成,每个数字的范围为 0 到 255。一些地址已保留用于专用网络和多播地址。
在 IPv4 的早期使用中,第一个八位字节(8 位单元)表示网络编号(也称为网络前缀或网络块),其余位表示其余字段(主机标识符)。后来,使用了三个类来划分地址:A、B 和 C。这些系统基本上已被废弃,并已被无类域间路由( CIDR )所取代。这种路由方法在位边界上分配地址,提供了更大的灵活性。与早期的全类系统相比,这种方案被称为无类。在 IPv6 中,使用 64 位网络标识符。
IPv4 中的私有地址
专用网络不一定需要全球访问 Internet。这导致为这些专用网络分配一系列地址。
范围 |
位数 |
地址数 |
10.0.0.0 到 10.255.255.255 |
24 位 |
16,777,216 |
172.16.0.0 到 172.31.255.255 |
20 位 |
1,048,576 |
192.168.0.0 到 192.168.255.255 |
16 位 |
65,536 |
您可能会认识到最后一组地址是由家庭网络使用的。专用网络通常使用 NAT 与 Internet 连接。这种技术将本地 IP 地址映射到可在 Internet 上访问的地址。它最初是为了缓解 IPv4 地址短缺而引入的。
IPv4 地址类型
有三个地址类型是在IPv4的支持:
的Inet4Address类支持IPv4协议。接下来我们将更深入地研究这个类。
Inet4Address 类
的Inet4Address类从派生InetAddress的类。作为派生类,它不会覆盖InetAddress该类的许多方法。例如,要获取InetAddress实例,我们可以使用getByName任一类的方法,如下所示:
Inet4Address 地址;
地址 = (Inet4Address)
InetAddress.getByName("www.google.com");
地址 = (Inet4Address)
Inet4Address.getByName("www.google.com");
无论哪种情况,都需要强制转换地址,因为在任何一种情况下都使用基类方法。本Inet4Address类不加超出了任何新的方法InetAddress的类。
特殊 IPv4 地址
有几个特殊的 IPv4地址,包括这两个:
如果地址是通配符地址,该isAnyLocalAddress方法将返回true。此方法在此处演示,它返回true:
地址 = (Inet4Address) Inet4Address.getByName("0.0.0.0");
System.out.println(address.isAnyLocalAddress());
该isLoopbackAddress方法如下所示并将返回true:
地址 = (Inet4Address) Inet4Address.getByName("127.0.0.1");
System.out.println(address.isLoopbackAddress());
我们将在后续章节中经常使用它来测试服务器。
除此之外,其他特殊地址包括用于协议分配、IPv6 到 IPv4 中继和测试目的的地址。有关这些地址和其他特殊地址的更多详细信息,请访问https://en.wikipedia.org/wiki/IPv4#Special-use_addresses。
介绍 Inet6Address 类
IPv6 地址使用 128 位(16 个八位字节)。这允许最多 2128 个地址。IPv6 地址被写成一系列 8 个组,每个组有 4 个十六进制数字,用冒号分隔。数字不区分大小写。例如,www.google.com的IPv6 地址如下:
2607:f8b0:4002:0c08:0000:0000:0000:0067
可以通过多种方式简化 IPv6 地址。可以删除组中的前导零。前面的例子可以改写为:
2607:f8b0:4002:c08:0:0:0:67
连续的零组可以用 替换::,如下所示:
2607:f8b0:4002:c08::67
IPv6 支持三种寻址类型:
该协议不支持广播寻址。IPv6 的作用远不止网络规模的增加。它包括多项改进,例如更轻松的管理、更高效的路由功能、简单的标头格式以及不再需要 NAT。
IPv6 中的私有地址
私有地址空间在 IPv6 中可用。最初,它使用带有 fec0::/10 前缀的块使用站点本地地址。但是,由于其定义问题,这已被删除,并使用地址块将其替换为唯一本地( UL ) 地址。 fc00::/7
这些地址可以由任何人生成,不需要协调。但是,它们不一定是全球唯一的。其他专用网络可以使用相同的地址。它们不能使用全球 DNS 服务器分配,只能在本地地址空间中路由。
Inet6Address 类
通常,Inet6Address除非您正在开发仅支持 IPv6 的应用程序,否则没有必要使用该类。大多数网络操作都是透明处理的。的Inet6Address类从派生InetAddress的类。本Inet6Address类的getByName方法使用其基类的InetAddrress类的getAllByName方法,它找到返回的第一个地址,如下图所示。这可能不是 IPv6 地址:
公共静态 InetAddress getByName(String host)
抛出 UnknownHostException {
返回 InetAddress.getAllByName(host)[0];
}
笔记
为了让其中一些示例正常工作,您的路由器可能需要配置为支持 IPv6 Internet 连接。
Inet6Address该类仅在该类的方法之上和之外添加了一种方法InetAddress。这是使用 IPv4 兼容的 IPv6 地址部分中isIPv4CompatibleAddress讨论的方法。
特殊 IPv6 地址
有一个由 64 个网络前缀组成的地址块:2001:0000::/29通过2001:01f8::/29. 这些用于特殊需求。IANA 分配了三个:
大多数开发人员不需要使用这些地址。
测试 IP 地址类型
通常,我们不关心 IP地址是 IPv4 还是 IPv6。两者之间的差异隐藏在各种协议级别之下。当您确实需要了解差异时,您可以使用这两种方法中的任何一种。该getAddress方法返回一个字节数组。您检查字节数组的大小以确定它是 IPv4 还是 IPv6。或者您可以使用该instanceOf方法。这两种方法如下所示:
字节缓冲区[] = address.getAddress();
if(buffer.length <= 4) {
System.out.println("IPv4 地址");
} 别的 {
System.out.println("IPv6 地址");
}
如果(地址实例 Inet4Address){
System.out.println("IPv4 地址");
} 别的 {
System.out.println("IPv6 地址");
}
使用与 IPv4 兼容的 IPv6 地址
点分四元表示法是一种使用 IPv6 表示 IPv4 地址的方式。的::ffff:前缀被放置在任一的IPv4地址或等值的十六进制的前面。例如,IPv4 地址的十六进制等效项74.125.21.105是4a7d1569。两者都代表一个 32 位的数量。因此,以下三个地址中的任何一个都代表同一个网站:
地址 = InetAddress.getByName("74.125.21.105");
地址 = InetAddress.getByName("::ffff:74.125.21.105");
address = InetAddress.getByName("::ffff:4a7d:1569");
如果我们在displayInetAddressInformation方法中使用这些地址,输出将是相同的,如下所示:
/74.125.21.105
规范主机名:yv-in-f105.1e100.net
主机名:yv-in-f105.1e100.net
主机地址:74.125.21.105
规范主机名:83.166.169.231
这些被称为与 IPv4 兼容的 IPv6 地址。
本Inet6Address类都有一个isIPv4CompatibleAddress方法。true 如果地址只是一个放置在 IPv6 地址内的 IPv4 地址,则该方法返回。发生这种情况时,除最后四个字节外的所有字节都为零。
以下示例说明了如何使用此方法。与www.google.com关联的每个地址都经过测试以确定它是 IPv4 地址还是 IPv6 地址。如果是 IPv6 地址,则对其应用该方法:
尝试 {
Inet地址名称[] =
InetAddress.getAllByName("www.google.com");
对于(InetAddress 地址:名称){
if ((address instanceof Inet6Address) &&
((Inet6Address)地址)
.isIPv4CompatibleAddress()) {
System.out.println(地址
+ "是 IPv4 兼容地址");
} 别的 {
System.out.println(地址
+ " 不是 IPv4 兼容地址");
}
}
} catch (UnknownHostException ex) {
// 处理异常
}
输出取决于可用的服务器。以下是一种可能的输出:
www.google.com/173.194.46.48 不是 IPv4 兼容地址
www.google.com/173.194.46.51 不是 IPv4 兼容地址
www.google.com/173.194.46.49 不是 IPv4 兼容地址
www.google.com/173.194.46.52 不是 IPv4 兼容地址
www.google.com/173.194.46.50 不是 IPv4 兼容地址
www.google.com/2607:f8b0:4009:80b:0:0:0:2004 不是 IPv4 兼容地址
另一种 Java 8 解决方案如下:
名称 = InetAddress.getAllByName("www.google.com");
Arrays.stream(名称)
.map(地址 -> {
if ((address instanceof Inet6Address) &&
((Inet6Address)地址)
.isIPv4CompatibleAddress()) {
退货地址 +
"是 IPv4 兼容地址";
} 别的 {
退货地址 +
“不是 IPv4 兼容地址”;
}
})
.forEach(result -> System.out.println(result));
在许多操作系统上,默认行为是使用 IPv4 而不是 IPv6。执行 Java 应用程序时可以使用以下 JVM 选项来控制此行为。第一个设置如下:
-Djava.net.preferIPv4Stack=false
这是默认设置。如果 IPv6 可用,则应用程序可以使用 IPv4 或 IPv6 主机。如果设置为true
,它将使用 IPv4 主机。不会使用 IPv6 主机。
第二个设置处理使用的地址类型:
-Djava.net.preferIPv6Addresses=false
这是默认设置。如果 IPv6 可用,它会优先选择 IPv4 地址而不是 IPv6 地址。这是首选,因为它允许 IPv4 服务向后兼容。如果设置为true
,它将尽可能使用 IPv6 地址。
本章概述了基本网络术语和概念。网络是一个庞大而复杂的主题。在本章中,我们重点介绍了与 Java 网络相关的概念。
在NetworkInterface
介绍类。此类提供对连接到支持网络的计算机的设备的低级别访问。我们还学习了如何获取设备的 MAC 地址。
我们专注于 Java 为访问 Internet 提供的支持。详细介绍了基础IP协议。InetAddress
类支持此协议。Java 使用Inet4Address
和Inet6Address
类分别支持 IPv4 和 IPv6 地址。
我们还说明了URI
和URL
类的使用。这些类拥有多种方法,使我们能够获取有关特定实例的更多信息。我们可以使用这些方法将 URI 或 URL 拆分为多个部分以供进一步处理。
我们还讨论了如何控制一些网络连接属性。我们将在后面的章节中更详细地介绍这个主题。
有了这个基础,我们现在可以继续前进并解决使用 NIO 包来支持网络的问题。NIO 面向缓冲区并支持非阻塞 IO。此外,它还为许多 IO 操作提供了更好的性能。