TCP VS UDP 区别
2017-12-12 计算机网络
概述
TCP 是面向连接的,提供可靠交付、流量控制,拥塞控制,提供全双工通信,面向字节流(把应用层的数据看成字节流,然后根据 MSS 把字节流组织成大小不等的数据段,最后添加 TCP 首部),每个 TCP 连接只能是点对点(一对一)通信。
UDP 是无连接的,尽最大可能交付 (网络 IP 层),不提供流量控制,拥塞控制,面向报文(对于应用程序的数据不合并也不拆分,直接添加 UDP 首部),支持一对一、一对多、多对一和多对多 (也就是多播和广播) 通信。
上面这张图已经概括地很好了,下面就一些细节问题,结合自己的理解进行补充。
头部格式
TCP 头部格式
UDP 头部格式
在 UDP 协议头中,只有端口号、包长度和校验码,总共 8 个字节,小巧的头部给它带来了一些优势:
- 由于 UDP 头部长度还不到 TCP 头部的一半,所以在同样大小的 (IP) 数据包里,UDP 包携带的净数据比 TCP 包多一些
- 由于 UDP 没有 Seq 和 Ack 等概念,不需要维持一个连接,所以没有建立连接、关闭连接等额外的开销 (这个优势在 DNS 查询中体现得淋漓尽致)
性能
TCP 传输数据时,会有建立连接的三次握手和断开连接的四次 (或者三次) 挥手开销,在短连接业务场景中,建立连接和断开连接占据相当一大部分比重,所以 UDP 的性能要比 TCP 高。
但是如果在长连接业务场景中,当连接持续的时间很长,并且传输了大量的数据,TCP 的性能要比 UDP 高。
MTU
TCP 通信双方会告知对方自己的 MTU, 通信过程中发送的数据包大小以 MTU 较小的一方为基准。同时,TCP 可以避免应用数据被发送方 (网络层) 分片,因为应用数据到了传输层就会被 (TCP) 分片,然后再将切分好的小数据段交给网络层。其中,最大的分段大小称为 MSS(Maximum SegmentSize),它等于把 MTU 大小减去 IP 头部 + TCP 头部之后的大小,所以一个 MSS 正好可以装进一个 MTU 中。
$$ MSS = MTU - IP \ Header - TCP\ Header $$
但是有的时候 TCP 头部超过 20 字节,所以会侵占一些 MSS 的空间,比如下图的例子中就占用 12 字节作为 TCPOptions,那传输层真正用来承载数据的就剩下 1500-20-20-12=1448 字节了,这些字节数都能在 Wireshark 中抓包时都可以看到。
UDP 无视通信双方的 MTU 大小,获取到应用数据之后,添加上 UDP 头部后直接交给网络层。如果数据 MTU 大小的话,发送方的网络层负责分片 (数据允许分片的前提下),接收方的网络层再将分片后的包组装起来。相对 TCP 来说,数据分片再组装的过程会带来额外的性能消耗。
重传
TCP 要保证所有的数据包可以到达接受端 (传输可靠性),所以必须要有重传机制 (细节本文先省略,后面专门写一篇文章来说一说)。
UDP 没有重传机制,所以丢包需要由应用层来处理,所以健壮的 UDP 程序至少需要处理以下两个问题:
- 发送方在 RTO 时间内,没有收到接收方确认应答,应该重传报文
- 接收方确保确认应答和请求是正确匹配的
如果某项操作中的一个 (或多个) 数据包丢包了,应用层需要重传该操作中的所有数据包。相比之下,TCP 只需要重传丢失的数据包即可。在海量连接场景中,一个操作可能需要数十个包来完成,UDP 的劣势就会被放大。
但是在理想的网络传输下 (例如内网),数据传输 (几乎) 不会丢包,UDP 要优于 TCP。
不安全因素
网络层的数据分片机制存在弱点 (缺陷),会成为黑客的攻击目标。接收方组装分片的依据是,每个包里都有 More fragments = 1
的 Flag, 1 表示后续还有分片,0 则表示这是最后一个分片。
如果黑客持续快速地发送 More fragments = 1
的 UDP 包,接收方永远无法将这些包组装起来,最终内存耗尽。
多播和广播
UDP 还有一个明显的优势: 支持多播和广播,对于需要在内网中通知多个节点的场景,非常方便和高效。
而如果使用 TCP 实现多播和广播,就需要多个独立的连接才可以完成。
UDP 优化
- 增大套接字缓冲区大小以及 UDP 缓冲区范围;
- 增大本地端口号的范围;
- 根据 MTU 大小,(应用层主动) 调整 UDP 数据包的大小,减少或者避免分片的发生 (例如以太网中,UDP 包大小可以参考 1500-8-20 = 1472 这个值)
UDP VS DNS
由于历史的原因,互联网上物理链路的 最小 MTU = 576
,基于 UDP 传输的 DNS 为了限制报文不超过 576,所以将 DNS 报文限制在 512 字节
。一旦 DNS 查询应答超过 512 字节,基于 UDP 的 DNS 就只有截短为 512 字节,那么用户得到的 DNS 应答就是不完整的。
为了解决这个问题,最简单的方式就是使用 TCP 重新查询,虽然查询时间可能比较长,但可以得到完整的应答。
那么对大于 576 字节的数据传输,UDP 如何解决呢?答案:网络层数据分片。也就是说,只有 DNS 会限制 UDP 报文的长度,那么,当基于 UDP 传输的 DNS 有 1000 字节需要传输时,会将 1000 字节切成两个 500 字节的报文传输吗?不会!只会保留前面的 512 字节,剩下的 488 字节直接丢掉。