我们平时极少使用Java来直接写网络通信相关的程序,一般都使用Tomcat Web服务或者Netty等框架来帮助我们做,不过呢,要想将技术学到家,我们研究一下基本的网络编程还是非常必要的,这样可以让我们将很多内容融会贯通,更加通透。本文,我们就来练习一下相关内容。
目录
1.感受一下Java的网络编程
2.IP地址的类型
3.使用NetworkInterface类获得网络接口信息
1.感受一下Java的网络编程
在Java的JDK里提供了很多基础的工具来帮助处理网络相关请求。例如我们获取本地或者远程的IP地址可以使用InetAddress来完成,该类是Java中用于描述IP地址的类。它在java.net包中。在Java中分别用Inet4Address和Inet6Address类来描述IPv4和IPv6的地址。这两个类都是InetAddress的子类。由于InetAddress没有public的构造方法,因此,要想创建InetAddress对象,必须得依靠它的四个静态方法。InetAddress可以通过getLocalHost方法得到本机的InetAddress对象,也可以通过getByName、getAllByName和getByAddress得到远程主机的InetAddress对象。
getLocalHost方法
使用getLocalHost可以得到描述本机IP的InetAddress对象。这个方法的定义如下:
public static InetAddress getLocalHost() throws UnknownHostException
这个方法抛出了一个UnknownHostException异常,因此,必须在调用这个方法的程序中捕捉或抛出这个异常。下面的代码演示了如何使用getLocalHost来得到本机的IP和计算机名。
public static void test1() throws UnknownHostException {
InetAddress inetAddress = InetAddress.getLocalHost();
System.out.println(inetAddress);
}
此时我们得到这样的一个结果:
***MacBook-Pro.local/127.0.0.1
127.0.0.1 这个就是我们本地的测试地址。
getByName方法
如果我们想获取远端的服务地址该如何做呢?可以使用getByName()来完成,这个方法是InetAddress类最常用的方法。它可以通过指定域名从DNS中得到相应的IP地址。getByName一个String类型参数,可以通过这个参数指定远程主机的域名,它的定义如下:
public static InetAddress getByName(String host) throws UnknownHostException
如果host所指的域名对应多个IP,getByName返回第一个IP。如果本机名已知,可以使用getByName方法来代替getLocalHost。当host的值是localhost时,返回的IP一般是127.0.0.1。如果host是不存在的域名,getByName将抛出UnknownHostException异常,如果host是IP地址,无论这个IP地址是否存在,getByName方法都会返回这个IP地址(因此getByName并不验证IP地址的正确性)。使用getByName的方法如下:
public static void test2(String[] args) throws UnknownHostException {
String host = args[0];
InetAddress inetAddress = InetAddress.getByName(host);
System.out.println(inetAddress);
}
此时我们需要给程序设置一下入参,在idea中的操作如下所示:
此时执行的结果如下:
www.baidu.com/39.156.66.18
这样我们就可以非常方便的获取各类服务器的Id地址了。
我们将这个IP地址输入到浏览器里,也可以打开百度的:
不过有些网站为了安全是不允许直接输入IP地址的。
getAllByName方法
使用getAllByName方法可以从DNS上得到域名对应的所有的IP。这个方法返回一个InetAddress类型的数组。我们还是用上面的百度地址来测试一下:
public static void test3(String[] args) throws UnknownHostException {
String host = args[0];
InetAddress inetAddress[] = InetAddress.getAllByName(host);
for(InetAddress address:inetAddress){
System.out.println(address);
}
}
得到的结果如下:
www.baidu.com/39.156.66.14
www.baidu.com/39.156.66.18
www.baidu.com/2409:8c00:6c21:1051:0:ff:b0af:279a
www.baidu.com/2409:8c00:6c21:104f:0:ff:b03f:3ae
将前面两个结果进行比较,可以得出一个结论,getByName方法返回的IP地址就是getAllByName方法返回的第一个IP地址。事实上,getByName的确是这样实现的,getByName的实现代码如下:
public static InetAddress getByName(String host)
throws UnknownHostException {
return InetAddress.getAllByName(host)[0];
}
getByAddress方法
这个方法必须通过IP地址来创建InetAddress对象,而且IP地址必须是byte数组形式。这个我们就不演示了。
通过前面的例子,可以看到,如果我们通过JDK提供的方法可以很方便的获取机器信息的。
大家能想到这个在哪里有用吗?就是如果要为运维设计一套机器监控系统,那这时候自动获取设备的信息就非常必要了。
2.IP地址的类型
IP地址分为普通地址和特殊地址,我们一个机器上往往也会有多种类型的多个地址。我们可以利用InetAddress类提供的方法来确定一个IP地址是否是一个特殊的IP地址。常见的方法有:
(1)isAnyLocalAddress方法
当IP地址是通配符地址时返回true,否则返回false。这个通配符地址对于拥有多个网络接口(如两块网卡)的计算机非常拥有。使用通配符地址可以允许在服务器主机接受来自任何网络接口的客户端连接。IPv4的通配符地址是0.0.0.0。IPv6的通配符地址是0:0:0:0:0:0:0:0,也可以简写成::。
(2)isLoopbackAddress方法
当IP地址是loopback地址时返回true,否则返回false。loopback地址就是代表本机的IP地址。IPv4的loopback地址的范围是127.0.0.0 ~ 127.255.255.255,也就是说,只要第一个字节是127,就是lookback地址。如127.1.2.3、127.0.200.200都是loopback地址。IPv6的loopback地址是0:0:0:0:0:0:0:1,也可以简写成::1。我们可以使用ping命令来测试lookback地址。如下面的命令行所示:
ping 127.200.200.200
运行结果:
Reply from 127.0.0.1: bytes=32 time<1ms TTL=128
Reply from 127.0.0.1: bytes=32 time<1ms TTL=128
Reply from 127.0.0.1: bytes=32 time<1ms TTL=128
Reply from 127.0.0.1: bytes=32 time<1ms TTL=128Ping statistics for 127.200.200.200:
Packets: Sent = 4, Received = 4, Lost = 0 (0% loss),
Approximate round trip times in milli-seconds:
Minimum = 0ms, Maximum = 0ms, Average = 0ms
虽然127.255.255.255也是loopback地址,但127.255.255.255在Windows下是无法ping通的。这是因为127.255.255.255是广播地址,在Windows下对发给广播地址的请求不做任何响应,而在其他操作系统上根据设置的不同,可能会得到不同的结果。
(3)isLinkLocalAddress方法
当IP地址是本地连接地址(LinkLocalAddress)时返回true,否则返回false。IPv4的本地连接地址的范围是169.254.0.0 ~ 169.254.255.255。IPv6的本地连接地址的前12位是FE8,其他的位可以是任意取值,如FE88::、FE80::ABCD::都是本地连接地址。
(4)isSiteLocalAddress方法
当IP地址是地区本地地址(SiteLocalAddress)时返回true,否则返回false。IPv4的地址本地地址分为三段:10.0.0.0 ~ 10.255.255.255、172.16.0.0 ~ 172.31.255.255、192.168.0.0 ~ 192.168.255.255。IPv6的地区本地地址的前12位是FEC,其他的位可以是任意取值,如FED0::、FEF1::都是地区本地地址。
(5)isMulticastAddress方法
当IP地址是广播地址(MulticastAddress)时返回true,否则返回false。通过广播地址可以向网络中的所有计算机发送信息,而不是只向一台特定的计算机发送信息。IPv4的广播地址的范围是224.0.0.0 ~ 239.255.255.255。IPv6的广播地址第一个字节是FF,其他的字节可以是任意值。关于广播地址的详细内容将在以后的章节中讨论。
(6)isMCGlobal方法
当IP地址是全球范围的广播地址时返回true,否则返回false。全球范围的广播地址可以向Internet中的所有的计算机发送信息。IPv4的广播地址除了224.0.0.0和第一个字节是239的IP地址都是全球范围的广播地址。IPv6的全球范围的广播地址中第一个字节是FF,第二个字节的范围是0E ~ FE,其他的字节可以是任意值,如FFBE::、FF0E::都是全球范围的广播地址。
(7)isMCLinkLocal方法
当IP地址是子网广播地址时返回true,否则返回false。使用子网的广播地址只能向子网内的计算机发送信息。IPv4的子网广播地址的范围是224.0.0.0 ~ 224.0.0.255。IPv6的子网广播地址的第一个字节是FF,第二个字节的范围是02 ~ F2,其他的字节可以是任意值,如FFB2::、FF02:ABCD::都是子网广播地址。
(8)isMCNodeLocal方法
当IP地址是本地接口广播地址时返回true,否则返回false。本地接口广播地址不能将广播信息发送到产生广播信息的网络接口,即使是同一台计算机的另一个网络接口也不行。所有的IPv4广播地址都不是本地接口广播地址。IPv6的本地接口广播地址的第一个字节是FF,第二个节字的范围是01 ~ F1,其他的字节可以是任意值,如FFB1::、FF01:A123::都是本地接口广播地址。
(9)isMCOrgLocal方法
当IP地址是组织范围的广播地址时返回ture,否则返回false。使用组织范围广播地址可以向公司或企业内部的所有的计算机发送广播信息。IPv4的组织范围广播地址的第一个字节是239,第二个字节不小于192,第三个字节不大于195,如239.193.100.200、239.192.195.0都是组织范围广播地址。IPv6的组织范围广播地址的第一个字节是FF,第二个字节的范围是08 ~ F8,其他的字节可以是任意值,如FF08::、FF48::都是组织范围的广播地址。
(10)isMCSiteLocal方法
当IP地址是站点范围的广播地址时返回true,否则返回false。使用站点范围的广播地址,可以向站点范围内的计算机发送广播信息。IPv4的站点范围广播地址的范围是239.255.0.0 ~ 239.255.255.255,如239.255.1.1、239.255.0.0都是站点范围的广播地址。IPv6的站点范围广播地址的第一个字节是FF,第二个字节的范围是05 ~ F5,其他的字节可以是任意值,如FF05::、FF45::都是站点范围的广播地址。
3.使用NetworkInterface类获得网络接口信息
我们电脑的网络接口往往有很多种类型,例如本地的、wifi的、各类虚拟的,甚至蓝牙的。我们该如何准确找到我们想要的接口类型呢?Java提供了一个NetworkInterface类,通过这个类可以获得本机所有的物理网络接口和虚拟机等软件利用本机的物理网络接口创建的逻辑网络接口的信息。
NetworkInerface类和InetAddress一样,也没有public的构造方法。因此,必须通过它的两个静态方法来创建NetworkInterface对象。可以使用两种方法来创建NetworkInterface对象:网络接口名(getByName方法)和IP地址(getByInetAddress方法)。
1. getByName方法
这个方法可以通过网络接口名来创建NetworkInterface对象。这个网络接口名并不是计算机名,而是用于标识物理或逻辑网络接口的名字,一般是由操作系统设置的。网络接口名在大多数操作系统上(包括Windows、Linux和Unix)是以eth开头,后面是网络接口的索引号,从0开始。如本机安了三块网卡,那么网络接口名就依次是eth0、eth1和eth2。NetworkInterface对象的toString方法可以返回网络接口的名称、显示名和这个网络接口上绑字的所有IP地址等信息。当网络接口名不存在时,getByName返回null。
public static void test4(String[] args) throws Exception {
NetworkInterface networkInterface = NetworkInterface.getByName(args[0]);
System.out.println(networkInterface == null);
}
然后我们使用命令(mac/linux 下使用ifconfig、window下使用ipconfig),找一个已经存在的例如en0,再找一个不存在的例如pp0,来测试就可以判断我们电脑上是否存在指定名称的网络接口名。
getByInetAddress方法
除了可以使用网络接口名来得到网络接口的信息,还可以利用getByInetAddress方法来确定一个IP地址属于哪一个网络接口。由于getByInetAddress方法必须使用一个InetAddress对象封装的IP地址来作为参数,因此,在使用getByInetAddress方法之前,必须先创建一个InetAddress对象。但要注意不能使用远程的IP的域名来创建InetAddress对象,否则getByInetAddress将返回null。
public static void test5(String[] args) throws Exception {
InetAddress local=InetAddress.getByName(args[0]);
NetworkInterface networkInterface = NetworkInterface.getByInetAddress(local);
System.out.println(networkInterface == null);
}
此时我们在main的入参里输入"127.0.0.1" 或者其他地址,就可以检测是否存在该地址。
同样,我们也可以使用getNetworkInterfaces()参数来获得本机的所有网络接口信息。代码如下:
public static void test6(String[] args) throws Exception {
InetAddress local=InetAddress.getByName(args[0]);
Enumeration<NetworkInterface> networkInterface = NetworkInterface.getNetworkInterfaces();
while(networkInterface.hasMoreElements()){
System.out.println(networkInterface.nextElement());
}
}
本文我们介绍了网络编程的基本操作,这些代码本身比较简单 ,请大家自己也手敲一下,感受一下网络编程的魅力。
参考:
本文主要参考李宁(蒙娜丽宁)老师的文章等材料整理而成。