为什么我在 C++ 服务器上看到奇怪的 UDP 碎片?
Why do I see strange UDP fragmentation on my C++ server?
我已经用 C++ 构建了一个 UDP 服务器,对此我有几个问题。
目标:
我有传入的 TCP 流量,我需要将其作为 UDP 流量进一步发送。然后我自己的 UDP 服务器处理这个 UDP 数据。
TCP 数据包的大小可能会有所不同。
详情:
在我的示例中,我有一个总共包含 2000 个字节 (4 random bytes, 1995 'a' (0x61) bytes and the last byte being 'b' (0x62)
) 的 TCP 数据包。
我的 UDP 服务器有一个缓冲区 (recvfrom buffer
),其大小大于 2000 字节。
我的 MTU 大小到处都是 1500。
我的服务器正在正确接收此数据包。在我的 UDP 服务器中,我可以看到接收到的数据包长度为 2000,如果我检查最后一个字节 buffer[1999]
,它会打印 'b' (0x62),这是正确的。但是,如果我打开 tcpdump -i eth0
,我只会看到一个 UDP 数据包:09:06:01.143207 IP 192.168.1.1.5472 > 192.168.1.2.9000: UDP, bad length 2004 > 1472
。
使用 tcpdump -i eth0 -X
命令,我看到了数据包的数据,但只有 ~1472 字节,其中不包括 'b' (0x62) 字节。
ethtool -k eth0
命令打印 udp-fragmentation-offload: off
.
所以我的问题是:
- 为什么我只看到一个数据包而不是两个(第 1 部分和第 2 部分的碎片)?
- 为什么我在 tcpdump 中看不到 'b' (0x62) 字节?
- 在我的 C++ 服务器中,最好使用多大的缓冲区?我现在在 65535 上有它,因为传入的 TCP 数据包可以是任何大小。
- 如果大小超过 65535 字节会发生什么情况,我是否必须在将 TCP 数据包作为 UDP 发送之前制定自己的分片方案?
The size of the TCP packets can vary.
虽然上面的句子没有显示代码,但您的描述表明您对 TCP 工作原理的假设是错误的。
与UDP相反,TCP不是基于消息的协议而是字节流。这尤其意味着它不保证发送方的单个 send
将与接收方的单个 recv
匹配。因此,即使 send
完成了 2000 个字节,第一个 recv
仍然可能只得到 1400 个字节,而另一个 recv
将得到其余的 - 不管是否一切都适合立即套接字缓冲区。
好的,问题中出现的情况更复杂,从您的评论中提取了以下可用信息:
- 一些客户端通过 TCP 向服务器发送数据——两者都不可修改。
- 不过,两者之间有一个防火墙,只允许通过 UDP 与服务器进行单向通信。
- 您现在打算实现一个代理,该代理由位于其间的两个服务器组成,并通过 UDP 传输 TCP 数据。
- 服务器无法向后回复也不构成问题。
我个人的做法如下:
- 让代理服务器完全无数据感知!让出站接收方接受(
recv
或 recvfrom
取决于可用的单个或多个客户端)适合 UDP 数据包的数据块,并简单地按原样转发它们。
- 应用一些方法来确保至少检测到丢失的数据,更好的是可以重建丢失的数据。由于防火墙的限制,确认或重新请求消息是不可能的,但是提高可靠性的唯一机会是通过冗余。
- 将最终目标服务器配置为仅侦听环回。
- 让入站代理通过 TCP 连接到目标服务器,只要没有(不可恢复的)错误发生就按原样转发任何传入数据。
为了能够检测丢失的消息,我至少会在发送的 any UDP 消息前添加一个数据包计数器。如果两个后续消息未提供连续的计数器值,则消息在其间丢失。
由于不可能进行反向通信,因此提高可靠性的唯一方法是无条件冗余,以部分传输速率换取,例如通过发送 每条 消息不止一次并在接收端忽略多余的重复项。
一种更精细的方法可能会将冗余数据分布在多个数据包上,这样就可以从剩余的数据包中重建丢失的数据——可能类似于 RAID level 5 所做的。承认,你需要非常投入地尝试......
最后一个问题是路由的外观。 UDP 无法保证数据包的接收顺序与发送顺序相同。如果实际上只有一个修复路由可以通过防火墙从出站代理到入站代理,那么数据包不应该相互超越——您可能仍然希望至少适当地记录到文件以监控入站 UDP 数据包,并在发生错误的情况下应用适当的意味着(缓冲数据包并在需要时重新排序)。
我已经用 C++ 构建了一个 UDP 服务器,对此我有几个问题。
目标:
我有传入的 TCP 流量,我需要将其作为 UDP 流量进一步发送。然后我自己的 UDP 服务器处理这个 UDP 数据。 TCP 数据包的大小可能会有所不同。
详情:
在我的示例中,我有一个总共包含 2000 个字节 (4 random bytes, 1995 'a' (0x61) bytes and the last byte being 'b' (0x62)
) 的 TCP 数据包。
我的 UDP 服务器有一个缓冲区 (recvfrom buffer
),其大小大于 2000 字节。
我的 MTU 大小到处都是 1500。
我的服务器正在正确接收此数据包。在我的 UDP 服务器中,我可以看到接收到的数据包长度为 2000,如果我检查最后一个字节 buffer[1999]
,它会打印 'b' (0x62),这是正确的。但是,如果我打开 tcpdump -i eth0
,我只会看到一个 UDP 数据包:09:06:01.143207 IP 192.168.1.1.5472 > 192.168.1.2.9000: UDP, bad length 2004 > 1472
。
使用 tcpdump -i eth0 -X
命令,我看到了数据包的数据,但只有 ~1472 字节,其中不包括 'b' (0x62) 字节。
ethtool -k eth0
命令打印 udp-fragmentation-offload: off
.
所以我的问题是:
- 为什么我只看到一个数据包而不是两个(第 1 部分和第 2 部分的碎片)?
- 为什么我在 tcpdump 中看不到 'b' (0x62) 字节?
- 在我的 C++ 服务器中,最好使用多大的缓冲区?我现在在 65535 上有它,因为传入的 TCP 数据包可以是任何大小。
- 如果大小超过 65535 字节会发生什么情况,我是否必须在将 TCP 数据包作为 UDP 发送之前制定自己的分片方案?
The size of the TCP packets can vary.
虽然上面的句子没有显示代码,但您的描述表明您对 TCP 工作原理的假设是错误的。
与UDP相反,TCP不是基于消息的协议而是字节流。这尤其意味着它不保证发送方的单个 send
将与接收方的单个 recv
匹配。因此,即使 send
完成了 2000 个字节,第一个 recv
仍然可能只得到 1400 个字节,而另一个 recv
将得到其余的 - 不管是否一切都适合立即套接字缓冲区。
好的,问题中出现的情况更复杂,从您的评论中提取了以下可用信息:
- 一些客户端通过 TCP 向服务器发送数据——两者都不可修改。
- 不过,两者之间有一个防火墙,只允许通过 UDP 与服务器进行单向通信。
- 您现在打算实现一个代理,该代理由位于其间的两个服务器组成,并通过 UDP 传输 TCP 数据。
- 服务器无法向后回复也不构成问题。
我个人的做法如下:
- 让代理服务器完全无数据感知!让出站接收方接受(
recv
或recvfrom
取决于可用的单个或多个客户端)适合 UDP 数据包的数据块,并简单地按原样转发它们。 - 应用一些方法来确保至少检测到丢失的数据,更好的是可以重建丢失的数据。由于防火墙的限制,确认或重新请求消息是不可能的,但是提高可靠性的唯一机会是通过冗余。
- 将最终目标服务器配置为仅侦听环回。
- 让入站代理通过 TCP 连接到目标服务器,只要没有(不可恢复的)错误发生就按原样转发任何传入数据。
为了能够检测丢失的消息,我至少会在发送的 any UDP 消息前添加一个数据包计数器。如果两个后续消息未提供连续的计数器值,则消息在其间丢失。
由于不可能进行反向通信,因此提高可靠性的唯一方法是无条件冗余,以部分传输速率换取,例如通过发送 每条 消息不止一次并在接收端忽略多余的重复项。
一种更精细的方法可能会将冗余数据分布在多个数据包上,这样就可以从剩余的数据包中重建丢失的数据——可能类似于 RAID level 5 所做的。承认,你需要非常投入地尝试......
最后一个问题是路由的外观。 UDP 无法保证数据包的接收顺序与发送顺序相同。如果实际上只有一个修复路由可以通过防火墙从出站代理到入站代理,那么数据包不应该相互超越——您可能仍然希望至少适当地记录到文件以监控入站 UDP 数据包,并在发生错误的情况下应用适当的意味着(缓冲数据包并在需要时重新排序)。