网络编程中 TCP 半开连接和TIME_WAIT 学习

https://blog.csdn.net/chrisnotfound/article/details/80112736

    上面的链接就是说明来 SO_KEEPALIVE 选项 为什么还需要 在应用层开发 心跳协议的原因  包括分布式系统开发中也必须自己设计  应用层 心跳协议的开发   

熟悉基于TCP协议进行linux高性能、高并发服务端编程的朋友肯定应该知道每个文件描述符及其所占的资源对并发量的影响。

在这种7*24甚至*365不间断运行的服务器上,一个描述符被浪费,两个被浪费...如果被浪费的多了,那还何谈高并发,高性能。除去文件描述被正常占用的情况外,是什么导致了我们可用的文件描述符越来越少呢?

 

 https://blog.csdn.net/gettogetto/article/details/76736371

 

什么是半开连接?

当客户端与服务器建立起正常的TCP连接后,如果客户主机掉线(网线断开)、电源掉电、或系统崩溃,服务器进程将永远不会知道(通过我们常用的select,epoll监测不到断开或错误事件),如果不主动处理或重启系统的话对于服务端来说会一直维持着这个连接,任凭服务端进程如何望穿秋水,也永远再等不到客户端的任何回应。这种情况就是半开连接,浪费了服务器端可用的文件描述符。

如何处理?

熟悉套接字通用选项的朋友一定已经有了想法。TCP套接字不是有个保持存活选项SO_KEEPALIVE嘛,如果在两个小时之内在该套接字的任何一个方向上都没数据交换,TCP就自动给对端发送一个保持存活探测分节,如果此TCP探测分节的响应为RST,说明对端已经崩溃且已经重新启动,该套接字的待处理错误被置为ECONNRESET,套接字本身则被关闭。如果没有对此TCP探测分节的任何响应,该套接字的处理错误就被置为ETIMEOUT,套接字本身则被关闭。

确实,这个选项确实可以处理我们前面遇到的TCP半开连接的问题,但是默认两小时间隔探测的实时性是不是差了些呢?

当然,我们可以通过修改内核参数改小时间间隔,完美了吧?

但是必须注意的是大多数内核是基于整个内核维护这些时间参数的,而不是基于每个套接字维护的,因此如果把无活动周期从两小时改为(比如)2分钟,那将影响到该主机上所有开启了此选项的套接字。

我想大家都不会愿意承担服务器端的这种不确定性吧。另外,心跳除了说明应用程序还活着(进程存在,网络畅通),更重要的是表明应用程序能正常工作。而SO_KEEPALIVE由操作系统负责探查,即便是进程死锁或有其他异常,操作系统也会正常收发TCP keepalive消息,而对方无法得知这一异常

没关系,其实我们可以在应用层模拟SO_KEEPALIVE的方式,用心跳包来模拟保活探测分节

由于服务器通常要承担成千上万的并发连接,所以肯定是由客户端在应用层进行心跳来模拟保活探测分节,客户端多次收不到服务器的响应时可终止此TCP连接,而服务端可监测客户端的心跳包,若在一定时间间隔内未收到任何来自客户端的心跳包则可以终止此TCP连接,这样就有效避免了TCP半开连接的情况。

 

 

 

 

 

 

在进行TCP高并发服务器开发时,有些规则仿佛是约定俗成的,很多朋友会依据这些规则去做,比如高并发TCP服务器中进行主动关闭的一方最好是客户端服务器端程序最好启用SO_REUSEADDR选项,但是很多人却不知所以然,我们为什么要这么做呢?


先上图

可以看到执行主动关闭端和被动关闭端的各个阶段的状态,今天咱的重点就是TIME_WAIT状态,可以看出TIME_WAIT状态是执行主动关闭的那一端产生的。

TIME_WAIT状态有两个存在的理由

  1. 可靠地实现TCP全双工连接的终止;

  2. 允许老的重复分节在网络中消逝;

第一个理由参考上图。 假设主动关闭端最终发送的ACK丢失了。对端将重新发送FIN,主动关闭端只有在维护状态信息的情况下才可以重新发送最终的那个ACK。如果不维护这个状态信息,主动关闭端将会响应一个RST,对端会将此响应标记为错误,所以不能进行正常的关闭。

第二个理由假设我们在ip A:端口B主机和ip C:端口D主机之间建立一个TCP连接。我们关闭这个连接,过一段时间在相同的IP地址和端口之间建立另一个连接。由于他们的IP地址和端口号都相同,所以如果上一个连接的老的重复分组再出现会影响新的连接。为了做到这一点,TCP将不会给处于TIME_WAIT状态的连接发起这个新的连接。这个持续时间如果大于MSL(IP数据报在因特网中的最大生存时间)

如果要满足以上实现,TIME_WAIT状态必须要有一定的持续时间,所以TIME_WAIT也被称为2MSL等待状态,一般持续时间在1分钟到4分钟之间。

高并发TCP服务器中进行主动关闭的一方最好是客户端:因为对于高并发服务器来说文件描述符资源是很重要的资源,如果对于每一个连接都要经历TIME_WAIT这个2MSL的时长,势必造成资源不能立马复用的浪费。虽然对于客户端来说TIME_WAIT状态会占用端口和句柄资源,但是客户端一般很少有并发资源限制,所以客户端执行主动关闭是比较合适的。

服务器端程序最好启用SO_REUSEADDR选项:我们想这样做一种情况,如果生产环境中服务端程序由于某种错误操作关闭了,我们肯定是要立马重启服务程序,但是TIME_WAIT还在占用着这些地址端口资源让你的服务起不来,那你着不着急。SOREUSEADDR这个选项正是允许地址端口的重复绑定。

你可能感兴趣的:(网络编程中 TCP 半开连接和TIME_WAIT 学习)