我竟然到现在才发现《Fundamental Networking in Java》这本神作,真有点无地自容的感觉。最近几年做的都是所谓的企业级开发,免不了和网络打交道,但在实际工作中,往往会采用框架将底层细节和上层应用隔离开,感觉就像是在一个 Word 模板表单里面填写内容,做出来也没什么成就感。虽然没有不使用框架的理由,但我还真是有点怀念当初直接用套接字做网络编程的日子,既能掌控更多东西,还可以学到更多知识,为研究框架的实现原理打基础。闲话完毕,转入今天的正题:IP(Internet Protocol,互联网协议)。
IP 基础知识
说到 IP,大多数人的第一反应估计都是 IP 地址。其实 IP 是一种协议,IP 地址只是协议的一部分。《RFC 791 - INTERNET PROTOCOL》说:“互联网协议是为在包交换计算机通信网络的互联系统中使用而设计的。”IP 包含三方面的功能:
- 用于查找主机的寻址系统
- 包格式的定义
- 传输和接收包的规则
IP 的相关 Java 类
从 Java 的角度来看上面说到的三个功能,只有第一个是开发人员需要关心的。另外两个都依赖底层系统的实现,JDK 也没有提供相关的类去操作。下面一一介绍 JDK 提供的用于处理 IP 地址的类。
InetAddress
此类用来表示 IP 地址,它有两个子类:Inet4Address
和 Inet6Address
,分别用于处理 IPv4 和 IPv6 两个版本。在实际应用中,InetAddress
足以应付绝大多数情况。它提供了一些静态方法来构造实例,能根据参数格式自动识别 IP 版本:
public static InetAddress[] getAllByName(String host) throws UnknownHostException
- 解析指定的主机地址,并返回其所有的 IP 地址;如果传入 IP 地址字符串,则只会校验格式,返回的数组也只包含一个代表该 IP 地址的实例。例如,想看看谷歌有多少马甲的话,
InetAddress.getAllByName("www.google.com")
就可以了。
public static InetAddress getByAddress(byte[] addr) throws UnknownHostException
- 用表示 IP 地址的字节数组(专业术语称为“原始 IP 地址”)构造一个实例。IPv4 地址必须是 4 个字节,IPv6 必须 16 个。不常用。
public static InetAddress getByAddress(String host, byte[] addr) throws UnknownHostException
- 用主机地址和原始 IP 地址构造一个实例。此方法应该慎用,因为它不会对主机名进行解析。即使主机名为 IP 地址字符串,也不会检查是否与字节数组一致。
public static InetAddress getByName(String host) throws UnknownHostException
- 用主机地址构造一个实例,也可以直接传入 IP 地址字符串,等同于
getAllByName(host)[0]
。
public static InetAddress getLocalHost() throws UnknownHostException
- 返回本机在网络中的地址。
public static InetAddress getLoopbackAddress()
- 返回环回地址
127.0.0.1
,不抛出异常,等同于 getByName("localhost")
(不要和 getLocalHost()
搞混)。环回地址使主机能够自己连接自己,常被用来对在同一台机器上测试网络应用程序。在 IPv4 中,环回地址的网段为 127.0.0.0/8
,通常用 127.0.0.1
;IPv6 中只有一个 ::1
。
接下来看看 InetAddress
中定义的部分实例方法:
public byte[] getAddress()
- 返回原始 IP 地址。
public String getCanonicalHostName()
- 返回全限定域名。这个方法可以用来探查实际的主机名,例如
InetAddress.getByName("www.google.com").getCanonicalHostName()
返回 we-in-f99.1e100.net。
public String getHostAddress()
- 返回构造时传入的主机地址。
public String getHostName()
- 返回主机名。如果构造时传入的主机地址为 IP 地址字符串,则调用
getCanonicalHostName()
,否则直接返回构造时传入的主机地址。
public boolean isAnyLocalAddress()
- 检查是否为通配符地址。通配符地址为
0.0.0.0
(IPv4)或 ::0
(IPv6),代表所有的本地 IP 地址。例如,假设电脑有两块网卡,各有一个地址,如果想让一个程序同时监听这两个地址,就需要用通配符地址。
public boolean isLinkLocalAddress()
- 检查是否为链路本地地址。IPv4 里定义为地址段
169.254.0.0/16
,Ipv6 里是以 fe80::/64
为前缀的地址。在电脑没联网的时候查看本机 IP,就能看到这种地址。
public boolean isLoopbackAddress()
- 检查是否为环回地址。
public boolean isSiteLocalAddress()
- 检查是否为站点本地地址。站点本地地址这个名词实际上已经过时了,现在叫唯一本地地址。IPv4 中未定义;IPv6 中定义为地址段
fc00::/7
。这些地址用于私有网络,例如企业内部的局域网。
此外还有一些有关多播地址的方法,暂时略过。
JDK 默认同时支持 IPv4 和 IPv6。如果只想使用一种,可以根据情况将 java.net.preferIPv4Stack
或 java.net.preferIPv6Addresses
这两个系统属性之一设为 true
。两个属性的默认值都为 false
。一般来说不需要去惊动它们。
SocketAddress
该类是一个空壳,事实上应用程序使用的是它的唯一子类 InetSocketAddress
,目前还看不出这样设计有什么意义。该类只不过在 InetAddress
的基础上增加了一个端口属性。
NetworkInterface
该类代表网络接口,例如一块网卡。一个网络接口可以绑定一些 IP 地址。具有多个网络接口的主机被称为多宿主主机。下面的代码可打印出所有本机网络接口的信息:
for (NetworkInterface ni : Collections.list(NetworkInterface.getNetworkInterfaces())) {
System.out.println(ni);
for (InterfaceAddress ia : ni.getInterfaceAddresses()) {
System.out.println("\t" + ia);
}
System.out.println();
}
在我的笔记本上运行结果为:
name:lo (Software Loopback Interface 1)
/127.0.0.1/8 [/127.255.255.255]
/0:0:0:0:0:0:0:1/128 [null]
name:net0 (WAN Miniport (SSTP))
name:net1 (WAN Miniport (L2TP))
name:net2 (WAN Miniport (PPTP))
name:ppp0 (WAN Miniport (PPPOE))
name:eth0 (WAN Miniport (IPv6))
name:eth1 (WAN Miniport (Network Monitor))
name:eth2 (WAN Miniport (IP))
name:ppp1 (RAS Async Adapter)
name:net3 (WAN Miniport (IKEv2))
name:net4 (Intel(R) Wireless WiFi Link 4965AGN)
/fe80:0:0:0:288a:2daf:3549:1811%11/64 [null]
name:eth3 (Broadcom NetXtreme 57xx Gigabit Controller)
/10.140.1.133/24 [/10.140.1.255]
/fe80:0:0:0:78c7:e420:1739:f947%12/64 [null]
name:net5 (Teredo Tunneling Pseudo-Interface)
/fe80:0:0:0:e0:0:0:0%13/64 [null]
name:net6 (Bluetooth Device (RFCOMM Protocol TDI))
name:eth4 (Bluetooth Device (Personal Area Network))
name:eth5 (Cisco AnyConnect VPN Virtual Miniport Adapter for Windows x64)
name:net7 (Microsoft ISATAP Adapter)
/fe80:0:0:0:0:5efe:a8c:185%17/128 [null]
name:net8 (Microsoft ISATAP Adapter #2)
name:net9 (Intel(R) Wireless WiFi Link 4965AGN-QoS Packet Scheduler-0000)
name:eth6 (Broadcom NetXtreme 57xx Gigabit Controller-TM NDIS Sample LightWeight Filter-0000)
name:eth7 (Broadcom NetXtreme 57xx Gigabit Controller-QoS Packet Scheduler-0000)
name:eth8 (Broadcom NetXtreme 57xx Gigabit Controller-WFP LightWeight Filter-0000)
name:eth9 (WAN Miniport (Network Monitor)-QoS Packet Scheduler-0000)
name:eth10 (WAN Miniport (IP)-QoS Packet Scheduler-0000)
name:eth11 (WAN Miniport (IPv6)-QoS Packet Scheduler-0000)
name:net10 (Intel(R) Wireless WiFi Link 4965AGN-Native WiFi Filter Driver-0000)
name:net11 (Intel(R) Wireless WiFi Link 4965AGN-TM NDIS Sample LightWeight Filter-0000)
name:net12 (Intel(R) Wireless WiFi Link 4965AGN-WFP LightWeight Filter-0000)