目录
[TCP 三次握手,客户端第三次发送的确认包丢失了发生什么?](#TCP 三次握手,客户端第三次发送的确认包丢失了发生什么?)
[三次握手和 accept 是什么关系? accept 做了哪些事情?](#三次握手和 accept 是什么关系? accept 做了哪些事情?)
[客户端发送的第一个 SYN 报文,服务器没有收到怎么办?](#客户端发送的第一个 SYN 报文,服务器没有收到怎么办?)
[服务器收到第一个 SYN 报文,回复的 SYN + ACK 报文丢失了怎么办?](#服务器收到第一个 SYN 报文,回复的 SYN + ACK 报文丢失了怎么办?)
[假设客户端重传了 SYN 报文,服务端这边又收到重复的 SYN 报文怎么办?](#假设客户端重传了 SYN 报文,服务端这边又收到重复的 SYN 报文怎么办?)
第一次握手,客户端发送SYN报后,服务端回复ACK报,那这个过程中服务端内部做了哪些工作?
[TCP 四次挥手过程说一下?](#TCP 四次挥手过程说一下?)
[断开连接时客户端 FIN 包丢失,服务端的状态是什么?](#断开连接时客户端 FIN 包丢失,服务端的状态是什么?)
传输层
说一下tcp的头部

序列号:在建立连接时由计算机生成的随机数作为其初始值 ,通过 SYN 包传给接收端主机,每发送一次数据,就「累加」一次该「数据字节数」的大小。用来解决网络包乱序问题。
确认应答号:指下一次「期望」收到的数据的 序列号 ,发送端收到这个确认应答以后可以认为在这个序号以前的数据都已经被正常接收。用来解决丢包的问题。
控制位:
• ACK:该位为 1 时,**「确认应答」**的字段变为有效,TCP 规定除了最初建立连接时的 SYN 包之外该位必须设置为 1 。
• RST:该位为 1 时,表示TCP 连接中出现异常必须强制断开连接。
• SYN:该位为 1 时,表示希望建立连接,并在其「序列号」的字段进行序列号初始值的设定。
• FIN:该位为 1 时,表示**今后不会再有数据发送,希望断开连接。**当通信结束希望断开连接时,通信双方的主机之间就可以相互交换 FIN 位为 1 的 TCP 段。
TCP三次握手过程说一下?

- 一开始,客户端和服务端都处于 CLOSE 状态。先是服务端主动监听某个端口,处于 LISTEN 状态
- 客户端会随机初始化序号(client_isn),将此序号置于 TCP 首部的「序号」字段中,同时把 SYN 标志位置为 1,表示 SYN 报文。接着把第一个 SYN 报文发送给服务端,
- 服务端收到客户端的 SYN 报文后,首先服务端也随机初始化自己的序号(server_isn),将此序号填入 TCP 首部的「序号」字段中,其次把 TCP 首部的「确认应答号」字段填入 client_isn + 1, 接着把 SYN 和 ACK 标志位置为 1。最后把该报文发给客户端,
- 客户端收到服务端报文后,发送应答报文 ,在TCP的 [确认应答号」字段填入 server_isn + 1 ,ACK 标志位置为 1 ,之后客户端处于 ESTABLISHED 状态。
- 服务端收到客户端的应答报文后,也进入 ESTABLISHED 状态。
第三次握手是可以携带数据的,前两次握手是不可以携带数据的,
tcp为什么需要三次握手建立连接?
三次握手的原因:
• 三次握手才可以阻止重复历史连接的初始化(主要原因)
• 三次握手才可以同步双方的初始序列号
• 三次握手才可以避免资源浪费
- 三次握手可以阻止历史连接进行初始化
三次握手的场景:
客户端连续发送多次 SYN(都是同一个四元组)建立连接的报文,在网络拥堵情况下:
• 一个「旧 SYN 报文」比「最新的 SYN」 报文早到达了服务端,那么此时服务端就会回一个 SYN + ACK 报文给客户端,此报文中的确认号是 91(90+1)。
• 客户端收到后,发现自己期望收到的确认号应该是 100 + 1,而不是 90 + 1,于是就会回 RST 报文。
• 服务端收到 RST 报文后,就会释放连接。
• 后续最新的 SYN 抵达了服务端后,客户端与服务端就可以正常的完成三次握手了。
上述中的「旧 SYN 报文」称为历史连接,TCP 使用三次握手建立连接的最主要原因就是防止「历史连接」初始化了连接。
两次握手的场景:
在两次握手的情况下,服务端在收到 SYN 报文后,就进入 ESTABLISHED 状态,可以给对方发送数据,但是客户端此时还没有进入 ESTABLISHED 状态,假设这次是历史连接,客户端判断到此次连接为历史连接,那么就会回 RST 报文来断开连接,也就是说在RST报文到达前,服务端会一直发送数据。
如果采用两次握手建立 TCP 连接的场景下,服务端在向客户端发送数据前,并没有阻止掉历史连接, 导致服务端建立了一个历史连接,又白白发送了数据,妥妥地浪费了服务端的资源。

- 同步双方初始序列号
TCP 协议的通信双方, 都必须维护一个「序列号」,它的作用:
• 接收方可以去除重复的数据;
• 接收方可以根据数据包的序列号按序接收;
• 可以标识发送出去的数据包中, 哪些是已经被对方收到的(通过 ACK 报文中的序列号知道);
可见,序列号在 TCP 连接中占据着非常重要的作用,所以当客户端发送携带「初始序列号」的 SYN 报文的时候,需要服务端回一个 ACK 应答报文,表示客户端的 SYN 报文已被服务端成功接收,那当服务端发送「初始序列号」给客户端的时候,依然也要得到客户端的应答回应,这样一来一回,才能确保双方的初始序列号能被可靠的同步。

四次握手与三次握手
四次握手其实也能够可靠的同步双方的初始化序号,但由于**第二步和第三步可以优化成一步,**所以就成了「三次握手」。
而两次握手只保证了一方的初始序列号能被对方成功接收,没办法保证双方的初始序列号都能被确认接收。
- 避免资源浪费
如果只有「两次握手」,当客户端发生的 SYN 报文在网络中阻塞,客户端没有接收到 ACK 报文,就会重新发送 SYN ,由于没有第三次握手,服务端不清楚客户端是否收到了自己回复的 ACK 报文,所以服务端每收到一个 SYN 就只能先主动建立一个连接,
如果客户端发送的 SYN 报文在网络中阻塞了,重复发送多次 SYN 报文,那么服务端在收到请求后就会建立多个冗余的无效链接,造成不必要的资源浪费。
TCP 三次握手,客户端第三次发送的确认包丢失了发生什么?
当第三次握手丢失了,如果服务端那一方迟迟收不到这个确认报文,就会触发超时重传机制,重传 SYN-ACK 报文,直到收到第三次握手,或者达到最大重传次数就断开连接。
注意,ACK 报文是不会有重传的,当 ACK 丢失了,就由对方重传对应的报文。(就是说第三次握手不会重传,只有第二次握手会一直重传)

服务端发送第二个报文后连接的状态进入什么状态
syn_rcvd 状态
三次握手和 accept 是什么关系? accept 做了哪些事情?
tcp 完成三次握手后,连接会被保存到内核的全连接队列,调用 accpet 就是从把连接取出来给用户程序使用。

客户端发送的第一个 SYN 报文,服务器没有收到怎么办?
在这之后,如果客户端迟迟收不到服务端的 SYN-ACK 报文(第二次握手),就会触发「超时重传」机制,重传 SYN 报文,而且重传的 SYN 报文的序列号都是一样的。
通常,第一次超时重传是在 1 秒后,第二次超时重传是在 2 秒,第三次超时重传是在 4 秒后,第四次超时重传是在 8 秒后,第五次是在超时重传 16 秒后。没错,每次超时的时间是上一次的 2 倍。
如果超过了最大重传次数,还是没有收到服务端的 SYN-ACK 报文,就断开连接。
服务器收到第一个 SYN 报文,回复的 SYN + ACK 报文丢失了怎么办?
因此,当第二次握手丢失了,客户端和服务端都会重传:
- 客户端会重传 SYN 报文,也就是第一次握手,最大重传次数由 tcp_syn_retries内核参数决定;
- 服务端会重传 SYN-ACK 报文,也就是第二次握手,最大重传次数由 tcp_synack_retries 内核参数决定。
客户端重传超过最大次数后,会断开客户端的连接。
服务端重传超过最大次数后,会断开服务端的连接。

假设客户端重传了 SYN 报文,服务端这边又收到重复的 SYN 报文怎么办?
会继续发送第二次握手报文。
第一次握手,客户端发送SYN报后,服务端回复ACK报,那这个过程中服务端内部做了哪些工作?
服务端收到客户端发起的 SYN 请求后,内核会把该连接存储到半连接队列 ,并向客户端响应 SYN+ACK,接着客户端会返回 ACK,服务端收到第三次握手的 ACK 后,内核会把连接从半连接队列移除 ,创建一个全连接放到全连接队列 ,并将其添加到 accept 中,等待进程调用 accept 函数时把连接取出来。

不管是半连接队列还是全连接队列,都有最大长度限制,超过限制时,内核会直接丢弃,或返回 RST 包。
大量SYN包发送给服务端服务端会发生什么事情?
会导致TCP 半连接队列打满,这样当 TCP 半连接队列满了,后续再在收到 SYN 报文就会丢弃,导致客户端无法和服务端建立连接。
避免 SYN 攻击方式,可以有以下四种方法:
• 调大 netdev_max_backlog(增大网卡接收数据包的最大值);
• 增大 TCP 半连接队列;
• 开启 tcp_syncookies;
• 减少 SYN+ACK 重传次数(就是减少第二次握手重试的次数)
- 开启 tcp_syncookies:

- 当 「 SYN 队列」满之后,后续服务端收到 SYN 包,不会丢弃,而是根据算法,计算出一个 cookie值;
- • 将 cookie 值放到第二次握手报文的「序列号」里,然后服务端回第二次握手给客户端;
- • 服务端接收到客户端的应答报文时,服务端会检查这个 ACK 包的合法性。如果合法,将该连接对象放入到「 Accept 队列」。
- • 最后应用程序通过调用 accpet() 接口,从「 Accept 队列」取出的连接。
TCP 四次挥手过程说一下?

具体过程:
• 客户端主动调用关闭连接的函数,于是就会发送 FIN 报文,这个 FIN 报文代表客户端不会再发送数据了,进入 FIN_WAIT_1 状态;
•服务端收到了 FIN 报文,然后马上回复一个 ACK 确认报文 ,此时服务端进入 CLOSE_WAIT 状态。在收到 FIN 报文的时候,**TCP 往接收缓冲区末尾插入 EOF(**EOF之前的数据都是没处理完的)
• 接着,当服务端在 read 数据的时候,最后自然就会读到 EOF,接着 read() 就会返回 0,这时服务端应用程序如果有数据要发送的话,就发完数据后才调用关闭连接的函数,如果服务端应用程序没有数据要发送的话,可以直接调用关闭连接的函数,这时服务端就会发一个 FIN 包,这个 FIN 报文代表服务端不会再发送数据了,之后处于 LAST_ACK 状态;
•客户端接收到服务端的 FIN 包,并发送 ACK 确认包给服务端,此时客户端将进入 TIME_WAIT 状态;
• 服务端收到 ACK 确认包后,就进入了最后的CLOSE 状态;
• 客户端经过 2MSL 时间之后,也进入 CLOSE 状态;
为什么4次挥手中间两次不能变成一次?
服务器收到客户端的 FIN 报文时,内核会马上回一个 ACK 应答报文,但是服务端应用程序可能还有数据要发送,所以并不能马上发送 FIN 报文,而是将发送 FIN 报文的控制权交给服务端应用程序:
• 如果服务端应用程序有数据要发送的话,就发完数据后,才调用关闭连接的函数;
• 如果服务端应用程序没有数据要发送的话,可以直接调用关闭连接的函数,
第二次和第三次挥手能合并嘛
服务端「没有数据要发送」并且「开启了 TCP 延迟确认机制」,那么第二和第三次挥手就会合并传输,这样就出现了三次挥手。
第三次挥手一直没发,会发生什么?
- 如果连接是用 shutdown 函数关闭的 ,连接可以一直处于 FIN_WAIT2 状态 ,因为它可能还可以接收数据。
- 但对于close 函数关闭的孤儿连接,由于无法再发送和接收数据 ,所以这个状态不可以持续太久,而 tcp_fin_timeout 控制了这个状态下连接的持续时长,默认值是 60 秒,超时后连接就会直接关闭。
第二次和第三次挥手之间,主动断开的那端能干什么
如果主动断开的一方,是调用了 shutdown 函数来关闭连接 ,并且只选择了关闭发送能力且没有关闭接收能力的话,那么主动断开的一方在第二次和第三次挥手之间还可以接收数据。

断开连接时客户端 FIN 包丢失,服务端的状态是什么?
如果第一次挥手丢失了,那么客户端迟迟收不到被动方的 ACK 的话,也就会触发超时重传机制,重传 FIN 报文,重发次数由 tcp_orphan_retries 参数控制,超过最大次数后,会断开连接。
当客户端重传 FIN 报文的次数超过 tcp_orphan_retries 后,就不再发送 FIN 报文,则会在等待一段时间(时间为上一次超时时间的 2 倍),如果还是没能收到第二次挥手,那么客户端直接进入到 close 状态,而服务端还是ESTABLISHED状态

为什么四次挥手之后要等2MSL?
TTL 的值一般是 64,Linux 将 MSL 设置为 30 秒,意味着 Linux 认为数据报文经过 64 个路由器的时间不会超过 30 秒,如果超过了,就认为报文已经消失在网络中了。
TIME_WAIT 等待 2 倍的 MSL,比较合理的解释是: 网络中可能存在来自发送方的数据包,当这些发送方的数据包被接收方处理后又会向对方发送响应,所以一来一回需要等待 2 倍的时间。
比如,如果被动关闭方没有收到断开连接的最后的 ACK 报文,就会触发超时重发 FIN 报文,另一方接收到 FIN 后,会重发 ACK 给被动关闭方, 一来一去正好 2 个 MSL。
可以看到 2MSL时长 这其实是相当于至少允许报文丢失一次。比如,若 ACK 在一个 MSL 内丢失,这样被动方重发的 FIN 会在第 2 个 MSL 内到达,TIME_WAIT 状态的连接可以应对。
兜底迟滞报文:等待网络中残留的旧 TCP 报文彻底消亡,防止新连接建立后收到旧连接的过期报文;
保障四次挥手最后 ACK 可靠送达 :若最后一次 ACK 报文丢失,被动关闭方会超时重传 FIN;2MSL 刚好覆盖「FIN 重传 + ACK 再次回复」一来一回的全网传输时长;
服务端出现大量的timewait有哪些原因?
- 第一个场景:HTTP 没有使用长连接
- 第二个场景:HTTP 长连接超时
- 第三个场景:HTTP 长连接的请求数量达到上限
HTTP 没有使用长连接:
只要任意一方的 HTTP header 中有 Connection:close 信息,就无法使用 HTTP 长连接机制,这样在完成一次 HTTP 请求/处理后,就会关闭连接。
根据大多数 Web 服务的实现,不管哪一方禁用了 HTTP Keep-Alive,都是由服务端主动关闭连接,那么此时服务端上就会出现 TIME_WAIT 状态的连接。
HTTP 长连接超时:
如果现象是有大量的客户端建立完 TCP 连接后,很长一段时间没有发送数据,那么大概率就是因为 HTTP 长连接超时,导致服务端主动关闭连接,产生大量处于 TIME_WAIT 状态的连接。
HTTP 长连接的请求数量达到上限:
比如 nginx 的 keepalive_requests 这个参数,这个参数是指一个 HTTP 长连接建立之后,nginx 就会为这个连接设置一个计数器,记录这个 HTTP 长连接上已经接收并处理的客户端请求的数量。如果达到这个参数设置的最大值时,则 nginx 会主动关闭这个长连接,那么此时服务端上就会出现 TIME_WAIT 状态的连接。
TCP和UDP区别是什么?
连接:TCP 是面向连接的传输层协议,传输数据前先要建立连接;UDP 是不需要连接,即刻传输数据。
• 服务对象:TCP 是一对一的两点服务,即一条连接只有两个端点。UDP 支持一对一、一对多、多对多的交互通信
• 可靠性:TCP 是可靠交付数据的,数据可以无差错、不丢失、不重复、按序到达。UDP 是不可靠的传输协议,不保证可靠交付数据,发送的数据丢了就丢了,不会有任何措施。但是我们可以基于 UDP 传输协议实现一个可靠的传输协议,比如 QUIC 协议
• 拥塞控制、流量控制:TCP 有拥塞控制和流量控制机制,保证数据传输的安全性。UDP 则没有,即使网络非常拥堵了,也不会影响 UDP 的发送速率。
• 首部开销:TCP 首部长度较长,会有一定的开销,首部在没有使用「选项」字段时是 20 个字节,如果使用了「选项」字段则会变长的。UDP 首部只有 8 个字节,并且是固定不变的,开销较小。
• 传输方式:TCP 是流式传输,没有边界,但保证顺序和可靠。UDP 是一个包一个包的发送,是有边界的,但可能会丢包和乱序。
TCP为什么可靠传输
连接管理:即三次握手和四次挥手。连接管理机制能够建立起可靠的连接,这是保证传输可靠性的前提。
• 序列号:TCP将每个字节的数据都进行了编号,这就是序列号。序列号的具体作用如下:能够保证可靠性,既能防止数据丢失,又能避免数据重复。能够保证有序性,按照序列号顺序进行数据包还原。能够提高效率,基于序列号可实现多次发送,一次确认。
• 确认应答:接收方接收数据之后,会回传ACK报文,报文中带有此次确认的序列号,用于告知发送方此次接收数据的情况。在指定时间后,若发送端仍未收到确认应答,就会启动超时重传。
• 超时重传:超时重传主要有两种场景:数据包丢失:在指定时间后,若发送端仍未收到确认应答,就会启动超时重传,向接收端重新发送数据包。确认包丢失:当接收端收到重复数据(通过序列号进行识别)时将其丢弃,并重新回传ACK报文。
• 流量控制:接收端处理数据的速度是有限的,如果发送方发送数据的速度过快,就会导致接收端的缓冲区溢出,进而导致丢包。为了避免上述情况的发生,TCP支持根据接收端的处理能力,来决定发送端的发送速度。这就是流量控制。流量控制是通过在TCP报文段首部维护一个滑动窗口来实现的。
• 拥塞控制:拥塞控制就是当网络拥堵严重时,发送端减少数据发送。拥塞控制是通过发送端维护一个拥塞窗口来实现的。可以得出,发送端的发送速度,受限于滑动窗口和拥塞窗口中的最小值。拥塞控制方法分为:慢开始,拥塞避免、快重传和快恢复。
怎么用udp实现http?
UDP 是不可靠传输的,但基于 UDP 的 QUIC 协议 可以实现类似 TCP 的可靠性传输,在http3 就用了 quic 协议。
• 连接迁移:QUIC支持在网络变化时快速迁移连接,例如从WiFi切换到移动数据网络,以保持连接的可靠性。
• 重传机制:QUIC使用重传机制来确保丢失的数据包能够被重新发送,从而提高数据传输的可靠性。
• 前向纠错:QUIC可以使用前向纠错技术,在接收端修复部分丢失的数据,降低重传的需求,提高可靠性和传输效率。
• 拥塞控制:QUIC内置了拥塞控制机制,可以根据网络状况动态调整数据传输速率,以避免网络拥塞和丢包,提高可靠性。