在Go语言的网络编程场景中,准确区分IP地址属于IPv4还是IPv6是非常基础且常用的需求,比如在做IP访问控制、网络协议适配时都需要用到这类判断逻辑。错误的判断方式可能会导致功能异常,因此需要掌握基于标准库的正确实现方法。

IP地址的基础概念
IPv4地址是32位二进制数,通常表示为点分十进制格式,比如192.168.1.1,而IPv6地址是128位二进制数,通常表示为冒分十六进制格式,比如2001:0db8:85a3:0000:0000:8a2e:0370:7334。Go语言的标准库net包中提供了net.IP类型来统一表示IP地址,这个类型本质上是[]byte的别名,不同类型的IP地址对应的字节长度不同。
基于net包的判断方法
Go的net.IP类型自带了两个方法可以直接判断IP类型,分别是To4()和To16(),这两个方法的返回值和判断逻辑有明确区别:
To4()方法:如果IP是IPv4地址,会返回一个4字节的net.IP实例,否则返回nil。注意这个方法不仅支持标准IPv4格式,还支持IPv4映射的IPv6格式地址。To16()方法:如果IP是IPv4或者IPv6地址,都会返回16字节的net.IP实例,只有非法IP才会返回nil。
基础判断函数实现
我们可以先通过net.ParseIP()把字符串形式的IP解析为net.IP类型,再结合上述两个方法做判断,示例代码如下:
package main
import (
"fmt"
"net"
)
// 判断IP类型,返回 "IPv4" "IPv6" "invalid" 三种结果
func checkIPType(ipStr string) string {
ip := net.ParseIP(ipStr)
if ip == nil {
return "invalid"
}
// 先判断是否是IPv4,注意To4不为nil说明是IPv4或者IPv4映射的IPv6
if ip.To4() != nil {
return "IPv4"
}
// 走到这里说明不是IPv4,再判断是否是IPv6
if ip.To16() != nil {
return "IPv6"
}
return "invalid"
}
func main() {
testIPs := []string{
"192.168.1.1",
"2001:0db8:85a3:0000:0000:8a2e:0370:7334",
"invalid_ip",
"::ffff:192.168.1.1", // IPv4映射的IPv6地址
}
for _, ip := range testIPs {
fmt.Printf("IP: %s, 类型: %sn", ip, checkIPType(ip))
}
}
特殊场景处理
上面的基础函数会把IPv4映射的IPv6地址判断为IPv4,如果业务需要严格区分原生IPv6和映射类型,可以调整判断逻辑,直接检查net.IP的字节长度:
package main
import (
"fmt"
"net"
)
// 严格判断IP类型,区分原生IPv6和IPv4映射地址
func strictCheckIPType(ipStr string) string {
ip := net.ParseIP(ipStr)
if ip == nil {
return "invalid"
}
// 原生IPv4的字节长度是4
if len(ip) == net.IPv4len {
return "IPv4"
}
// 原生IPv6的字节长度是16,且不是IPv4映射的情况
if len(ip) == net.IPv6len {
// 检查是否是IPv4映射的IPv6地址,这类地址的前12字节是0:0:0:0:0:0:ffff
if ip[0] == 0 && ip[1] == 0 && ip[2] == 0 && ip[3] == 0 &&
ip[4] == 0 && ip[5] == 0 && ip[6] == 0 && ip[7] == 0 &&
ip[8] == 0 && ip[9] == 0 && ip[10] == 0xff && ip[11] == 0xff {
return "IPv4映射的IPv6"
}
return "IPv6"
}
return "invalid"
}
func main() {
testIPs := []string{
"192.168.1.1",
"2001:0db8:85a3:0000:0000:8a2e:0370:7334",
"::ffff:192.168.1.1",
}
for _, ip := range testIPs {
fmt.Printf("IP: %s, 类型: %sn", ip, strictCheckIPType(ip))
}
}
常见误区说明
很多开发者会尝试通过字符串是否包含冒号来判断IPv6,这种方式很容易出错,比如IPv6地址可以简写,也可能出现错误的字符串格式,而net.ParseIP已经做了格式校验,比手动判断字符串可靠得多。另外不要直接通过ip.To16() != nil判断IPv6,因为IPv4地址调用To16()也会返回非nil值,会导致误判。
总结
在Go语言中判断IP地址类型的核心是先使用net.ParseIP解析字符串得到net.IP实例,再根据业务需求选择用To4()方法或者字节长度做判断,避免手动解析字符串格式。如果不需要区分IPv4映射的IPv6地址,用基础判断函数即可,需要严格区分的话可以使用字节长度校验的方式实现。