一 网络设备接口信息
Java中提供了NetworkInterface供应用访问设备IP地址信息,这是一个被final修饰的类,足见其重要程度:
/**
* This class represents a Network Interface made up of a name,
* and a list of IP addresses assigned to this interface.
* It is used to identify the local interface on which a multicast group
* is joined.
*
* Interfaces are normally known by names such as "le0".
*
* @since 1.4
*/
public final class NetworkInterface {
...
NetworkInterface() {
}
NetworkInterface(String name, int index, InetAddress[] addrs) {
this.name = name;
this.index = index;
this.addrs = addrs;
}
...
}
NetworkInterface的构造函数都是非public的,那么应用如果需要获取此类对象,可以通过下例中的静态方法获取当前设备中的所有网络接口对象:
public class NetworkInterfaceTest {
public static void main(String[] args) throws Exception {
Enumeration<NetworkInterface> nifs = NetworkInterface.getNetworkInterfaces();
}
}
NetworkInterface提供了诸多访问网络设备信息,下面列举一些常见的API:
API | 功能 | 说明 |
---|---|---|
getName() | 网络设备名称 | 一般网络设备名称是由操作系统设定的,如网卡名称多以eth开头,表示Ethernet以太网的意思,如果系统中存在多个网卡,那么设备名会多出序号后缀,eth0、eth1等,linux、macos可通过ifconfig查看到,windows可通过ipconfig查看 |
getDisplayName() | 包含设备厂商和设备具体型号的完整名称 | 是getName返回值的一个扩充版本 |
getIndex() | 返回设备索引 | 不同的操作系统返回值不同,未知时返回-1 |
isUp() | 此设备接口是否启用 | |
isLoopback() | 此设备接口是否为回环接口,见附录 一 回环网卡 | 常见于web服务器如tomcat,即使本机没有安装网卡,也可通过localhost(127.0.0.1)来访问tomcat服务 |
getMTU() | 获取此设备接口支持的最大的数据包长度,见[附录 二 MTU(#fl2)] | 这里需要注意的是不建议对MTU进行修改,因为MTU太小,则传输包变多,传输速度变慢,反之虽然传输速度会变快,但是延迟会增高(接收端处理大数据包变慢导致),另外如果MTU值为-1,说明此接口可能被禁用了 |
isVirtual() | 返回此接口是否为虚拟接口 |
二 网络设备子接口信息
子接口指的是基于现有物理网卡通过软件模拟的虚拟网络接口,目前Linux操作系统支持网络设备子接口,在Java中通过NetworkInterface对象的getSubInterfaces()方法获取子接口信息集合:
public class NetworkInterfaceTest {
public static void main(String[] args) throws Exception {
Enumeration<NetworkInterface> nifs = NetworkInterface.getNetworkInterfaces();
for (NetworkInterface ni : nifs) {
Enumeration<NetworkInterface> sis = ni.getSubInterfaces();
}
}
}
实际上可以子接口信息依然是NetworkInterface类型,差异点在于getParent方法:
/**
* Returns the parent NetworkInterface of this interface if this is
* a subinterface, or {@code null} if it is a physical
* (non virtual) interface or has no parent.
*
* @return The {@code NetworkInterface} this interface is attached to.
* @since 1.6
*/
public NetworkInterface getParent() {
return parent;
}
三 地址信息
做网络编程,有两个地址信息是绕不过去的,一个是硬件地址,一个是IP地址。下面分两个小节分别介绍,不会太过详细,读者可以从其他媒体资源搜索相关信息。
3.1 硬件地址
所谓硬件地址是指硬件设备的唯一标识号,就是常见的MAC地址(Media Access Control),这个标识号是RA(RA是IEEE的注册管理机构)分配的,采用16进制共计48位长度的标识号来描述,其中RA只会分配高3位,低3位由厂商自行分配。
操作系统都提供查看硬件地址的方法,Windows下可通过ipconfig指令查询,MacOS和Linux可通过ifconfig指令查询,当然并不限于这些手段。
在Java中访问硬件地址可通过NetworkInterface对象的gethardwareAddress方法实现(我是在MacOS上做的测试):
public class NetworkInterfaceTest {
public static void main(String[] args) throws Exception {
Enumeration<NetworkInterface> nis = NetworkInterface.getNetworkInterfaces();
while (nis.hasMoreElements()) {
NetworkInterface ni = nis.nextElement();
System.out.println(ni.getName() + ":" + ni.getHardwareAddress());
}
}
}
输出结果:
en5:[B@5e481248
utun0:null
awdl0:[B@66d3c617
vboxnet0:[B@63947c6b
en0:[B@2b193f2d
lo0:null
注意,getHardwareAddress方法返回的是byte[],那么想看到16进制的表示的标识号,需要对返回值进行处理:
public class NetworkInterfaceTest {
public static void main(String[] args) throws Exception {
Enumeration<NetworkInterface> nis = NetworkInterface.getNetworkInterfaces();
while (nis.hasMoreElements()) {
NetworkInterface ni = nis.nextElement();
System.out.println(ni.getName() + ":" + bytes2HexString(ni.getHardwareAddress()));
}
}
private static String bytes2HexString(byte[] bytes) {
if (bytes == null) {
return null;
}
StringBuilder builder = new StringBuilder();
for (int i = 0; i < bytes.length; i++) {
String hex = Integer.toHexString(bytes[i] & 0xFF);
if (hex.length() == 1) {
hex = '0' + hex;
}
builder.append(hex.toUpperCase());
if (i < bytes.length - 1) {
builder.append("-");
}
}
return builder.toString();
}
}
输出结果:
utun0:null
awdl0:3E-F0-43-8B-3C-97
vboxnet0:0A-00-27-00-00-00
en0:38-F9-D3-72-E8-47
lo0:null
通过终端查硬件地址核对下上面的API返回是否正确:
awdl0: flags=8943<UP,BROADCAST,RUNNING,PROMISC,SIMPLEX,MULTICAST> mtu 1484
ether 3e:f0:43:8b:3c:97
inet6 fe80::3cf0:43ff:fe8b:3c97%awdl0 prefixlen 64 scopeid 0xc
nd6 options=201<PERFORMNUD,DAD>
media: autoselect
status: active
可以看到ether行的地址信息和API返回结果一致。
3.2 IP地址
IP是Internet Protocol(网际互连协议)的缩写,是TCP/IP体系中的网络层协议。设计IP的目的是提高网络的可扩展性:一是解决互联网问题,实现大规模、异构网络的互联互通;二是分割顶层网络应用和底层网络技术之间的耦合关系,以利于两者的独立发展。根据端到端的设计原则,IP只为主机提供一种无连接、不可靠的、尽力而为的数据报传输服务。(叶阿勇.计算机网络实验与学习指导.北京:电子工业出版社,2017:69-70)
IP是每一个网络设备在此网络中的唯一标识。IP地址分为IPv4和IPv6两类,现阶段IPv6已经逐步普及开来,但是IPv4依然是主流。
和硬件地址一样的,各个操作系统都提供访问IP信息的手段,在Java中通过InetAddress类用于描述IP地址信息。InetAddress和NetworkInterface一样都是不提供构造函数的,NetworkInterface对象的getInetAddress方法可以返回InetAddress对象,因为IP地址分为IPv4和IPv6,因此InetAddress有两个派生类Inet4Address和Inet6Address分别对应不同的IP类型。
因此我们打开InetAddress的源码会发现它的结构非常简单:
public class InetAddress implements java.io.Serializable {
static final int IPv4 = 1;
static final int IPv6 = 2;
static transient boolean preferIPv6Address = false;
static class InetAddressHolder {
......
}
}
class InetAddressImplFactory {
static InetAddressImpl create() {
return InetAddress.loadImpl(isIPv6Supported() ?
"Inet6AddressImpl" : "Inet4AddressImpl");
}
static native boolean isIPv6Supported();
}
InetAddress已经为IPv4和IPv6预定义了常量值,并且通过InetAddressImplFactory创建不同类型的派生类实例对象,通过InetAddress提供的API可以便捷的访问IP地址相关信息:
方法名 | 应用场景 | 特殊说明 |
---|---|---|
getCanonicalHostName | 获取IP地址的全限定名 | 全限定指的是主机名+路径 |
getHostName | 获取主机名 | |
getHostAddress | 获取IP地址 | 此API返回的是IP地址字符串 |
getAddress | 获取IP地址 | 此API返回的是byte[]描述的IP地址信息 |
以上四个API的具体区别简单测试一下就知道了,这里不再赘述,需要补充的是除了通过NetworkInterface对象来获取InetAddress对象外,InetAddress 本身还提供了一个名为getLocalHost的静态方法,意指返回本机IP,但如果本机存在多个IP,则返回第一个IP信息(数组第一个元素)。
如果想获取本机的全部IP地址信息,则需要通过getAllByName方法实现,这个API要求传入一个String类型的参数,参数值可以是IP地址、计算机名,甚至是域名。
最后一个是回环IP地址信息的获取,通过getLoopbackAddress方式实现,此方法返回的依然是类型为InetAddress的对象,再调用此对象的getAddress方法即可访问到回环地址信息了。
如果我们知道了IP信息,能否反过来获取对应的InetAddress对象?答案是可以的,这里仅列出相关API:
API | 应用场景 | 特殊说明 |
---|---|---|
static InetAddress getByAddress(byte[] addr) | 根据IP地址信息获取对应的InetAddress对象 | |
static InetAddress getByAddress(String host, byte[] addr) | 根据主机名+IP地址信息获取对应的InetAddress对象 | 这里的host参数无任何逻辑作用 |
四 网络接口地址
描述网络接口地址的类是InterfaceAddress,这个类在JDK文档中是这样描述的:
此类表示网络接口地址。简言之,对于 IPv4 地址,是指 IP 地址、子网掩码和广播地址。对于 IPv6 地址,是指 IP 地址和网络前缀长度。
通过NetworkInterface对象的getInterfaceAddresses方法可返回InterfaceAddress对象集合,InterfaceAddress对象提供的核心API一共就三个:
API | 应用场景 | 特殊说明 |
---|---|---|
InetAddress getAddress() | 返回此地址的InetAddress | |
InetAddress getBroadcast() | 返回此 InterfaceAddress 广播地址的 InetAddress | 只有 IPv4 网络具有广播地址,因此对于 IPv6 网络将返回 null |
short getNetworkPrefixLength() | 返回此地址的网络前缀长度 | 网络前缀长度在 IPv4 地址上下文中也称为子网掩码。典型的 IPv4 值是 8 (255.0.0.0)、16 (255.255.0.0) 或 24 (255.255.255.0)。典型的 IPv6 值是 128 (::1/128) 或 10 (fe80::203:baff:fe27:1243/10) |
五 其他设备信息
除前文介绍的API,NetworkInterface还提供了一些比较有用的用于访问API,如判断此设备是否为点对点设备:
public boolean isPointToPoint();
点对点设备指的是通过拨号或者专用线路实现的点对点通信,是网桥、路由器等设备间最简单的通信连接解决方案。
判断设备是否支持多播:
public boolean supportsMulticast();
对应于多播的是单播、广播,简单了说单路广播就是一对一的通信过程,比如两个人聊微信,广播是一对多通信,这个多的范围极大,指网络中的所有主机;多播和广播一样,都是一对多的,但是可以对支持多播类型的IP地址进行分组,仅和指定分组内的主机进行通信,因此也称组播。
NetworkInterface中还有一些文中尚未介绍的API,受篇幅影响,读者可自行查阅源码,或者从其他媒体途径获取相关信息,总之NetworkInterface是一个设备信息的集大成者,知道它提供的常用API的应用场景就可以了。
附录
一 回环网卡
回环网卡(Loopback adaptor),是一种特殊的网络接口,不与任何实际设备连接,而是完全由软件实现。与回环地址(127.0.0.0/8 或::1/128)不同,回环网卡对系统“显示”为一块硬件。任何发送到该网卡上的数据都将立刻被同一网卡接收到。例子有Linux下的 lo 接口和Windows下的 Microsoft Loopback Interface 网卡。
百度知道
二 MTU
最大传输单元(Maximum Transmission Unit,MTU)用来通知对方所能接受数据服务单元的最大尺寸,说明发送方能够接受的有效载荷大小。
是包或帧的最大长度,一般以字节记。如果MTU过大,在碰到路由器时会被拒绝转发,因为它不能处理过大的包。如果太小,因为协议一定要在包(或帧)上加上包头,那实际传送的数据量就会过小,这样也划不来。大部分操作系统会提供给用户一个默认值,该值一般对用户是比较合适的。
百度知道