Golang中Net包:如何在已连接的UDP套接字上发送数据包
Golang中Net包:如何在已连接的UDP套接字上发送数据包
我有一个 Golang TCP 服务器,即 net.TCPConn,连接在一个端口上。除了 TCP 流之外,该服务器还需要接收 UDP 数据包并响应 UDP 数据包(原因如下所述)。传入的 UDP 数据包会在服务器端出现(通过 net.TCPConn.Read() 读取),但我不知道如何通过该套接字再次发送 UDP 数据包。所有的 UDP 写入方法仅适用于 net.UDPConn。net.UDPConn.WriteMsgUDP() 中提到了是否应用于已连接或未连接的套接字,这让我很感兴趣,但我不知道如何从 net.TCPConn 派生 net.UDPConn。我尝试过粗暴地将 net.TCPConn 类型断言为 net.UDPConn,但不出所料,这导致了 panic。
实现我的目标的正确方式是什么?
为什么我想这样做:这个 UDP 数据包的目的是测试该套接字上的连接(服务器只需将其回显)。该套接字是一个(希望是)已建立的 SSH 端口转发隧道,因此我不想使用另一个套接字,因为这样无法测试我想要测试的内容(即套接字和 SSH 隧道是否都处于打开状态;SSH 端口转发隧道的一个缺点是,由于应用程序连接到 localhost,套接字会立即报告已连接,即使服务器当时并未实际连接,因此需要进行此测试)。SSH 隧道其他情况下传输的是 TCP 流量,而我特别希望使用 UDP 进行测试,因为我不希望 UDP 连接测试被 TCP 流量队列阻塞。在这个应用中,时间非常重要,UDP 数据包携带时间戳以进行测量。在已连接的套接字上发送 UDP 数据包 是一个有效的套接字操作,Go 一定有办法实现它……?
更多关于Golang中Net包:如何在已连接的UDP套接字上发送数据包的实战教程也可以访问 https://www.itying.com/category-94-b0.html
嗯,你可以在同一个端口上监听UDP和TCP。
所以你可以让它们并行运行,也许这对你有帮助?
更多关于Golang中Net包:如何在已连接的UDP套接字上发送数据包的实战系列教程也可以访问 https://www.itying.com/category-94-b0.html
[下一页 →](/t/net-pkg-sending-a-udp-packet-on-a-connected-socket/8810?page=2)
这是一个来自TCP监听器的TCP套接字。传入的UDP数据包不可能出现在该代码部分。
"已连接的UDP套接字"与TCP套接字并不相同。
是的,我无法告诉你系统里发生了什么,但我可以告诉你没有发生什么——你没有在TCP套接字上接收UDP数据报。
请尝试使用 netcat -u $host $port(根据您的发行版可能是 nc 命令)连接您正在监听的 TCP 套接字。
您将不会看到任何信息!
那么让我换一个问题:当远程客户端通过已连接的UDP套接字发送数据包时(这是完全有效的套接字行为),在Golang中它应该出现在哪里?
在 TCP 连接上执行 sendto() 确实会发送 TCP 数据包。
正如我们之前所确认的,您所指出的 printf 输出源自对 TCP 套接字的读取操作。这里并不涉及任何未定义行为。
这是一个很好的测试,netcat -u 没有显示任何内容,而 netcat 显示了,所以当在 TCP 套接字上使用时,sendto() 变成了 send() 的别名。真希望文档里能说明这一点!看来没有办法实现我想要的功能了。得重新构思解决方案了。
感谢大家的帮助。
@calmh 已经告诉过你 SOCK_STREAM 是 TCP。
SOCK_STREAM 提供有序、可靠、双向、基于连接的字节流。 可能支持带外数据传输机制。
"基于连接"显然不是 UDP,所以只剩下 TCP。
我确实在同一个端口上同时打开了TCP和UDP监听器,但当我看到sendto()的结果出现在TCP端口上时,我假设UDP监听器被某种方式覆盖了。如果在这种情况下sendto()实际上发送的是TCP数据包,那情况就不同了。所以我想,在远端,我可以在同一个端口上打开UDP套接字,然后它就能实现我想要的功能?现在我们有点进展了 :-)。
我基于对此链接中sendto()函数描述的理解,认为通过已连接的UDP套接字发送数据包是完全可行的:
该链接明确说明了具体操作方法。再加上实际运行的printf()输出结果也证实了这一点。不过您并非第一个告诉我无法在已连接的UDP套接字上发送数据包的人:请问有人能提供支持这一说法的参考资料吗?
这是一个彻头彻尾的TCP套接字。甚至有数十条评论明确指出它是TCP,并且常量名称中也包含TCP。
gSocket = socket(AF_INET, SOCK_STREAM, 0);
SOCK_STREAM 表示TCP协议。
RobMeades: 当远程客户端通过已连接的套接字发送UDP数据包时(这是完全有效的套接字行为),在Golang世界中它应该出现在哪里?
在相应的UDP套接字上。但这里的情况并非如此。
但事实确实如此:我可以展示printf()的输出。当在Linux上用C语言编写的远端,在已连接的套接字上执行sendto()发送一个10字节的UDP数据包时(由于套接字已连接,目标地址为null),这个10字节的UDP数据包会从TCP套接字的Read()调用中弹出。
虽然我知道Golang使用了这个术语,但我不理解"已连接UDP套接字"这个概念:实际上并不存在这样的东西,因为UDP是无连接的。
已连接的 UDP 套接字来自 net.Dial("udp", ...) 或 net.DialUDP(...),其特性是具有隐式目标地址,你可以通过 Write() 向其发送数据包。它仍然是 *net.UDPConn 类型。
RobMeades: 传入的 UDP 数据包在服务器端出现(通过 net.TCPConn.Read())
我相当确定情况并非如此,因为 UDP 数据包不可能出现在 TCP 连接上。能否展示一下相关代码?
func main() {
fmt.Println("hello world")
}
当然。我还没有提交读取和响应UDP数据包的部分,因为它目前无法正常工作,但这一行是UDP数据包到达的位置:如果我在那里插入printf()函数,当我从远端发送UDP数据包时,可以清楚地看到我的UDP数据包到达。
为什么您认为UDP数据包到达已连接的套接字是不合理的?仅仅因为套接字已连接,并不意味着与无连接的UDP数据包传输有任何关联。它们随时都可以双向传输,这正是其设计特点。
你混淆了"已连接套接字"与"TCP"的概念。TCP连接确实是已连接的,并使用已连接的套接字。UDP套接字也可以"已连接",这仅表示它们被绑定到默认的写入目标。你完全可以在已连接的UDP套接字上发送UDP数据包。在接收端,没有区别也无法判断发送方是否使用了已连接的套接字。但你不能向TCP连接发送UDP数据包,也不能在TCP连接上发送UDP数据包,就像你不能将IPv4数据包发送到IPv6目的地,或者将圆钉插入方孔一样。它们根本是不同的协议。
那么printf()的出现是否只是Linux中某种未指定的行为,允许我在以SOCK_STREAM方式打开的套接字上使用sendto()?关于sendto()的文档说明:
如果在面向连接的套接字(
SOCK_STREAM、SOCK_SEQPACKET)上使用sendto(),参数dest_addr和addrlen将被忽略(当它们非NULL且不为0时可能会返回错误EISCONN),并且当套接字未实际连接时会返回错误ENOTCONN。
是否有可能以这种方式调用sendto()时,底层实际上发送的是TCP数据包而非UDP数据包?
为免误解,以下是打印输出:
Received 10 byte(s): []byte{0xae, 0x0, 0x0, 0x5, 0x69, 0xcd, 0x24, 0x1a, 0xf8, 0xbf}.
Received 10 byte(s): []byte{0xae, 0x1, 0x0, 0x5, 0x69, 0xcd, 0x24, 0x2a, 0x3b, 0x6}.
Received 10 byte(s): []byte{0xae, 0x2, 0x0, 0x5, 0x69, 0xcd, 0x24, 0x39, 0x7d, 0x29}.
Received 10 byte(s): []byte{0xae, 0x3, 0x0, 0x5, 0x69, 0xcd, 0x24, 0x48, 0xbf, 0x79}.
Received 10 byte(s): []byte{0xae, 0x4, 0x0, 0x5, 0x69, 0xcd, 0x24, 0x58, 0x1, 0x92}.
远端的C语言代码是:
sendto(gSocket, helloDatagram, sizeof(helloDatagram), 0, NULL, 0)
…其中 helloDatagram 包含一个 0xae 起始标记、一个递增的序列字节,以及一个64位微秒级时间戳。
在 Go 中,net.TCPConn 和 net.UDPConn 是不同的类型,不能直接相互转换,因为它们底层处理的是不同的传输层协议。你的需求是在已经建立的 TCP 连接上发送 UDP 数据包,这在网络协议层面是不可能的,因为 TCP 和 UDP 是独立的协议,具有不同的连接语义。
然而,从你的描述来看,你可能误解了 SSH 端口转发隧道的行为。SSH 隧道通常用于转发 TCP 流量,而不支持 UDP(除非使用特定扩展如 netcat 模式)。如果你在 SSH 隧道中尝试发送 UDP 数据包,它可能被丢弃或导致错误,因为 SSH 本身不处理 UDP。
如果你确实需要在同一网络地址上同时处理 TCP 和 UDP,正确的方法是使用两个独立的套接字:一个 net.TCPConn 用于 TCP 通信,另一个 net.UDPConn 用于 UDP。以下是一个示例代码,展示如何在同一个 Go 程序中创建 TCP 和 UDP 服务器,并响应 UDP 数据包:
package main
import (
"fmt"
"net"
"time"
)
func main() {
// 启动 TCP 服务器
go startTCPServer()
// 启动 UDP 服务器
go startUDPServer()
// 保持程序运行
select {}
}
func startTCPServer() {
listener, err := net.Listen("tcp", ":8080")
if err != nil {
fmt.Println("TCP Server error:", err)
return
}
defer listener.Close()
fmt.Println("TCP Server listening on :8080")
for {
conn, err := listener.Accept()
if err != nil {
fmt.Println("TCP accept error:", err)
continue
}
go handleTCPConnection(conn)
}
}
func handleTCPConnection(conn net.Conn) {
defer conn.Close()
buffer := make([]byte, 1024)
for {
n, err := conn.Read(buffer)
if err != nil {
fmt.Println("TCP read error:", err)
return
}
fmt.Printf("TCP received: %s\n", string(buffer[:n]))
// 处理 TCP 数据,例如回显
conn.Write(buffer[:n])
}
}
func startUDPServer() {
addr, err := net.ResolveUDPAddr("udp", ":8080")
if err != nil {
fmt.Println("UDP resolve error:", err)
return
}
conn, err := net.ListenUDP("udp", addr)
if err != nil {
fmt.Println("UDP listen error:", err)
return
}
defer conn.Close()
fmt.Println("UDP Server listening on :8080")
buffer := make([]byte, 1024)
for {
n, clientAddr, err := conn.ReadFromUDP(buffer)
if err != nil {
fmt.Println("UDP read error:", err)
continue
}
fmt.Printf("UDP received from %s: %s\n", clientAddr, string(buffer[:n]))
// 回显 UDP 数据包,包括时间戳
response := fmt.Sprintf("Echo at %v: %s", time.Now(), string(buffer[:n]))
_, err = conn.WriteToUDP([]byte(response), clientAddr)
if err != nil {
fmt.Println("UDP write error:", err)
}
}
}
在这个示例中:
- TCP 服务器监听端口 8080,处理传入的 TCP 连接。
- UDP 服务器也监听端口 8080,读取 UDP 数据包并回显,附加当前时间戳。
如果你坚持要通过同一个套接字发送 UDP 数据包,这在标准 Go net 包中不可行,因为 net.TCPConn 不提供 UDP 方法。你提到的 Linux sendto 系统调用用于 UDP 套接字,而不是 TCP 套接字。在 Go 中,你必须使用 net.UDPConn 来发送 UDP 数据包。
对于你的 SSH 隧道测试场景,考虑以下替代方案:
- 使用独立的 UDP 套接字进行测试,但通过相同的网络路径(例如,配置 SSH 隧道支持 UDP,如果可能)。
- 在应用层实现一个简单的基于 TCP 的“心跳”协议来测试连接状态,而不是依赖 UDP。
如果你必须使用 UDP 测试 SSH 隧道,请检查你的 SSH 客户端和服务器是否支持 UDP 转发(例如,通过 -w 选项在 OpenSSH 中创建 tun/tap 设备)。但这不是标准端口转发的一部分,可能需要额外配置。


