赞
踩
tcp连接的建立与断开是面试和工作中经常遇到问题,最近下定决心好好学习一下。文章主要参考了极客时间的刘超老师的《趣谈网络协议》课程(内容真的很不错,大家有兴趣可以去学习一下)课程,以下主要是对其中的一些知识点的。总结。
它天然认为网络环境是恶劣的,丢包、乱序、重传,拥塞都是常有的事情,一言不合就可能送达不了,因而要从算法层面来保证可靠性。TCP 包头格式
tcp包中有以下重要字段:
1、源端口号和目标端口号 这是不可少的,这一点和 UDP 是一样的。如果没有这两个端口号。数据就不知道应该发给哪个应用。
2、包的序号。 为什么要给包编号呢?当然是为了解决乱序的问题。不编好号怎么确认哪个应该先来,哪个应该后到呢。编号是为了解决乱序问题。
3、确认序号。发出去的包应该有确认,要不然我怎么知道对方有没有收到呢?如果没有收到就应该重新发送,直到送达。这个可以解决不丢包的问题。
4、状态位。SYN 是发起一个连接,ACK 是回复,RST 是重新连接,FIN 是结束连接等。TCP 是面向连接的,因而双方要维护连接的状态,这些带状态位的包的发送,会引起双方的状态变更。
5、窗口大小。用来做流量控制,控制数据发送的快慢。
通过对 TCP 头的解析,我们知道要掌握 TCP 协议,重点应该关注以下几个问题:
1、顺序问题
2、丢包 问题
3、连接维护
4、流量控制
5、拥塞控制
TCP三次握手的作用:
1、双方建立连接。
2、沟通TCP 包的序号的问题。
TCP 的三次握手。可以简单理解为如下过程:
A:您好,我是 A。B:您好 A,我是 B。A:您好 B。
我们也常称为“请求 -> 应答 -> 应答之应答”的三个回合。
建立连接为什么要三次,而不是两次,两次为什么不可靠?为了可靠,为什么不是四次?
我们假设这个通路是非常不可靠的,当A发起SYN包请求连接后,到达了B。B收到包,回复了应答包ACK。此时对于B来说他不知道应答包是否到达了A,A的应答包是否发出来了。如果网络出现故障,这个应答包可能永远也到不了A,或者A的应答包永远也发不过来。所以这时候(完成了两次握手)建立连接并不能保证可用性。
所以对于B来说,两次握手只能保证A的包能发送过来B,并不能保证B的包能发送到A
tcp包其实始终遵循一个可靠性,即是每个发送了的包都需要有ACK的确认包。所以,B的ack回包是对于A的syn包的确认,A的ACK包是对于B的ACK包的确认。所以理论上可以无限继续下去,但是无论继续多少次,都无法保证一定可靠,并且还会浪费网络资源,延长建立连接时间。所以三次握手应该是在可靠性和建立连接速度上的一个平衡。
大部分情况下,A 和 B 建立了连接之后,A 会马上发送数据的,一旦 A 发送数据,则很多问题都得到了解决。例如 A 发给 B 的应答丢了,当 A 后续发送的数据到达的时候,B 可以认为这个连接已经建立,或者 B 压根就挂了,A 发送的数据,会报错,说 B 不可达,A 就知道 B 出事情了
如果A建立连接后,不发数据,建立连接后空着。我们在程序设计的时候,可以要求开启 keepalive 机制,即使没有真实的数据包,也有探活包,来保证连接不被服务端回收。
服务端 B 的程序设计者,对于 A 这种长时间不发包的客户端,可以主动关闭,从而空出资源来给其他客户端使用。
A 要告诉 B,我这面发起的包的序号起始是从哪个号开始的,B 同样也要告诉 A,B 发起的包的序号起始是从哪个号开始的。
因为这样往往会出现冲突。例如:
A 连上 B 之后,发送了 1、2、3 三个包,但是发送 3 的时候,中间丢了,或者绕路了,于是重新发送,后来 A 掉线了,重新连上 B 后,序号又从 1 开始,然后发送 2,但是压根没想发送 3,但是上次绕路的那个 3 又回来了,发给了 B,B 自然认为,这就是下一个包,于是发生了错误
所以,序号不从1而且要从时间计数器的原因是,为了防止有效时间内包的序号重复。发生拼接包的错误。因而,每个连接都要有不同的序号。这个序号的起始序号是随着时间变化的,可以看成一个 32 位的计数器,每 4 微秒加一。如果计算一下,如果到重复,需要 4 个多小时,那个绕路的包早就死翘翘了。因为我们都知道 IP 包头里面有个 TTL,也即网络中可以转发的最大跳数。
简单解释一下:
1、一开始,客户端和服务端都处于 CLOSED 状态。先是服务端主动监听某个端口,处于 LISTEN 状态。
2、然后客户端主动发起连接 SYN,之后处于 SYN-SENT 状态。
3、服务端收到发起的连接,返回 SYN,并且 ACK 客户端的 SYN,之后处于 SYN-RCVD 状态。
这里会存在一个问题,如果服务端B发送了ACK之后,客户端A长时间回复ACK,则这个连接状态在服务端会长时间处于SYN-RCVD状态。
当短时间内过多时,这时就会出现大量的半连接(Half-Open)连接,占用服务器的资源,当半连接的数量达到系统所允许的最大值时,服务器将无法再接受新的连接请求。就是SYN泛洪攻击。
解决方法主要有以下几个:
1、增加 SYN-RCVD 状态的队列长度,可以通过修改系统内核参数来实现,例如修改 /etc/sysctl.conf 文件中的 net.ipv4.tcp_max_syn_backlog 参数。
2、减少半连接的数量,可以通过增加服务器的处理能力,例如增加 CPU 和内存资源,或者通过优化网络程序的代码来提高处理效率。
3、使用防火墙等技术,限制 SYN 请求的流量,例如使用 SYN Cookies 技术来减少 SYN 攻击的影响。
4、客户端收到服务端发送的 SYN 和 ACK 之后,发送 ACK 的 ACK,
5、之后处于 ESTABLISHED 状态,因为它一发一收成功了。
6、服务端收到 ACK 的 ACK 之后,处于 ESTABLISHED 状态,因为它也一发一收了。
三次握手就是保证了两边各自的一收一发。
4次挥手同样可以简单理解为以下四步:
1、A:B 啊,我不想玩了。
2、B:哦,你不想玩了啊,我知道了
3、B:A 啊,好吧,我也不玩了,拜拜。
4、A:好的,拜拜。
这四次握手中:
前两步是A告诉B自己需要关闭连接,B进行回复。
第一个问题:
这一步B是否能够直接断开连接呢,当然不行。当然不可以,很有可能 A 是发完了最后的数据就准备不玩了,但是 B 还没做完自己的事情,还是可以发送数据的,所以称为半关闭的状态。
这个时候 A 可以选择不再接收数据了,也可以选择最后再接收一段数据,等待 B 也主动关闭。
第二个问题:
A 说完“不玩了”之后,直接跑路,也是会有问题的,因为 B 还没有发起结束,而如果 A 跑路,B 就算发起结束,也得不到回答,B 就不知道该怎么办了。
后两步是B告诉A我也打算关闭连接了,然后A进行回复。
梳理一下整个时序图:
1、当 A FIN包后,就A进入 FIN_WAIT_1 的状态,
如果A服务器在一段时间内没有响应,那么客户端A就会一直处于这种状态,从而导致连接数过多。
要解决这个问题,可以通过以下方法:
1、调整操作系统的参数:可以增加操作系统的最大并发连接数限制,或者缩短操作系统回收TIME_WAIT状态的时间。
2、检查网络连接是否存在问题:有可能是网络连接不稳定,导致服务器不能及时响应客户端的FIN报文,可以通过网络监控工具来诊断网络问题。
3、优化应用程序:如果是应用程序的问题,可以通过优化应用程序的代码来改进应用程序的性能。
4、加强服务器的性能:可以增加服务器的带宽或者处理能力,来提高服务器的性能和响应速度。
5、升级服务器的硬件设备:如果以上方法都无法解决问题,可以考虑升级服务器的硬件设备,以提高服务器的性能和响应速度。
2、B 收到A的FIN包的消息后,发送相应的ACK包,就进B入 CLOSE_WAIT 的状态。
3、A 收到B的ACK,就A进入 FIN_WAIT_2 的状态。
如果这个时候 B 直接跑路,则 A 将永远在这个状态。TCP 协议里面并没有对这个状态的处理,但是 Linux 有,可以调整 tcp_fin_timeout 这个参数,设置一个超时时间。
4、如果 B 没有跑路,发送了ACK+FIN,随后B进入LAST_ACK状态。
LAST_ACK。如果服务器B长时间没有收到ACK+FIN的确认包(ACK),那么会长时间处于LAST_ACK。
可以有以下解决方法:
1、增加服务器的资源,以应对更多的TCP连接和数据流量。
2、调整TCP参数,如减小TIME_WAIT和FIN_WAIT_2等状态的时间。
3、检查网络设备,如防火墙和路由器,以确保它们正确地处理TCP连接和数据包。
4、使用连接池技术,以重用已经建立的TCP连接,避免频繁地建立和关闭TCP连接
5、A收到B的请求后,A 发送的 ACK 后,A从 FIN_WAIT_2 状态结束,进入TIME_WAIT状态。
到这里按说 A 可以跑路了,但是最后的这个 ACK 万一 B 收不到呢?则 B 会重新发一个ACK+FIN,这个时候 A 已经跑路了的话,B 就再也收不到 ACK 了。
因而 TCP 协议要求 A 最后等待一段时间 TIME_WAIT,这个时间要足够长,长到如果 B 没收到 ACK 的话,B会重发FIN+ACK包的,A 会重新发一个 ACK 并且足够时间到达 B。
此外,如果A直接跑路还有一个问题:
A 的端口就直接空出来了,但是 B 不知道,B 原来发过的很多包很可能还在路上,如果 A 的端口被一个新的应用占用了,这个新的应用会收到上个连接中 B 发过来的包,虽然序列号是重新生成的,但是这里要上一个双保险,防止产生混乱,因而也需要等足够长的时间,等到原来 B 发送的所有的包都死翘翘,再空出端口来。
6、A发送ACK后,进入TIME_WAIT状态,等待2MSL后进入Close状态。
MSL 是 Maximum Segment Lifetime,报文最大生存时间,它是任何报文在网络上存在的最长时间,超过这个时间报文将被丢弃。因为 TCP 报文基于是 IP 协议的,而 IP 头中有一个 TTL 域,是 IP 数据报可以经过的最大路由数,每经过一个处理他的路由器此值就减 1,当此值为 0 则数据报将被丢弃,同时发送 ICMP 报文通知源主机。协议规定 MSL 为 2 分钟,实际应用中常用的是 30 秒,1 分钟和 2 分钟等。
还有一个异常情况就是,B 超过了 2MSL 的时间,依然没有收到它发的 FIN 的 ACK,怎么办呢?按照 TCP 的原理,B 当然还会重发 FIN,这个时候 A 再收到这个包之后,A 就表示,我已经在这里等了这么长时间了,已经仁至义尽了,之后的我就都不认了,于是就直接发送 RST,B 就知道 A 早就跑了。
TIME_WAIT状态的设立可以确保所有因网络延迟而传输较慢的分组能够发送和接收,避免分组重复。然而,在高并发请求环境下,如果服务器上同时存在许多处于TIME_WAIT状态的套接字,会浪费大量的内存资源。在这种情况下,可以通过调整TCP/IP内核参数来减少TIME_WAIT状态下的连接数量,例如启用TCP_TW_RECYCLE、TCP_TW_REUSE等选项。
7、B收到ACK后,从LAST_ACK状态结束。进入closed状态。
加黑加粗的部分,是上面说到的主要流程,其中阿拉伯数字的序号,是连接过程中的顺序,而大写中文数字的序号,是连接断开过程中的顺序。加粗的实线是客户端 A 的状态变迁,加粗的虚线是服务端 B 的状态变迁。
以下是一些常见的tcp相关的内核参数:
net.ipv4.tcp_fin_timeout
定义了 TCP 连接的关闭时间,默认为 60 秒。该值可以根据实际情况调整,以减少 TIME_WAIT 状态的数量。
net.ipv4.tcp_tw_reuse
启用TIME-WAIT socket快速重用(Fast Open),以减少timewait状态持续时间,减小服务器负载。当该选项打开时,在TIME_WAIT结束后仍然保留相应的socket用于新的连接。默认值为0,不启用此功能。有风险。
net.ipv4.tcp_tw_recycle
如果该参数值为 1,则表示开启 TCP 连接的快速回收机制,并且对于某些具有时间戳错误的包,内核会忽略该错误并且仍然使用连接。这样可以减少 TIME_WAIT 状态的数量。
当此参数被启用时,内核会在接收到客户端的 ACK 包后立即回收对应的 TW 连接,这样就可以尽快地回收资源,提高服务器性能。
然而,TCP_TW_RECYCLE 参数存在一定的风险。在某些情况下,它可能会导致客户端收到过期的数据包,从而导致连接失败或数据损坏。这是因为当启用此参数时,内核会使用一种基于时间戳的算法来判断 TW 连接是否过期,如果客户端和服务器的时钟不同步,就可能会导致判断出现错误。
主要是这两个:
1、可能会导致连接复用问题:在TCP_TW_RECYCLE开启的情况下,如果客户端IP地址和端口号相同并且包含了FIN标志的新连接请求到达服务器时,可能会被错误地认为是之前的连接,在该连接上的数据可能会交换,导致错误的结果。
2、可能会导致一个TCP连接的另一部分收到旧的数据: 如果在一个新连接开始之前,数据包找到了需要处理TIME_WAIT状态的连接,则会将数据清除,并在此次连接中阻止这些数据包。但是,由于TCP_TW_RECYCLE重新启动了ACK队列,因此集合中的部分数据可能被发送到新到达的连接。
net.ipv4.tcp_max_syn_backlog
定义了 TCP SYN 请求队列的最大长度。该值可以根据实际情况调整,以避免 SYN 攻击。
net.core.somaxconn
定义了每个监听套接字的最大连接数。该值可以根据实际情况调整,以避免服务器过载。
net.ipv4.tcp_keepalive_time
定义了 TCP 连接在无数据交换时,保持存活状态的时间,默认为 7200 秒。该值可以根据实际情况调整,以避免空闲连接占用资源。
net.ipv4.tcp_syn_retries
定义了发送 TCP SYN 请求的最大次数,默认为 5 次。该值可以根据实际情况调整,以避免 SYN 攻击。
net.ipv4.tcp_synack_retries
定义了发送 TCP SYN-ACK 包的最大次数,默认为 5 次。该值可以根据实际情况调整,以避免 SYN 攻击。
参数风险
tcp_fin_timeout:tcp_fin_timeout
用于设置在发送FIN后等待另一端确认FIN的超时时间。如果该值过小,则可能会将未完成的数据包丢弃,导致通信错误;而如果该值过大,则会占用系统资源,并减慢连接速度。
tcp_tw_reuse:
启用 TCP_TW_REUSE 参数可以减少 TIME_WAIT 状态的持续时间,从而提高性能和可靠性。然而,在某些情况下(例如在存在地址重用攻击漏洞的系统中),攻击者有可能利用源IP/port伪造数据、进行欺骗和其他安全等级极高的行为。
tcp_tw_recycle
启用 TCP_TW_RECYCLE 参数可以快速回收处于 TIME_WAIT 状态的 socket。虽然它可以消除 TIME_WAIT 的资源浪费问题,从而提高系统的性能,但也可能引起连接复用问题,进而导致数据包传输错误等问题。
tcp_max_syn_backlog
此参数限制了应用程序后台监听允许积压的半链接(即暂未完成三次握手的套接字)的最大数量。如果设得太低,会导致短时间内有大量请求时,服务器无法应答所有请求;如果设得太高,则可能会耗尽系统资源或面临SYN洪水攻击。
tcp_syn_retries 和 tcp_synack_retries
两个参数用于设置在 SYN 或 SYN + ACK 超时后重试的次数。过大的重试次数可能导致连接超时,而过小则会使服务器容易受到SYN Flood攻击
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。