Go net.Resolver.LookupNetIP 怎样只取 IPv4 或 IPv6 结果
来源:17golang原创
时间:2026-11-11 03:57:13 427浏览 收藏
双栈服务里,如果连接器明确只支持 IPv4 或 IPv6,直接调用 LookupNetIP 得到一组地址后再“猜”哪一个能用,往往会把地址族选择和连接失败混在一起。更直接的做法是:查询阶段就把 network 设为 ip4 或 ip6;只有确实需要同时保留两类结果时,才用 netip.Addr.Is4 或 Is6 做二次筛选。
官方文档:https://pkg.go.dev/net#Resolver.LookupNetIP
LookupNetIP(ctx, "ip4", host)只请求 IPv4 结果,LookupNetIP(ctx, "ip6", host)只请求 IPv6 结果,"ip"则保留两种地址。返回值是[]netip.Addr,不要把它和net.ParseIP的字符串解析混为一谈。
先用 network 参数决定查询结果
我在给连接器增加 IPv6 兼容时,最先踩到的坑是把 LookupNetIP 当成“先查出所有地址,再由调用方随便过滤”的接口。官方签名已经把地址族选择放在第二个参数里:network 只能使用 ip、ip4 或 ip6。因此,调用方的能力边界如果在查询前已经确定,就应该直接传对应值。

package main
import (
"context"
"fmt"
"net"
)
func lookupFamily(ctx context.Context, resolver *net.Resolver, network, host string) error {
// network 只接受 ip、ip4、ip6;由调用方的连接能力决定地址族。
addrs, err := resolver.LookupNetIP(ctx, network, host)
if err != nil {
// DNS 错误和“查询成功但没有目标地址”要分开处理。
return fmt.Errorf("lookup %s %q: %w", network, host, err)
}
if len(addrs) == 0 {
// 没有可用地址时不要继续拼接目标地址或盲目重试。
return fmt.Errorf("lookup %s %q returned no address", network, host)
}
for _, addr := range addrs {
// netip.Addr 可直接转换成文本,用于后续拨号或记录日志。
fmt.Println(addr.String())
}
return nil
}
这里的重点不是把 ip4 或 ip6 写成字符串常量,而是让这个选择和后续连接策略保持一致。例如只创建 IPv4 socket 时,不要先用 ip 拿到 IPv6 地址,再等连接阶段报错。查询阶段过滤还能让“没有该地址族”更早暴露出来。
保留双栈查询时再筛选返回值
有些场景需要把两种地址都留在内存里:例如连接器会根据运行环境选择顺序,或者要先记录完整 DNS 结果。这时可以传 ip,然后按 netip.Addr 的方法筛选。Is4 和 Is6 表达的是地址语义,比检查字符串里有没有冒号更稳妥。

package main
import (
"context"
"fmt"
"net"
"net/netip"
)
func splitAddresses(ctx context.Context, resolver *net.Resolver, host string) (ipv4, ipv6 []netip.Addr, err error) {
// ip 保留双栈结果,适合把地址选择延后到连接策略层。
addrs, err := resolver.LookupNetIP(ctx, "ip", host)
if err != nil {
return nil, nil, fmt.Errorf("lookup %q: %w", host, err)
}
for _, addr := range addrs {
// 用地址类型方法筛选,不依赖 String 的展示格式。
switch {
case addr.Is4():
ipv4 = append(ipv4, addr)
case addr.Is6():
ipv6 = append(ipv6, addr)
}
}
return ipv4, ipv6, nil
}
二次筛选的代价是调用方要自己处理空切片:只有 IPv4 结果时,IPv6 列表为空是正常情况,不应该被当成 DNS 失败;反过来也一样。若业务必须使用某一地址族,建议把“无结果”转成带有 network 和 host 的错误,让上层决定降级、换地址族还是结束请求。
把地址族选择接到连接逻辑
我最后会把规则压缩成下面三种判断:
| 目标 | 查询参数 | 适合的后续处理 |
|---|---|---|
| 只要 IPv4 | ip4 | 直接遍历返回的 IPv4 地址 |
| 只要 IPv6 | ip6 | 直接遍历返回的 IPv6 地址 |
| 保留双栈 | ip | 用 Is4/Is6 归类,再按策略排序 |
不要用 net.ParseIP 代替这层选择:它解决的是“把一段文本解析成 IP”,并不负责 DNS 查询,也不会告诉你某个主机名应该返回哪一类地址。LookupNetIP 已经返回 netip.Addr,直接使用其地址族方法即可。
常见追问
ip4 和 ip6 可以写成 tcp4、tcp6 吗?
不可以。LookupNetIP 的 network 参数只接受 ip、ip4、ip6;tcp4、tcp6 是拨号等 TCP API 的网络名,不能直接套到这里。
为什么不总是先查 ip 再过滤?
如果调用方从一开始就只支持一种地址族,直接传 ip4 或 ip6 更清楚,也能把地址族约束放在查询入口;只有需要保留双栈候选或自定义排序时,才值得延后筛选。
-
Golang · Go问答 | 23分钟前 | 标准库 · 性能优化 · Go问答 · Go Reader.Next debug/dwarf DWARF Reader.Seek Entry.Offset188 收藏
-
298 收藏
-
275 收藏
-
105 收藏
-
402 收藏
-
239 收藏
-
165 收藏
-
418 收藏
-
Golang · Go问答 | 46分钟前 | 单元测试 · Cookie · Go问答 · 兼容性 · cookie Go net/http/cookiejar Options.PublicSuffixList 内网测试域名406 收藏
-
291 收藏
-
329 收藏
-
319 收藏
-
- 前端进阶之JavaScript设计模式
- 设计模式是开发人员在软件开发过程中面临一般问题时的解决方案,代表了最佳的实践。本课程的主打内容包括JS常见设计模式以及具体应用场景,打造一站式知识长龙服务,适合有JS基础的同学学习。
- 立即学习 543次学习
-
- GO语言核心编程课程
- 本课程采用真实案例,全面具体可落地,从理论到实践,一步一步将GO核心编程技术、编程思想、底层实现融会贯通,使学习者贴近时代脉搏,做IT互联网时代的弄潮儿。
- 立即学习 516次学习
-
- 简单聊聊mysql8与网络通信
- 如有问题加微信:Le-studyg;在课程中,我们将首先介绍MySQL8的新特性,包括性能优化、安全增强、新数据类型等,帮助学生快速熟悉MySQL8的最新功能。接着,我们将深入解析MySQL的网络通信机制,包括协议、连接管理、数据传输等,让
- 立即学习 500次学习
-
- JavaScript正则表达式基础与实战
- 在任何一门编程语言中,正则表达式,都是一项重要的知识,它提供了高效的字符串匹配与捕获机制,可以极大的简化程序设计。
- 立即学习 487次学习
-
- 从零制作响应式网站—Grid布局
- 本系列教程将展示从零制作一个假想的网络科技公司官网,分为导航,轮播,关于我们,成功案例,服务流程,团队介绍,数据部分,公司动态,底部信息等内容区块。网站整体采用CSSGrid布局,支持响应式,有流畅过渡和展现动画。
- 立即学习 485次学习