(五千字-值得背诵)一篇文章搞定几乎所有TCP/UDP面试题

739 阅读19分钟

无论是大厂面试还是小厂面试,以及平时工作学习中TCP/UDP协议都是我们绕不开的话题,本文的行文顺序为什么会是这样的?这是按照真实出现的面试题目来进行分章节的,也就是说每一个小标题都是一道面试题。本次让我们一起下定决心将这个问题彻底解决,加油!

一、TCP和UDP的区别

关于TCP和UDP的区别,我们可以考虑从以下六个维度出发进行阐述。

  1. 是否连接
    • TCP是面向连接的,UDP是面向无连接的
  2. 是否可靠
    • TCP是可靠的,UDP是不可靠的
  3. 连接对象的个数
    • TCP只支持一对一通信,而UDP支持一对一、一对多、多对一、多对多通信。
  4. 传输方式
    • TCP是面向字节流的,UDP是面向报文的。
  5. 首部开销
    • TCP首部开销大,最小20字节,但是UDP首部仅8字节。
  6. 应用场景
    • TCP适用于可靠传输的场景比如支付场景,UDP适合实时应用,例如视频会议直播。

二、TCP为什么是可靠的?

TCP主要靠以下六种机制来保证传输的可靠性。

1. TCP校验和

TCP校验和是一个端对端的校验和,由发送端计算,然后由接收端进行验证,其目的是为了发现TCP首部和数据在发送端到接收端之间发生的任何改动,如果接收方检测到校验和有差错,则TCP段会被直接丢弃。

2. 确认应答和序列号

确认应答指的是发送端每次发送数据后都会收到接收端的确认应答ACK,发送端只有接收到确认应答ACK之后才会继续发送数据,假如在特定时间内没有收到接收端的确认应答ACK,发送端会重新发送数据。序列号机制的存在是为了防止在有网络延迟的情况下发送端不断发送相同数据的情况发生。每当发送端发送数据时,会将序列号放入TCP的首部,然后接收端在序列号的基础上+1生成新的序列号,然后通过ACK传回给发送端,发送端会根据这个序列号来判断是否重新发送之前的数据。

3. 超时重传

超时重传就是在发送数据时,设定一个定时器,当超过指定的时间后,没有收到对方的ACK确认应答报文,就会重发该数据。

4. 连接管理

连接管理主要是指的三次握手和四次挥手,详细的情况请看本文的第四部分。

5. 流量控制

流量控制的目的是控制发送端的发送速度,使其按照接收端的数据处理速度来发送数据,避免接收端处理不过来,产生丢包或网络拥塞,TCP实现流量控制的关键是滑动窗口,发送端和接收端均有一个滑动窗口对应一个缓冲区,记录当前发送或接收到的数据,接收端会在返回的ACK报文中包含自己可用于接收数据的缓冲区的大小,在TCP的报文首部里用window表示,发送端发送的数据不会超过window的大小。

6. 拥塞控制

问题1:为什么要有拥塞控制?

如果网络出现拥堵,此时TCP还在继续重传数据,就会加重网络的负担,会导致更大的延迟以及更多的丢包。所以TCP为了避免这一现现象就设计了拥塞控制,目的就是避免发送方的数据填满整个网络。

问题2:什么是拥塞窗口?

上文我们提到了拥塞控制是为了避免发送发的数据填满整个网络,所以我们需要调节发送方发送的数据量,定义了一个叫做拥塞窗口的概念。

概念:拥塞窗口cwnd是发送方维护的一个状态量,它会根据网络的拥塞程度动态变化,有了拥塞窗口之后意味着发送窗口只能是拥塞窗口和接收窗口之间的最小值。

  • 拥塞窗口cwnd的变化规则:
  1. 只要网络中没有出现拥塞,cwnd就会增大。
  2. 一旦网络中出现拥塞,cwnd就会减少。

只要发送方没有在对丁的时间内接收到ACK确认应答报文就认为网络中出现了拥塞。

问题3:拥塞控制的主要算法都有哪些?

  1. 慢启动

慢启动指的是发送方每收到一个ACK,拥塞窗口cwnd的大小就会+1.当拥塞窗口大于等于慢启动门限的时候就会启动拥塞避免算法。慢启动的增长速率为指数增长。

  1. 拥塞避免
  • 拥塞避免的规则

每收到一个ACK确认,cwnd增加1/cwnd。

  • 例子

假如慢启动门限的值为16,当收到16个ACK确认的时候,cwnd才增加1。

  • 拥塞避免算法的意义

将原本慢启动算法的指数增长变为了线性增长,降低了增长速度。但是这样增长着还是会进入拥塞状态,此时就会进行重传,然后进入拥塞发生的情况。

  1. 拥塞发生

当网络发生拥塞的时候会启动重传机制,重传机制包括超时重传和快速重传,针对这两种重传方式有两种不同的拥塞发生算法。

  • 针对超时重传的拥塞发生算法

慢启动门限ssthresh变为cwnd/2,然后将cwnd置为1。然后重新开始慢启动,这种方式会突然减少数据流,造成网络卡顿。

  • 针对快速重传的拥塞发生算法

当接收方发现丢了一个包的时候,会发送三次前一个包的ACK,当发送端收到这三个ACK的时候,就会启动快速重传,不必等待超时重传。此时cwnd变为原来的一半,然后慢启动门限也等于这个cwnd,然后开始快速恢复算法。

  • 快速恢复算法的规则

拥塞窗口cwnd = 慢启动门限ssthresh + 3(之所以加3是因为发送端收到三个ACK确认报文,表明网络中少了三个报文,所以拥塞窗口可以+3),然后重传丢失的数据包,如果收到重复的ACK,cwnd+1,如果收到新的ACK,将cwnd置为原本的慢启动门限值,表明丢失的报文都已经收到,恢复过程已经结束,可以再次进入拥塞避免状态了。

拥塞控制的实例图

image.png

三、什么是滑动窗口?

1. 为什么要有滑动窗口?

当发送方一次性发送过多的数据给接收端,接收端是接收不过来的,所以发送端发送数据时必须限定在一定的容量内,这就是为什么要设置滑动窗口的原因(滑动窗口是一种流量控制策略,接收端告诉发送端一次最多可以发送多少数据)。

2. 滑动窗口中的四个基本概念

  • 发送方发送缓冲区内的数据可以被分为下面的四类:
  1. 已发送并收到ACK确认的数据。
  2. 已发送但未收到ACK确认的数据。
  3. 允许发送当尚未发送的数据。
  4. 发送窗口外发送缓存区内暂时不允许发送的数据。

3. 窗口滑动的移动规则

  • 接收端在未接收到接收窗口最左边的数据时是不会发送ACK确认包的,直到最左边接收到数据才会发送ACK确认包并向右移动。
  • 发送端在未收到发送窗口最左边的数据的ACK确认包时也是不会向右移动的。发送端根据自己收到的最大的ACK序号向右移动。

4. 发送方的发送窗口和接收方的接收窗口总是一样大的吗?

在同一时刻并不是,因为发送方可能根据当时的网络情况适当改变自己的发送窗口的尺寸,并且网络传送窗口值有一定的时延。

5. 窗口边沿的三种移动方式

  • 窗口合拢:指的是窗口左边沿向右边沿靠近,这种现象代表左边的数据被发送并确认。
  • 窗口收缩:指的是窗口右边沿向左移动,这种现象可能代表的是接收端通知发送端窗口变小。
  • 窗口张开:指的是窗口右边沿向右移动,这种现象代表了接收方读取了缓冲区里的数据。

四、TCP的三次握手与四次挥手

问题1. 为什么要进行三次握手?

因为TCP是面向连接的协议,所以使用TCP前必须先建立连接,而建立连接是通过三次握手来进行的。

问题2:三次握手的流程和状态变迁

  • 刚开始时,客户端和服务端都处于closed状态,先是服务端主动监听某个端口,处于listen状态。

  • 第一次握手:发送SYN报文。客户端会随机初始化一个序号x,然后将此序号置于TCP首部的序号字段中,然后把SYN标志置为1,然后把这个SYN报文发送给服务端,表示向服务端发起连接,该报文不包含应用层的数据,之后客户端处于SYN-SENT状态。

  • 第二次握手:发送SYN+ACK报文。服务端收到客户端的SYN报文后,服务端也会随机初始化一个序号y,这个y也是放入TCP首部的序号字段中,然后把TCP首部的确认应答号(ack)字段填入x+1,接着把SYN和ACK标志置为1,最后把该报文发送给客户端,该报文也不包含应用层数据。之后服务端处于SYN-RCVD状态。

  • 第三次握手:发送ACK报文。客户端收到服务端报文后,会将TCP首部的ACK标志位置为1,确认应答号ack置为y+1,序号置为x+1,然后把这个报文发送给服务端。这次报文可以携带应用层的数据,之后客户端处于established状态,服务器收到这个报文后也会进入established状态。

下图是博主绘制的TCP三次握手的流程图和状态变迁图,值得背诵。

image.png

问题3:为什么是三次握手而不是二次或者四次?

  • 常见不完整解释:三次握手可以确保通信的双方都具有收发数据的能力。

仅仅通过上面的解释1来回答面试官的问题是远远不够的,我们还需要给出下面的解释。

  • 正确解释:(三次握手可以避免以下问题)
  1. 三次握手可以避免历史连接。如果客户端发出连接请求,但是这个请求在传输中丢失了,客户端又重发了一遍,这一次收到了确认并建立了连接,数据传输完毕之后,关闭了这个连接,但是之前丢失的这个连接请求因为网络延迟的原因再次到达服务器端,服务器端以为是一个新的连接,于是向客户端发送确认报文段,同意连接。如果是二次握手,此时就已经建立了连接,但是客户端会忽略服务端发来的确认报文段,也不发送数据,但是服务器端却在一直等待来自客户端的数据,这样会造成数据浪费。如果是三次握手的话,当客户端收到历史连接的时候会发出终止的报文来终止连接。
  2. 三次握手可以同步双方初始序列号。TCP通信的双方都要确保初始序列号能被可靠的同步。这个序列号可以标识发送出去的数据包中哪些是已经被对方收到的。如果是二次握手的话只能保证一方的序列号被正确的接收,难以保证同步。

问题4:四次挥手的过程和状态改变

  • 断开连接之前,双方都处于连接状态也就是establised状态。
  • 第一次挥手:客户端发送一个FIN位置为1,序号seq为x的FIN报文,然后客户端进入FIN_WAIT_1状态。
  • 第二次挥手:服务端收到客户端的FIN报文后,会给客户端发送一个ACK报文,表示已经收到了,然后进入CLOSED_WAIT状态。客户端收到这个报文后进入FIN_WAIT2状态。
  • 第三次挥手:等到服务端处理完数据之后,也会向客户端发送一个FIN报文,然后服务端进入LAST_ACK状态。
  • 第四次挥手:客户端收到服务端发送过来的FIN报文,会向服务端发送一个ACK报文,表示自己收到了,然后客户端进入TIME_WAIT状态,服务端收到这个ACK报文之后就会进入CLOSED状态。至此服务器端关闭结束,经过2MSL之后客户端进入CLOSED状态,也关闭了。

下图是博主绘制的四次挥手的流程图和状态改变。

image.png

问题5:为什么是四次挥手而不是三次?

原因:服务端收到客户端发送的FIN包后,可能还有没有发送完的数据,所以先回复一个ACK确认包,表示已经收到了FIN包,等服务端将剩余数据发送完毕之后再发送一个FIN包给客户端表示已经没有要发送的数据了,可以结束了连接了。所以是四次挥手而不是三次。

问题6:如果客户端没有收到服务端返回的FIN包,客户端会一直等待吗?

答:并不会,因为如果没有收到服务器端的FIN包,会让客户端一直处于FIN_WAIT2状态,FIN_WAIT2没有时间限制,如果本端进入FIN_WAIT2如果,对端失去响应,本端也不会一直卡在FIN_WAIT2状态,因为还可以使用TCP keepalive计时器避免此问题。

问题7:什么是2MSL?TIME_WAIT状态为什么要设置2MSL?

  • 1MSL表示报文的最大生存时间,超过这个时间的报文将被丢弃。

之所以TIME_WAIT要设置为2MSL是因为第四次挥手的这个ACK确认包,服务器端未必能收到,如果收不到这个包进行重传的话,一来一回最多2MSL的时间,如果在这个时间都没有收到服务端的重传说明服务端已经收到了这个包,此时客户端就可以正常关闭了。

五、TCP/IP的四层网络模型

关于TCP/IP的四层网络模型主要包括以下的四个方面。

  • 应用层
  • 传输层
  • 网络层
  • 网络接口层

六、TCP的拆包与粘包问题

问题1:TCP为什么要进行拆包?(TCP是如何进行拆包的?)

  1. 要发送的数据大于TCP发送缓冲区剩余空间大小,将会发生拆包。
  2. 待发送数据大于MSS(最大报文长度),TCP在传输前将进行拆包。

问题2:什么是粘包?

TCP是面向字节流的协议,不会发送数据包,这个数据包是网络层的概念,应用程序交付给TCP的是结构化的数据,TCP会对这个结构化的数据做流式传输,流式传输最大的问题就是没有边界,没有边界就会造成数据粘在一起,这种粘在一起就是粘包,具体的概念可以理解为发送方发送的若干个数据包到接收方接收时粘在一起,从接收缓冲区看后一包数据的头紧接着前一包数据的尾。

问题3:什么情况下会发生粘包?

  1. 要发送的数据小于TCP发送缓冲区大小,TCP将多次写入缓冲区的数据一次发送出去,将会发生粘包。
  2. 接收数据的应用层没有及时读取接收缓冲区中的数据,将发生粘包。
  3. 由于TCP是有连接复用机制的,多个进程使用一个TCP连接,多种不同结构的数据进入TCP的流式传输也可能造成粘包现象。
  4. Nagle算法可能导致粘包,因为Nagle算法会将多个分组拼装为一个数据段发送出去,如果没有处理好边界,也可能会发生粘包问题。

问题4:如何解决粘包问题?

  1. 发送端给每个数据包添加包首部,首部中应该至少包含数据包的长度,这样接收端在接收到数据后,通过读取包首都的长度字段,便知道每一个数据包的实际长度了。
  2. 发送端将每个数据包封装为固定长度,这样接收端每次从接收缓冲区中读取固定长度的数据就自然而然的把每个数据包拆分开来。
  3. 在数据包之间设置边界,如添加特殊符号。

七、TCP与HTTP的区别

  1. TCP是传输层协议,定义的是数据传输和连接方式的规范,HTTP是应用层协议,定义的是传输数据的内容的规范。
  2. HTTP是无状态的短连接,TCP是有状态的长链接。
  3. HTTP协议是在TCP协议之上建立的,HTTP在发起请求时通过TCP协议建立起连接服务器的通道。

注意:从HTTP/1.1版本起,默认开启了Keep-Alive,保持连接性,当一个网页打开后,客户端和服务器端之间用于传输HTTP数据的TCP连接不会被关闭,如果客户端再次访问这个服务器上的网页,会继续使用这一条已经建立好的Keep-Alive,但是这个Keep-Alive会有一个保持的时间。

八、流量控制和拥塞控制的区别?

流量控制是由接收端控制的,拥塞控制是由发送端控制的,最终都是控制发送端的发送速率。

九、TCP/UDP的报文格式

image.png

下面对UDP报文格式字段进行解释

  • 源端口:这个字段是UDP报文头的前16位,代表的是发送这个数据报的应用程序所使用的UDP端口,这个字段是可选的,接收端根据这个字段发送响应。
  • 目的端口:接收端计算机上UDP应用程序使用的端口。
  • 长度:该字段也占据16位,表示的是UDP数据包的长度,包含UDP报文头和UDP数据长度。
  • UDP校验和:可以检验数据在传输中是否被损坏。

十、UDP应该如何解决可靠性的问题?

UDP不可靠的原因在于传输层无法保证数据的可靠传输,所以我们可以通过应用层来实现可靠传输,例如在应用层实现超时重传,提供确认序列号,滑动窗口等。

十一、Nagle算法与延迟确认机制

问题1:什么是Nagle算法?为什么要设置Nagle算法?

TCP总是希望尽可能的发送足够大的数据,Nagle算法就是为了实现尽可能的发送大块数据,避免网络中充斥着许多小数据块。Nagle算法的基本定义是任意时刻,最多只能有一个未被确认的小段,这个小段指的是小于MSS尺寸的数据块,所谓未被确认是指一个数据块发送出去之后没有收到对方的ACK确认。

问题2:Nagle算法的规则

  1. 如果数据报的长度达到了MSS,则允许发送。
  2. 如果数据包中包含有FIN,则允许发送。
  3. 如果设置了TCP_NODELAY选项,则允许发送。
  4. 如果没有设置TCP_CORK选项,且所有发出去的小数据包都被确认了,则允许发送。
  5. 如果上述四个条件都没有得到满足,但是发生了超时则立即发送。

问题3:什么是延迟确认机制?

TCP的接收端在收到报文后,不会立即发送ACK,而是等待一段时间发送ack,以便将ack和要发送的数据一起发送给发送端,同时这样做可以合并ACK,比如连续收到了两个TCP包,只要回复最终的ACK即可。但是这个延迟不会无限延长,在何时发送ACK时有如下规定:

  • 当有响应数据要发送时,ack会随着数据一块发送。
  • 如果此时没有响应数据要发送,会有一个延迟时间范围,如果在这个延迟时间范围内有响应数据要发送则随着这个数据一起发送,超过了这个时间范围则直接发送。
  • 如果在延迟期间内,新的数据到了,则直接发送ACK。

参考资料