TCP基本知识
头格式
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-vnu9Zc16-1679922372289)(https://s3-us-west-2.amazonaws.com/secure.notion-static.com/187db681-3cfb-4638-a243-7f6e7d01e27d/Untitled.png)]
序列号:用来解决网络包乱序问题。
确认应答号:确认已收到的,下一次期望收到的。用来解决丢包的问题。
控制位:
- ACK 确认应答有效
- RST 出现异常
- SYN 希望建立连接
- FIN 希望断开连接
什么是TCP
传输控制协议(Transmission Control Protocol)
- 面向连接:一定是「一对一」才能连接,不能像 UDP 协议可以一个主机同时向多个主机发送消息,也就是一对多是无法做到的;
- 可靠的:无论的网络链路中出现了怎样的链路变化,TCP 都可以保证一个报文一定能够到达接收端;
- 字节流:用户消息通过 TCP 协议传输时,消息可能会被操作系统「分组」成多个的 TCP 报文,如果接收方的程序如果不知道「消息的边界」,是无法读出一个有效的用户消息的。并且 TCP 报文是「有序的」,当「前一个」TCP 报文没有收到的时候,即使它先收到了后面的 TCP 报文,那么也不能扔给应用层去处理,同时对「重复」的 TCP 报文会自动丢弃。
UDP 和 TCP 有什么区别呢?分别的应用场景是?
tcp:ftp文件传输 http /https
udp:dns、snmp、视频音频、广播
可以使用同一个端口号 不冲突
- 连接上
- TCP 是面向连接的传输层协议,传输数据前先要建立连接。
- UDP 是不需要连接,即刻传输数据。
- 服务对象
- TCP 是一对一的两点服务,即一条连接只有两个端点。
- UDP 支持一对一、一对多、多对多的交互通
- 可靠性
- tcp 可靠,数据可以无差错、不丢失、不重复、按序到达
- UDP 尽最大努力 不保证可靠交付
- 拥塞控制、流量控制
- TCP有
- udp没有
- 首付开销
- tcp大
- UDP只有8个字节
- 传输方式
- tcp是流式 没有边界
- udp 一个包一个包
- 分片
- tcp如果大于mss(maximum segement size),在传输层进行分片,
- udp 如果大于mtu 再ip层分片
三次握手
三次握手过程
- 一开始,客户端和服务端都处于
CLOSE
状态。先是服务端主动监听某个端口,处于LISTEN
状态 - 客户端会随机初始化序号(
client_isn
),同时把SYN
标志位置为1
,表示SYN
报文。接着把第一个 SYN 报文发送给服务端,表示向服务端发起连接,该报文不包含应用层数据,之后客户端处于SYN-SENT
状态。 - 服务端收到客户端的
SYN
报文后,首先服务端也随机初始化自己的序号server_isn
。其次把 TCP 首部的「确认应答号」字段填入client_isn + 1
接着把SYN
和ACK
标志位置为1
。表示收到请求连接的,也请求连接。最后把该报文发给客户端,该报文也不包含应用层数据,之后服务端处于SYN-RCVD
状态。 - 客户端收到后,还要向服务端回应最后一个应答报文,首先该应答报文 TCP 首部
ACK
标志位置为1
,其次「确认应答号」字段填入server_isn + 1
,最后把报文发送给服务端,这次报文可以携带客户到服务端的数据,之后客户端处于ESTABLISHED
状态。 - 服务端收到客户端的应答报文后,也进入
ESTABLISHED
状态。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ZljsKv9l-1679922372290)(https://s3-us-west-2.amazonaws.com/secure.notion-static.com/75af1efb-582f-49b3-8813-179d97cdd365/Untitled.png)]
为什么是三次握手?不是两次、四次?
原因一、避免历史连接 :阻止历史连接的初始化 (主要原因
- 一个「旧 SYN 报文」比「最新的 SYN」 报文早到达了服务端,那么此时服务端就会回一个
SYN + ACK
报文给客户端,此报文中的确认号是 91(90+1)。 - 客户端收到后,发现自己期望收到的确认号应该是 100 + 1,而不是 90 + 1,于是就会回 RST 报文。 没有进行第三次握手!
- 服务端收到 RST 报文后,就会释放连接。
- 后续最新的 SYN 抵达了服务端后,客户端与服务端就可以正常的完成三次握手了。
最主要原因就是防止「历史连接」初始化了连接 因为在两次握手的情况下,服务端没有中间状态给客户端来阻止历史连接,导致服务端可能建立一个历史连接,造成资源浪费
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-smNr2bt5-1679922380586)(null)]
原因二、同步双方初始序列号
序列号在 TCP 连接中占据着非常重要的作用。
当客户端发送携带「初始序列号」的 SYN
报文的时候,需要服务端回一个 ACK
应答报文,表示客户端的 SYN 报文已被服务端成功接收
=====》
所以当服务端发送「初始序列号」syn给客户端的时候,依然也要得到客户端的应答回应,这样一来一回,才能确保双方的初始序列号能被可靠的同步。
原因三、避免资源浪费
如果客户端发送的 SYN
报文在网络中阻塞了,重复发送多次 SYN
报文,那么服务端在收到请求后就会建立多个冗余的无效链接,造成不必要的资源浪费。
三次握手就已经理论上最少可靠连接建立,所以不需要使用更多的通信次数。
为什么每次建立 TCP 连接时,初始化的序列号都要求不一样呢?
- 为了防止历史报文被下一个相同四元组的连接接收
- 为了安全,防止黑客伪造相同序列号的tcp报文被对方接收
初始序列号 是如何随机产生的?
随机数是会基于时钟计时器递增的,基本不可能会随机成一样的初始化序列号。
既然 IP 层会分片,为什么 TCP 层还需要 MSS 呢?
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-IHczDJjU-1679922380547)(null)]
MTU
:一个网络包的最大长度,以太网中一般为1500
字节;MSS
:除去 IP 和 TCP 头部之后,一个网络包所能容纳的 TCP 数据的最大长度;
如果ip层分片,当一个分片丢失的话,整个ip报文的所有分片都得重传。因为ip层是没有差超时重传机制的,必须交给传输层,由于丢失了分片,无法组装报文,自认也无法到达tcp层,1,所以接收方不会响应,只能重传所有的,所以ip分片传输没有效率
为了达到最佳的传输效能 TCP 协议在建立连接的时候通常要协商双方的 MSS 值。tcp发现数据超过了就先分片,
经过 TCP 层分片后,如果一个 TCP 分片丢失后,**进行重发时也是以 MSS 为单位。**而不用重传所有的分片,大大增加了重传的效率
第一次握手丢失了,会发生什么
如果客户端迟迟收不到服务端的 SYN-ACK 报文(第二次握手),就会触发「超时重传」机制,重传 SYN 报文,而且重传的 SYN 报文的序列号都是一样的。
当第五次超时重传后,会继续等待 32 秒,如果服务端仍然没有回应 ACK,客户端就不再发送 SYN 包,然后断开 TCP 连接。
客户端重传!!
第二次握手丢失了,会发生什么
- 因为第二次握手报文里是包含对客户端的第一次握手的 ACK 确认报文,所以,如果客户端迟迟没有收到第二次握手,那么客户端就觉得可能自己的 SYN 报文(第一次握手)丢失了,于是客户端就会触发超时重传机制,重传 SYN 报文 如果还是没收到第二次,client就断开连接
- 因为第二次握手中包含服务端的 SYN 报文,服务端就收不到第三次握手,于是服务端这边会触发超时重传机制,重传 SYN-ACK 报文 如果还是没收到第三次握手,server就断开
客户端和服务端都重传!!
第三次握手丢失了,会发生什么
如果服务端那一方迟迟收不到这个第三次握手的ACK报文,就会触发超时重传机制,重传 SYN-ACK 报文,直到收到第三次握手,或者达到最大重传次数。SERVER就断开连接
ACK 报文是不会有重传的,当 ACK 丢失了,就由对方重传对应的报文
服务端重传!!!
什么是SYN攻击,怎样避免
what:攻击者短时间伪造不同 IP 地址的 SYN
报文,服务端每接收到一个 SYN
报文,就进入SYN_RCVD
状态,但服务端发送出去的 ACK + SYN
报文,无法得到未知 IP 主机的 ACK
应答,久而久之就会占满服务端的半连接队列,使得服务端不能为正常用户服务。
在 TCP 三次握手的时候,Linux 内核会维护两个队列,分别是:
- 半连接队列,也称 SYN 队列;
- 全连接队列,也称 accept 队列;
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-tjnkKWl4-1679922372290)(https://s3-us-west-2.amazonaws.com/secure.notion-static.com/03573474-f94f-4b18-9424-ffd229de69c9/Untitled.png)]
SYN 攻击方式最直接的表现就会把 TCP 半连接队列打满,这样当 TCP 半连接队列满了,后续再在收到 SYN 报文就会丢弃,导致客户端无法和服务端建立连接。
避免 SYN 攻击方式,可以有以下四种方法:
- 减少 SYN+ACK 重传次数 针对 SYN 攻击的场景,我们可以减少 SYN-ACK 的重传次数,以加快处于 SYN_REVC 状态的 TCP 连接断开。
- 增大 TCP 半连接队列;
- 开启 tcp_syncookies;开启这个功能,可以在不使用SYN半连接队列的情况下建立连接,即使受到 SYN 攻击而导致 SYN 队列满时,也能保证正常的连接成功建立。
- 调大 netdev_max_backlog; 当接收速度大于处理速度,会存在这个队列里。可以调大
四次挥手
四次挥手过程
- 客户端打算关闭连接,此时会发送一个 TCP 首部
FIN
标志位被置为1
的报文,也即FIN
报文,之后客户端进入FIN_WAIT_1
状态。(我不发了,我申请断开) - 服务端收到后发送
ACK
应答报文,接着服务端进入CLOSE_WAIT
状态。(我知道你要断开了) - 客户端收到服务端的
ACK
应答报文后,之后进入FIN_WAIT_2
状态。 - 等待服务端处理完数据后也向客户端发送
FIN
报文,之后服务端进入LAST_ACK
状态。(我发一下没发完的数据,我也申请断开了,等待对方的ack) - 客户端收到服务端的
FIN
报文后,回一个ACK
应答报文,之后进入TIME_WAIT
状态(我知道你要断开了,发一个ack确认消息,ok,那我再等等,一会就断开) - 服务端收到了
ACK
应答报文后,就进入了CLOSE
状态,至此服务端已经完成连接的关闭。(好,知道我要断开的消息了,那我断开了) - 客户端在经过
2MSL
一段时间后,自动进入CLOSE
状态,至此客户端也完成连接的关闭(我最后再多等等!!)主动关闭连接的,才有 TIME_WAIT 状态。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-An8JeWwQ-1679922372291)(https://s3-us-west-2.amazonaws.com/secure.notion-static.com/2befadc3-eabe-4734-b663-f4300f466118/Untitled.png)]
为什么需要四次
- 客户端发送fin时,仅代表他不发了,但是还可以接收
- 服务端收到后先回一个ack,但是服务端可能还有数据要发,等发完了再发送fin,表述可以关闭了。
- 因为服务端要等数据发完,所以ack和fin分开发送,所以需要四次。
第一次挥手丢失了 会发生什么
客户端重传+断开
假设重传次数为3,当客户端超时重传 3 次 FIN 报文后,已达到最大重传次数,于是再等待一段时间(时间为上一次超时时间的 2 倍),如果还是没能收到服务端的第二次挥手(ACK报文),那么客户端就会断开连接。
第二次挥手丢失了 会发生什么
客户端重传+断开
所以如果服务端的第二次挥手丢失了,客户端就会触发超时重传机制,重传 FIN 报文,直到收到服务端的第二次挥手,或者达到最大的重传次数。
第三次挥手丢失了 会发生什么
服务端重传+断开 但是多了一个 客服端处于FIN_WAIT2等久了主动关闭的动作
- 当服务端重传第三次挥手报文的次数达到了 3 次后,达到了重传最大次数于是再等待一段时间(时间为上一次超时时间的 2 倍),如果还是没能收到客户端的第四次挥手(ACK报文),那么服务端就会断开连接。 服务端重传+断开
- 客户端因为是通过 close 函数关闭连接的,处于 FIN_WAIT_2 状态是有时长限制的,如果 tcp_fin_timeout 时间内还是没能收到服务端的第三次挥手(FIN 报文),那么客户端就会断开连接。
第四次挥手丢失了 会发生什么
服务端重传+断开 但是多了一个 客户端等待了2MSL然后关闭的状态
- 当服务端重传第三次挥手报文的次数达到了 3 次后,达到了重传最大次数,于是再等待一段时间(时间为上一次超时时间的 2 倍),如果还是没能收到客户端的第四次挥手(ACK报文),那么服务端就会断开连接。(和第三次挥手丢失一样)服务端重传+断开
- 客户端在收到第三次挥手后,就会进入 TIME_WAIT 状态,开启时长为 2MSL 的定时器,如果途中再次收到第三次挥手(FIN 报文)后,就会重置定时器,当等待 2MSL 时长后,客户端就会断开连接。
为什么 TIME_WAIT 等待的时间是 2MSL?
MSL: Maximum Segment Lifetime,报文最大生存时间 linux默认30s
因为tcp协议是基于ip的,ip中的TTL是经过的最大路由数,为0的时候,报文就被丢弃了,MSL单位是时间,大于等于TTL消耗为0的时间。
TTL一般设置为64,Linux 将 MSL 设置为 30 秒,意味着 Linux 认为数据报文经过 64 个路由器的时间不会超过 30 秒,如果超过了,就认为报文已经消失在网络中了
一来一回需要等待2倍的时间:如果服务端没有收到断开连接的最后的 ACK 报文(丢失一次了,一个msl),就会触发超时重发 FIN
报文,客户端接收到 FIN 后(第二个msl),会重发 ACK 给服务端, 一来一去正好 2 个 MSL。
2MSL时长相当于**至少允许报文丢失一次。**比如,若 ACK 在一个 MSL 内丢失,这样服务端重发的 FIN 会在第 2 个 MSL 内到达,TIME_WAIT 状态的连接可以应对。
连续两次丢包的概率只有万分之一,这个概率实在是太小了,所以不是4个或者8个
2MSL
的时间是从客户端接收到 FIN 后发送 ACK 开始计时的。如果在 TIME-WAIT 时间内,客户端又接收到了服务端重发的 FIN 报文,那么 2MSL 时间将重新计时。
为什么需要TIME_WAIT 状态?
主动发起关闭连接的一方,才会有 TIME-WAIT
状态。
需要 TIME-WAIT 状态,主要是两个原因:
- 防止历史连接中的数据,被后面相同四元组的连接错误的接收;
序列号和初始化序列号并不是无限递增的,会发生回绕为初始值的情况,这意味着无法根据序列号来判断新老数据。为了防止历史连接中的数据,被后面相同四元组的连接错误的接收,因此 TCP 设计了 TIME_WAIT 状态,状态会持续 2MSL
时长,这个时间足以让两个方向上的数据包都被丢弃,使得原来连接的数据包在网络中都自然消失,再出现的数据包一定都是新建立连接所产生的。
- 保证「被动关闭连接」的一方,能被正确的关闭;
TIME-WAIT 作用是等待足够的时间以确保最后的 ACK 能让被动关闭方接收,从而帮助其正常关闭。
如果最后一次ACK丢失了,那么重传的FIN到达时,会触发RST错误状态,为了放防止这种,客户端必须等待足够长的时间,确保服务端能够收到 ACK,如果服务端没有收到,那么触发 TCP 重传机制,重新传 FIN,这样一去一来刚好两个 MSL 的时间。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ApVdSibQ-1679922372291)(https://s3-us-west-2.amazonaws.com/secure.notion-static.com/31259b1f-515c-4da3-860e-511ffcde7418/Untitled.png)]
TIME_WAIT 过多有什么危害?
危害不同!!
- 如果客户端(主动发起关闭连接方)的 TIME_WAIT 状态过多,占满了所有端口资源,那么就无法对「目的 IP+ 目的 PORT」都一样的服务端发起连接了。还是可以与其他服务器建立连接的,只要客户端连接的服务器不是同一个,那么端口是重复使用的。
- 如果服务端(主动发起关闭连接方)的 TIME_WAIT 状态过多,并不会导致端口资源受限,因为服务端只监听一个端口,而且由于一个四元组唯一确定一个 TCP 连接,因此理论上服务端可以建立很多连接,但是 TCP 连接过多,会占用系统资源,比如文件描述符、内存资源、CPU 资源、线程资源等
如何优化 TIME_WAIT?
通过各种linux内核参数,比如复用处于tme_wait的socket为新连接所用
但不建议
TIME_WAIT 是我们的朋友,它是有助于我们的,不要试图避免这个状态,而是应该弄清楚它。
如果服务端要避免过多的 TIME_WAIT 状态的连接,就永远不要主动断开连接,让客户端去断开,由分布在各处的客户端去承受 TIME_WAIT。
服务器出现大量 TIME_WAIT 状态的原因有哪些?
说明服务器主动断开了很多 TCP 连接
什么场景下服务端会主动断开连接呢?
- 第一个场景:HTTP 没有使用长连接 (Keep-Alive)机制 客户端或服务端禁用了 HTTP Keep-Alive
- 第二个场景:HTTP 长连接超时 假设设置了 HTTP 长连接的超时时间是 60 秒,nginx 就会启动一个「定时器」 如果客户端在完后一个 HTTP 请求后,在 60 秒内都没有再发起新的请求,定时器的时间一到,nginx 就会触发回调函数来关闭该连接,那么此时服务端上就会出现 TIME_WAIT 状态的连接
- 第三个场景:HTTP 长连接的请求数量达到上限
如果已经建立了连接,但是客户端突然出现故障了怎么办?
服务端的 TCP 连接将一直处于 ESTABLISH
状态,占用着系统资源。为了避免这种情况,TCP 有一个保活机制
定义一个时间段,在这个时间段内,如果没有任何连接相关的活动,TCP 保活机制会开始作用,每隔一个时间间隔,发送一个探测报文,该探测报文包含的数据非常少,如果连续几个探测报文都没有得到响应,则认为当前的 TCP 连接已经死亡,系统内核将错误信息通知给上层应用程序。
如果已经建立了连接,但是服务端的进程崩溃会发生什么?
TCP 的连接信息是由内核维护的,所以当服务端的进程崩溃后,内核需要回收该进程的所有 TCP 连接资源,于是内核会发送第一次挥手 FIN 报文,后续的挥手过程也都是在内核完成,并不需要进程的参与,所以即使服务端的进程退出了,还是能与客户端完成 TCP 四次挥手的过程。