TCP 和 UDP
介绍一下 TCP 和 UDP,说说你对这两种协议的理解?
TIP
UDP 是指用户数据报协议,无论应用层交给 UDP 多长的报文,都会统统发送,一次发送一个报文。而接收方,也只是直接除去首部,然后把内容交给应用层就完成了任务。UDP 不会建立连接,出现丢包也不会重发,乱序到达也不会纠正,并且它是将应用程序发来的数据在收到的那一刻,立即按照原样发送到网络上,即使出现拥塞也无法控制。
TCP 是面向连接的、可靠的传输层通信协议,TCP 充分实现了数据传输时各种控制功能,也就丢包进行重发、对乱序的进行顺序控制,此外还有流量控制,进而实现了高可靠性的传输。
TCP 和 UDP 的区别有哪些?
UDP | TCP | |
---|---|---|
是否连接 | 无连接 | 面向连接 |
是否可靠 | 不可靠传输 不使用流量控制和拥塞控制 | 可靠传输(数据顺序和正确性) 使用流量控制和拥塞控制 |
连接对象个数 | 支持一对一 一对多 多对一 | 只能一对一 |
传输方式 | 面向报文 | 面向字节流 |
首部开销 | 首部开销小 仅8 字节 | 首部最小20 字节 最大60 字节 |
使用场景 | 实时应用 视频会议 直播 | 可靠传输的应用 文件传输 |
TCP 和 UDP 的使用场景
TCP
效率要求比较低,但是要求准度比较高,因为传输过程中要对数据进行确认、重发、排序等,例如文件传输(准确度高,可以牺牲速度),接收邮件,远程登录
UDP
效率要求高,但是要求准确度比较低,例如 qq
聊天,在线视频,网络语音电话(即时通讯 速度要求比较高,偶尔断续无所谓,不能使用重发机制),广播通信(广播,多播)
(追问)UDP 为什么不可靠?
TIP
UDP 在传输数据之前不需要先建立连接,远程主机在收到 UDP 报文后也不需要确认,提供不可靠的传输。
- 不保证消息交付:不确认、不重传、无超时
- 不保证交付顺序:不设置包序号、不重排、不会发生对头阻塞
- 不进行拥塞控制
- 不跟踪连接状态
TCP 是 如何实现可靠传输的呢?
TIP
首先,TCP 的连接是基于三次握手,而断开则是四次挥手。确保连接和断开的可靠性。 其次,TCP 的滑动窗口机制,记录了哪些数据发送了,哪些数据接收了,哪些没有,同时 TCP 还有超时重传的机制,以及流量控制和拥塞控制。
TCP 基于字节为单位的滑动窗口来实现可靠传输
TCP 协议在发送方维持了一个发送窗口,发送窗口以前的报文段是已经发送并确认的报文段,发送窗口中包含了已经发送但未确认的报文段和允许发送但还未发送的报文段,发送窗口以后的报文段是缓存中还不允许发送的报文段。
当发送方向接收方发送报文时,会依次发送窗口内的所有报文段,并设置一个定时器,这个定时器可以理解为超时重传计时器。
如果定时器时间内,收到了报文段的确认回答,则滑动窗口将会移动到确认报文段的后一个位置,如果还有已经发送但未确认的报文段,则重新设置定时器,如果没有了就关闭定时器。
如果定时器超时,则会重新发送所有已经发送但未确认的报文段,并将超时时间间隔翻倍
当发送方接收到了接收方的三个冗余确认应答后,则会触发快重传机制,在定时器结束前,发送已发未确认的报文段。
接收方采用的是累计确认的机制,对于按序到达的报文段,返回肯定的回答,如果是收到了乱序的,那么会直接丢弃,并返回一个最近的按序到达的报文段的序号。使用累计确认保证了返回的确认号前的报文段都是收到了,发送窗口可以移动了。
发送窗口的大小是变化的,它是由接收窗口剩余大小和网络中拥塞程度来决定的,TCP 就是通过控制发送窗口的长度来控制报文 段的发送速率。
(追问)TCP 是如何保证数据包传输的有序可靠的?
TIP
对字节流分段进行编号,然后通过 ACK 回复和超时重发机制来保证数据包传输的有序可靠的。
其实还是用滑动窗口,发送方把已发送的数据留在缓冲区内,如果接收方没有收到就重发。并且会为每一个已经发送的数据包启动一个超时定时器。在定时器超时前接收方收到了数据包,则会根据确认号,更改滑动窗口位置和大小。释放该数据包占用的缓冲区。
接收方收到数据包后,先进行 CRC 校验,如果正确则把数据交给上层协议,然后给发送方发送一个累计应答包,表达该数据已收到,如果接收方正好也有数据要发给发送方,应答包也可方在数据包中捎带过去。
TCP 的流量控制
(追问)在 TCP 滑动窗口协议中,如果接收方给发送方发送接收窗口大小报文段丢失了怎么办?
发送方一直等待不到接收方的接收窗口大小,就会一直保持窗口大小为 0 不变,而接收方也一直等待数据,如果没有措施,就造成了死锁的局面
TIP
TCP 为每一个连接设置一个持续计时器。
如果接收端将接收窗口大小设置为 0,发送端会启动一个持续计时器,定时器超时,就会发送一个零窗口探测报文,携带一字节的数据,接收方接收到后,会返回当前的接收窗口大小,如果接收窗口不是 0 就打破了死锁的局面
(追问)如果零窗口探测报文段丢失了怎么办?
零窗口探测报文段也有超时重传计时器,如果超时,会进行重传
TCP 的拥塞控制
- 慢开始、拥塞避免、快重传、快恢复
在发送端会维护一个拥塞窗口的状态变量,会根据网络拥塞程度来动态变化,如果网络没有拥塞,就会增大一些,如果出现拥塞就会减小一些
发送方会将拥塞窗口的大小作为发送窗口的大小
判断有没有出现网络拥塞的依据,没有按时收到应当到达的确认报文,发生了超时重传
发送方还会维护一个慢开始门限 ssthresh
状态变量,用来判断使用哪种算法进行拥塞控制
- 拥塞窗口小于
ssthresh
时,使用慢开始算法 - 大于时,使用拥塞避免算法
- 等于时,用哪个都可以
TCP 的慢开始算法
拥塞窗口一开始是 1,门限值是 16,收到一次报文确认后,增加为 2,发送 2 个报文段,再收到确认后,拥塞窗口增加 2,达到 4,发送 4 个报文段,收到确认,拥塞窗口增加 4 达到 8,按照这样下去,当达到门限值 16 时,接下来就是拥塞避免算法,每次收到确认只会 +1。
当拥塞窗口增大到一定值时,假设到了 24,如果此时出现了报文段的丢失,就会触发超时重传,发送方就会认为此时出现了拥塞,会进行以下工作
- 将门限值
ssthresh
设置为当前拥塞窗口的一半,即 12 - 将拥塞窗口设置为 1
(追问)如果只是出现了报文段的丢失,但是并不是由于拥塞导致的,也触发了慢开始算法,降低了传输效率,怎么办?
这就要说到快重传快恢复了,它可以让发送方尽早知道发生了个别报文段的丢失,而不是等到超时重传才知道。
- 要求发送方不要等待自己发送数据时再捎带确认,而是要立即发送确认
- 即使收到了失序的报文段,也要立即发出已收到报文的重复确认
- 收到三个连续的重复确认,就将相应报文段立即重传,而不是等待超时重传
简单说一下这个图
快重传的机制其实就是提前重传,不等到超时重传,那么我们就需要提前知道报文段丢失了。
再上面的图中,每次发送报文段,接收方都会进行确认,发送 1 号后,接收方发回针对 1 号报文段的确认。发送 2 号后,发回 2 号的确认,发送 3 号丢失了,继续发送 4 号,此时仍然会收到 2 号报文段的确认,继续发送 5 号,6 号,仍然收到 2 号报文段的确认,此时就会发现 3 号报文段丢失了,就会立即重传 3 号报文段,而不是等待超时重传。
在重传 3 号报文段,就知道只是丢失了个别的报文段,而执行快恢复算法:
- 将慢开始门限值和拥塞窗口大小调整为一半,开始执行拥塞避免算法
TCP 的重传机制
TCP 在发送一个数据后,就会开启一个定时器,若是在这段时间内没有收到发送数据的 ACK 确认报文,则对该报文进行重传,在达到一定次数后还没有成功就放弃并发送一个复位信号
TCP 使用两套独立的机制来完成重传,一是基于时间,二是基于确认信息。
一个 RTT 往返时间,指的是 TCP 发送报文段到确认报文段的往返时间。
(追问)如何选择合适的超时重传时间?
超时重传时间,简称 RTO。
- 如果 RTO 比较小,那很可能数据都没有丢失,就重发了,就会导致网络阻塞,会导致更多的超时出现
- 如果 RTO 比较大,那空闲时间就会变长
一般情况下,RTO 略大于 RTT,效果是最好的
(追问)如果 TCP 没有滑动窗口机制还能工作吗?
TIP
无法正常工作,滑动窗口允许发送方在一定时间内发送一定数量的数据,然后等待接收方的窗口机制来接收,如果发送方没有滑动窗口的情况下,发送数据,可能会导致接收方无法处理,导致 TCP 连接的中断
同时滑动窗口机制也是控制数发送和接收速度的关键,没有了滑动窗口,发送方会一直发送数据,导致接收方无法承载这样的数据量,导致中断。
TCP 三次握手的过程
TIP
三次握手主要时为了确认通信双方的接收和发送能力是否正常,指定自己的初始序列号为可靠传输做准备。实质上其实就是连接服务器指定端口,建立 TCP 连接,并同步连接双方的序列号和确认号,交换 TCP 窗口大小信息
怎么答
第一次握手是客户端发送 SYN 给服务端,客户端进入同步已发送状态,第二次握手是,服务端接收到 SYN,传回 ACK 确认信号,告知客户端自己接收到了请求,表示客户端能够正确发送请求,并且服务端请求建立连接发送 SYN 给客户端,服务端进入同步已接收状态。第三次握手,客户端接收到服务端的 SYN,传回 ACK 确认,告知服务端自己成功接收了请求,双方进入连接已建立状态。
一开始,客户端和服务端都处于 CLOSE 状态。先是服务端主动监听某个端口,处于 LISTEN 状态
- 第一次握手,客户端给服务端发送一个 SYN = 1 初始序号 seq = x 的报文,表示向服务端发起连接,该报文不能包含数据,客户端处于同步已发送状态
SYN = 1 的报文段不能携带数据,但要消耗掉一个序号。
- 第二次握手,服务端收到 SYN 报文后,服务端也会初始自己的序号,此时会将序号填入 TCP 首部的序号中,seq = y,然后把 TCP 首部确认应答号 x + 1 写入 ack,并把 SYN 和 ACK 标志位置为 1,最后把该报文发给客户端,服务端处于同步已接收状态
- 第三次握手,客户端收到服务端报文后,还要向服务端回应最后一个应答报文,也是一样把服务器的 ISN + 1 作为 ack 的值,表示已经收到了服务端的 SYN 报文,此时客户端处于链接已建立状态。服务器收到 ACK 报文之后,也处于 链接已建立状态,此时,双方已建立起了连接。
(追问) 为什么需要三次握手?
第一点是为了确保客户端和服务端双方的接收和发送能力均正常。
- 第一次握手:服务端收到客户端 SYN,能确保客户端发送正常,服务端确保自己正常接收
- 第二次握手:客户端收到 SYN 和 ACK,确保服务端能够正常发送,自己的发送和接收能力均正常
- 第三次握手:服务端收到客户端 SYN,服务端能确保自己发送和接收都正常
三次握手后,双方都知道自己能正常发送和接收
第二点是:防止已经失效的历史连接到达,这里指的是旧的 SYN 先到了
比如:第一次发送 SYN【seq = 10】然后客户端重启后,发送了新的 SYN【seq = 20】
基于三次握手:
- 旧的 SYN 先到了,服务端收到 seq = 10
- 服务端接收后,返回 ACK = 10 + 1 给客户端
- 客户端期望收到的是 20 + 1,会终止历史连接
- 然后断开
如果只是两次握手,SYN 到达服务端后,服务端就进入连接建立的状态
基于两次握手:
- 旧的 SYN 先到了,服务端收到 seq = 10
- 服务端接收后,返回 ACK = 10 + 1给客户端,进入连接建立的状态
- 客户端比较后,发送 RST 报文,终止连接
- 服务端收到后,终止,浪费了本次建立的连接
超时重传的旧报文,被服务端正常接收了
第三点:保证双方的初始序列号都能被接收
序列号很重要,保证了数据能够按序送达,并且在自己的滑动窗口中,可以得知自己成功发送了那部分的数据【通过对方的ACK】
所以一个 seq,就要有对应的 ack 来回应,而如果只有两次握手的话,只能保证一方的初始序列号能接收到 ACK
(追问)TCP 握手为什么是三次,不能是两次?不能是四次?
首先是为什么不能两次握手,因为在两次握手的情况下,服务端没有中间状态给客户端来阻止历史连接,导致服务端可能建立一个历史连接,造成资源浪费。
如果只是两次握手,SYN 到达服务端后,服务端就进入连接建立的状态。
但是如果客户端第一次发送的 SYN 报文超时了,客户端会重发 SYN 报文,这里会出现 2 种出现问题情况:
- 服务端先收到了重传的 SYN 报文
- 服务端先收到了第一次的 SYN 报文
如果是第一种情况,服务端会正常的建立起连接,因为它们的 seq 都是正确的,在连接建立完成,并关闭后,如果再次收到了第一次超时的那个报文,服务端会当做是一个新的连接,然后发送 ACK 给客户端,但是客户端并没有发送 SYN 报文,所以服务端会一直等待,直到超时。
如果是第二种情况,服务端接收到第一次的 SYN 报文,直接回复 ACK,进入连接已建立的状态,然后客户端收到 ACK 后,发现不是期望的那个,就会发送 RST 终止连接
不管哪种都会导致资源的浪费
TCP 不会为没有数据的 ACK 超时重传。
那么如果是 四次呢?
四次握手其实也能够可靠的同步双方的初始化序号,但由于第二步和第三步可以优化成一步,所以就成了「三次握手」。
第二次握手,其实可以拆分为:
- 先发送确认报文段:ACK=1,ack=x+1
- 再发送同步报文段:SYN=1,seq=y
(追问)第一次握手丢失了,会发生什么?
第一次握手丢失后,会触发超时重传机制,重传 SYN 报文,而且重传的 SYN 报文的序列号是一样的。
每次重传的间隔时间是上次的两倍,在一定次数的重传过后,还没有收到对方的 SYN 报文,就会放弃连接。
(追问)第二次握手丢失了,会发生什么?
当服务端收到客户端的第一次握手后,就会回 SYN-ACK 报文给客户端,这个就是第二次握手,此时服务端会进入 同步已接收 状态。
如果客户端迟迟没有收到第二次握手,客户端会认为是自己的 SYN 报文丢失了,客户端会触发超时重传第一次的 SYN 报文。同时服务器端会因为收不到第三次握手,于是服务器会触发超时重传机制,重传 SYN-ACK 报文。
(追问)第三次握手丢失了,会发生什么?
第三次握手是,双方连接已建立状态。
因为这是对第二次握手 SYN 的确认报文,所以,如果第三次握手丢失了,服务端那一方迟迟收不到这个确认报文,就会触发超时重传机制,重传 SYN-ACK 报文,直到收到,或者达到最大重传次数。
ACK 报文是不会有重传的,当 ACK 丢失了,就由对方重传对应的报文。
TCP 四次挥手的过程
TIP
首先客户端和服务端双方都处于连接已建立的状态
- 客户端想要关闭连接,就会发送一个 FIN 报文,进入终止等待1 状态。
- 服务端收到 FIN 报文后,会发送一个 ACK 确认报文,服务端进入关闭等待状态
- 客户端收到 ACK 后,会进入 终止等待2 状态
- 在这期间服务端还可以继续发送数据,因为有一些数据可能还没有发送完,在发送完毕后,服务端会发送 FIN 报文,进入最后确认状态
- 客户端在收到 FIN 报文后,会发送 ACK 确认报文,进入 TIME_WAIT 状态
等待 2MSL 时间后,客户端进入 CLOSED 状态,服务端进入 CLOSED 状态
可以看到每个方向都需要一个 FIN 和一个 ACK,因此通常被称为四次挥手。
(追问)为什么需要四次挥手呢?
TIP
服务器收到客户端的 FIN 报文时,内核会马上回一个 ACK 应答报文,但是服务端应用程序可能还有数据要发送,所以并不能马上发送 FIN 报文,而是将发送 FIN 报文的控制权交给服务端应用程序
我没啥要说的了。 我知道了 我也没什么要说了 好,知道了
回顾一下
- 关闭连接时,客户端向服务端发送 FIN 时,仅仅表示客户端没有数据要发送了,但是仍然可以接收数据。
- 服务端收到后,先回 ACK 确认,而服务端可能还有数据需要处理和发送,等服务端不需要发送数据时,才发送 FIN 报文给客户端同意关闭连接
服务端通常需要等待完成数据的发送和处理,所以服务端 ACK 和 FIN 都会分开发送
(追问)为什么需要 2MSL 时间呢?
MSL 是 Maximum Segment Lifetime,报文最大生存时间
TIP
1 个 MSL 保证 4 次挥手种主动关闭最后的 ACK 报文能最终到达对方
1 个 MSL 保证对方没有收到 ACK 那么进行重传的 FIN 报文也能够收到
(追问)为什么需要 TIME_WAIT 状态?
主动发起关闭连接的一方会有最后的时间等待
需要 TIME-WAIT 状态,主要是两个原因:
- 防止历史连接中的数据,被后面相同四元组的连接错误的接收;
- 保证「被动关闭连接」的一方,能被正确的关闭;
如果最后一个 ACK 报文丢失了,对方会一直重传 FIN 报文,但是如果此时对方已经关闭了,就一直收不到 ACK 报文,就会一直重传 FIN 报文,直到超时。