Java网络编程之Internet地址

520 阅读3分钟

1. 定义

连接到Internet的设备称为节点(node)。计算机节点称为主机(host)。每个节点或主机都由至少一个唯一的数来标识,这称为Internet地址或IP地址。

由于IP地址太长,Internet的设计者发明了域名系统DNS。将人们可以记忆的主机名与计算机可以记忆的IP地址关联在一起。

2. InetAddress类

java.net.InetAddres s类是Java对IP地址(包括IPv4和IPv6)的高层表示。大多数其他网络类都要用到这个类,包括Socket、ServerSocket、URL、DatagramSocket、 DatagramPacket等。一般地讲,它包括一个主机名和一个IP地址。

2.1 创建新的InetAddress对象

InetAddress类没有公共构造函数。实际上,InetAddress有一些静态工厂方法,可以连接到DNS服务器来解析主机名。最常用的是InetAddress.getByName()。

显示www.baidu.com地址的程序。

package com.zyz.internet;

import java.net.InetAddress;
import java.net.UnknownHostException;

/**
 * 显示www.baidu.com地址的程序
 */
public class BaiduByName {

    public static void main(String[] args) {
        try {
            InetAddress address = InetAddress.getByName("www.baidu.com");
            System.out.println(address);
        } catch (UnknownHostException e) {
            e.printStackTrace();
        }
    }

}

可以用InetAddress.getLocalHost()方法查找本机的信息。

查找本地机器的地址。

package com.zyz.internet;

import java.net.InetAddress;
import java.net.UnknownHostException;

/**
 * 查找本地机器的地址
 */
public class MyAddress {

    public static void main(String[] args) {
        try {
            InetAddress address = InetAddress.getLocalHost();
            System.out.println(address);
        } catch (UnknownHostException e) {
            System.out.println("Could not find this computer's address");
        }
    }

}

输出如下:

zhangyunzhengdeMacBook-Pro.local/127.0.0.1

2.2 缓存

由于DNS查找的开销可能相当大(如果请求需要经过多个中间服务器,或者尝试解析一个不可达的主机,这大约需要几秒的时间),所以InetAddress类会缓存查找的结果。一旦得到一个给定主机的地址,就不会再次查找,即使你为同一个主机创建一个新的 InetAddress对象,也不会再次查找地址。只要在程序运行期间IP地址没有改变,这就没有问题。

可以通过该方法进行设置。

java.security.Security.setProperty("networkaddress.cache.ttl", "0");

2.3 按IP地址查找

主机名要比IP地址稳定得多。有些服务多年以来一直使用同一个主机名,但IP地址更换了很多次。如果要在使用主机名(如www.oreilly.com)或使用IP地址(如208.201.239.37)之间做出选择,一定要选择主机名。只有当主机名不可用时才使用IP地址。

2.4 获取方法

InetAddress包含4个获取方法,可以将主机名作为字符串返回,将IP地址返回为字符串和字节数组:

public String getHostName();
public String getCanonicalHostName();
public byte[] getAddress()
public String getHostAddress()

没有对应的setHostName()和setAddress()方法,这说明java.net之外的包无法在后台改变InetAddress对象的字段。这使得InetAddress不可变,因此是线程安全的。

给定地址,找出主机名。

package com.zyz.internet;

import java.net.InetAddress;
import java.net.UnknownHostException;

public class ReverseTest {

    public static void main(String[] args) throws UnknownHostException {
        InetAddress inetAddress = InetAddress.getByName("127.0.0.1");
        System.out.println(inetAddress.getCanonicalHostName());
    }

}

输出:

localhost

getHostAddress()方法返回一个字符串,其中包含点分四段格式的IP地址。

package com.zyz.internet;

import java.net.InetAddress;
import java.net.UnknownHostException;

/**
 * 找到本机对应的IP地址
 */
public class MyIPAddress {

    public static void main(String[] args) {
        InetAddress inetAddress = null;
        try {
            inetAddress = InetAddress.getLocalHost();
        } catch (UnknownHostException e) {
            e.printStackTrace();
        }
        System.out.println("My address is " + inetAddress.getHostAddress());
    }

}

输出:

My address is 127.0.0.1

与C不同,Java没有无符号字节这种基本数据类型。值大于127的字节会当作负数。因此,如果要对getAddress()返回的字节做任何处理,需要将字节提升为int,并做适当的调整。下面给出一种做法∶

int unsignedByte = signedByte < 0 ? signedByte + 256 : signedByte;

这里,signedByte可能为正也可能为负。条件操作符?测试signedByte是否为负。如果为负,则为signedByte加上256使其成为正数。否则保持不变。signedByte会在完成加法操作之前自动提升为int,所以不存在这种回绕(wraparound)问题。

确定IP地址是IPv4还是IPv6

package com.zyz.internet;

import java.net.InetAddress;

public class AddressTests {

    public static int getVersion(InetAddress inetAddress){
        byte[] address = inetAddress.getAddress();
        if (address.length == 4) return 4;
        else if(address.length == 16) return 6;
        else return -1;
    }
    
}

2.5 测试可达性

InetAddress类有两个isReachable()方法,可以测试一个特定节点对当前主机是否可达(也就是说,能否建立一个网络连接)。连接可能由于很多原因而阻塞,包括防火墙、代理服务器、行为失常的路由器和断开的线缆等,或者只是因为试图连接时远程主机没有开机。

public boolean isReachable(int timeout) throws IOException
public boolean isReachable(NetworkInterface netif, int ttl,
                               int timeout) throws IOException

这些方法尝试使用traceroute(更确切地讲,就是ICMPecho请求)查看指定地址是否可达。如果主机在timeout毫秒内响应,则方法返回true;否则返回false。如果出现网络错误则抛出IOException异常。第二个方法还允许指定从哪个本地网络接口建立连接,以及"生存时间"(连接被丢弃前尝试的最大网络跳数)。

2.6 eauals方法

InetAddress重写了Object类的equals方法,如果两个InetAddress对象有相同的IP地址,只有此时才会与该InetAddress对象相等。

    public boolean equals(Object obj) {
        return (obj != null) && (obj instanceof Inet4Address) &&
            (((InetAddress)obj).holder().getAddress() == holder().getAddress());
    }

2.7 Inet4Address和Inet6Address

Java使用了两个类Inet4Address和Inet6Address,来区分IPv4地址和IPv6地址:

public final class Inet4Address extends InetAddress
public final class Inet6Address extends InetAddress

2.8 NetworkInterface类

NetworkInterface类表示一个本地IP地址。这可以是一个物理接口,如额外的以太网卡(常见于防火墙和路由器),也可以是一个虚拟接口,与机器的其他IP地址绑定到同一个物理硬件。NetworkInterface类提供了一些方法可以枚举所有本地地址(而不考虑接口),并由它们创建InetAddress对象,然后这些InetAddress对象可用于创建socket、服务器socket等。

2.8.1 工厂方法

由于NetworkInterface对象表示物理硬件和虚拟地址,所以人不能任意构造.与Inet Address类一样,有一些静态工厂方法可以返回与某个网络接口关联的 NetworkInterface对象。可以通过IP地址、名字或枚举来请求一个NetworkInterface。

getByName()方法返回一个NetworkInterface对象,表示有指定名字的网络接口。如果没有这样一个接口,就返回null。如果在查找相关网络接口时底层网络栈遇到问题,会抛出一个SocketException异常,不过这种情况不太可能发生。

public static NetworkInterface getByName(String name) throws SocketException

getByInetAddress()方法返回一个NetworkInterface对象,表示与指定IP地址绑定的网络接口。如果本地主机上没有网络接口与这个IP地址绑定,就返回null。如果发生错误,就抛出一个SocketException异常。

public static NetworkInterface getByInetAddress(InetAddress addr) throws SocketException

列出所有网络接口的程序

package com.zyz.internet;

import java.net.NetworkInterface;
import java.net.SocketException;
import java.util.Enumeration;

/**
 * 列出所有网络接口的程序
 */
public class InterfaceLister {

    public static void main(String[] args) throws SocketException {
        Enumeration<NetworkInterface> networkInterfaces = NetworkInterface.getNetworkInterfaces();
        while (networkInterfaces.hasMoreElements()){
            NetworkInterface networkInterface = networkInterfaces.nextElement();
            System.out.println(networkInterface);
        }
    }

}

输出:

name:utun3 (utun3)
name:utun2 (utun2)
name:utun1 (utun1)
name:utun0 (utun0)
name:llw0 (llw0)
name:awdl0 (awdl0)
name:en5 (en5)
name:en7 (en7)
name:en0 (en0)
name:lo0 (lo0)

2.8.2 获取方法

有了NetworkInterface对象,就可以查询其IP地址和名字。

返回该网络接口的所有IP地址

public Enumeration<InetAddress> getInetAddresses()
package com.zyz.internet;

import java.net.InetAddress;
import java.net.NetworkInterface;
import java.net.SocketException;
import java.util.Enumeration;

public class GetNetworkIP {

    public static void main(String[] args) throws SocketException {
        NetworkInterface en0 = NetworkInterface.getByName("en0");
        Enumeration<InetAddress> inetAddresses = en0.getInetAddresses();
        while (inetAddresses.hasMoreElements()){
            System.out.println(inetAddresses.nextElement());
        }
    }
}

返回某个特定NetworkInterface对象的名,如eth0。

public String getName()

返回特定NetworkInterface的一个更友好的名字。

public String getDisplayName()