版本 1.26 • 标准库 •
摘要
在Go语言中,net.Dialer类型用于根据指定的网络协议(如TCP、UDP、IP或Unix套接字)连接到目标地址。在Go官方Issue 49097中,一项新的提案被提出,旨在为net.Dialer添加一系列上下文感知的、特定于网络协议的新方法(如DialTCP、DialUDP等)。这些新方法融合了现有顶级网络函数(跳过地址解析与分发)的高效性,以及Dialer.DialContext方法所提供的上下文取消能力。
动机
当前net包已经为不同网络提供了顶层的连接函数,例如DialTCP、DialUDP、DialIP和DialUnix。然而,这些函数是在context.Context被引入Go语言之前设计的,因此它们天然不支持基于上下文的取消或超时控制:
func DialTCP(network string, laddr, raddr *TCPAddr) (*TCPConn, error)
func DialUDP(network string, laddr, raddr *UDPAddr) (*UDPConn, error)
func DialIP(network string, laddr, raddr *IPAddr) (*IPConn, error)
func DialUnix(network string, laddr, raddr *UnixAddr) (*UnixConn, error)
另一方面,net.Dialer类型提供了一个通用的DialContext方法,它支持context.Context,可以用于连接任何已知的网络:
func (d *Dialer) DialContext(ctx context.Context, network, address string) (Conn, error)
但是,如果你已经明确知晓网络类型和地址,使用DialContext会比使用上述网络特定函数(如DialTCP)效率稍低,主要原因有二:
- 地址解析开销:
DialContext需要根据你提供的network和address字符串在内部进行地址解析(例如执行DNS查询,并转换为net.TCPAddr或net.UDPAddr)。而网络特定函数直接接受预解析好的地址对象,因此跳过了这一步。
- 网络类型分发:
DialContext必须根据传入的network参数将调用路由到对应协议的拨号器。网络特定函数已经明确了协议类型,因此也跳过了这一步。
因此,现状是:net包中的网络特定函数高效但不支持取消;Dialer类型支持取消但效率略低。此提案正是为了解决这一不匹配问题。
此外,向Dialer添加这些新方法还带来了一个额外优势:它允许开发者使用netip包中更现代的地址类型(如netip.AddrPort来替代net.TCPAddr),这已成为现代Go代码中的首选实践。
描述
提案建议向net.Dialer类型添加以下四个新方法:
DialTCP(ctx context.Context, network string, laddr, raddr netip.AddrPort) (*TCPConn, error)
DialUDP(ctx context.Context, network string, laddr, raddr netip.AddrPort) (*UDPConn, error)
DialIP(ctx context.Context, network string, laddr, raddr netip.Addr) (*IPConn, error)
DialUnix(ctx context.Context, network string, laddr, raddr *UnixAddr) (*UnixConn, error)
这些方法的签名与现有的顶级net函数类似,但关键改进在于它们都接受一个context.Context参数以支持并发控制与超时管理,并且对于TCP/UDP连接,使用了netip.AddrPort这类更高效的地址类型。
示例
使用DialTCP方法连接TCP服务器
以下代码展示了如何使用新的DialTCP方法,并设置一个5秒的超时上下文。
var d net.Dialer
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
// 拨号将失败,因为服务器未运行。
raddr := netip.MustParseAddrPort("127.0.0.1:12345")
conn, err := d.DialTCP(ctx, "tcp", netip.AddrPort{}, raddr)
if err != nil {
log.Fatalf("Failed to dial: %v", err)
}
defer conn.Close()
if _, err := conn.Write([]byte("Hello, World!")); err != nil {
log.Fatal(err)
}
使用DialUnix方法连接Unix套接字
此示例演示了如何连接到Unix域套接字。
var d net.Dialer
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
// 拨号将失败,因为服务器未运行。
raddr := &net.UnixAddr{Name: "/path/to/unix.sock", Net: "unix"}
conn, err := d.DialUnix(ctx, "unix", nil, raddr)
if err != nil {
log.Fatalf("Failed to dial: %v", err)
}
defer conn.Close()
if _, err := conn.Write([]byte("Hello, socket!")); err != nil {
log.Fatal(err)
}
注意:在上述两个示例中,拨号均会失败,因为对应的服务器并未启动。
延伸阅读