【问题标题】:Data type for storing IP addresses用于存储 IP 地址的数据类型
【发布时间】:2011-12-30 08:45:36
【问题描述】:

在 Java 中是否有用于存储 IP 地址的特定数据类型?我有一个特定的功能要求:

  1. 给定一个 IP 范围和一个 IP,如果在其中则返回 true,否则返回 false。 例如:范围 10.10.10.1-10.10.11.255 和 IP 10.10.10.192 应该返回 true。

我知道java.net.inetaddress,但我相信它没有给我这个功能。有什么想法吗?

【问题讨论】:

  • 你能把它变成字符串吗?

标签: java ip


【解决方案1】:

我会使用java.net.InetAddress 或其子类之一,并编写一个自定义比较器和一个范围类:

  • 使用显式类型 InetAddress 而不是仅使用 long 更容易维护和调试:您的调试器实际上将显示“10.10.10.1”而不是“168430081”
  • IPv6 要么不存在问题,要么可以轻松实施。

InetAddress 的一个缺点是getByName 会导致 DNS 访问。如果您想避免对 DNS 的惩罚,您可能需要查看 Guavacom.google.common.net.InetAddresses 助手类。

public enum InetAddressComparator implements Comparator<InetAddress> {

  INSTANCE;

  public int compare(InetAddress first, InetAddress second) {
    byte[] firstBytes = first.getAddress();
    byte[] secondBytes = second.getAddress();
    if (firstBytes.length != secondBytes.length) {
      throw new IllegalArgumentException("Cannot compare IPv4 and IPv6 addresses");
    }
    // getAddress returns bytes in network byte order:
    // the least significant byte is at the last index
    for (int i = firstBytes.length - 1; i >= 0; i--) {
      // translate the byte to an int with only the last 8 bits set,
      // effectively treating it as unsigned
      int a = firstBytes[i] & 0xff;
      int b = secondBytes[i] & 0xff;
      if (a < b) {
        return -1;
      } else if (a > b) {
        return 1;
      }
    }
    return 0;
  }

}

public class Range<T> {

  private T lower;
  private T upper;
  private Comparator<T> comparator;

  public Range(T lower, T upper, Comparator<T> comparator) {
    if (comparator.compare(lower, upper) <= 0) {
      this.lower = lower;
      this.upper = upper;
    } else {
      this.lower = upper;
      this.upper = lower;
    }
    this.comparator = comparator;
  }

  public boolean contains(T element) {
    return comparator.compare(lower, element) <= 0 &&
      comparator.compare(upper, element) >= 0;
  }

}

public class Main {
  public static void main(String[] args) throws Exception {
    InetAddress start = InetAddress.getByName("10.10.10.1");
    InetAddress end = InetAddress.getByName("10.10.11.255");
    InetAddress test = InetAddress.getByName("10.10.10.192");
    assert InetAddressComparator.INSTANCE.compare(start, test) == -1;
    assert InetAddressComparator.INSTANCE.compare(end, test) == 1;
    assert InetAddressComparator.INSTANCE.compare(test, test) == 0;
    assert new Range<InetAddress>(start, end, InetAddressComparator.INSTANCE)
      .contains(test);
  }
}

【讨论】:

  • 这个答案和 Ray Tayek 的答案看起来都是不错的解决方案。我想知道现在是否有任何新的想法,因为答案已经过去了 2 年。 FWIW 看起来 Java 7 没有对 InetAddress.getByName() 中的 IP 地址进行 dns 检查
【解决方案2】:

IP (IPv4) 是 32 位(与 Java 中的 int 大小相同)。由于您想使用无符号整数进行比较(如果您需要支持 128.0.0.0 以上的 IP),您需要使用 long。

10.10.10.1 is: (10 << 24) + (10 << 16) + (10 << 8) + 1 = 168430081
10.10.11.255 is: (10 << 24) + (10 << 16) + (11 << 8) + 255 = 168430591

10.10.10.192 is: (10 << 24) + (10 << 16) + (10 << 8) + 192 = 168430272

由于168430081 &lt;= 168430272 &amp;&amp; 168430272 &lt;= 168430591,(换句话说,168430272 介于 168430081 和 168430272 之间)您的 IP 在范围内。

【讨论】:

  • 如果你想进行跨越 127.255.255.255 和 128.0.0.0 边界的范围比较,你只需要无符号整数,在这种情况下你可以翻转编码中的最高位。
  • 您总是可以手动检测它,但我认为这更容易,并且除了 32 个浪费的位之外,使用 long 并没有发现任何问题,除非他将大量这些位存储在一个内存中的数组或其他东西。
  • 恕我直言,您应该更新您的示例以使用long,因为目前您的示例仅使用我建议的int。 ;)
  • 在这个例子中你将如何转换回字符串?
  • @c-qjv0xfi:您可以将其转换回:StringBuilder sb = new StringBuilder(); int a = (ipv4 &gt;&gt; 24) &amp; 127; sb.append(a); a = (ipv4 &gt;&gt; 16) &amp; 255; sb.append("." + a); a = (ipv4 &gt;&gt; 8) &amp; 255; sb.append("." + a); a = ipv4 &amp; 255; sb.append("." + a); return sb.toString();
【解决方案3】:

对于 IPv4,我会使用 int 值。将IP地址转换成数字,就可以进行数字运算了。

如果您想要进行范围从 127.x.x.x(环回局域网)到 128.x.x.x 的比较,这不太可能,但如果您这样做,您可以翻转最高位,范围比较仍然有效。

使用@user1 的示例。检查210.210.210.192是否在210.210.210.1和210.210.211.255之间

210.210.210.1 is: (210 << 24) + (210 << 16) + (210 << 8) + 1 = -757935615
210.210.211.255 is: (210 << 24) + (210 << 16) + (211 << 8) + 255 = -757935105

210.210.210.192 is: (210 << 24) + (210 << 16) + (210 << 8) + 192 = -757935424

最后一个 IP 地址在范围内,因为 -757935615

【讨论】:

  • 您需要很长时间才能轻松处理 IPv4,因为您不希望使用 Java 的签名比较来比较以大于 127 的字节开头的 IP。
  • 此外,long 有 64 位,而 IPv6 使用 128 位 - 所以 long 是不够的。
  • @nd。我不知道。谢谢。
  • @user1 恕我直言,这只是特殊情况下的问题,并且有一个简单的解决方法。
【解决方案4】:

如果你的IP地址范围可以用常见的CIDR format表示,你可以使用Apache Common's SubnetUtils

SubnetUtils.SubnetInfo subnet = new SubnetUtils("192.168.0.3/31");
return subnet.isInRange(testIpAddress);

【讨论】:

    【解决方案5】:

    将 IP 地址转换为长整数。 请参阅 Given a list of IP address, how do you find min, max? 以获得关于同一问题的精彩讨论。

    【讨论】:

      【解决方案6】:

      试试这个:

      import static org.junit.Assert.*;
      import org.junit.Test;
      import java.math.BigInteger;
      import java.net.*;
      class IpRange {
          IpRange(InetAddress from, InetAddress to) {
              if(!from.getClass().equals(to.getClass())) throw new RuntimeException("different versions of ip address!");
              this.from = new BigInteger(from.getAddress());
              this.to = new BigInteger(to.getAddress());
          }
          boolean isInRange(InetAddress inetAddress) {
              BigInteger bigInteger = new BigInteger(inetAddress.getAddress());
              return !(from.compareTo(bigInteger) == 1 || bigInteger.compareTo(to) == 1);
          }
          final BigInteger from, to;
      }
      public class IpRangeTestCase {
          @Test public void testInRange() throws UnknownHostException {
              InetAddress from = InetAddress.getByAddress(new byte[] { 10, 10, 10, 1 });
              InetAddress x = InetAddress.getByAddress(new byte[] { 10, 10, 10, 42 });
              InetAddress to = InetAddress.getByAddress(new byte[] { 10, 10, 10, (byte) 192 });
              IpRange ipRange = new IpRange(from, to);
              assertTrue(ipRange.isInRange(from));
              assertTrue(ipRange.isInRange(x));
              assertTrue(ipRange.isInRange(to));
              InetAddress toSmall = InetAddress.getByAddress(new byte[] { 10, 10, 9, 1 });
              assertFalse(ipRange.isInRange(toSmall));
              InetAddress toBig = InetAddress.getByAddress(new byte[] { 10, 10, 10, (byte) 193 });
              assertFalse(ipRange.isInRange(toBig));
              InetAddress fromv6=InetAddress.getByAddress(new byte[] {(byte)0xfe,(byte)0x80,0x00,0x00,0x00,0x00,0x00,0x00,0x02,0x02,(byte)0xb3,(byte)0xff,(byte)0xfe,0x1e,(byte)0x83,0x20});
              InetAddress xv6=InetAddress.getByAddress(new byte[] {(byte)0xfe,(byte)0x80,0x00,0x00,0x00,0x00,0x00,0x00,0x02,0x02,(byte)0xb3,(byte)0xff,(byte)0xfe,0x1e,(byte)0x83,0x29});
              InetAddress tov6=InetAddress.getByAddress(new byte[] {(byte)0xfe,(byte)0x80,0x00,0x00,0x00,0x00,0x00,0x00,0x02,0x02,(byte)0xb3,(byte)0xff,(byte)0xfe,0x1e,(byte)0x83,0x40});
              IpRange ipRangev6 = new IpRange(fromv6, tov6);
              assertTrue(ipRangev6.isInRange(xv6));
          }
          @Test (expected=RuntimeException.class) public void testInRangeThrows() throws UnknownHostException {
              InetAddress v4 = InetAddress.getByAddress(new byte[] { 10, 10, 10, 1 });
              InetAddress v6=InetAddress.getByAddress(new byte[] {(byte)0xfe,(byte)0x80,0x00,0x00,0x00,0x00,0x00,0x00,0x02,0x02,(byte)0xb3,(byte)0xff,(byte)0xfe,0x1e,(byte)0x83,0x29});
              new IpRange(v4, v6);
          }
      }
      

      【讨论】:

      • 我最初考虑将 BigInteger 用于我的答案中提出的比较器,但后来决定反对:它很乐意将 IPv4 和 IPv6 地址相互比较,这可能是您不想要的。
      • 我的似乎适用于 ipv6,但没有检测到一个是 v4 什么时候是 v6。
      • 我为不同版本的ip地址添加了一个测试。
      • 它确实适用于任意字节长度的互联网地址(即 IPv4 和 IPv6),但如果将 v4 与 v6 进行比较,则 v4 地址使用 ::v4-address 进行别名。这实际上与“IPv4 兼容”地址相同,可能正是您想要的,但由于还有其他 v4v6 转换系统,它可能是错误的。
      • 这很好,但请注意,您应该使用将签名考虑在内的 BigInteger 构造函数,即BigInteger(1, byte[])。否则,像 255.255.255.255 这样的地址将被表示为 -1。
      【解决方案7】:

      只需使用java的原语long

      【讨论】:

      • IP 地址包含 4 个数字,每个数字可以存储在 1 个字节(0-255)中。因此,您需要 4 个字节来存储整个 IP。获得 minIp 和 maxIp 值后,您可以简单地测试 testIp &gt; minIp &amp;&amp; testIp &lt; maxIp 是否在范围内。有关如何实现转换的详细信息,请参阅@user1 答案。
      • @Max IPv6 怎么样? long 将处理一般情况
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2014-05-16
      • 2011-07-05
      • 1970-01-01
      • 2011-10-30
      • 2011-12-30
      • 2010-11-05
      • 2013-10-13
      相关资源
      最近更新 更多