关于TCP的连接与释放,面试官想听到什么?

208 阅读11分钟

你好,我是南一。这是我在准备面试八股文的笔记,如果有发现错误或者可完善的地方,还请指正,万分感谢🌹

开启掘金成长之旅!这是我参与「掘金日新计划 · 2 月更文挑战」的第 8 天,点击查看活动详情

一个数据包的“旅程”

TCP首部部分字段意义

  • 确认号:占4个字节,是期望收到对方下一个报文段的第一个数据字节的序号总之,若确认号 = N,则表明:到序号 N - 1 为止的所有数据都已正确收到
  • 确认ACK:仅当ACK = 1时确认号字段才有效。当ACK = 0时,确认号无效。TCP 规定,在连接建立后所有传送的报文段都必须把ACK置为1
  • 同步 SYN:在建立连接时用来同步序号。当SYN = 1ACK = 0时,表明这是一个连接请求保文段。对方若同意建立连接,则在响应的报文段中使 SYN = 1ACK = 1.因此 SYN1就表示这是一个连接请求或连接接受报文
  • 终止 FIN:用来释放一个连接。当FIN = 1时,表明此报文段的发送方的数据已经发送完毕,并请求释放运输连接

TCP 连接的建立

TCP 连接建立的过程叫握手,握手需要在客户端和服务器之间交换三个TCP报文段。

Snipaste_2022-10-30_15-09-53.png

上图,主机A运行的是TCP客户端程序,主机B运行的是TCP服务器程序。最初两端的TCP进程都处于CLOSED(关闭)状态。图中主机下方框表示TCP进程所处的状态。

​ 一开始,BTCP 服务器进程先创建传输控制块 TCB,准备接受客户进程的连接请求。然后服务器进程就处于LISTEN(收听)状态,等待客户端的连接请求。如有,即做出响应。

ATCP 客户进程也是首先创建传输控制块 TCB。然后,在打算建立 TCP 连接时,向 B 发出连接请求报文段,这时首部中的同步位 SYN = 1,同时选择一个初始序号 seq = xTCP 规定, SYN 报文段(即 SYN = 1 的保文段)不能携带数据,但要消耗掉一个序号。这时,TCP客户进程进入 SYN-SENT(同步已发送)状态。

B 收到连接请求报文段后,如果同意建立连接,则向A发送确认。在确认报文段中应把SYN位和ACK位都置1,确认号是ack = x + 1,同时也为自己选择一个初始序号 seq = y。请注意,这个报文段也不能携带数据,但同样要消耗一个序号。这时TCP服务器进程进入SYN-RCVD(同步收到)状态。

TCP 客户进程收到 B 的确认后,还要向 B 给出确认。确认报文段 ACK1,确认号 ack = y + 1,而自己的序号seq = x + 1TCP 标准规定,ACK报文段可以携带数据,但如果不携带数据则不消耗序号,在这种情况下,下一个报文段的序号仍是seq = x + 1。这时,TCP 连接已经建立,A 进入ESTABLISHED(已建立连接)状态。

​ 当 B 收到 A 的确认后,也进入ESTABLISHED(已建立连接)状态。

面试版回答:

  1. 开始客户端服务端都处于CLOSED状态,服务端主动监听某个端口,进入LISTEN状态
  2. 客户端发送TCP连接请求,随机生成序列号,发送SYN报文(第一次握手),进入SYN-SENT状态。
  3. 服务端接收到SYN报文,将客户端的序列号+1作为确认号,随机生成序列号,发送SYN-ACK报文(第二次握手),进入SYN-RCVD状态。
  4. 客户端收到SYN-ACK报文,序列号+1,将服务端的序列号+1作为确认号,发送ACK报文(第三次握手),进入establish状态。
  5. 服务端收到ACK报文,进入establish状态。

三次握手的作用是什么?

  1. 为了确认客户端和服务器双方的发送和接收功能是否正常。
  2. 指定自己的初始化序列号

为什么三次握手才能确认双方的发送和接收功能是否正常,而两次不行?

第一次握手,客户端发送数据包,服务端收到了,服务端得出结论:客户端发送功能正常。

第二次握手,服务端发送数据包,客户端收到了,客户端得出结论:服务端发送和接受功能正常。

此时服务端还不知道客户端的接收功能是否正常,因此需要第三次握手

第三次握手,客户端发送数据包,服务端收到了,服务端得出结论:客户端的接收功能正常。

初始化序列号是固定的吗?

是随机的。防止被攻击者猜出。

你知道半链接队列吗?

第一次握手,服务端接收到客户端的SYN报文之后,就会处于SYN-RCVD状态,此时双方还没完全建立连接,服务端会把这种状态下的请求连接放在一个队列里,我们把这个队列称为半连接队列。与之对应还有全连接队列,已完成三次握手的请求连接就放在全连接队列里。

超时重传

第一次握手丢失会发生什么?

客户端发送SYN报文,进入SYN-SENT状态。如果迟迟等不到服务端发送SYN-ACK报文(第二次握手),就会触发超时重传机制。客户端重新发送SYN报文,重传的SYN报文序列号是不变的。

通常,第一次超时重传是1s后,第二次是2s后,每次超时的时间是上一次的两倍,依此类推;在第五次重传后,等待32s,如果还没收到服务端的SYN-ACK报文,客户端不再重传,然后断开TCP连接。

总耗时:1+2+4+8+16+32 = 63s,总耗时在1分钟左右

第二次握手丢失会发生什么?

客户端发送SYN报文,进入SYN-SENT状态。服务端收到客户端SYN报文,进入SYN-RCVD状态。服务端发送SYN-ACK报文,客户端等不到服务端发送SYN-ACK报文(第二次握手),触发客户端超时重传。服务端接收不到客户端的ACK报文,触发超时重传,重传SYN-ACK报文。

第三次握手丢失会发生什么?

服务端发送SYN-ACK报文,进入SYN-RCVD状态,没收到客户端的ACK报文,触发超时重传。

三次握手能不能携带数据?

第一次和第二次握手不能携带数据,第三次握手可以携带数据。假如第一次握手携带数据的话,那服务器就需要更多的时间处理数据,更容易引起洪泛攻击。而第三次握手客户端处于ESTABLISHED状态,客户端已知服务端发送和接收的功能正常,所以可以携带数据

洪泛攻击:又称SYN攻击,TCP第一次握手后,服务端会将TCP请求连接放入半连接队列,并向客户端发出SYN-ACK报文,进入SYN-RCVD状态。此时服务端迟迟收不到客户端的ACK报文,就会触发超时重传。洪泛攻击就是利用这点,发出大量TCP请求,而不发起第三次握手,造成服务端半连接队列溢出崩溃,即使服务端资源足够,也会因为处理这些SYN请求,无暇处理用户正常的连接请求,一次服务端就会失去响应。

最后一次确认是为了防止已失效的连接请求报文段又突然传送到B,造成资源的浪费

TCP 连接的释放

数据传输结束后,通信的双方都可以释放连接

Snipaste_2022-10-30_16-19-02.png

​ 现在 AB 都处于 ESTAB-LISHED 状态。A 的应用进程先向其TCP发出连接释放报文段,并停止再发送数据,主动关闭TCP连接。A 把连接释放报文段首部的终止控制位FIN1,其序号 seq = u,它等于前面已经传送过的数据的最后一个字节的序号加1。这时 A 进入 FIN-WAIT-1(终止等待1)状态,等待 B 的确认。请注意,TCP规定,FIN 报文段即使不携带数据,它也消耗一个序号。

B 收到连接释放报文段后发出确认,确认号ack = u +1,而这个报文段自己的序号是 v,等于B 前面已经传送过的数据的最后一个字节的序号加1。然后B就进入CLOSE-WAIT(关闭等待)状态。TCP服务器进程这时应通知高层应用程序,因而从 AB 这个方向的连接就释放了,这时的TCP连接处于半关闭(half-close)状态,即A已经没有数据要发送了,但B若发送数据,A任要接收。也就是说,从BA这个方向的连接并未关闭,这个状态可能会持续一段时间。

A 收到 B的确认后,就进入了FIN-WAIT-2(终止等待2)状态,等B发出连接释放的报文段。

​ 若B已经没有要向A发送的数据,其应用进程就通知TCP释放连接。这时B发出的连接释放报文段必须使FIN=1。现假定B的序号是w(在半关闭状态,B可能又发送了一些数据)。B还必须重复上次已发送过的确认号ack=u+1。这时B就进入了LAST-ACK(最后确认)状态,等待A的确认。

A 在收到 B 的连接释放报文段后,必须对此发出确认。在确认报文段中把ACK1,确认号ack=w+1,而自己的序号是seq=u+1(根据TCP标准前面发送过的FIN报文段需要消耗一个序号)。然后进入了TIME-WAIT(时间等待)状态。请注意,现在TCP连接还没有释放掉。必须经过时间等待计时器(TIME-WAIT timer)设置的时间 2MSL 后,A 才进入到 CLOSED 状态。时间 MSL 叫作最长报文段寿命,MSL = 2分钟。因此 A 进入到TIME-WAIT状态后,要经过4分钟才能进入到CLOSED状态,才能开始建立下一个新的连接。当A撤销响应的传输控制块TCB后,就结束了这次的TCP连接。

B收到A发出的确认,就进入CLOSED状态。同样,B撤销响应的传输控制块TCB后,就结束了这次的TCP连接。我们注意到,B结束TCP连接的时间要比A早一些

面试版回答:

  1. 刚开始双方处于establish状态,客户端先发起关闭请求。
  2. 客户端发送FIN报文(第一次挥手),消耗一个序列号,进入FIN-WAIT-1状态。
  3. 服务端接收到FIN报文,将客户端序列号+1作为确认号,发送ACK报文(第二次挥手),进入CLOSE-WAIT状态;此时TCP连接处于半关闭状态,即客户端到服务端方向已经没有数据要发送。
  4. 客户端收到ACK报文,进入FIN-WAIT-2状态。
  5. 服务端发送FIN-ACK报文(第三次挥手),并带上上一次的确认号,进入LAST-ACK状态。
  6. 客户端收到FIN报文,将服务端序列号+1作为确认号,发送ACK报文(第四次挥手),进入TIME-WAIT状态。
  7. 服务端收到ACK报文,进入CLOSED关闭状态。
  8. 客户端进入TIME-WAIT状态之后,等待2MSL的时间,没有收到服务端重传的FIN报文的话,证明ACK报文已被服务端接收,即可进入CLOSED状态。如果在2MSL时间内又收到服务端FIN报文,证明服务端未收到ACK报文,则重新发送ACK报文,重置2MSL的计时器。

为什么客户端要在TIME-WAIT状态必须等待 2MSL 的时间?

  1. 为保证客户端发送的最后一个ACK报文段能够到达服务端,如果中途丢失,服务端没收到确认报文段,就会超时重传FIN+ACK这个报文段,此时A在等待才能接收到超时重传的报文
  2. 防止已失效的连接请求报文段出现在本连接中

保活计时器: 服务器每收到一次客户的数据,就重新设置保活计时器,时间的设置通常是两小时。若两小时没有收到客户的数据,服务器就发送一个探测报文段,以后每隔75秒就发送一次。若一连发送10个探测报文段后客户仍没有响应,服务器就认为客户端出了故障,接着就关闭这个连接