有关TCP 连接的退出,你该知道的事....(下)

前言

上节中说到在连接建立的过程中出现的各种异常,这里接着说下面的部分。
在这里插入图片描述


需要说明一点点,下面所述的异常很多都可以转换(客户端和服务器端都可以主动发起连接) 其中的过程也是类似的,下文就不赘述了。


二、数据传输阶段可能出现的异常

2.1 ④⑤阶段发生的异常:服务器主机崩溃

这种情况描述的是:当客户端C和服务器端S之间正常“沟通”之时,服务器主机哎呀,over宕机了,over的相当彻底,彻底的都没时间处理完剩下的事情(断开连接…)

这种情况比较简单,客户端C发送数据后由于收不到“ACK”, 将会一重发,直到超时返回ETIMEDOUT或者返回EHOSTUNEACH。
在这里插入图片描述

下图是我用tcpdump测试的重发示意图:
在这里插入图片描述


2.2 ④⑤阶段发生的异常:服务器主机崩溃后重启

这种情况是在上面情况中延续了一个条件,即服务器崩溃后重新启动了。这种情况也比较简单,由于服务器在重启的过程中丢掉了所有的TCP连接信息,所以当客户端C通过重启前(上辈子的连接)的连接发送数据时,服务器端会感到莫名其妙:“你是谁啊,我不认识你啊?” 最终会返回一个RST分节,告诉客户端,这条连接已经失效了。

具体的示意图如下。
在这里插入图片描述


3.3 ④⑤阶段发生的异常:突然拔掉客户端网线或者客户端主机突然奔溃

这里和上面3.1所说的情况有点类似,不同的地方在于一点:就是在突然断开一端连接之时,连接中是否有数据交互。 如果有数据交互的话,则就是3.1中的情况,无数据交互的话就是这里说的情况。

先说一下结论,如果不采取其他的措施,当客户端突然拔掉网线则服务器端是无法知道客户端已经“over”了。

为了解决这个问题,tcp协议中有一个keepalive的机制,它使得在连接空闲的时候会发送一个探测包,以此来检测对端是否还存活,如果发送的探测包长时间无回复,服务器才会清除这个连接。

这个TCP协议自带的keepalive机制只适合清除死亡时间比较长的连接。 比如说几个小时,但是如果需要快速或者实时监控连接状态的机制,这种机制就不太适合了(周期比较长),这时候就需要应用层设计自己的“heart-beat” 来检测是否对端还存活了。【7】【8】中论述的比较详细。


三、断开连接阶段可能出现的异常

3.1 ⑦⑧…⑨阶段发生的异常:服务器进程终止

这里首先说明一下,虽然本节发生正常逻辑的数据传输阶段,但是引发的结果是进入到了断开连接的过程。所以标题以⑦⑧…⑨为编号。 同时,这里是服务端因为进程终止引发主动断开连接的过程,和最上面的图示刚好相反,请注意。

事是这么个事:客户端C和服务器端S正在友好的交流玩耍,突然之间服务器程序因为各种原因终止了(这里只是程序终止,不是主机崩溃),这会引发服务器内核开始主动发送“FIN” 分节(内核清理进程未关闭的连接),然后进入到FIN_WAIT1状态; 客户端C收到这个“FIN”分节之后会给出一个确认“ACK”,然后进入到CLOSE_WAIT状态,服务器端收到这个确认“ACK”之后会进入到FIN_WAIT2状态(OK,前半段挥手过程结束,接下来就是等待客户端的发来的“FIN”分节,接着完成后半段“挥手”过程)。

基本的示意图如下所示。
在这里插入图片描述



ok。那么下面问题来了,如果在这个时候客户端C又给服务器端write了一些数据怎么办?(注意,上述过程中客户端是不知道服务器端已经挂了的,它只是以为服务器端“正常”的关闭了一端的连接(也就是说它只知道服务器端S不会在写数据给“我”(客户端C)了,但是,“我”(客户端)依然可以write 数据给服务器端)


简单来说一句话,如果客户端给已经close连接(写端和读端)的服务器端继续write数据会发生什么?

答:服务器端的内核会给客户端返回一个“RST”分节,来告诉客户端这个连接已经关闭了,请不要在发送数据过来了。


上面了解了吗? 了解了话,我们来点“刺激”的?

如果这个时候客户端继续write 或者read 怎么办?

你可能会问,这个客户端是不是傻,是不是损色? 人家都已经关闭“交流”的通道了,还热脸贴着冷屁股,一个劲的往上凑? 老老实实close连接得了呗。 嗯嗯,

这是个问题,我们过会再说。先认为这个客户端就是比较“执着”,还在往已经关闭的通道了继续read或者write 数据。

所以,最终的结果呢?

这里需要视情况考虑:
(1)、如果在已经收到RST分节的端口上继续read?

这种情况的话,客户端内核会返回一个ECONNREST(“connection rest by peer”),来通知上层应用程序,对端已经关闭连接了。

这里说点小插曲,我在我的电脑上实验,最后read 返回0 ,而不是-1,就很奇怪。我搜半天,也找到原因,只找到【1】中类似下图的说法,
在这里插入图片描述
我也看不懂,╮(╯▽╰)╭, 希望有懂的小伙伴教教我,(* ^ ▽ ^ *)。

(2)、如果在已经收到RST分节的端口上继续write?
如果是这种情况的话,那么内核会向该进程发送一个SIGPIPE信号,而这个信号默认的处理方案就是终止进程。

也就是说,写一个已经接受了FIN的socket没有问题(这个时候对端会返回一个RST分节);如果写一个已经接受了“RST”分节的socket则会出现错误。

好了,让我们多空几行权当做休息。马上来说说上面遗留的问题:为何客户端那么“倔”? 非要往已经关闭连接的socket中继续操作(read或者write)?



说起来,也很简单,成也内核,败也内核
在这里插入图片描述
现代的操作系统中内核扮演了相当重要的作用。两个进程之间的socket通信中,内核帮用户程序完成的大部分的功能(连接的建立,数据的传输,连接的断开)。这是好的一方面,但是也有不太好的一方面,就是对端(peer)发来的数据需要通过内核的代理才能转给进程。

连接的断开也是内核先知道,然后进程通过系统调用read, write 返回的信息才知道对端连接是否断开。 比如说上面谈到的,服务器端发送了“FIN”分节,断开它这一端的连接之时,客户端S的内核首先知道,但是这个时候客户端的上层应用(进程)是不清楚的,进程只有通过read操作返回0(或者其他信息)才能知道对端的情况。 如果这个时候进程没有read,反而往socket中write了一些数据(因为复杂的业务需求),服务器端返回了RST,客户端也是不清楚的,所以它还可能继续read或者write,直到客户端的内核发送相应的出错信息。

3.2 ⑦⑧…⑨阶段发生的异常:服务器端出现了大量的CLOSE_WAIT的连接

这个异常其实算是属于上面3.1 情形的一部分,这里特地拿出了强调一下,因为其比较重要,面试过程中经常考。

这里还是回到正常状态,把客户端当成主动断开连接的一端。
那什么时候服务器端会出现CLOSE_WAIT状态呢?

还是回到那张经典的不能再经典的图吧。
在这里插入图片描述
当客户端和服务器端的挥手过程只进行到前一半的时候,服务端就会处于CLOSE_WAIT状态。

那为什么服务器端不继续把接下来另外半段挥手过程进行完毕呢?
答案是: 服务器端的代码有问题,程序写的不够健壮,导致服务器端在返回给客户端“ACK”分节之后一直没有调用close,进行释放自己这一端的连接。 如果服务器端运行的时间比较长,就有可能在服务器端遗留大量的“将死未死”的连接。【4】、【5】都是在实际情况中遇到的实例。

解决方案也很简单,优化代码,调bug就可以。

3.3 ⑨⑩阶段出现的异常情况:服务器端出现了大量的TIME_WAIT的连接

由上面分析可知,出现TIME_WAIT是主动关闭连接的一端,这里假设是服务器端主动关闭连接。

在这里插入图片描述

这种情况如果非要较真的话,算不上什么异常的情况,因为TIME_WAIT的存在是有其必要的:
在这里插入图片描述
但是如果服务器端如果同时出现了大量的的连接(如爬虫服务器),那么服务器端在close本端的连接之后就会有大量处于TIME_WAIT状态的连接,这种连接占用了一定的资源,影响了效率。

对于这种情况,现代的内核都会采用一定的优化方式来让服务器能够快速回收和重用那些TIME_WAIT的资源。具体有哪些优化方式可以参照【6】,这儿就不赘述了。


四、总结


好了,终于到了激动人心的总结环节了,兄弟们,坚持住,小山坡马山就攀登过去了…

在这里插入图片描述

enen, tcp协议算是比较复杂的网络通信协议(看看上图就了解了),本文主要是论述了tcp连接过程中遇到的一些常见情况(尤其是异常情况),还有很多情况没有考虑到(【9】中也列举了一些,大家可以参考一下。 ),这些就留待以后遇到了在好好探究吧。


在这里插入图片描述


参考

【1】、read一个收到RST的socket会返回0?
【2】、《Unix网络编程-第一卷》
【3】、linux网络编程常见socket错误分析
【4】、线上大量CLOSE_WAIT的原因深入分析
【5】、又见CLOSE_WAIT
【6】、再谈应用环境下的TIME_WAIT和CLOSE_WAIT
【7】、tcp 服务端如何判断客户端断开连接
【8】、断开TCP连接
【9】、tcp socket 异常关闭总结

猜你喜欢

转载自blog.csdn.net/plm199513100/article/details/114728302