计算机网络——3.实际情况下的TCP连接

前两篇文章我们讲了TCP强大的能力,但是仔细想想,我们不是生活在乌托邦,各种问题层出不穷,比如同一座城市中的每家每户都在使用同一条光缆,当处在上网高峰期的时候,难免会“堵车”,这对连接的速度和稳定难免产生影响,各种各样的情况数不胜数,那么对于每种情况,TCP应该如何面对呢?

1.丢包

丢包可谓是传输中最常见的问题,就好比我们送快递我们购买快递的频率相比网络传输的频率可低得多,但是我们的快递也难免会丢失,那现实中数据丢包可就更常见了,TCP是如何巧妙的解决这个问题呢?

在前两期的内容中介绍到了,seq(序列号)和ack(确认应答号)这两个数据,TCP连接中每一次的数据交互都至少包含了这两个数据其中之一,那么这样的一来一回到底做到了什么呢?

就像我们打电话,我说一句你就答一句,这样我才知道我上一句话你有没有听清,seq和ack就是这样的一问一答的过程,具现化到TCP中就是这样的

1.客户端 发送数据并生成序列号1-500发送

2.服务器 成功接收1-500的数据包并生成501的确认应答号,表示我1-500我已成功接收,下一次我想收到501开头的数据

3.客户端 接收501开头的确认应答数据,对方已成功接收1-500的数据包,发送序列号为501-1000的数据包

4.服务器 成功接收501-1000的数据包,并生成1001的确认应答号发送给客户端表示我501-1000我已成功接收,下一次我想收到1001开头的数据

如此往复下去,就是一次成功的传输,但是一问一答的形式太过于缓慢,所以现实中是发送数据次数大于等于回应次数,那么丢包的时候是怎么样的呢?

1.重传机制

1快速重传

1.客户端 发送数据并生成序列号1-500发送(假设此数据包丢失)

2.客户端 发送数据并生成序列号501-1000发送

3.服务器 成功接收501-1000的数据包,但是501之前的数据包我没有成功接收,所以生成确认应答号为1的回应给客户端,表示我下一次想要序列号为1开头的数据包

4.客户端 成功接收到确认应答,我已知晓1-500数据丢失,触发重传,将1-500数据包重新传输

5.服务器 成功接收1-500的数据,并且501-1000的数据我上一次已成功接受,所以生成1001开头的确认应答包并发送,表示我下一次想要1001开头的数据

这就是TCP处理丢包的方式——重传机制,当数据成功发送但是未能被成功接收时就会再次发送这个数据,直达确认应答号正常,而重传也分为两种情况,上述情况叫做快速重传,当确认应答号与预期不符合则立刻重新发送数据

2.超时重传

每一次数据交互,在发送方发送数据时都会设置一个计时器,如果计时器的时间倒数完后仍没有收到应答那么就会触发相应的超时或者基于计时器的重传操作,计时器超时通常被称为重传超时(RTO),发送方认为要么是我上一个数据包没有被成功接收,或者确认应答数据包丢失,为了防止第一种情况,则会重传这个数据包,这个行为就是超时重传

这两种重传方式一个是基于信息,一个基于时间,相比之下基于信息的方式更加具有效率,并且从这点就可以看出,TCP 的确认和重传,都是基于数据包是否被确认为前提的。

在 Linux 下,SYN-ACK 报文的最大重传次数由 tcp_synack_retries 内核参数决定,默认值是 5
cat /proc/sys/net/ipv4/tcp_synack_retries

3.特殊的丢包

数据交互的时候丢包是现在看来很平常的,毕竟有重传机制去维护,即使丢失也有“售后服务”那么仔细想想,三次握手和四次挥手的数据包和数据交互的数据其实没有差别,那么这么看来他们也会丢包,那么如果在三次握手和四次挥手的时候发生了丢包会发生什么呢?

1.第一次握手丢失

当客户端想和服务端建立 TCP 连接的时候,首先第一个发的就是 SYN 报文,然后进入到 SYN_SENT 状态。在这之后,如果客户端迟迟收不到服务端的 SYN-ACK 报文(第二次握手),就会触发「 超时重传 」机制,重传 SYN 报文,而且 重传的 SYN 报文的序列号都是一样的

2.第二次握手丢失

当服务端收到客户端的第一次握手后,就会回 SYN-ACK 报文给客户端,这个就是第二次握手,此时服务端会进入 SYN_RCVD 状态。

第二次握手的 SYN-ACK 报文其实有两个目的 :

  • 第二次握手里的 ACK, 是对第一次握手的确认报文;
  • 第二次握手里的 SYN,是服务端发起建立 TCP 连接的报文;

所以,如果第二次握手丢了,就会发生比较有意思的事情,具体会怎么样呢?

因为第二次握手报文里是包含对客户端的第一次握手的 ACK 确认报文,所以,如果客户端迟迟没有收到第二次握手,那么客户端就觉得可能自己的 SYN 报文(第一次握手)丢失了,于是客户端就会触发超时重传机制,重传 SYN 报文

然后,因为第二次握手中包含服务端的 SYN 报文,所以当客户端收到后,需要给服务端发送 ACK 确认报文(第三次握手),服务端才会认为该 SYN 报文被客户端收到了。

那么,如果第二次握手丢失了,服务端就收不到第三次握手,于是服务端这边会触发超时重传机制,重传 SYN-ACK 报文

总结:

如果你了解第二次握手的过程和重传机制你就会明白这种现象,第二次握手的数据包包含第一次握手的应答,和第二次握手的请求,这个数据包的丢失会让两边都认为自己发送失败,所以两边都会重传

3.第三次握手丢失

到这里就很简单了,客户端收到服务端的 SYN-ACK 报文后,就会给服务端回一个 ACK 报文,也就是第三次握手,此时客户端状态进入到 ESTABLISH 状态。

因为这个第三次握手的 ACK 是对第二次握手的 SYN 的确认报文,所以当第三次握手丢失了,如果服务端那一方迟迟收不到这个确认报文,就会触发超时重传机制,重传 SYN-ACK 报文,直到收到第三次握手,或者达到最大重传次数。

到这里需要知道一件事,在网络通畅的情况下丢包其实是小概率事件(趋近于零),如果一直丢包,是因为网络原因(网络拥塞,下次讲)或者其他问题(例如可能这一瞬间有人宕机了),导致这三次握手的数据包一直丢失,那么连接会被断开,计算机就是这么想的,你的网络条件都这么差了就别通信了(多么残酷的现实   哭哭)

4.第一次挥手丢失

当客户端(主动关闭方)调用 close 函数后,就会向服务端发送 FIN 报文,试图与服务端断开连接,此时客户端的连接进入到 FIN_WAIT_1 状态。
如果第一次挥手丢失了,那么客户端迟迟收不到被动方的 ACK 的话,也就会触发超时重传机制,重传 FIN 报文,重发次数由 tcp_orphan_retries 参数控制。

当客户端重传 FIN 报文的次数超过 tcp_orphan_retries 后,就不再发送 FIN 报文,则会在等待一段时间(时间为上一次超时时间的 2 倍),如果还是没能收到第二次挥手,那么直接进入到 close 状态。因为请求方本来就要断开连接,我的事务已处理完毕,没必要一直等待你。

5.第二次挥手丢失

当服务端收到客户端的第一次挥手后,就会先回一个 ACK 确认报文,此时服务端的连接进入到 CLOSE_WAIT 状态。 ACK 报文作为一个应答是不会重传的 ,所以如果服务端的第二次挥手丢失了,客户端就会触发超时重传机制,重传 FIN 报文,直到收到服务端的第二次挥手,或者达到最大的重传次数。
对于 close 函数关闭的连接,由于无法再发送和接收数据,所以 FIN_WAIT2 状态不可以持续太久,而 tcp_fin_timeout 控制了这个状态下连接的持续时长,默认值是 60 秒。这意味着对于调用 close 关闭的连接,如果在 60 秒后还没有收到 FIN 报文,客户端(主动关闭方)的连接就会直接关闭

6.第三次挥手丢失

当服务端(被动关闭方)收到客户端(主动关闭方)的 FIN 报文后,内核会自动回复 ACK,同时连接处于 CLOSE_WAIT 状态,顾名思义,它表示等待应用进程调用 close 函数关闭连接。

此时,内核是没有权利替代进程关闭连接,必须由进程主动调用 close 函数来触发服务端发送 FIN 报文。服务端处于 CLOSE_WAIT 状态时,调用了 close 函数,内核就会发出 FIN 报文,同时连接进入 LAST_ACK 状态,等待客户端返回 ACK 来确认连接关闭。如果迟迟收不到这个 ACK,服务端就会重发 FIN 报文,重发次数仍然由 tcp_orphan_retries 参数控制,这与客户端重发 FIN 报文的重传次数控制方式是一样的。

7.第四次挥手丢失

当客户端收到服务端的第三次挥手的 FIN 报文后,就会回 ACK 报文,也就是第四次挥手,此时客户端连接进入 TIME_WAIT 状态。

在 Linux 系统,TIME_WAIT 状态会持续 2MSL 后才会进入关闭状态。然后,服务端(被动关闭方)没有收到 ACK 报文前,还是处于 LAST_ACK 状态。如果第四次挥手的 ACK 报文没有到达服务端,服务端就会重发 FIN 报文

看到这里大家应该非常了解了重传是怎么回事了,这是TCP对于丢包问题的解决方式,那么连接除了丢包还会遇到什么问题呢?

2.宕机

谁还没经历过电脑蓝屏呢?当你忙了一天的工作终于完成了几十上百页的PPT,或者写了几万字论文终于到了致谢环节,这一刻的舒爽感和打了一场胜仗无异,但是就在此时,电脑突然发出了耀眼的蓝光!或者电脑突然黑屏了!这一刻又和突然坠入深渊无异,如果你运气真的很差,在TCP连接中出现了这种问题怎么办?TCP会作何反应?

1.半开启

TCP 连接处于半开启的这种状态是因为连接的一方关闭或者终止了这个TCP连接却没有通知另一方,此时就认为这条连接处于半开启状态。这种情况可能发生在通信中的一方处于主机崩溃的情况下,但是,我电脑死机了我咋告诉你?只要处于半开启状态的一方不传输数据的话,那么是无法检测出来对方主机已经下线的。另外一种处于半开启状态的原因是通信的一方关闭了主机电源而不是正常关机。这种情况下会导致服务器上有很多半开启的TCP连接。如果我不知道你已经断开连接,只要我没收到你的断连请求,那么我会一直保留着这次连接,这样下去这种无意义的连接对服务器的损耗就会越来越大

那么TCP如何解决这个看似无解的问题呢?

TCP保活机制(心跳检测)

顾名思义,这个机制就是因为考虑到了这种情况而用来检测你是否还活着,具体是怎么做得呢?

心跳检测通常通过发送心跳包(也称为心跳消息或保持活动消息)来实现。这是一种小型的数据包或消息,它在连接上定期发送,用于验证连接的存活状态。

客户端发起心跳:客户端每隔一段时间发送心跳包,如果客户端在设定时间没有收到服务端返回的应答消息,经重试机制后,客户端认为服务器异常,断开与服务器的连接。

服务器发起心跳:服务器设置超时机制,主动向客户端发送心跳包,客户端未在规定时间内返回应答包导致超时,服务器将认为客户端异常,关闭与客户端的连接。

举例来说就像两个人打电话,我们长时间不说话,所以我就隔一段时间问一句你在吗,你回答了我就知道你在,但是如果你不回答我,我就知道你可能是睡着了,那我就可以把电话挂了,半开启的问题就是被这么解决的,对于一对惰性连接(长时间没有数据交互)的连接就经常会出现心跳检测,每一次心跳检测的时间大概是两小时(系统默认),如果你没有回应那么这个连接我还会维护11分钟左右,期间我会不断尝试检测心跳,直到收到响应,否则就会直接断开连接

2.半关闭

TCP 半关闭也并不常见。TCP 的半关闭操作是指仅仅关闭数据流的一个传输方向。两个半关闭操作合在一起就能够关闭整个连接。在一般情况下,通信双方会通过应用程序互相发送 FIN 报文段来结束连接,但是在 TCP 半关闭的情况下,应用程序会表明自己的想法:"我已经完成了数据的发送,并发送了一个 FIN 报文段给对方,但是我依然希望接收来自对方的数据直到它发送一个 FIN 报文段给我"。 下面是一个 TCP 半关闭的示意图。

首先客户端主机和服务器主机一直在进行数据传输,一段时间后,客户端发起了 FIN 报文,要求主动断开连接,服务器收到 FIN 后,回应 ACK ,由于此时发起半关闭的一方也就是客户端仍然希望服务器发送数据,所以服务器会继续发送数据,一段时间后服务器发送另外一条 FIN 报文,在客户端收到 FIN 报文回应 ACK 给服务器后,断开连接。

TCP 的半关闭操作中,连接的一个方向被关闭,而另一个方向仍在传输数据直到它被关闭为止。只不过很少有应用程序使用这一特性。

3.双向奔赴

1.同时打开

友友们又来顾名思义了,两个应用程序同时主动打开连接。虽然这种情况看起来不太可能,但是在特定的安排下却是有可能发生的。我们主要讲述这个过程。

通信双方在接收到来自对方的 SYN 之前会首先发送一个 SYN,这个场景还要求通信双方都知道对方的 IP 地址 + 端口号。

通信双方都在收到对方报文前主动发送了 SYN 报文,都在收到彼此的报文后回复了一个 ACK 报文 一个同时打开过程需要交换四个报文段,比普通的三次握手增加了一个,由于同时打开没有客户端和服务器一说,所以这里我用了通信双方来称呼。

2.同时关闭

像同时打开一样,同时关闭也是通信双方同时提出主动关闭请求,发送 FIN 报文,下图显示了一个同时关闭的过程。

同时关闭过程中需要交换和正常关闭相同数量的报文段,只不过同时关闭不像四次挥手那样顺序进行,而是交叉进行的。

今天就先说这么多,好悬没给我写死啊,关于TCP的问题还有特别特别多,友友们等我更新哈,包给你们计算机网络这块整通透(下图为沉默的帮凶)

                            

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值