TCP 数据包可以分块到达接收方吗?
例如,如果我使用 TCP 协议发送 20 个字节,我是否可以 100% 确定我将一次收到 20 个字节,而不是先收到 10 个字节,然后再收到 10 个字节左右?
对于 UDP 协议,我也有同样的问题。
我知道 UDP 不可靠,数据包可能根本无法到达或以不同的顺序到达,但单个数据包呢?如果它到达了,我能确定它是一个完整的数据包,而不是一个碎片吗?
答案1
TCP 数据包能够分块到达接收方吗?
是的。IP 支持分段,但 TCP 通常会尝试确定路径 MTU,并使其数据包小于该值,以保证性能。分段会严重增加数据报丢失率。如果路径的数据包丢失率为 10%,则将数据报分段为两个数据包会使数据报丢失率接近 20%。(如果任何一个数据包丢失,数据报就会丢失。)
不过你不必担心这一点,TCP 层也不必担心。IP 层会将数据包重新组装成整个数据报。
例如:如果我使用 TCP 协议发送 20 个字节,我可以 100% 确定我将一次收到 20 个字节,而不是先收到 10 个字节,然后再收到 10 个字节左右吗?
不,但这与数据包无关。从根本上讲,TCP 是一种不保留应用程序消息边界的字节流协议。
对于 UDP 协议,我也有同样的问题。我知道 UDP 不可靠,数据包根本无法到达,或者到达的顺序不同,
UDP 也是如此。数据包就是数据包。不同之处在于 TCP 协议中内置了重试和重新排序功能,而 UDP 没有。
但是如果只有 1 个数据包呢?如果它到达,我能确定它是一个完整的数据包,而不是一个碎片吗?
不,但这不是你的问题。UDP 协议处理数据报重组。这是它工作的一部分。(事实上,IP 协议为 UDP 协议执行此操作,因此 UDP 只是通过位于 IP 之上的分层来实现此操作。)如果数据报被拆分为两个数据包,则 IP 协议将为 UDP 协议重组它,因此你将看到完整的数据。
答案2
您无法确定它们是否真的同时到达。如果愿意,TCP/UDP 下面的“数据链路层”可能会拆分您的数据包。特别是如果您通过互联网或任何您无法控制的网络发送数据,则很难预测这一点。
但无论数据是以一个数据包还是多个数据包的形式到达接收方,操作系统都应该抽象这些数据包的连接,因此对于您的应用程序来说,它仍然看起来像是所有内容同时到达。因此,除非您是内核黑客,否则在大多数情况下,您无需担心这些数据是以一个数据包还是多个数据包传输的。
对于 UDP,操作系统也会进行一些抽象,因此接收数据的应用程序不必知道数据被传输了多少个数据包。但与 TCP 的不同之处在于,无法保证数据确实到达。数据也可能被拆分成多个数据包,其中一些到达,而另一些则没有到达。对于接收应用程序来说,它看起来就像一个数据流,无论它是否完整。
答案3
示例。连续字符块对应于 send() 调用:
TCP:
Send: AA BBBB CCC DDDDDD E Recv: A ABB B BCC CDDD DDDE
所有发送的数据都按顺序接收,但不一定是相同的块。
UDP:
Send: AA BBBB CCC DDDDDD E Recv: CCC AA E
数据不一定按照相同的顺序,也不一定完全被接收,但消息会被完整地保留。
答案4
可以肯定的是,如果您在 TCP 流的最开始发送 20 个字节,它不会以两个 10 字节的片段到达。这是因为 TCP 堆栈不会发送如此小的段:存在最小 MTU 大小。但是,如果发送是在流的中间任何地方,则所有赌注都无效。可能是您的协议堆栈占用 10 个字节的数据来填充一个段并将其发送出去,然后接下来的 10 个字节进入另一个段。
协议栈将数据分成块并将它们放入队列中。块大小基于路径 MTU。如果您执行发送操作,并且仍有排队数据待处理,则协议栈通常会查看队列末尾的段,并查看该段中是否有空间添加更多数据。空间可能小到一个字节,因此即使是两字节的发送也可以分成两部分。
另一方面,数据的分段意味着可以进行部分读取。接收操作可以在只有一个段到达时被唤醒并获取数据。在广泛实施的套接字 API 中,接收调用可以请求 20 个字节,但它可能返回 10 个字节。当然,可以在其上构建一个缓冲层,该缓冲层将阻塞直到接收到 20 个字节或连接中断。在 POSIX 世界中,该 API 可以是标准 I/O 流:您可以使用fdopen
套接字描述符来获取FILE *
流,并且可以使用fread
它来填充缓冲区,以便通过尽可能多的调用来满足完整请求read
。
UDP 数据报构成数据。每次发送调用都会生成一个数据报(但请参阅下文关于 corking 的内容)。另一端会收到一个完整的数据报(并且在套接字 API 中,它必须指定一个足够大的缓冲区来容纳它,否则数据报将被截断)。大型数据报会因 IP 碎片而碎片化,并重新组装到应用程序上,而应用程序对此是透明的。如果任何片段丢失,整个数据报都会丢失;在这种情况下,无法读取部分数据。
接口中存在允许多个操作指定单个数据报的扩展。在 Linux 中,套接字可以被“塞住”(阻止发送)。当套接字被塞住时,写入的数据将被组装成一个单元。然后,当套接字被“解开塞住”时,就可以发送单个数据报。