Go语言的net/netip
包详解:Addr
类型
大家好!今天我们深入探讨Go语言的net/netip
包,重点关注Addr
类型。如果您一直在使用Go的网络代码,您可能已经遇到了旧的net.IP
类型。虽然它为我们服务良好,但它有一些缺点,使其不太适合现代网络代码。net/netip
包(在Go 1.18中引入)为我们提供了一种更强大、更高效的处理IP地址的方法。
net/netip.Addr
?在我们深入细节之前,让我们了解为什么存在这种类型。传统的net.IP
类型基本上是一个字节切片([]byte
),这意味着:
==
运算符进行比较新的Addr
类型解决了所有这些问题。它是一个值类型(内部结构体),不可变,并且始终表示有效的IP地址。不再需要防御性编程!
Addr
让我们看看创建和使用Addr
的基本方法:
<code class="language-go">package main import ( "fmt" "net/netip" ) func main() { // 从字符串创建Addr addr, err := netip.ParseAddr("192.168.1.1") if err != nil { panic(err) } // 如果你绝对确定输入 addr2 := netip.MustParseAddr("2001:db8::1") fmt.Printf("IPv4: %v\nIPv6: %v\n", addr, addr2) }</code>
ParseAddr
的一个优点是它非常严格。它不会接受奇怪的格式或无效的地址。例如:
<code class="language-go">// 这些将会失败 _, err1 := netip.ParseAddr("256.1.2.3") // 无效的IPv4八位字节 _, err2 := netip.ParseAddr("2001:db8::1::2") // 无效的IPv6(双冒号) _, err3 := netip.ParseAddr("192.168.1.1/24") // Addr不允许CIDR表示法</code>
Addr
方法让我们探索您将与Addr
一起使用的关键方法。我将分享一些实际示例,说明每个方法在哪些情况下派上用场。
<code class="language-go">func checkAddressType(addr netip.Addr) { if addr.Is4() { fmt.Println("这是IPv4") // 你可以在这里安全地使用As4() bytes := addr.As4() fmt.Printf("作为字节:%v\n", bytes) } else if addr.Is6() { fmt.Println("这是IPv6") // 你可以在这里安全地使用As16() bytes := addr.As16() fmt.Printf("作为字节:%v\n", bytes) } }</code>
专业提示:当处理IPv4映射的IPv6地址(例如::ffff:192.0.2.1
)时,使用Is4In6()
来检测它们。这在编写与协议无关的代码时特别有用。
Addr
类型提供了几种方法来对IP地址进行分类。这是一个全面的示例:
<code class="language-go">func classifyAddress(addr netip.Addr) { checks := []struct { name string fn func() bool }{ {"IsGlobalUnicast", addr.IsGlobalUnicast}, {"IsPrivate", addr.IsPrivate}, {"IsLoopback", addr.IsLoopback}, {"IsMulticast", addr.IsMulticast}, {"IsLinkLocalUnicast", addr.IsLinkLocalUnicast}, {"IsLinkLocalMulticast", addr.IsLinkLocalMulticast}, {"IsInterfaceLocalMulticast", addr.IsInterfaceLocalMulticast}, {"IsUnspecified", addr.IsUnspecified}, } for _, check := range checks { if check.fn() { fmt.Printf("地址是 %s\n", check.name) } } }</code>
实际示例:假设您正在编写一项服务,该服务需要绑定到除环回接口之外的所有接口:
<code class="language-go">func getBindableAddresses(addrs []netip.Addr) []netip.Addr { var bindable []netip.Addr for _, addr := range addrs { if !addr.IsLoopback() && !addr.IsLinkLocalUnicast() { bindable = append(bindable, addr) } } return bindable }</code>
如果您使用的是IPv6,最终会遇到区域。它们主要与链路本地地址一起使用,以指定要使用的网络接口:
<code class="language-go">func handleZones() { // 创建一个带有区域的地址 addr := netip.MustParseAddr("fe80::1%eth0") // 获取区域 zone := addr.Zone() fmt.Printf("区域:%s\n", zone) // 比较带有区域的地址 addr1 := netip.MustParseAddr("fe80::1%eth0") addr2 := netip.MustParseAddr("fe80::1%eth1") // 由于区域不同,这些是不同的地址 fmt.Printf("相同的地址?%v\n", addr1 == addr2) // false // WithZone创建一个具有不同区域的新地址 addr3 := addr1.WithZone("eth2") fmt.Printf("新的区域:%s\n", addr3.Zone()) }</code>
让我们在一个实际示例中将所有这些放在一起。这是一个简单的IP过滤器,可用于Web服务:
<code class="language-go">type IPFilter struct { allowed []netip.Addr denied []netip.Addr } func NewIPFilter(allowed, denied []string) (*IPFilter, error) { f := &IPFilter{} // 解析允许的地址 for _, a := range allowed { addr, err := netip.ParseAddr(a) if err != nil { return nil, fmt.Errorf("无效的允许地址 %s: %w", a, err) } f.allowed = append(f.allowed, addr) } // 解析拒绝的地址 for _, d := range denied { addr, err := netip.ParseAddr(d) if err != nil { return nil, fmt.Errorf("无效的拒绝地址 %s: %w", d, err) } f.denied = append(f.denied, addr) } return f, nil } func (f *IPFilter) IsAllowed(ip string) bool { addr, err := netip.ParseAddr(ip) if err != nil { return false } // 首先检查拒绝列表 for _, denied := range f.denied { if addr == denied { return false } } // 如果没有指定允许的地址,则允许所有未被拒绝的地址 if len(f.allowed) == 0 { return true } // 检查允许列表 for _, allowed := range f.allowed { if addr == allowed { return true } } return false }</code>
使用方法示例:
<code class="language-go">func main() { filter, err := NewIPFilter( []string{"192.168.1.100", "10.0.0.1"}, []string{"192.168.1.50"}, ) if err != nil { panic(err) } tests := []string{ "192.168.1.100", // 允许 "192.168.1.50", // 拒绝 "192.168.1.200", // 不在任何列表中 } for _, ip := range tests { fmt.Printf("%s 允许?%v\n", ip, filter.IsAllowed(ip)) } }</code>
net/netip.Addr
的一大优点是其性能特性。由于它是一个值类型:
net.IP
不同,其中零值可能是有效的)net.IP
和netip.Addr
虽然可以在它们之间进行转换,但为了保持一致性,尽量在整个代码库中坚持使用netip.Addr
。MustParseAddr
虽然在测试或初始化代码中很方便,但在处理用户输入的生产代码中,更喜欢ParseAddr
。WithZone
)实际上都会返回一个新的地址。本文介绍了Addr
类型的基础知识和一些高级用法,但net/netip
包中还有更多内容需要探索。在下一篇文章中,我们将研究AddrPort
,它将IP地址与端口号结合在一起——这对于网络编程非常有用。
在那之前,祝您编程愉快!如果您在项目中使用net/netip.Addr
有任何问题,请随时联系我们。
以上是了解 Go 的 net/netip Addr 类型:深入探讨的详细内容。更多信息请关注PHP中文网其他相关文章!