32位,4个字节的数字表示。
点分格式展示为0-255.0-255.0-255.0-255
因此,IPv4格式还比较好判断。
^表示行开始,$表示行结束,?表示0或者1个
(25[0-5]|2[0-4]\\d|[0-1]?\\d?\\d) //0-25525[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} 前面括号里的内容重复三次
128位,按每16位划分为一个段,将每个段转换成十六进制数字,并用冒号隔开。
通过省略前导零指定 IPv6 地址。例如,IPv6 地址 1050:0000:0000:0000:0005:0600:300c:326b 可写为 1050:0:0:0:5:600:300c:326b。
使用双冒号通过使用双冒号(::)代替一系列零来指定 IPv6 地址。例如,IPv6 地址 ff06:0:0:0:0:0:0:c3 可写为 ff06::c3。一个 IP 地址中只可使用一次双冒号。
通过 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(短格式)。
下面的程序能判断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); }其实本质上,这个方法底层也是调用了相同的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地址的工具函数,用起来很方便,而且比较准确。
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格式判断的逻辑,没必要自己再写一遍。 真是花了时间,做了吃力不讨好的事情,不过并不是浪费时间,暂且记录一下。
