IPv4格式

32位,4个字节的数字表示。

点分格式展示为0-255.0-255.0-255.0-255

因此,IPv4格式还比较好判断。

IPv4 java正则表达式判断

"^(25[0-5]|2[0-4]\\d|[0-1]?\\d?\\d)(\\.(25[0-5]|2[0-4]\\d|[0-1]?\\d?\\d)){3}$"

^表示行开始,$表示行结束,?表示0或者1个

(25[0-5]|2[0-4]\\d|[0-1]?\\d?\\d) //0-255

25[0-5] 如果是25开头的三位数,则后面只能跟着0-5

2[0-4]\\d 如果是2开头(非25X)的三位数,则后面可以跟着0-4,后面跟任意数字

[0-1]?\\d?\\d 不是以2开头的,则可能范围是0-199。百位:0和1选一个,或没有;十位:任意数字或者没有,个位:任意数字。

(\\.(25[0-5]|2[0-4]\\d|[0-1]?\\d?\\d)) 前面加一个.,后面和前面的分析一样

{3} 前面括号里的内容重复三次

IPv6格式

128位,按每16位划分为一个段,将每个段转换成十六进制数字,并用冒号隔开。

IPv6压缩规则:

  1. 省略前导零

通过省略前导零指定 IPv6 地址。例如,IPv6 地址 1050:0000:0000:0000:0005:0600:300c:326b 可写为 1050:0:0:0:5:600:300c:326b

  1. 使用双冒号

通过使用双冒号(::)代替一系列零来指定 IPv6 地址。例如,IPv6 地址 ff06:0:0:0:0:0:0:c3 可写为 ff06::c3。一个 IP 地址中只可使用一次双冒号。

Ipv6中混合Ipv4

通过 IPv4 映射的 IPv6 地址

此类型的地址用于将 IPv4 节点表示为 IPv6 地址。它允许 IPv6 应用程序直接与 IPv4 应用程序通信。例如,0:0:0:0:0:ffff:192.1.56.10::ffff:192.1.56.10/96(短格式)。

兼容 IPv4 的 IPv6 地址

此类型的地址用于隧道传送。它允许 IPv6 节点通过 IPv4 基础结构通信。例如,0:0:0:0:0:0:192.1.56.10::192.1.56.10/96(短格式)。

Java 判断IP地址合法

下面的程序能判断Ip字符串是否是Ipv4地址,并能判断一部分情况是否是合法的ipv6地址
下面的程序来自参考博客,程序没有考虑IPv6中混合IPv4格式的情况,因此不够全面。
程序中以JDK中的方法IPAddressUtil.isIPv4LiteralAddress(ip)来进行结果对比,但是JDK中的方法判断IP格式存在一些不全面的地方,例如判断IPv4格式时,输入“123”也会返回true,具体原因我之后再找时间分析一下源码。

private static final Pattern IPV4_REGEX =
            Pattern.compile("^(25[0-5]|2[0-4]\\d|[0-1]?\\d?\\d)(\\.(25[0-5]|2[0-4]\\d|[0-1]?\\d?\\d)){3}$");
    //无全0块,标准IPv6地址的正则表达式
    private static final Pattern IPV6_STD_REGEX =
            Pattern.compile("^([0-9a-fA-F]{1,4}:){7}[0-9a-fA-F]{1,4}$");
    //压缩正则表达式
    private static final Pattern IPV6_COMPRESS_REGEX =
            Pattern.compile("^(([0-9A-Fa-f]{1,4}(:[0-9A-Fa-f]{1,4})*)?)::((([0-9A-Fa-f]{1,4}:)*[0-9A-Fa-f]{1,4})?)$");

//判断是否为合法IPv4地址
    public static boolean isIPv4Address(final String input) {
        return IPV4_REGEX.matcher(input).matches();
    }

    //判断是否为合法IPv6地址
    public static Map isIPv6Address(String input) {
        Map resultMap = new HashMap();
        // 8*4 位数字,加上七个: ipv6地址长度不可能超过39位
        if (input.length() > 39) {
            resultMap.put("value", "false");
            resultMap.put("method", "长度非法");
        } else if (IPV6_STD_REGEX.matcher(input).matches()) {
            //标准格式判断
            resultMap.put("value", "true");
            resultMap.put("method", "标准格式");
        } else if (IPV6_COMPRESS_REGEX.matcher(input).matches()) {
            //压缩发生在IP地址内部;唯一确定内容,IP地址首尾无冒号 (:)
            //压缩位置未知,压缩的连续全0块数量未知
            resultMap.put("value", "true");
            resultMap.put("method", "压缩格式");
        } else {
            resultMap.put("value", "false");
            resultMap.put("method", "匹配错误");
        }
        return resultMap;
    }

//测试数据
    public static String[] getParams() {
        return new String[]{
                "::",
                "::192.168.89.9",
                "2001:4860:4860::8888",
                "2400:da00::dbf:0:100",
                "2404:6800:8005::d2",
                "2A00:1450:8006::30",
                "0:0:0:0:0:0:0:0",
                "0:0:0:0:0:ffff:192.1.56.10",
                "::192.1.56.10",
                "::ffff:192.1.56.10/96",
                "1030:0:0:0:C9B4:FF12:48AA:1A2B",
                "0:0:0:0:0:0:192.1.56.10",
                "::",
                "a:b:c:D:e:f:f:F",
                "a:b:c:D:G:f:f:F",
                "a:b:c:D:g:f:f:F",
                "fe80:1295:8030:49ec::1fc6:57fa:2222:0000",
                "fe80:1295:8030:49ec:1fc6:57fa:0000:",
                ":1295:8030:49ec:1fc6:57fa:0000:0000",
                "fe80:1295:8030:1fc6:57fa:0000:0000",
                "fe80:1295:8030:49ec:1fc6:57fa::0000",
                "fe80::0000",
                "fe80::",
                "::0000",
                "fe80:1295:8030:0000",
                "fe80:1295:8030::0000:0000",
                "fe80:1295:8030:49ec:1fc6:57fa:::0000:0000:0000:0000:0000:0000:0000:0000",
                "::8030:49ec:1fc6:57fa:0000:0000:0000:0000:0000:0000",
                "fe80:1295::49ec:1fc6:57fa:0000:0000",
                "fe80:1295:8030:49ec::1fc6:57fa:0000:0000:0000:0000:0000:0000:0000:0000:0000",
                "fe80::1295:8030:49ec:1fc6:57fa:0000:0000:0000",
                "fe80:1295:8030:49ec::1fc6:57fa:0000:0000:0000:0000:0000:0000:0000:0000:0000:0000:0000",
                "fe80:1295:8030:49ec:1fc6::57fa:0000:0000:0000:0000:0000",
                "fe80:1295:8030:49ec:1fc6:57fa::0000:0000:0000:0000:0000",
                "fe80:1295:8030:49ec:1fc6:57fa:0000::0000:0000:0000:0000",
                "fe80:0000:0000:0000:1fc6:57fa::1241:0000:0000:0000:0000:0000:0000",
                "::fe80:1295:8030:49ec:1fc6:57fa:2222:0000",
                ":1295:8030:49ec:1fc6:57fa:2222:0000",
                "fe80:1295:8030:49ec:1fc6:57fa:2222:0000:",
                "fe80:1295:8030:49ec:1fc6:57fa:2222:0000::",
                "::fe80:1295:8030:49ec:1fc6:57fa:2222:0000",
                "fe80:1295:8030:49ec:1fc6:57fa:2222:",
                ":1295:8030:49ec:1fc6:57fa:2222:0000",
                "fe80:1295:8030:49ec:1fc6:57fa::",
                "::fe80:1295:8030:49ec:1fc6:57fa",
                "fe80:1295:8030:49ec:1fc6:57fa:a::",
                "::b:fe80:1295:8030:49ec:1fc6:57fa",
                "fe80:1295:8030:49ec:1fc6:57fa:2222::",
                "::1295:8030:49ec:1fc6:57fa:2222:0000",
                "::8030:49ec:1fc6:57fa:2222:0000",
                "fe80:1295:8030:49ec:1fc6:57fa:0000:0000"
        };
    }

//验证ipv4
      String[] examples = {
                "192.168.1.1",
                "192.0.1.1",
                "999.168.1.1",
                "192.168.1.1.0",
                "192.168.256.1",
                "0.0.0.0",
                "0000",
                "1",
                "0.1.1.1"
        };
        for(String example:examples){
            System.out.print("IPAddressUtil:"+IPAddressUtil.isIPv4LiteralAddress(example));
            System.out.print("  selfMethod:"+isIPv4Address(example));
            if(IPAddressUtil.isIPv4LiteralAddress(example)==isIPv4Address(example)){
                System.out.print("自定义正则判断无误!");
            }else{
                System.out.print("自定义正则判断失准!");
            }
            System.out.println("  "+example);
        }
//验证ipv6
 String[] ipAddrs =null;
        for(String ipAddr:ipAddrs){
            Map result = null;
            boolean selfMethod = Boolean.valueOf(result.get("value").toString());
            //官方判断IPv6地址方法
            boolean sunMethod = IPAddressUtil.isIPv6LiteralAddress(ipAddr);
            if(selfMethod==sunMethod){
                System.out.print("##############自定义正则匹配结果与官方结果相同----判断一致!");
            }else{
                System.out.print("##############自定义正则匹配结果与官方结果相同----正则错误!");
            }
            System.out.print("  "+result.get("method"));
            System.out.print("  IPAddressUtil:"+sunMethod);
            if(selfMethod){
                System.out.println("  IPv6合法");
                System.out.println(IPDBUtil.resolveIp(ipAddr));
            }else{
                System.out.println("  IPv6非法");
                System.out.println(IPDBUtil.resolveIp(ipAddr));
            }
            System.out.println("  "+ipAddr);
        }

Java 转成InetAddress判断IP

其实本质上,这个方法底层也是调用了相同的JDK内部方法,源码分析之后再加。

package mynet;

import java.net.*;

public class MyIP
{
    public static void main(String[] args) throws Exception
    {
        if (args.length == 0)
            return;
        InetAddress address = InetAddress.getByName(args[0]);
        System.out.println("IP: " + address.getHostAddress());
        switch (address.getAddress().length)
        {
            case 4:
                System.out.println("根据byte数组长度判断这个IP地址是IPv4地址!");
                break;
            case 16:
                System.out.println("根据byte数组长度判断这个IP地址是IPv6地址!");
                break;
        }
        if (address instanceof Inet4Address)
            System.out.println("使用instanceof判断这个IP地址是IPv4地址!");
        else if (address instanceof Inet6Address)
            System.out.println("使用instanceof判断这个IP地址是IPv6地址!");
    }
}

Golang 判断IP地址

Golang中有判断IP地址的工具函数,用起来很方便,而且比较准确。

import (
	"fmt"
	"net"
)
 
// 0: invalid ip
// 4: IPv4
// 6: IPv6
func ParseIP(s string) (net.IP, int) {
	ip := net.ParseIP(s)
	if ip == nil {
		return nil, 0
	}
	for i := 0; i < len(s); i++ {
		switch s[i] {
		case '.':
			return ip, 4
		case ':':
			return ip, 6
		}
	}
	return nil, 0
}

后话

研究了半天用正则判断IP类型然后调用不同的方法,同时校验了合法性,可以减少后方调用方法时抛出异常的次数。结果mentor跟我说正则判断太慢了,直接根据是否有冒号判断一下是否是IPv6,然后丢给后方的调用方法。后面的方法本身就有IP格式判断的逻辑,没必要自己再写一遍。
真是花了时间,做了吃力不讨好的事情,不过并不是浪费时间,暂且记录一下。