这些函数都是 Go 语言标准库 net
包中用于创建网络监听器的函数,它们的主要区别在于监听的网络类型、返回的连接类型以及使用的场景。
在介绍具体函数之前,需要先理解两个核心概念:
- Listener (监听器): 负责监听指定网络地址上的连接请求。可以理解为服务器的“接线员”,等待客户端拨号。
- Conn (连接): 代表一个客户端和服务器之间的网络连接。可以理解为“通话中的线路”,用于双方的数据传输。
一、那些 Listener
和创建函数
1.1 net.Listen(network, address string) (Listener, error)
功能: 这是最通用的监听函数,根据传入的网络类型 (
network
) 创建相应的监听器。参数:
TCP/IP:
"host:port"
,例如"127.0.0.1:8080"
或":8080"
(监听所有本地 IP 的 8080 端口)。Unix: 文件路径,例如
"/var/run/my.sock"
。"tcp"
: TCP 协议。"tcp4"
: IPv4 的 TCP 协议。"tcp6"
: IPv6 的 TCP 协议。"unix"
: Unix 域套接字(用于同一台机器上的进程间通信)。network
: 网络类型,字符串。常见的有:address
: 监听地址,字符串。格式取决于网络类型:
返回值:
Listener
: 监听器接口。具体类型取决于network
,例如*net.TCPListener
(TCP 监听器)。error
: 错误信息,如果监听失败则返回。
示例:
listener, err := net.Listen("tcp", ":8080")
if err != nil {
// 处理错误
}
defer listener.Close() // 记得关闭监听器
// 接受连接
conn, err := listener.Accept()
if err != nil {
// 处理错误
}
defer conn.Close() // 记得关闭连接
1.2 net.ListenTCP(network string, laddr *TCPAddr) (*TCPListener, error)
功能: 创建一个 TCP 监听器。它是
net.Listen("tcp", address)
的更具体版本。参数:
network
: 网络类型,字符串,只能是"tcp"
、"tcp4"
或"tcp6"
。laddr
: 本地地址,*net.TCPAddr
类型。可以使用net.ResolveTCPAddr
函数创建:
addr, err := net.ResolveTCPAddr("tcp", ":8080")
返回值:
*net.TCPListener
: TCP 监听器。error
: 错误信息。
1.3 net.ListenUDP(network string, laddr *UDPAddr) (*UDPConn, error)
功能: 创建一个 UDP 连接。注意,UDP 是无连接的,所以这里返回的是
*net.UDPConn
,而不是Listener
。UDPConn 既可以用于服务端监听,也可以用于客户端发送数据。参数:
network
: 网络类型,字符串,只能是"udp"
、"udp4"
或"udp6"
。laddr
: 本地地址,*net.UDPAddr
类型。可以使用net.ResolveUDPAddr
函数创建。
返回值:
*net.UDPConn
: UDP 连接。error
: 错误信息。
1.4 net.ListenPacket(network, address string) (PacketConn, error)
功能: 创建一个可以发送和接收数据包的连接。适用于 UDP 和其他面向数据包的协议。
参数:
network
: 网络类型,字符串。常见的有"udp"
、"udp4"
、"udp6"
、"ip4:ICMP"
等。address
: 本地地址,字符串。
返回值:
PacketConn
: 数据包连接接口。error
: 错误信息。
1.5 net.ListenIP(network string, laddr *IPAddr) (Conn, error)
功能: 创建一个 IP 监听器,允许直接操作 IP 数据包。
参数:
network
: 网络类型,字符串。例如"ip4:ICMP"
、"ip6:ipv6-icmp"
。network 参数的格式是 “ip4:” 或 “ip6:”, 其中部分指定了 IP 协议之上的协议。laddr
: 本地地址,*net.IPAddr
类型。可以使用net.ResolveIPAddr
函数创建。
返回值:
Conn
: 连接接口。error
: 错误信息。
1.6 ListenConfig.Listen(ctx context.Context, network, address string) (Listener, error)
和 ListenConfig.ListenPacket(ctx context.Context, network, address string) (PacketConn, error)
功能: 这两个方法允许使用
ListenConfig
进行更细粒度的监听配置,例如控制双栈模式、仅监听 IPv4 或 IPv6 等。ctx context.Context
参数可以用于控制监听的生命周期。参数:
ctx
: 上下文,用于取消监听操作。network
: 网络类型,字符串。address
: 本地地址,字符串。
返回值:
Listener
或PacketConn
: 监听器或数据包连接接口。error
: 错误信息。
总结:
net.Listen
是最通用的监听函数,根据network
参数创建不同类型的监听器。net.ListenTCP
、net.ListenUDP
和net.ListenIP
是针对特定协议的便捷函数。net.ListenPacket
用于处理面向数据包的协议。ListenConfig.Listen
和ListenConfig.ListenPacket
提供了更高级的配置选项。
二、那些 Dial 函数
Listen
函数用于服务器端监听连接,而 Dial
函数用于客户端发起连接。它们就像电话的两端: Listen
是“接听电话”, Dial
是“拨打电话”。
net
包中提供了几种 Dial
函数,分别对应不同的网络类型:
2.1 net.Dial(network, address string) (Conn, error)
功能: 这是最通用的拨号函数,根据传入的网络类型 (
network
) 和地址 (address
) 建立连接。参数:
TCP/IP:
"host:port"
,例如"192.168.1.100:8080"
或"example.com:443"
。UDP/IP:
"host:port"
,例如"192.168.1.100:53"
。Unix:文件路径,例如
"/var/run/my.sock"
。network
:网络类型,字符串。常见的有: 稍后单独介绍 network,因为它的格式很重要。address
:连接地址,字符串。格式取决于网络类型:
返回值:
Conn
:连接接口。具体类型取决于network
,例如*net.TCPConn
(TCP 连接)。error
:错误信息,如果连接失败则返回。
2.2 net.DialTCP(network string, laddr, raddr *TCPAddr) (*TCPConn, error)
功能: 建立一个 TCP 连接。它是
net.Dial("tcp", address)
的更具体版本,允许指定本地地址 (laddr
)。参数:
network
:网络类型,字符串,只能是"tcp"
、"tcp4"
或"tcp6"
。laddr
:本地地址,*net.TCPAddr
类型。如果为nil
,则由系统自动选择。raddr
:远程地址,*net.TCPAddr
类型。
返回值:
*net.TCPConn
:TCP 连接。error
:错误信息。
2.3 net.DialUDP(network string, laddr, raddr *UDPAddr) (*UDPConn, error)
功能: 建立一个 UDP 连接。同样允许指定本地地址。
参数:
network
:网络类型,字符串,只能是"udp"
、"udp4"
或"udp6"
。laddr
:本地地址,*net.UDPAddr
类型。如果为nil
,则由系统自动选择。raddr
:远程地址,*net.UDPAddr
类型。
返回值:
*net.UDPConn
:UDP 连接。error
:错误信息。
2.4 net.DialIP(network string, laddr, raddr *IPAddr) (Conn, error)
功能: 建立一个 IP 连接,用于原始 IP 通信。
参数:
network
:网络类型,字符串。例如"ip4:ICMP"
、"ip6:ipv6-icmp"
。laddr
:本地地址,*net.IPAddr
类型。如果为nil
,则由系统自动选择。raddr
:远程地址,*net.IPAddr
类型。
返回值:
Conn
:连接接口。error
:错误信息。
2.5 net.DialTimeout(network, address string, timeout time.Duration) (Conn, error)
功能: 与
net.Dial
类似,但增加了超时时间。如果在指定时间内无法建立连接,则返回错误。参数:
network
:网络类型,字符串。address
:连接地址,字符串。timeout
:超时时间,time.Duration
类型。
返回值:
Conn
:连接接口。error
:错误信息。
2.6 net.DialContext(ctx context.Context, network, address string) (Conn, error)
功能: 允许使用
context.Context
来控制连接的生命周期,例如取消连接操作。参数:
ctx
:上下文,用于取消连接操作。network
:网络类型,字符串。address
:连接地址,字符串。
返回值:
Conn
:连接接口。error
:错误信息。
2.7 network
的格式
支持的网络类型 (Known networks):
"tcp"
: TCP 协议(同时支持 IPv4 和 IPv6)。"tcp4"
: TCP 协议(仅支持 IPv4)。"tcp6"
: TCP 协议(仅支持 IPv6)。"udp"
: UDP 协议(同时支持 IPv4 和 IPv6)。"udp4"
: UDP 协议(仅支持 IPv4)。"udp6"
: UDP 协议(仅支持 IPv6)。"ip"
: 原始 IP 通信(同时支持 IPv4 和 IPv6)。"ip4"
: 原始 IP 通信(仅支持 IPv4)。"ip6"
: 原始 IP 通信(仅支持 IPv6)。"unix"
: Unix 域套接字。"unixgram"
: 基于数据报的 Unix 域套接字。"unixpacket"
: 基于数据包的 Unix 域套接字。
地址格式:
TCP 和 UDP:
"host:port"
host
: 可以是文字 IP 地址、可解析为 IP 地址的主机名,或者留空(表示本地系统)。port
: 端口号或服务名称(例如,"http"
表示端口 80)。- IPv6 地址需要用方括号括起来,例如
"[2001:db8::1]:80"
。
IP (原始 IP 通信):
network
:"ip"
,"ip4"
, 或"ip6"
。protocol
: 协议名称(例如,"icmp"
)或协议号(例如,"1"
表示 ICMP 协议)。"network:protocol"
或"network:protocol_number"
host
: 可以是文字 IP 地址、可解析为 IP 地址的主机名,或者留空(表示本地系统)。
关键点:
- TCP 和 UDP 连接的
address
必须包含端口号 (host:port
)。 - 原始 IP 通信 (
ip
,ip4
,ip6
) 需要指定协议 ,必须是network:protocol
形式。 - 空主机 (
""
) 或未指定 IP 地址(例如,TCP/UDP 的":80"
、"0.0.0.0:80"
或"[::]:80"
,IP 的""
、"0.0.0.0"
或"::"
)表示本地系统。 - Unix 网络
2.8 总结:
Listen 函数 |
对应的 Dial 函数 |
功能 |
---|---|---|
net.Listen(network, address) |
net.Dial(network, address) |
通用连接函数,根据网络类型建立连接。 |
net.ListenTCP(network, laddr) |
net.DialTCP(network, laddr, raddr) |
建立 TCP 连接,可指定本地地址。 |
net.ListenUDP(network, laddr) |
net.DialUDP(network, laddr, raddr) |
建立 UDP 连接,可指定本地地址。 |
net.ListenIP(network, laddr) |
net.DialIP(network, laddr, raddr) |
建立 IP 连接,用于原始 IP 通信。 |
net.Listen(network, address) |
net.DialTimeout(network, address, timeout) |
建立连接,并设置超时时间。 |
net.Listen(network, address) |
net.DialContext(ctx context.Context, network, address) |
建立连接,并使用 Context 控制连接生命周期。 |
理解 Listen
和 Dial
的对应关系是进行网络编程的基础。希望以上信息能够帮助你更好地使用 Go 的网络库。
三、那些 Conn 和读写方法
现在我来详细介绍 net.Conn
接口的各种实现,它们的功能,以及发送和接收方法,并说明发送和接收的数据包类型。
3.1 net.Conn
接口:
net.Conn
是 Go 语言 net
包中定义的一个接口,它代表一个通用的面向流的网络连接。多个 Goroutine 可以同时调用 Conn
上的方法。它定义了基本的网络 I/O 操作:
type Conn interface {
Read(b []byte) (n int, err error)
Write(b []byte) (n int, err error)
Close() error
LocalAddr() Addr
RemoteAddr() Addr
SetDeadline(t time.Time) error
SetReadDeadline(t time.Time) error
SetWriteDeadline(t time.Time) error
}
Read(b []byte) (n int, err error)
: 从连接中读取数据到b
中。Write(b []byte) (n int, err error)
: 将b
中的数据写入连接。Close() error
: 关闭连接。LocalAddr() Addr
: 返回本地网络地址。RemoteAddr() Addr
: 返回远程网络地址。SetDeadline(t time.Time) error
: 设置读写操作的截止时间。SetReadDeadline(t time.Time) error
: 设置读操作的截止时间。SetWriteDeadline(t time.Time) error
: 设置写操作的截止时间。
3.2 net.TCPConn
TCP 连接。通过 net.DialTCP
或 net.Dial("tcp", address)
创建。
功能: 提供可靠的、面向连接的字节流传输。保证数据按序到达,并提供错误检测和重传机制。
发送/接收方法: 使用
Read()
和Write()
方法。数据包类型: 发送和接收的是 TCP segment(TCP 分段)。在网络传输过程中,TCP segment 会被封装在 IP packet 中,然后可能进一步封装在 Ethernet frame 中。
- 发送过程:
Write()
写入的 payload 数据会被 TCP 协议栈分段成 TCP segment,加上 TCP 头部(包含源端口、目标端口、序列号、确认号等),然后交给 IP 层封装成 IP packet,再交给数据链路层封装成 Ethernet frame。 - 接收过程: 接收端收到 Ethernet frame 后,依次解封装 IP packet 和 TCP segment,然后将 TCP segment 中的 payload 传递给
Read()
。
- 发送过程:
有效负载 (Payload):
Read()
返回的是 TCP segment 的 payload,即应用程序实际发送的数据。Write
写入的也是 payload 数据,不包含各种协议头。半关闭 (Half-close): TCP 连接支持半关闭,即可以单独关闭读或写方向。
CloseRead()
: 关闭连接的读方向。调用此方法后,无法再从连接中读取数据,但仍然可以向连接写入数据。CloseWrite()
: 关闭连接的写方向。调用此方法后,无法再向连接写入数据,但仍然可以从连接中读取数据。Close()
: 同时关闭连接的读和写方向。
3.3 net.UDPConn
UDP 连接。通过 net.DialUDP
或 net.Dial("udp", address)
创建。
功能: 提供无连接的、不可靠的数据报传输。不保证数据按序到达,也不提供错误检测和重传机制。
发送/接收方法: 使用
ReadFrom()
和WriteTo()
方法。数据包类型: 发送和接收的是 UDP datagram(UDP 数据报)。同样会被封装在 IP packet 和 Ethernet frame 中。
- 发送过程:
WriteTo()
写入的 payload 数据会被封装成 UDP datagram,加上 UDP 头部(包含源端口和目标端口),然后交给 IP 层封装成 IP packet,再交给数据链路层封装成 Ethernet frame。 - 接收过程: 接收端收到 Ethernet frame 后,依次解封装 IP packet 和 UDP datagram,然后将 UDP datagram 中的 payload 传递给
ReadFrom()
。
- 发送过程:
有效负载 (Payload):
ReadFrom()
返回的是 UDP datagram 的 payload。Write
写入的也是 payload 数据,不包含各种协议头。总结
- 使用
DialUDP
创建的客户端连接,可以使用Read()
和Write()
方法进行简单的读写操作。 - 使用
ListenUDP
创建的服务器连接,必须使用ReadFrom()
和WriteTo()
方法,以便处理不同的客户端地址。 ReadMsgUDP
和WriteMsgUDP
用于处理带外数据,通常用于传递控制信息。ReadFromUDPAddrPort
、WriteToUDPAddrPort
、ReadMsgUDPAddrPort
、WriteMsgUDPAddrPort
使用netip.AddrPort
类型,可以提高性能。
- 使用
3.4 net.IPConn
IP 连接(使用 IP 层的 rawsocket, AF_INET
)。通过 net.DialIP
或 net.ListenIP
创建。
功能: 允许直接发送和接收 IP 数据包,绕过 TCP 和 UDP 协议栈。用于实现自定义网络协议或进行网络诊断工具开发。
发送/接收方法: 使用
ReadFrom()
和WriteTo()
方法。数据包类型: 发送和接收的是 IP packet。可能进一步封装在 Ethernet frame 中。
- 发送过程:
WriteTo()
写入的数据会被封装成 IP packet,加上 IP 头部(包含源 IP 地址、目标 IP 地址、协议号等),然后交给数据链路层封装成 Ethernet frame。 - 接收过程: 接收端收到 Ethernet frame 后,解封装 IP packet,然后将 IP packet 传递给
ReadFrom()
。
- 发送过程:
有效负载 (Payload):
ReadFrom()
返回的是 IP packet 的 payload,这部分数据可能包含上层协议头部(例如 ICMP 头部)和更上层的数据。你需要根据 IP 头部中的协议号来判断 payload 的类型。总结
Read
方法族:
Write
方法族:
关键要点:
使用
net.IPConn
进行原始 IP 通信时, 必须(或强烈建议)在network
参数中指定协议(例如"ip4:ICMP"
)。你需要 自行构建完整的 IP 数据包(包括 IP 头部和任何上层协议头部)。Go 语言的
net
包不会自动添加 IP 头部。Read
和Write
方法 不应该 用于ListenIP
创建的连接。Write(b []byte)
: 不推荐 用于ListenIP
创建的连接。仅在DialIP
创建且已知对端地址时使用。需要自行构建完整的 IP 数据包。WriteTo(b []byte, addr Addr)
: 将 IP 数据包发送到指定地址。需要自行构建完整的 IP 数据包。WriteToIP(b []byte, addr *IPAddr)
: 将 IP 数据包发送到指定地址。需要自行构建完整的 IP 数据包。推荐使用。WriteMsgIP(b, oob []byte, addr *IPAddr)
: 发送 IP 数据包、带外数据。需要自行构建 IP 头部。Read(b []byte)
: 不推荐 用于ListenIP
创建的连接。仅在DialIP
创建且已知对端地址时使用。ReadFrom(b []byte)
: 读取 IP 数据包并返回发送方地址(Addr
接口类型)。ReadFromIP(b []byte)
: 读取 IP 数据包并返回发送方地址(*IPAddr
类型)。推荐使用。ReadMsgIP(b, oob []byte)
: 读取 IP 数据包、带外数据和标志。
3.5 net.PacketConn
面向数据包的连接。 net.PacketConn
代表一个通用的面向数据包的网络连接,例如 UDP 连接。
实现类型:
*net.UDPConn
、*net.UnixConn
(用于unixgram
和unixpacket
)等。读写方法:
ReadFrom(b []byte) (n int, addr Addr, err error)
: 从连接中读取一个数据包到b
中。返回读取的字节数n
、发送方地址addr
和可能的错误err
。WriteTo(b []byte, addr Addr) (n int, err error)
: 将b
中的数据包发送到地址addr
。返回写入的字节数n
和可能的错误err
。
数据包类型: UDP datagram(UDP 数据报)。
有效负载: 应用数据。
3.6 syscall.RawConn
原始连接。 net.RawConn
提供了对网络连接底层操作的访问,允许直接操作 socket 文件描述符。这通常用于高级网络编程,例如实现自定义网络协议或进行性能优化。
- 获取
net.RawConn
: 可以通过类型断言从net.TCPConn
、net.UDPConn
和net.IPConn
获取net.RawConn
。例如:
rawConn, err := conn.(*net.TCPConn).SyscallConn()
if err != nil {
// 处理错误
}
主要方法:
Control(f func(fd uintptr) error) error
: 允许你通过函数f
直接控制底层的 socket 文件描述符fd
。这可以用来设置 socket 选项、执行特定的系统调用等。Read(b []byte) (int, error)
: 从连接中读取数据。Write(b []byte) (int, error)
: 将数据写入连接。
使用
Control
方法进行高级操作:Control
方法接受一个函数f
作为参数,该函数接收一个uintptr
类型的参数fd
,它代表底层的 socket 文件描述符。你可以在f
中使用syscall
包执行底层的系统调用。例如,设置 socket 的SO_REUSEADDR
选项:
rawConn, err := conn.(*net.TCPConn).SyscallConn()
if err != nil {
// 处理错误
}
err = rawConn.Control(func(fd uintptr) error {
err := syscall.SetsockoptInt(int(fd), syscall.SOL_SOCKET, syscall.SO_REUSEADDR, 1)
if err != nil {
return err
}
return nil
})
3.7 各种 Conn 的读写数据行为
Go 语言 net
标准库中, net.Conn
接口有多种实现,针对不同的网络类型( network
), ReadXXX
和 WriteXXX
方法操作的数据类型有所不同。下面是一个列表,详细说明了在 network
为 udp
, tcp
, ip:udp
, ip:icmp
时, ReadXXX
和 WriteXXX
操作的数据类型:
Network | Conn 类型 | ReadXXX 数据类型 | WriteXXX 数据类型 | 说明 |
---|---|---|---|---|
udp |
*net.UDPConn |
UDP payload (用户数据) | UDP payload (用户数据) | 当使用 udp 时, ReadFrom 和 WriteTo 方法直接操作 UDP 数据报中的 payload 部分,不包含 UDP 头部。 |
tcp |
*net.TCPConn |
TCP payload (用户数据流) | TCP payload (用户数据流) | TCP 是面向连接的流式协议,操作的是 TCP 数据流,而不是单个的数据包。 |
ip:udp |
*net.IPConn |
UDP datagram (包含 UDP 头部和 payload) | UDP datagram (包含 UDP 头部和 payload) | 使用 ip:udp 创建的 IPConn , ReadFrom 和 WriteTo 操作的数据 包含 UDP 头部和 payload,但不包含 IP 头部。Go 会自动处理 IP 头部。这意味着你需要构建/解析 UDP 头部,但无需关心 IP 头部。 |
ip:icmp |
*net.IPConn |
ICMP message (包含 ICMP 头部和 data) | ICMP message (包含 ICMP 头部和 data) | 类似于 ip:udp , ip:icmp 操作的数据 包含 ICMP 头部和 data,但不包含 IP 头部。Go 会自动处理 IP 头部。你需要构建/解析 ICMP 头部,但无需关心 IP 头部。 |
接下来我介绍更准确的读写时是否包含 IP header。
3.8 IPConn 读取时
如果没有特殊设置,IPConn 使用 ReadFrom
读取数据时剥离头部的代码:
func (c *IPConn) readFrom(b []byte) (int, *IPAddr, error) {
// TODO(cw,rsc): consider using readv if we know the family
// type to avoid the header trim/copy
var addr *IPAddr
n, sa, err := c.fd.readFrom(b)
switch sa := sa.(type) {
case *syscall.SockaddrInet4:
addr = &IPAddr{IP: sa.Addr[0:]}
n = stripIPv4Header(n, b)
case *syscall.SockaddrInet6:
addr = &IPAddr{IP: sa.Addr[0:], Zone: zoneCache.name(int(sa.ZoneId))}
}
return n, addr, err
}
func stripIPv4Header(n int, b []byte) int {
if len(b) < 20 {
return n
}
l := int(b[0]&0x0f) << 2
if 20 > l || l > len(b) {
return n
}
if b[0]>>4 != 4 {
return n
}
copy(b, b[l:])
return n - l
}
这里 b[:n]
的格式是 udp header + payload。
使用 ReadMsgIP
时,数据中包含 ip header,这时 b[:n]
的格式是 ip header + udp header + payload。
3.9 IPConn 写入时
无论 WriteTo
还是 WriteMsgIP
,都无需(没有办法)在数据中增加 IP header。
for {
n, _, _, remote, err := conn.ReadMsgIP(buf, nil) // payload = ip header + udp header + udp payload
if err != nil {
panic(err)
}
fmt.Printf("received request from %s: %s\n", remote.String(), buf[28:n])
// conn.WriteTo(buf[20:n], remote) // udp header + udp payload
conn.WriteMsgIP(buf[20:n], nil, remote) // udp header + udp payload
}
3.10 IPConn 读写古怪行为的背后
为什么这么古怪呢?和 IP_HDRINCL
选项有关。
写入时是否需要 IP 头部?
写入时 需要 提供完整的 IP 头部。
你需要手动构造 IP 头部和传输层数据(如 UDP 头部和数据)。
这种模式通常用于需要完全控制 IP 头部的场景。
写入时 不需要 提供 IP 头部。
操作系统会自动为你构造 IP 头部。
你只需要提供 传输层数据(如 UDP 头部和数据)。
例如,如果你发送 UDP 数据包,只需要构造 UDP 头部和数据部分,操作系统会自动添加 IP 头部。
默认情况下(未设置
IP_HDRINCL
):设置了
IP_HDRINCL
选项:
读取时是否包含 IP 头部?
读取时仍然 包含 IP 头部。
原始套接字的行为在读取时不受
IP_HDRINCL
选项的影响。读取时 包含 IP 头部。
原始套接字会返回完整的 IP 数据包,包括 IP 头部和传输层数据(如 UDP 头部和数据)。
默认情况下:
设置了
IP_HDRINCL
选项:
如何设置
IP_HDRINCL
选项? 在 Go 中,可以通过syscall.SetsockoptInt
设置IP_HDRINCL
选项:
import "syscall"
// 创建原始套接字
fd, err := syscall.Socket(syscall.AF_INET, syscall.SOCK_RAW, syscall.IPPROTO_UDP)
if err != nil {
panic(err)
}
// 设置 IP_HDRINCL 选项
err = syscall.SetsockoptInt(fd, syscall.IPPROTO_IP, syscall.IP_HDRINCL, 1)
if err != nil {
panic(err)
}
下面的代码和上面的设置 IP_HDRINCL
选项的效果一样,因为它的内部实现就会设置这个选项:
_, err = ipv4.NewRawConn(conn)
if err != nil {
panic(err)
}
4. 总结
底层始终包含 IP 头部。
Go 在
ReadFrom
族方法中为 IPv4 剥离了 IP HeaderGo 在
ReadMsg
族方法中保留了 IP Header默认情况下,不需要提供 IP 头部。
设置
IP_HDRINCL
后,需要手动构造 IP 头部。写入时:
读取时:
虽然 net.IPConn 提供了丰富的功能,但是因为我们需要记住各种场景的各种设置,反而增加了我们的心智负担,我每次使用它的时候,都不得不查一下我的总结文档。所以你对它的 API 有什么更好的设计?欢迎评论区写出来。
四、ipv4 包中的那些 Conn
golang.org/x/net/ipv4
是 Go 语言的一个扩展包,提供了对 IPv4 协议的底层支持。它允许开发者更精细地控制 IPv4 数据包的处理,包括访问和修改 IP 头部、设置套接字选项等。这个包中的 Conn
、 PacketConn
和 RawConn
是对 net 包中相应接口的扩展,提供了更多与 IPv4 相关的功能。
4.1 ipv4.Conn
ipv4.Conn
是对 net.Conn
的扩展,提供了读写 TOS 和 TTL 的功能。
Conn
的定义如下:
type Conn struct {
// contains filtered or unexported fields
}
func NewConn(c net.Conn) *Conn
func (c *Conn) SetTOS(tos int) error
func (c *Conn) SetTTL(ttl int) error
func (c *Conn) TOS() (int, error)
func (c *Conn) TTL() (int, error)
常用方法
SetTOS(tos int) error
:设置 IP 头部的服务类型(Type of Service, TOS)。SetTTL(ttl int) error
:设置 IP 头部的生存时间(Time to Live, TTL)。TOS() (int, error)
: 获取 IP 头部的服务类型。TTL() (int, error)
: 获取 IP 头部的生存时间。
4.2 ipv4.PacketConn
ipv4.PacketConn
是对 net.PacketConn
的扩展,用于处理基于 IPv4 的面向数据报(如 UDP)的连接。
PacketConn
代表一个使用 IPv4 传输的数据包网络端点。它用于控制多个 IP 级别的套接字选项,包括组播。它还提供基于数据报的网络 I/O 方法,这些方法专门用于 IPv4 及更高层协议,例如 UDP。
PacketConn
的定义如下:
type PacketConn struct {
// 包含 net.PacketConn 的功能
}
func NewPacketConn(c net.PacketConn) *PacketConn
func (c *PacketConn) ReadBatch(ms []Message, flags int) (int, error)
func (c *PacketConn) ReadFrom(b []byte) (n int, cm *ControlMessage, src net.Addr, err error)
func (c *PacketConn) SetBPF(filter []bpf.RawInstruction) error
func (c *PacketConn) SetControlMessage(cf ControlFlags, on bool) error
func (c *PacketConn) SetTOS(tos int) error
func (c *PacketConn) SetTTL(ttl int) error
func (c *PacketConn) TOS() (int, error)
func (c *PacketConn) TTL() (int, error)
func (c *PacketConn) WriteBatch(ms []Message, flags int) (int, error)
func (c *PacketConn) WriteTo(b []byte, cm *ControlMessage, dst net.Addr) (n int, err error)
...
它好包括多播和广播操作和 ICMPFilter 的设置,这里就不具体介绍了。
ipv4.PacketConn
上面的方法介绍
func (c *PacketConn) ReadBatch(ms []Message, flags int) (int, error)
:批量读取多个 IP 数据包,提高接收效率。ms
参数是一个Message
类型的切片,用于存储接收到的数据包和控制信息,每个Message
包含数据、控制信息和地址信息。flags
参数控制读取操作的行为,通常使用 0。返回值是成功读取的消息数和可能的错误。适用于需要高性能批量接收数据包的场景。func (c *PacketConn) ReadFrom(b []byte) (n int, cm *ControlMessage, src net.Addr, err error)
:读取一个 IP 数据包,并接收相关的控制信息。b
参数是用于存储接收数据的字节切片。返回值包括:n
(读取的字节数)、cm
(指向接收到的控制信息的指针,可能为nil
)、src
(发送方地址)和err
(可能发生的错误)。适用于需要接收 IP 头部信息(如 TTL、源地址等)的场景。func (c *PacketConn) SetBPF(filter []bpf.RawInstruction) error
:设置 BPF (Berkeley Packet Filter) 过滤器,用于根据复杂的规则选择性地接收特定的数据包。filter
参数是包含 BPF 指令的切片。如果设置过滤器失败,则返回错误。适用于网络监控或入侵检测等需要复杂数据包过滤的场景。func (c *PacketConn) SetControlMessage(cf ControlFlags, on bool) error
:启用或禁用接收特定的控制消息。cf
参数是一个ControlFlags
类型的位掩码,用于指定要接收的控制消息类型,例如ipv4.FlagTTL
、ipv4.FlagSrc
、ipv4.FlagDst
、ipv4.FlagInterface
等。on
参数为true
时启用接收,为false
时禁用。如果设置失败,则返回错误。适用于需要接收 IP 头部特定字段的场景。func (c *PacketConn) SetTOS(tos int) error
:设置 IP 数据包的 TOS(Type Of Service)字段,用于指定数据包的服务类型。tos
参数是 TOS 值,取值范围为 0-255。如果设置失败,则返回错误。func (c *PacketConn) SetTTL(ttl int) error
:设置 IP 数据包的 TTL(Time To Live)字段,用于限制数据包在网络中的生存时间。ttl
参数是 TTL 值,取值范围为 0-255。如果设置失败,则返回错误。func (c *PacketConn) TOS() (int, error)
:获取当前连接的 TOS 值。返回值是 TOS 值和可能的错误。func (c *PacketConn) TTL() (int, error)
:获取当前连接的 TTL 值。返回值是 TTL 值和可能的错误。func (c *PacketConn) WriteBatch(ms []Message, flags int) (int, error)
:批量发送多个 IP 数据包,提高发送效率。ms
参数是一个Message
类型的切片,包含要发送的数据包和控制信息。flags
参数控制发送操作的行为,通常使用 0。返回值是成功发送的消息数和可能的错误。适用于需要高性能批量发送数据包的场景。func (c *PacketConn) WriteTo(b []byte, cm *ControlMessage, dst net.Addr) (n int, err error)
:发送一个 IP 数据包,并发送相关的控制信息。b
参数是要发送的数据的字节切片。cm
参数是指向要发送的控制信息的指针,如果不需要发送控制信息,则为nil
。dst
参数是目标地址。返回值包括:n
(发送的字节数)和err
(可能发生的错误)。适用于需要发送 IP 头部信息(如设置 TTL、TOS)或发送带外数据的场景。
除了实现 TTL 和 TOS 的操作外,你还需要注意四点:
ReadFrom
和WriteTo
操作的是 IP 包,包含 IP Header 和它的数据。ReadBatch
和WriteBatch
支持批量读写。SetBPF
可以设置 cBPF 代码。SetControlMessage
可以设置控制消息,在某些场景下还是很有用的。
4.3 ipv4.RawConn
ipv4.RawConn
是对 net.RawConn
的扩展,用于处理基于 IPv4 的原始套接字连接。
RawConn
代表一个使用 IPv4 传输的数据包网络端点。它用于控制多个 IP 级别的套接字选项,包括 IPv4 头部操作。它还提供基于数据报的网络 I/O 方法,这些方法专门用于处理直接操作 IPv4 数据报的 IPv4 及更高层协议,例如 OSPF 和 GRE。
RawConn
的定义如下:
type RawConn struct {
// 包含 net.RawConn 的功能
}
func NewRawConn(c net.PacketConn) (*RawConn, error)
func (c *RawConn) ReadBatch(ms []Message, flags int) (int, error)
func (c *RawConn) ReadFrom(b []byte) (h *Header, p []byte, cm *ControlMessage, err error)
func (c *RawConn) SetBPF(filter []bpf.RawInstruction) error
func (c *RawConn) SetControlMessage(cf ControlFlags, on bool) error
func (c *RawConn) SetTOS(tos int) error
func (c *RawConn) SetTTL(ttl int) error
func (c *RawConn) TOS() (int, error)
func (c *RawConn) TTL() (int, error)
func (c *RawConn) WriteBatch(ms []Message, flags int) (int, error)
func (c *RawConn) WriteTo(h *Header, p []byte, cm *ControlMessage) error
...
你可以看到 PacketConn
和 RawConn
的 ReadFrom
和 WriteTo
是不同的。
PacketConn
的读写的数据都是 IP 包,包含 IP Header 和它的数据RawConn
的读写数据将 IP Header 和数据分开开来了,独立处理