2020年10月23日 / 14次阅读 / Last Modified 2020年10月23日
socket
要彻底搞明白tcp网络编程及相关配置,就需要把tcp状态转换的每个细节搞清楚。
本文详细介绍TCP连接的三次握手,四次挥手,2MSL,TIME_WAIT状态等概念。下图去掉CLOSED,刚好是TCP的11个状态:
这个图来自RFC793,第23页。
TCP传输的可靠性,是通过对每个传输字节的seq进行确认和checksum来保证的。
客户端状态: SYN_SENT ----> ESTAB。
客户端报文: 发送SYN ----> 接收ACK和SYN ----> 发送ACK。
服务器端状态: LISTEN ----> SYN_RCVD ----> ESTAB。
服务器端报文: 接收SYN ----> 发送ACK和SYN ----> 接收ACK。
客户端会先于服务器端一点点,进入ESTAB状态。
SYN报文中含有一个序列号SEQ,ACK报文中含有SEQ+1。
服务器端的TCP,其实就是不断地生产与客户端一样的TCP,因此在ESTAB状态下,任何一方都可以首先离开,发FIN。下面是一个TCP关闭流程说明:
因此,首先发起关闭TCP的一方,最终会进入著名的TIME_WAIT状态!
由于TCP连接是全双工的,因此每个方向都必须单独进行关闭。原则是当一方完成它的数据发送任务后就能发送一个FIN来终止这个方向的连接。收到一个 FIN只意味着这一方向上没有数据流动,一个TCP连接在收到一个FIN后仍能发送数据。首先进行关闭的一方将执行主动关闭,而另一方执行被动关闭。
我们说的四次挥手,具体是那四次呢?
其实就是双方格式放出的FIN报文和双方给对方的ACK报文,加起来一共4个报文。
主动关闭连接的一方A的状态变化: ESTAB ----> FINWAIT-1 ----> FINWAIT-2 ----> TIME_WAIT ---->CLOSED
A的报文:发送FIN ----> 收到ACK ----> 收到FIN ----> 发送ACK
被动关闭连接的一方B的状态变化:ESTAB ----> CLOSE_WAIT ----> LAST-ACK ----> CLOSED
B的报文:收到FIN ----> 发送ACK ----> 发送FIN ----> 收到ACK
由于网络传输,A有可能先收到FIN,在收到ACK,所以有了进入 CLOSING 状态,然后跳过 FINWAIT-2 状态,直接到 TIME_WAIT状态的转移路径!
要先记住,主动发起FIN的那一方(A),才会进入这个TIME_WAIT状态。主动发FIN,就意味着自己的数据传输已经完成了,后面不会再发送数据了!
此时的B,虽然收到了FIN,也发出了ACK,B的数据可能还没有发完,它还在继续发送数据。B要发完数据后,才会发出自己的FIN,进入LAST-ACK状态。
(?我理解为socket的send函数还没有返回时,协议层已经收到并处理了对方的FIN报文,而send将返回0,需要主动调用close函数发FIN;或者是异步的情况,send还没结束,别的线程发起了FIN,send返回0)
由于网络传输的各种不确定性,在TIME_WAIT状态下,虽然占用了一组(ip:port)暂时不能被系统回收再利用,但是确保了新的连接(继续使用相同的ip和port)不会收到旧连接在网络上重传的报文。
seq=3的报文,在上图场景下,会被新的连接错误的接收(此时正好seq=3能够跟其它报文的seq对应上,如果seq对不上,会被RST)。
上图是收到错误的数据包的情况,下图是收到错误类型的报文:
B发送FIN后,一直收不到ACK(丢了),此时如果正好A端复用了(ip:port)来简新的连接,A会发送SYN,B收到后认为这是个错误的报文,直接RST!
因此,TIME_WAIT状态其实就是等待网络出清,规避各种可能因为网络传输的不确定性造成的问题。
MSL:Max Segment Lifetime,是网络报文的最大生存时间!2MSL,就是2倍这个时间。
这个2MSL时间有多长呢?(1)30秒,内核写死的;(2)2分钟,协议规定2分钟;(3)不一定,1分钟、2分钟或者4分钟,还有的30秒。不同的发行版可能会不同。在Centos 7.6.1810 的3.10内核版本上是60秒。
TIME_WAIT会占用端口:
如果服务器访问量巨大,而系统端口数是有限的,端口全被TIME_WAIT占用肯定是不好的。
TIME_WAIT会占用内存和CPU:
个人认为大量的TIME_WAIT占用的内存不会很多,而CPU的计算时间也其实非常的快!因为端口号一共就65535个,这个量级的计算不会特别让人烦恼。
这个细节我没有仔细研究,待续......
两个应用程序同时执行主动打开的情况是可能的,虽然发生的可能性较低。每一端都发送一个SYN,并传递给对方,且每一端都使用对端所知的端口作为本地端口。但多数伯克利版的tcp/ip实现并不支持同时打开。
这种情况比较糟糕,通信两段都会进入TIME_WAIT状态。
如果应用程序同时发送FIN,或者由于网络传输的原因,导致双方都在发出自己的FIN后,收到对方的FIN。则双方在发送后会首先进入FIN_WAIT_1状态。在收到对端的FIN后,回复一个ACK,会进入CLOSING状态。在收到对端的ACK后,进入TIME_WAIT状态。这种情况称为同时关闭。同时关闭也需要有4次报文交换,与典型的关闭相同。
TCP真心复杂!
-- EOF --
本文链接:https://www.pynote.net/archives/2612
Ctrl+D 收藏本页
©Copyright 麦新杰 Since 2019 Python笔记