TCP的十一种状态与三次握手分析(有图)
我们知道TCP是面向连接的,我们只知道有连接断开,其实内部还有一些比较复杂的状态。去了解各个状态之间的切换有助于我们更加深入的了解TCP。下面我们就来分析各个状态。
1、如下图示(图源百度)图中显示出了10种状态。
我们假定断开时是client主动断开的。
对于server来说状态有:closed -> listen -> syn_recv -> enstablished -> close_wait -> last_ack -> closed
对于client来说状态有:closed -> syn_send -> enstablished -> fin_wait1 -> fin_wait2 -> time_wait
2.结合三次握手进行分析状态
我们知道三次握手的这样:
client 发送 syn x到server
server 回复syn y和ack x+1
client再回复 ack y+1
下面分析上面三步与状态的关系:
1)首先,刚创建的socket都是closed状态,server调用listen之后进入listen状态
2)接着、客户端调用connnect。(TCP协议会完成三次握手,client发送第一个syn之后就进入syn_send状态。与此同时,server收到syn并回复syn和ack,server进入syn_recv状态)
3)之后、client和server都进入了enstablisted状态。之后就可以互相发送数据了。
关于TCP协议头中的确认号ack的理解可以参考:http://www.cnblogs.com/xcywt/p/8075623.html
关于Wireshark的理解可以参考:http://www.cnblogs.com/xcywt/p/8025113.html
结合Wireshark抓包分析三次握手:
这里设置的过滤器,先清空当前捕获的包,在浏览器打开博客园。
假设TCPa -> TCPb
第一次:如下图,发送一个请求 syn,序号为0
第二次:B回复,发送syn,ack为0+1
第三次,A回复,ack = y + 1。其中y为之前B发送过来的序号,这里过来的序号的0,所以ack = 1;
3.结合断开时四次握手进行分析状态
如图(图源百度,侵删)
我们知道四次握手是这样的(假定是client先close 的):
client调用close。TCP协议会发送FIN x给server
server收到FIN x之后,会回复ack x+1
接着、server调用close,给客户端发送FIN y
最后,客户端回复ack y+1
分析与状态的关系:
1)client调用close,发送了FINx。client进入fin_wait1状态。server收到并回复ack,server进入close_wait状态。然后client会收到ack,进入fin_wait2状态
2)server接着调用close,给client发送了fin,server则进入了了last_ack状态。
3)client收到FIN 之后,回复ack。client进入time_wait状态。server收到ack之后,进入closed状态。
(client在保持了2个MSL之后就进入closed状态)
4.注意事项
1)client进入time_wait状态之后,会保持在这个状态2MSL。目的是为了确保发送过去的ack可以被收到(因为后面已经没有数据可以发送了)。
2)连接过程是状态的改变,促使状态的改变是用户的调用。所以切换状态不一定是用户的调用。(比如,server进入close_wait状态,纯粹是TCP协议做好的,用户并没有调用什么接口)
3)关于退出时的分析,存在一个主动一个被动关系。上面分析的client主动,则client会出现fin_wait1、fin_wait2、time_wait状态。server会出现close_wait、last_ack状态。
如果是server主动断开的,则关系刚刚反过来了。server先进入fin_wait1状态,然后是fin_wait2状态。后面整个就反过来了。
5.关于closing状态的出现(这里就是第十一种状态)
通过上面的分析我们知道,client主动退出时,先给server发送了一个FIN。接着会收到一个ack确认这个FIN。
如果没收到ack,而是收到server发来的FIN y。那么这时候则进入closing状态。
这种情况是怎么出现的呢:那就是双方几乎同时closer一个socket。这是双方都正在关闭socket连接。这种情况出现的几率很小
6.为什么连接需要三次握手,断开需要四次握手。
首先我们知道,TCP协议是去全双工的。可以在发送的同时进行接收数据。
假定是主机A和主机B进行通信,断开时是A主动断开的。
1)三次握手:第一次握手表明A可以发数据给B。但是无法保证B发给A的数据可以被收到。所以B也需要发送SYN给A,A对它进行回应,才保证了B也可以发数据给A。
个人理解可以把三步拆分为四步理解:
a)主机A给B发送SYN
b)主机B回复ack — 这时表明A可以发数据给B
c)主机B发送SYN给A
d)主机A回复ack — 这时表明B也可以发送数据给A
只不过协议中,把中间两步放在一步进行了。
2)四次握手,就像下面这样理解:
a)主机A给B发送FIN,表示对B说“我要断开了”
b)主机B回复ack进行确认,表示对A说“嗯,我知道了,你可以断开了”
c)然后B发送FIN给A,表示对A说“A,我也要断开了”
d)A回复ack进行确认,表示对B说:“嗯,知道了,你断开吧”
前两步对A进行断开,后两步对B进行断开。
那么为什么不能把中间两步进行合并呢,因为无法保证被断开的一方的数据已经传送完毕了。
就拿上面的例子来说,假如A断开了通知B,但是B还有数据没有发送完毕,如果立即断开(调用close发送FIN),就无法保证数据的可靠性。
如果等数据发送完毕再将fin和ack一起发过去,n那么A就会长时间处于fin_wait1状态。这样就比较不好了。
实际情况中我们可能会在server忘记关闭客户端的socket。那么client就会一直处于fin_wait2状态,越来越多的fin_wait2状态会导致系统崩溃。所以我们需要在编程中要注意在什么情况下要关闭双方的socket。
7.其他知识
什么时候会收到SIGPIPE这个信号呢?
当server关闭一个连接之后,client接着发送数据,第一次发送会收到一个RST的响应。如果接着发送则系统会发出一个SIGPIPE信号给client。
系统默认的处理是将应用程序退出。实际编程中,我们可以捕获这个信号,让应用程序不退出
如下(伪代码):
#include"signal.h" void sig_recvpipe(int sig) { } int main() { ...... signal(SIGPIPE, sig_recvpipe); ...... }