计算机网络八股(学习自小林coding)

TCP 的四次挥手

小林coding链接

四次挥手的过程(三次挥手)

img

  • ACK :表示响应。当该位为 1 的时候,确认应答字段有效, TCP规定,除了最初建立连接时的SYN包外,该位必须设置为 1。ACK通常于SYN 和 FIN同时使用。

  • SYN:表示建立连接。当该位为1 时,表示希望建立连接。

  • FIN:表示关闭连接。当该位为 1 时, 表示今后不会再有数据发送, 希望断开连接

  • RST:表示连接重置。RST一般是FIN之后才会出现为1的情况。

  • PSH表示有效的数据传输。PSH为1 1一般只出现在DATA内容不为 0 的包中, 此时表示有效的TCP数据包被传送。

具体过程
  • 客户端调用关闭连接的函数, 就会发送一个FIN报文,表示客户端不会再发送数据了, 此时客户端进入FIN_WAIT_1 状态;

  • 服务端收到FIN报文之后,会马上回复一个ACK确认报文, 此时服务端进入CLOSE_WAIT状态。在收到FIN报文的时候,TCP协议栈会为FIN包插入一个文件结束符到接收缓冲区中, 服务端程序可以通过read调用来感知这个FIN包, 并且这个EOF会被放在等待接收数据队列的队尾,此时服务端还需要继续读取缓冲区中的已经接收的数据, 同时会继续向客户端回传数据,直到读取到EOF.

  • 当服务端读取到EOF的时候, read()函数就会返回 0 , 此时如果服务端程序有数据需要发送的话就继续发送数据,数据发送完毕之后关闭连接, 没有数据需要发送就直接关闭连接,然后服务端就会发送一个FIN包, 表示服务端不会再发送数据了, 之后服务端处于LAST_ACK状态

  • 客户端收到服务端发送的FIN包之后,继续发送ACK确认包给服务端, 此时客户端进入TIME_WAIT状态。

  • 服务端收到ACK确认包之后就进入了最后的CLOSE状态。

  • 客户端经过 2MSL 时间之后也就进入了CLOSE状态。

需要四次挥手的原因

当服务端接收到客户端的FIN报文的时候,可能还有数据需要回传给客户端, 所以不能直接发送一个 FIN 报文,而是发送一个ACK确认报文。

  • 如果服务器有数据需要回传给客户端的话就需要等待数据回传完毕之后才关闭连接

  • 没有需要回传的数据就可以直接关闭连接

连接关闭的两种方式
  • 粗暴关闭。调用close()函数, socket同时关闭发送方向和读取方向, 此时socket不再有发送和接收数据的能力。

  • 优雅关闭。调用shutdown()函数, 可以指定socket只关闭发送方向而不关闭读取方向, 此时socket不在具有发送数据的能力,但是还具有接收数据的能力。

如果客户端是使用close()函数来关闭连接的话, 如果后续再接收到服务端回传的数据,客户端就会发送RST报文给服务端(此时客户端是没有接收数据的能力的), 然后内核就会释放连接,此时就不会经历TCP的四次挥手。

img

当客户端调用shutdown()方法只关闭发送方向而不关闭接收方向, 客户端后续还是可以继续接收服务端的数据, 此时会经历TCP的四次挥手的过程

优雅关闭.drawio.png

需要注意的是,shutdown()函数也可以指定socket只关闭读取方向而不关闭发送方向, 这个时候内核是不会发送FIN报文的, 发送FIN报文意味着我方不再具有发送数据的能力。

为什么会出现三次挥手

当被动关闭方没有数据需要发送的时候并且开启了TCP延迟确认机制,那么第二三次的ACK和FIN报文就会合并传输,也就出现了三次挥手。

什么是延迟确认机制

当发送没有携带数据的ACK确认包的时候,效率是非常低的, 因为发送的ACK确认报文也具有40字节的IP头和TCP头, 但是却没有携带数据报文,为了解决ACK传输效率低下的问题就出现了TCP延迟确认机制。

  • 当有响应数据需要发送时,ACK会随着响应数据一起发送给对方

  • 当没有响应数据需要发送时, ACK就会延迟一段时间,等待是否有响应数据可以一起发送

  • 如果在ACK延迟等待期间对方的第二个报文到达了,此时就会立刻发送ACK确认报文

问题

FIN报文调用关闭连接的函数之后才会发送吗?

不一定, 如果进程退出了, 不管是否是正常退出, 内会都会发送FIN报文, 与对方完成四次挥手

TCP的四次挥手过程出现异常会怎么样

挥手过程的异常处理

第一次挥手丢失

第一次挥手时,客户端调用close()函数,会触发内核向服务端发送一个FIN报文, 然后进入到FIN_WAIT_1状态,所以如果客户端迟迟没有接收到服务端的ACK确认报文,就会重传FIN报文。

第二次挥手丢失

同第一次一致。

如果客户端成功接收到ACK确认报文的话,客户端就没有发送和接收数据的能力了(因为是调用close()函数关闭连接的),此时客户端会继续等待服务端的FIN报文,在这里如果客户端迟迟接收不到FIN报文的话,客户端的连接就会直接关闭。tcp_fin_timeout 变量控制了接收ack报文之后等待fin报文的时长。

第三次挥手丢失

当服务端接收到客户端的FIN报文之后,内核会自动回复ACK确认报文,此时注意服务端还不会调用close()函数关闭连接,需要等待队列中的数据发送完毕之后由进程来调用close()函数,然后向客户端发送FIN报文。

如果第三次挥手丢失了,服务端就会重传FIN报文。

第四次挥手丢失

如果第四次挥手丢失了,服务端就会重传FIN报文。

总结

第一次挥手丢失,第二次挥手丢失:客户端重传fin报文

第三次挥手丢失,第四次挥手丢失:服务端会重传fin报文

TCP的三次握手

三次握手的过程

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

  • 客户端随机初始化序列号(client_isn), 将此序列号置于TCP首部的字段中, 同时把SYN标志位置为1 , 表示SYN报文。然后将第一个SYN报文发送给服务端,表示向服务端发起连接,之后客户端处于SYN_SENT状态。

  • 服务端收到客户端发送的SYN报文之后,首先初始化一个自己的序列号(server_isn),将此序列号填入TCP的首部的序号字段中, 接着将TCP首部的确认应答字段填入client_isn + 1, 接着把SYN和ACK的标志位置为 1。最后把该报文发送给客户端,之后服务端处于SYN_RCVD状态。

  • 客户端收到了服务端的确认报文之后, 还需要再向服务端回应最后一个应答报文,该报文的TCP首部ACK标志位为 1, 确认应答字段填入server_isn + 1, 最后把报文发送给服务端,此次发送的报文时可以携带数据的,之后客户端进入establish状态。

  • 服务端接收到应答报文之后也进入establish状态。

问题

1、为什么是三次握手,而不是两次, 四次 ?
  • 三次握手可以阻止历史连接的初始化(主要原因)

  • 三次握手才可以同步双方的初始序列号

  • 三次握手才可以避免资源的浪费

原因一:阻止历史连接

  • 当网络拥塞的情况下, 客户端会发送多次SYN的连接请求【旧的SYN(90), 新的SYN(100)】,当旧的SYN连接(90)报文先到达服务端, 服务端就会发送ack, syn报文给客户端, 确认应答号为(90 + 1)

  • 客户端接收到确认报文之后发现自己期望收到的是(100 + 1),于是就会回复rst报文,请求释放连接

  • 服务端接收到rst报文之后就会释放连接

  • 后续接收到了最新的syn(100)报文就会完成三次握手

服务端接收报文的顺序为 旧的syn(90)--- 新的syn(100) --- rst报文时会出现什么情况?
  • 第一次接收到旧的syn报文, 回复ack + syn报文

  • 再次接收到新的syn报文时回复Challenge Ack 报文, 这个确认报文的的确认号是上一次的ack的确认号(90 + 1), 此时客户端就会回复rst报文请求释方连接。

原因二:同步双方的初始序列号

tcp协议的通信双方都必须维护一个序列号, 序列号是可靠传输的关键因素, 作用:

  • 接收方可以去除重复数据

  • 接收方可以根据数据包的序列号按序接收

  • 可以标识发送出去的数据包中哪些是已经被对方收到的(通过ack确认报文知道)

两次握手只能保证一方的初始序列号能够被对方接收, 四次握手时(服务端的syn和ack报文可以一起发送给接收方,所以没必要使用四次握手)

原因三:避免资源的浪费

只有两次握手的话, 服务端接收到syn报文之后就会建立连接, 然后转到establish状态,此时可以向客户端发送数据, 如果建立的连接是历史连接的话,就会建立多个冗余无效连接,造成资源浪费

2、为什么两次握手不能阻止历史连接 ?(服务端在不知道syn是否是旧的, 会直接建立连接)

因为两次握手的情况下,服务端没有中间状态给客户端来阻止历史连接,导致服务端可能建立一个历史连接,导致资源浪费

如果是两次握手的话,服务端接收到syn报文之后就进入了establish状态,此时也就可以给客户端发送数据了, 但是客户端还没有进入establish状态,假设这次建立的是一个历史连接,那么客户端就会回复rst报文来断开连接。但是服务端不知道这是一个历史连接,而连接却已经建立了,所以无法阻止历史连接。

3、客户端发送第三次握手的确认报文时可以发送数据,但是被动方处于syn_received状态,如果ack确认包问丢了,携带的数据还能正常发送吗 ?

第三个报文 —— ACK 报文

由图上的报文信息我们可以看到, 如果携带数据的话psh标志位会置为 1, 报文中包含有ack确认号和数据, 所以服务端通过报文中的ack字段就会转到establish状态,此时是可以正常接收数据的。

4、TCP第二次握手时收到的ack确认报文不是自己期望的报文,是直接丢弃还是回复RST报文 ?

回复rst报文,解释看上面

5、为什么客户端会收到不正确的ack确认报文 ?

网络拥塞时会重传syn报文,接收到旧的syn报文的确认报文就是不正确的ack报文, 网络拥塞时重传的syn报文和旧的syn报文(序号是不同的)

TCP三次握手没有成功怎么办

三次握手时出现的异常

第一次握手丢失

客户端与服务端建立TCP链接发送的第一个报文是SYN(请求建立连接), 然后客户端就会进入SYN_SEND状态。

此时如果客户端迟迟接收不到服务端的ACK—SYN报文,那么就会触发客户端的超时重传机制,从而重传SYN报文,在不同的操作系统中都会有一个最大超时重传次数(tcp_syn_retries),这个参数是可以自定义的,默认值一般为5,通常来说客户端发送SYN报文一秒之后没有接收到服务端的ACK—SYN报文就会重传SYN报文,第二次重传是在两秒之后,第三次是四秒之后,每次超时的时间是上一次的2倍。

当第五次重传之后会等待32秒,如果客户端仍然没有接收到服务端的ACK确认报文就不会再重传SYN包,并且会断开连接。总的耗时为 1 + 2 + 4 + 8 + 16 + 32 = 63 秒 。

第二次握手丢失

当服务端接收到客户端的SYN报文之后,就会发送 ACK + SYN 报文给客户端,之后服务端会进入SYN_RCVD状态。

第二次握手的作用有两个:

  • 发送ACK报文:向客户端确认已经收到第一次握手的报文

  • 发送SYN报文:表示服务端发起建立TCP连接的报文

所以如果第二次握手丢失了,客户端会认为自己的第一次握手发送失败,然后触发超时重传机制。重传SYN报文,并且服务端也会认为自己发送的SYN_ACK报文丢失了,也触发超时重传机制。超时重传的最大次数(tcp_synack_retries)默认值为5.

第三次握手丢失

当客户端接收到了ACK_SYN报文之后会向服务端发送ACK确认报文,之后进入establish状态。所以如果第三次握手丢失了,那么服务端会重传ACK_SYN报文,这里注意:单独的ACK确认报文是不会被重传的

总结

第一次握手丢失:客户端重传SYN报文

第二次握手丢失:客户端重传SYN报文,服务端重传ACK_SYN报文

第三次握手丢失:服务端重传ACK_SYN报文

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值