写了这么多篇关于 TCP 和 UDP 的文章,还没有好好聊过这两个协议的区别,这篇文章我们就来开诚布公的谈一谈。
关于 TCP 和 UDP ,想必大家都看过一张这样的图。
有一个小姑娘在对着瓶口慢慢的喝水,下面写着可靠的传输,少女的衣服没有被水浸湿,这张图被称为 TCP 。
然后又有一个小姑娘在举着水瓶以很快的速度向下倒水,少女的头发凌乱,脸色泛红,衣服也被水浸湿,这张图被称为 UDP 。
这两张图我认为是个程序员都能大致总结出来这两个传输协议的不同点(毕竟图上都写的很清楚了)甚至不少同学对 UDP 产生了邪恶的念想,你说作者好好的画个图不行吗,非要在脸上挂个红,把衣服弄湿了才行。。。。。。。
咳,咱们言归正传,TCP 和 UDP 的区别一直是面试的重点,也是经常被用来拿来各种比较的两个协议。
TCP 建立连接需要经过三次握手,同时 TCP 断开连接需要经过四次挥手,这也表示 TCP 是一种面向连接的协议,这个连接不是用一条网线或者一个管道把两个通信双方绑在一起,而是建立一条虚拟通信管道。
TCP 的三次握手流程(客户端向服务器发送建立连接请求):
而 UDP 是面向数据报的协议,所以 UDP 压根不会有连接的概念,也就不会有三次握手建立连接的过程。
数据传输结束后,通信双方可以释放连接。数据传输结束后的客户端主机和服务端主机都处于 ESTABLISHED 状态,然后进入释放连接的过程。
(客户端主机主动关闭连接)
TCP 断开连接需要历经的过程如下
UDP 不存在这条连接,所以它也不需要四次挥手操作。
所以总结一点:TCP 是面向连接的,它的数据传输前需要维护一条虚拟连接,数据传输需要在这条虚拟连接上进行,数据传输完毕后需要断开这条连接,而 UDP 传输不是面向连接的,UDP 发送数据不会建立连接,也不会关心接收端的状态。
TCP 和 UDP 一个主要拿来作对比的就是可靠性,TCP 是一种可靠性的传输层协议,UDP 是一种不可靠的传输层协议。TCP 的这种可靠性主要由下面这些特征来保证:
通过序列号和应答号实现可靠性
计算机网络主机之间的相互通信非常类似于我们日常生活中两个人之间打电话,这种对话通常是一问一答形式,如果你讲了一句话并没有收到任何回应,你通常需要再说一次来确保对方是否听到,如果对方给你回应了一句话,就说明他已经听到你的讲话了,这就是一个完整的通话流程(抛开建立连接不谈,我们着重点放在建立连接之后)。
"对方给你的响应" 在计算机网络中被称为确认应答(ACK),TCP 就是通过 ACK 来实现可靠的数据传输,也就是说,发送方在发出请求之后会等待目标主机的响应,如果没有收到响应,发送方在经过一段时间后就会重传请求。所以,即使在发送过程中产生丢包,TCP 仍然能够通过重传来实现可靠性。
上面描述的情况属于发送方请求丢失,还有一种情况属于响应丢失,也就是说请求发送到目标主机后,目标主机会回发 ACK 给请求方,这个 ACK 也有可能丢失,如果 ACK 在链路中丢失,一段时间后请求方没有收到目标主机的 ACK ,仍然会选择重传未收到 ACK 的这个请求。
除了消息丢失之外,还存在一种延迟到达的现象,延迟到达指的是发送方发送一个报文段之后,这个报文也许是由于网络抖动或者网络拥堵导致一个报文段迟迟没有到达目标主机,或者目标主机的响应 ACK 迟迟没有到达发送方的现象。这个一段时间判断的标准就是重传时间,一旦过了重传时间发送方会重传报文段,很可能存在重传报文段到达之后,第一次发送的报文段才刚到的情况,这就存在一个问题:目标主机收到了两个相同的报文段。必须选择一个报文段进行丢弃,但是应该选择哪个报文段呢?
可以通过序列号(seq)来实现,序列号是按照顺序给发送数据的每一个字节都标上号码的编号。接收端通过查询 TCP 首部中的序列号和数据的长度,将自己下一步应该接收的序列号作为确认应答返送回去。通过序列号和确认应答号,TCP 能够识别是否已经接收数据,又能够判断是否需要接收,从而实现可靠传输。
如上图所示,请求按照顺序发送的话是 seq = 1 ,这个请求会把第 1 字节到第 n 字节的数据一起发送过去,等待目标主机一次确认每个字节后,再发送 seq = n + 1 的请求,确认完成后再发送 seq = m + 1 的请求,这样能够保证序列号不会重复。
UDP 没有所谓的序列号和确认号,所以不会对数据进行确认,数据丢失后也不会进行重传,所以 UDP 是一种不可靠的协议。
如果使用 TCP 和 UDP 来比喻开发人员:TCP 就是那种凡事都要设计好,没设计不会进行开发的工程师,需要把一切因素考虑在内后再开干!所以非常靠谱;而 UDP 就是那种上来直接干干干,接到项目需求马上就开干,也不管设计,也不管技术选型,就是干,这种开发人员非常不靠谱,但是适合快速迭代开发,因为可以马上上手!
我们上面说到,TCP 会对请求分开发送,每次请求所携带的数据都会被目标主机进行确认,目标主机依次确认每个请求后,就会对请求中的数据进行重组,由于请求是由 seq 的,所以 TCP 在重组这些数据时,也会按照顺序进行重组,而 UDP 没有有序性的这种保证。
TCP 和 UDP 同属于传输层协议,传输层协议传输的数据统称为报文段,TCP 和 UDP 的报文段的主要差异如下。
UDP 报文段结构
TCP 报文段结构
TCP 报文段结构相比 UDP 报文结构多了很多内容。但是前两个 32 比特的字段是一样的。它们是 源端口号 和 目标端口号。另外,和 UDP 一样,TCP 也包含校验和(checksum field) ,除此之外,TCP 报文段首部还有下面这些
所以从报文段结构的对比可以看出,TCP 相比 UDP 多了许多 Flags、序号和确认号,这些都属于 TCP 的连接控制。除此之外还有接收窗口,这些属于拥塞控制和流量控制的内容。TCP 的首部开销要比 UDP 大,因为 TCP 首部固定有 20 字节,UDP 首部固定才 8 字节。TCP 和 UDP 都提供了数据校验功能。
TCP 报文段的发送采用的是"一问一答"形式的,每个请求都会被目标主机确认后再发送下一条报文,效率很慢,后来为了解决这个问题,TCP 引入了 窗口 这个概念,即使在往返时间较长、频次很多的情况下,它也能控制网络性能的下降。
我们之前每次请求发送都是以报文段的形式进行的,引入窗口后,每次请求都可以发送多个报文段,也就是说一个窗口可以发送多个报文段。窗口大小就是指无需等待确认应答就可以继续发送报文段的最大值。
在这个窗口机制中,大量使用了 缓冲区 ,通过对多个段同时进行确认应答的功能。
如下图所示,发送报文段中高亮部分即是我们提到的窗口,在窗口内,即是没有收到确认应答也可以把请求发送出去。不过,在整个窗口的确认应答没有到达之前,如果部分报文段丢失,那么发送方将仍会重传。为此,发送方需要设置缓存来保留这些需要重传的报文段,直到收到他们的确认应答。
在滑动窗口以外的部分是尚未发送的报文段和已经接受到的报文段,如果报文段已经收到确认则不可进行重发,此时报文段就可以从缓冲区中清除。
在收到确认的情况下,会将窗口滑动到确认应答中确认号的位置,如上图所示,这样可以顺序的将多个段同时发送,用以提高通信性能,这种窗口也叫做 滑动窗口(Sliding window)。
UDP 发送的报文段不需要确认,也就没有窗口的概念,所以 UDP 传输效率比较高。
TCP 和 UDP 在效率、报文段、流量控制、连接管理上均存在差异,由于这些差异导致了应用场景要有不同的选择,由于 TCP 每个包都需要进行确认,因此 TCP 不适合告诉传输数据的场景,像是这种场景使用 UDP 就好了;像是 Ping 和 DNS Lookup,这类型的操作只需要一次简单的请求/返回,不需要建立连接,用 UDP 就足够了。比如 HTTP 协议需要考虑请求响应的可靠性,这种场景应该使用 TCP 协议,但是像 HTTP 3.0 这类应用层协议,从功能性上思考,暂时没有找到太多的优化点,但是想要把网络优化到极致,就会用 UDP 作为底层技术,然后在 UDP 基础上解决可靠性。