首页 > 极客资料 博客日记

socket close和shutdown的区别,TIME_WAIT和CLOSE_WAIT

2024-09-21 13:00:03极客资料围观20

本篇文章分享socket close和shutdown的区别,TIME_WAIT和CLOSE_WAIT,对你有帮助的话记得收藏一下,看极客之家收获更多编程知识

TCP主动关闭连接
 
appl: close(), --> FIN FIN_WAIT_1 //主动关闭socket方,调用close关闭socket,发FIN
               <-- ACK FIN_WAIT_2 //对方操作系统的TCP层,给ACK响应。然后给FIN
               <-- FIN
               --> ACK "TIME_WAIT" -- 2MSL timeout -->CLOSED //TIME_WAIT,防止ACK没有给到对方。
 
TCP被动关闭连接
 
               <-- FIN "CLOSE_WAIT" //被动方,收到对方的FIN,处于CLOSE_WAIT状态
               --> ACK //被动方的TCP层,给ACK响应
app2: close(), --> FIN LAST_ACK //被动方调用close,从CLOSE_WAIT转到LAST_ACK 不调close,将一直在CLOSE_WAIT状态。
               <-- ACK --> CLOSED tcp是全双工:: 因此close()关闭读写。 shutdown()可以选择关闭读或写。 time_wait的时间会非常长,因此server尽量减少主动关闭连接。
 
 
int close(int sockfd); int shutdown(int sockfd, int howto); // howto: SHUT_RD, SHUT_WR, SHUT_RDWR
shutdown()函数的两个作用:
close()将描述字的引用计数减1,当引用计数为0时,才关闭socket。
如fork()模式中,父进程在accept()返回后,fork()子进程,由子进程处理connfd,而父进程将close(connfd);但此时父进程的close()并不引发FIN。
 

shutdown()则不管socket的引用计数,直接发生FIN。
shutdown()可控制read/write两个方向的管道。
SHUT_RD shutdown(sockfd, SHUT_RD);后,来自对端的数据都被确认,然后悄然丢弃。 SHUT_WR half close状态。
 
close()引发的4次交互:(这里的close是client发起的)
 
client server
FIN_WAIT_1 ---- FIN M ------> (Server端操作系统的TCP层响应ACK包)
<---- ACK M+1---- CLOSE_WAIT FIN_WAIT_2 (这里必须调用close,才能从CLOSE_WAIT到LAST_ACK)
<------ FIN N ----- LAST_ACK TIME_WAIT (TIME_WAIT有一个重要的作用就是防止最后一个ACK丢失)
------- ACK N+1 ----------> CLOSE
TIME_WAIT 是主动关闭链接时形成的,等待2MSL时间,约4分钟。

         主要是防止最后一个ACK丢失。  由于time_wait的时间会非常长,因此server端应尽量减少主动关闭连接

 

CLOSE_WAIT是被动关闭链接是形成的 ,

        按状态机,我方收到FIN,则由TCP实现发送ACK,因此进入CLOSE_WAIT状态。

        但如果我方不执行close(),就不能由CLOSE_WAIT迁移到LAST_ACK,则系统中会存在很多CLOSE_WAIT状态的连接。

此时,可能是系统忙于处理读、写操作,而未将已收到FIN的连接,进行close。此时,recv/read已收到FIN的连接socket,会返回0。

 

大量TIME_WAIT和CLOSE_WAIT的存在,会产生怎样的影响?

        内核维护更多的状态。收到ip包,做hash运算,hlist冲突的概率更大。

 

1. 首先明确下什么是2MSL: TCP四次挥手断开连接时,主动断开连接的一方(这里我们称为客户端A)在收到对端(这里我们称为服务端B)发送的FIN包后,会立马发送ACK响应包并等待一段时间以确保自己发送的ACK包能够成功通过网络传输到达服务端B端,从而帮助B端正常断开连接(B服务端只有收到自己发出的FIN包对应的ACK包后,才能正常释放资源断开连接),这一等待时间的时长为2MSL,是从客户端A发出ACK包开始计算的,如果A在发送完ACK包后又收到了B端发送的新的FIN包,则会再次发送新的ACK包并重新计时(B端可能会因为超时或丢包等各种原因重新发送FIN包);
2. 其次明确下什么是MSL:MSL 即 Maximum Segment Lifetime,它是任何 TCP segment 在网络上存在的最长时间,超过这个时间的 TCP 报文就会被丢弃,RFC793定义了MSL为2分钟,但这完全是从工程上来考虑的,对于现在的网络,不同操作系统的TCP实现,可以根据具体网络情况配置使用更小的MSL;

3. 再次明确下为什么TCP第四次挥手需要等待2MSL:客户端A等待2MSL可以确保客户端A和服务端B之间的数据包可以完成一个完整的来回的传输即 round trip,所以如果A先前发送的ACK包因为某些原因在网络上丢失了,服务端B在超时没有收到客户端A的ACK包后会重新发送FIN包,2MSL的等待时间能够确保客户端A收到服务端B发送的新的FIN包以发送新的ACK包并重新计时;

4. 最后说下如何在LINUX操作系统中查看和配置MSL: 其实在LINUX操作系统中并没有直接配置 MSL 而是配置了 tcp_fin_timeout,由于 tcp_fin_timeout=2MSL,所以我们可以查看tcp_fin_timeout并据此推断MSL: 可以通过命令 sysctl net.ipv4.tcp_fin_timeout 查看 tcp_fin_timeout,可以通过命令 sysctl -w net.ipv4.tcp_fin_timeout=30 修改 tcp_fin_timeout;

简单概括下,因为网络是不可靠的,数据包在传输过程中可能丢失可能超时可能乱序,所以TCP为了在逻辑的虚拟的连接的基础上提供可靠性,在四次挥手断开连接时主动断开连接的一方需要2MSL的等待时间(当然三次握手和四次挥手,以及连接过程中的确认/累计确认/选择确认等机制,也都是这个原因)

 

 

TCP四次挥手也遵循相似的套路。

主动断开的一侧为A,被动断开的一侧为B。

第一个消息:A发FIN

第二个消息:B回复ACK

第三个消息:B发出FIN

此时此刻:B单方面认为自己与A达成了共识,即双方都同意关闭连接。

此时,B能释放这个TCP连接占用的内存资源吗?不能,B一定要确保A收到自己的ACK、FIN。

所以B需要静静地等待A的第四个消息的到来:

第四个消息:A发出ACK,用于确认收到B的FIN

当B接收到此消息,即认为双方达成了同步:双方都知道连接可以释放了,此时B可以安全地释放此TCP连接所占用的内存资源、端口号。

所以被动关闭的B无需任何wait time,直接释放资源。

但,A并不知道B是否接到自己的ACK,A是这么想的:

1)如果B没有收到自己的ACK,会超时重传FiN

那么A再次接到重传的FIN,会再次发送ACK

2)如果B收到自己的ACK,也不会再发任何消息,包括ACK

无论是1还是2,A都需要等待,要取这两种情况等待时间的最大值,以应对最坏的情况发生,这个最坏情况是:

去向ACK消息最大存活时间(MSL) + 来向FIN消息的最大存活时间(MSL)。

这恰恰就是2MSL( Maximum Segment Life)。

等待2MSL时间,A就可以放心地释放TCP占用的资源、端口号,此时可以使用该端口号连接任何服务器。

为何一定要等2MSL?

如果不等,释放的端口可能会重连刚断开的服务器端口,这样依然存活在网络里的老的TCP报文可能与新TCP连接报文冲突,造成数据冲突,

为避免此种情况,需要耐心等待网络老的TCP连接的活跃报文全部死翘翘,2MSL时间可以满足这个需求(尽管非常保守)!

 

 


版权声明:本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:jacktools123@163.com进行投诉反馈,一经查实,立即删除!

标签:

相关文章

本站推荐

标签云