引言
首先,需要了解TCP和UDP都是在传输层的协议
![](https://i-blog.csdnimg.cn/blog_migrate/7d68037894cf69daaa9c7ecb4af2b01b.png)
话不多说,开问!
Q&A
TCP与UDP区别:
![](https://i-blog.csdnimg.cn/blog_migrate/90a5d15f5880137870dba796e6cd6b07.png)
![](https://i-blog.csdnimg.cn/blog_migrate/e63444d134a0d44dcd380c0142a4ef3c.png)
TCP三次握手概述:
客户端会随机初始化序号(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 ,最后把报文发送给服务端,这次报文可以携带客户到服务端的数据,之后客户端处于 ESTABLISHED 状态。
服务端收到客户端的应答报文后,也进入 ESTABLISHED 状态。
为什么不能是两次或四次握手?
三次握手才能保证双方具有接收和发送的能力
三次握手才可以阻止重复历史连接的初始化(主要原因)(当客户端发送SYN后宕机同时网络拥堵,导致又发了一个SYN,防止建立历史连接)
三次握手才可以同步双方的初始序列号 (两次握手只保证了一方的初始序列号能被对方成功接收,没办法保证双方的初始序列号都能被确认接收)
三次握手才可以避免资源浪费(若为2次握手,当客户端多次发送时会导致客户端建立多个冗余的无效链接,造成不必要的资源浪费。)
总结:
「两次握手」:无法防止历史连接的建立,会造成双方资源的浪费,也无法可靠的同步双方序列号;
「四次握手」:三次握手就已经理论上最少可靠连接建立,所以不需要使用更多的通信次数。
TCP为什么要使用MSS而不是网络层的MTU
MTU:一个网络包的最大长度,以太网中一般为 1500 字节;
MSS:除去 IP 和 TCP 头部之后,一个网络包所能容纳的 TCP 数据的最大长度;
MSL:(Maximum Segment Lifetime,报文最大生存时间);TCP Segment在网络上的存活时间不会超过MSL(RFC793定义了MSL为2分钟,Linux设置成了30s)
![](https://i-blog.csdnimg.cn/blog_migrate/94e6343ef67c7941bc60c9878371c072.png)
当如果一个 IP 分片丢失,整个 IP 报文的所有分片都得重传。因为 IP 层本身没有超时重传机制,它由传输层的 TCP 来负责超时和重传。
当某一个 IP 分片丢失后,接收方的 IP 层就无法组装成一个完整的 TCP 报文(头部 + 数据),也就无法将数据报文送到 TCP 层,所以接收方不会响应 ACK 给发送方,因为发送方迟迟收不到 ACK 确认报文,所以会触发超时重传,就会重发「整个 TCP 报文(头部 + 数据)」。
因此,可以得知由 IP 层进行分片传输,是非常没有效率的。
所以,为了达到最佳的传输效能 TCP 协议在建立连接的时候通常要协商双方的 MSS 值,当 TCP 层发现数据超过 MSS 时,则就先会进行分片,当然由它形成的 IP 包的长度也就不会大于 MTU ,自然也就不用 IP 分片了。
经过 TCP 层分片后,如果一个 TCP 分片丢失后,进行重发时也是以 MSS 为单位,而不用重传所有的分片,大大增加了重传的效率。
第一次握手丢失了,会发生什么?
客户端重传相同的报文,间隔时间每次是上次的一倍,一般当第五次超时重传后,会继续等待 32 秒,如果服务端仍然没有回应 ACK,客户端就不再发送 SYN 包,然后断开 TCP 连接。
所以,总耗时是 1+2+4+8+16+32=63 秒,大约 1 分钟左右。
第二次握手丢失了,会发生什么?
客户端和服务端都会重传各自的SYN,间隔时间每次是上次的一倍(ACK报文不会单独重传)
第三次握手丢失了,会发生什么?
服务端重传SYN+ACK,间隔时间每次是上次的一倍
TCP四次挥手概述:
客户端打算关闭连接,此时会发送一个 TCP 首部 FIN 标志位被置为 1 的报文,也即 FIN 报文,之后客户端进入 FIN_WAIT_1 状态。
服务端收到该报文后,就向客户端发送 ACK 应答报文,接着服务端进入 CLOSE_WAIT 状态。
客户端收到服务端的 ACK 应答报文后,之后进入 FIN_WAIT_2 状态。
等待服务端处理完数据后,也向客户端发送 FIN 报文,之后服务端进入 LAST_ACK 状态。
客户端收到服务端的 FIN 报文后,回一个 ACK 应答报文,之后进入 TIME_WAIT 状态
服务端收到了 ACK 应答报文后,就进入了 CLOSE 状态,至此服务端已经完成连接的关闭。
客户端在经过 2MSL 一段时间后,自动进入 CLOSE 状态,至此客户端也完成连接的关闭。
为什么需要四次挥手?
关闭连接时,客户端向服务端发送 FIN 时,仅仅表示客户端不再发送数据了但是还能接收数据。
服务端收到客户端的 FIN 报文时,先回一个 ACK 应答报文,而服务端可能还有数据需要处理和发送,等服务端不再发送数据时,才发送 FIN 报文给客户端来表示同意现在关闭连接。
在特定情况下四次挥手可以变成三次,如果「没有数据要发送」,同时「开启TCP 延迟确认机制」,那么第二和第三次挥手就会合并传输,这样就出现了三次挥手。所以,出现三次挥手现象,是因为 TCP 延迟确认机制导致的。
TCP延迟确认机制:
当有响应数据要发送时,ACK 会随着响应数据一起立刻发送给对方
当没有响应数据要发送时,ACK 将会延迟一段时间,以等待是否有响应数据可以一起发送
如果在延迟等待发送 ACK 期间,对方的第二个数据报文又到达了,这时就会立刻发送 ACK
第一次挥手丢失了,会发生什么?
当客户端重传 FIN 报文的次数超过 tcp_orphan_retries 后,就不再发送 FIN 报文,则会在等待一段时间(时间为上一次超时时间的 2 倍),如果还是没能收到第二次挥手,那么直接进入到 close 状态。
第二次挥手丢失了,会发生什么?
客户端重传FIN,直到收到服务端的第二次挥手或者达到最大重传次数,(ACK报文不会单独重传)
第三次挥手丢失了,会发生什么?
服务端重传FIN报文的次数达到了 指定次数后,(时间为上一次超时时间的 2 倍),如果还是没能收到客户端的第四次挥手(ACK报文),那么服务端就会断开连接。
客户端因为是通过 close 函数关闭连接的,处于 FIN_WAIT_2 状态是有时长限制的,如果 tcp_fin_timeout 时间(一般是60s)内还是没能收到服务端的第三次挥手(FIN 报文),那么客户端就会断开连接。
第四次挥手丢失了,会发生什么?
服务端重传FIN报文的次数达到了 指定次数后,(时间为上一次超时时间的 2 倍),如果还是没能收到客户端的第四次挥手(ACK报文),那么服务端就会断开连接。
客户端在收到第三次挥手后,就会进入 TIME_WAIT 状态,开启时长为 2MSL 的定时器,如果途中再次收到第三次挥手(FIN 报文)后,就会重置定时器,当等待 2MSL 时长后,客户端就会断开连接。
为什么 TIME_WAIT 等待的时间是 2MSL?
网络中可能存在来自发送方的数据包,当这些发送方的数据包被接收方处理后又会向对方发送响应,所以一来一回需要等待 2 倍的时间。
为什么需要 TIME_WAIT 状态?
防止历史连接中的数据,被后面相同四元组的连接错误的接收;(2MSL这个时间足以让两个方向上的数据包都被丢弃,使得原来连接的数据包在网络中都自然消失,再出现的数据包一定都是新建立连接所产生的。)
保证「被动关闭连接」的一方,能被正确的关闭;
服务器出现大量 TIME_WAIT 状态的原因有哪些?
第一个场景:HTTP 没有使用长连接;可以排查下是否客户端和服务端都开启了 HTTP Keep-Alive
第二个场景:HTTP 长连接超时;可以往网络问题的方向排查,比如是否是因为网络问题,导致客户端发送的数据一直没有被服务端接收到,以至于 HTTP 长连接超时
第三个场景:HTTP 长连接的请求数量达到上限;调大 nginx 的 keepalive_requests 参数就行
服务器出现大量 CLOSE_WAIT 状态的原因有哪些?
当服务端出现大量 CLOSE_WAIT 状态的连接的时候,通常都是代码的问题,这时候我们需要针对具体的代码一步一步的进行排查和定位,主要分析的方向就是服务端为什么没有调用 close。
如果已经建立了连接,但是客户端突然出现故障了怎么办?
TCP 保活机制,每隔一个时间间隔,发送一个探测报文,该探测报文包含的数据非常少,如果连续几个探测报文都没有得到响应,则认为当前的 TCP 连接已经死亡,系统内核将错误信息通知给上层应用程序。
我们可以自己在应用层实现一个心跳机制
如果已经建立了连接,但是服务端的进程崩溃会发生什么?
当服务端的进程崩溃后,内核需要回收该进程的所有 TCP 连接资源,于是内核会发送第一次挥手 FIN 报文,后续的挥手过程也都是在内核完成,并不需要进程的参与,所以即使服务端的进程退出了,还是能与客户端完成 TCP 四次挥手的过程。
如果「客户端进程崩溃」,客户端的进程在发生崩溃的时候,内核会发送 FIN 报文,与服务端进行四次挥手。
但是,「客户端主机宕机」,那么是不会发生四次挥手的,具体后续会发生什么?还要看服务端会不会发送数据?
如果服务端会发送数据,由于客户端已经不存在,收不到数据报文的响应报文,服务端的数据报文会超时重传,当重传总间隔时长达到一定阈值(内核会根据 tcp_retries2 设置的值计算出一个阈值)后,会断开 TCP 连接;
如果服务端一直不会发送数据,再看服务端有没有开启 TCP keepalive 机制?
如果有开启,服务端在一段时间没有进行数据交互时,会触发 TCP keepalive 机制,探测对方是否存在,如果探测到对方已经消亡,则会断开自身的 TCP 连接;
如果没有开启,服务端的 TCP 连接会一直存在,并且一直保持在 ESTABLISHED 状态。