(先发出断开连接的进入TIME_WAIT状态)
(同时关闭,两者都进入TIME_WAIT状态)
用tcpdump -i lo 可以查看本地环路的通信情况。我这里的ACK的标志位显示的是“点”,即"."...下面的抓包过程中的flags[.]表示ACK包,flags[F.]表示FIN+ACK包。
用netstat -nap | grep port 可以监视与port端口相关的一些进程状态。
SYN
位置为1,Sequence Number
为x;然后,客户端进入SYN_SEND
状态,等待服务器的确认;SYN
报文段。服务器收到客户端的SYN
报文段,需要对这个SYN
报文段进行确认,设置Acknowledgment Number
为x+1(Sequence Number
+1);同时,自己自己还要发送SYN
请求信息,将SYN
位置为1,Sequence Number
为y;服务器端将上述所有信息放到一个报文段(即SYN+ACK
报文段)中,一并发送给客户端,此时服务器进入SYN_RECV
状态;SYN+ACK
报文段。然后将Acknowledgment Number
设置为1!!!!,向服务器发送ACK
报文段,这个报文段发送完毕以后,客户端和服务器端都进入ESTABLISHED
状态,完成TCP三次握手。FIN_WAIT_1
: 这个状态要好好解释一下,其实FIN_WAIT_1
和FIN_WAIT_2
状态的真正含义都是表示等待对方的FIN报文。而这两种状态的区别是:FIN_WAIT_1
状态实际上是当SOCKET在ESTABLISHED状态时,它想主动关闭连接,向对方发送了FIN
报文,此时该SOCKET即进入到FIN_WAIT_1
状态。而当对方回应ACK报文后,则进入到FIN_WAIT_2
状态,当然在实际的正常情况下,无论对方何种情况下,都应该马上回应ACK报文,所以FIN_WAIT_1
状态一般是比较难见到的,而FIN_WAIT_2
状态还有时常常可以用netstat看到。(主动方)FIN_WAIT_2
:上面已经详细解释了这种状态,实际上FIN_WAIT_2
状态下的SOCKET,表示半连接,即另一方还能再传输数据过来。(主动方)CLOSE_WAIT
:这种状态的含义其实是表示在等待关闭。怎么理解呢?当对方close一个SOCKET后发送FIN
报文给自己,你系统毫无疑问地会回应一个ACK报文给对方,此时则进入到CLOSE_WAIT
状态。接下来呢,实际上你真正需要考虑的事情是察看你是否还有数据发送给对方,如果没有的话,那么你也就可以 close这个SOCKET,发送FIN
报文给对方,也即关闭连接。所以你在CLOSE_WAIT
状态下,需要完成的事情是等待你去关闭连接。(被动方)LAST_ACK
: 这个状态还是比较容易好理解的,它是被动关闭一方在发送FIN
报文后,最后等待对方的ACK报文。当收到ACK报文后,也即可以进入到CLOSED可用状态了。(被动方)TIME_WAIT
: 表示收到了对方的FIN报文,并发送出了ACK报文,就等2MSL后即可回到CLOSED可用状态了。如果FINWAIT1状态下,收到了对方同时带FIN标志和ACK标志的报文时,可以直接进入到TIME_WAIT
状态,而无须经过FIN_WAIT_2
状态。(主动方)CLOSED
: 表示连接中断。开始过程:
A向B发送SYN包,并给定seq=一个比较大的随机数(ACK=0,SYN=1)
B收到后返回ACK+SYN包,ack=楼上随机数+1.seq=另一个比较大的随机数(ACK = 1, SYN = 1)
A收到后返回一个ack包。ack=1.。表示下次接受的序号从1开始。(ACK = 1, SYN = 0).
(但一些地方说第一个seq都=0,然后ack都等于1)另外TCP规定SYN=1时不能携带数据,但要消耗一个序号
结束过程:
A发出FIN-ACK后,进入FIN-WAIT-1状态,等待B的确认。
B发出ACK后,进入CLOSE-WAIT状态。
此时TCP连接处于半关闭状态,即A没有数据发给B了,但是如果B发数据给A,A仍要接收。
A收到B的ACK后,进入FIN-WAIT-2状态,等待B发出FIN-ACK
当B没有数据发给A时,则发出FIN-ACK给A,此时的seq number=w而不是n+上个包的数据部分长度(ACK包数据部分一般为空),
是因为半关闭状态中B仍然可能发了一些数据给A。此时的ack number还是必须和B上一次的ACK包一样,因为半关闭状态下A是不会再发数据给B的。此时B进入LAST-ACK状态,等待A的最后确认。
A发出ACK,进入TIME-WAIT状态,此时TCP连接还没有完全释放,必须经过时间等待计时器(TIME-WAIT timer)设置的2MSL后,A才进入CLOSED状态。MSL=maximum segment lifetime,最长报文段寿命。rfc793建议为2分钟,不过实际实现中一般都比这个短。进入CLOSED状态后,A撤销相应的传输控制块TCB,结束此次TCP连接。
tcpdump抓包显示数据:
首先是建立通信,server端口为6666,client端口随机分配为53498
此时的netstat -nap | grep 6666 如下:
(Not all processes could be identified, non-owned process info
will not be shown, you would have to be root to see it all.)
tcp 0 0 0.0.0.0:6666 0.0.0.0:* LISTEN 2523/roms
tcp 0 0 127.0.0.1:53498 127.0.0.1:6666 ESTABLISHED 2741/romc
tcp 0 0 127.0.0.1:6666 127.0.0.1:53498 ESTABLISHED 2523/roms
然后server向client发送hello回车,包括回车,共6个字符。seq表示[1,7)的序号。
11:22:07.304641 IP localhost.6666 > localhost.53498: Flags [P.], seq 1:7, ack 7, win 342, options [nop,nop,TS val 16488 ecr 15002], length 6
11:22:07.304651 IP localhost.53498 > localhost.6666: Flags [.], ack 7, win 342, options [nop,nop,TS val 16488 ecr 16488], length 0
然后client端发出断开连接请求,出现3次挥手,因为server返回的包中同时包含了FIN+ACK标志:
11:29:50.378281 IP localhost.53498 > localhost.6666: Flags [F.], seq 7, ack 11, win 342, options [nop,nop,TS val 132257 ecr 129848], length 0
11:29:50.378894 IP localhost.6666 > localhost.53498: Flags [F.], seq 11, ack 8, win 342, options [nop,nop,TS val 132257 ecr 132257], length 0
11:29:50.378907 IP localhost.53498 > localhost.6666: Flags [.], ack 12, win 342, options [nop,nop,TS val 132257 ecr 132257], length 0
此时的netstat -nap | grep 6666 状态如下:
(Not all processes could be identified, non-owned process info
will not be shown, you would have to be root to see it all.)
tcp 0 0 0.0.0.0:6666 0.0.0.0:* LISTEN 2523/roms
tcp 0 0 127.0.0.1:53498 127.0.0.1:6666 TIME_WAIT
————————————————
然后我们再试试FIN_WAIT2的状态
首先我们用两个client先后对server进行连接
11:32:07.357730 IP localhost.53500 > localhost.6666: Flags [S], seq 1256562554, win 43690, options [mss 65495,sackOK,TS val 166502 ecr 0,nop,wscale 7], length 0
11:32:07.357739 IP localhost.6666 > localhost.53500: Flags [S.], seq 200606784, ack 1256562555, win 43690, options [mss 65495,sackOK,TS val 166502 ecr 166502,nop,wscale 7], length 0
11:32:07.357748 IP localhost.53500 > localhost.6666: Flags [.], ack 1, win 342, options [nop,nop,TS val 166502 ecr 166502], length 0
11:32:11.002664 IP localhost.53502 > localhost.6666: Flags [S], seq 3644069957, win 43690, options [mss 65495,sackOK,TS val 167413 ecr 0,nop,wscale 7], length 0
11:32:11.002693 IP localhost.6666 > localhost.53502: Flags [S.], seq 1376811471, ack 3644069958, win 43690, options [mss 65495,sackOK,TS val 167413 ecr 167413,nop,wscale 7], length 0
11:32:11.002705 IP localhost.53502 > localhost.6666: Flags [.], ack 1, win 342, options [nop,nop,TS val 167413 ecr 167413], length 0
因为我用的是单线程的,所以第二个client无法与server正常通信,此时先退出第二个client,则抓包如下:
11:36:35.837327 IP localhost.53502 > localhost.6666: Flags [F.], seq 1, ack 1, win 342, options [nop,nop,TS val 233622 ecr 167413], length 0
11:36:35.841922 IP localhost.6666 > localhost.53502: Flags [.], ack 2, win 342, options [nop,nop,TS val 233623 ecr 233622], length 0
此时能轻松抓到server对client发送的ACK包(四次挥手的第二个包),因为此时还无法发送FIN包。
此时第二个client进入FIN_WAIT2,欲与之相连的server也进入了CLOSE_WAIT状态。
监视:
jason@ubuntu:~$ netstat -nap | grep 6666
(Not all processes could be identified, non-owned process info
will not be shown, you would have to be root to see it all.)
tcp 0 0 0.0.0.0:6666 0.0.0.0:* LISTEN 2523/roms
tcp 1 0 127.0.0.1:6666 127.0.0.1:53502 CLOSE_WAIT -
tcp 0 0 127.0.0.1:53502 127.0.0.1:6666 FIN_WAIT2 -
tcp 0 0 127.0.0.1:6666 127.0.0.1:53500 ESTABLISHED 2523/roms
tcp 0 0 127.0.0.1:53500 127.0.0.1:6666 ESTABLISHED 2831/romc
我们再把第一个client断开连接,则有:
11:36:49.729166 IP localhost.53500 > localhost.6666: Flags [F.], seq 1, ack 1, win 342, options [nop,nop,TS val 237094 ecr 166502], length 0
11:36:49.729769 IP localhost.6666 > localhost.53500: Flags [F.], seq 1, ack 2, win 342, options [nop,nop,TS val 237094 ecr 237094], length 0
11:36:49.729780 IP localhost.53500 > localhost.6666: Flags [.], ack 2, win 342, options [nop,nop,TS val 237094 ecr 237094], length 0
则第一个client直接进入TIME_WAIT状态。
监视:
jason@ubuntu:~$ netstat -nap | grep 6666
(Not all processes could be identified, non-owned process info
will not be shown, you would have to be root to see it all.)
tcp 0 0 0.0.0.0:6666 0.0.0.0:* LISTEN 2523/roms
tcp 1 0 127.0.0.1:6666 127.0.0.1:53502 CLOSE_WAIT -
tcp 0 0 127.0.0.1:53502 127.0.0.1:6666 FIN_WAIT2 -
tcp 0 0 127.0.0.1:53500 127.0.0.1:6666 TIME_WAIT -
然后这时候CLOSE_WAIT状态的server和第二个client连接,但是第二个已经断开,进入了FIN_WAIT2状态,所以此时server会马上发送一个FIN+ACK包过去给client。
11:36:58.845801 IP localhost.6666 > localhost.53502: Flags [F.], seq 1, ack 2, win 342, options [nop,nop,TS val 239374 ecr 233622], length 0
11:36:58.845814 IP localhost.53502 > localhost.6666: Flags [.], ack 2, win 342, options [nop,nop,TS val 239374 ecr 239374], length 0
然后client进入TIME_WAIT状态。
监视:
jason@ubuntu:~$ netstat -nap | grep 6666
(Not all processes could be identified, non-owned process info
will not be shown, you would have to be root to see it all.)
tcp 0 0 0.0.0.0:6666 0.0.0.0:* LISTEN 2523/roms
tcp 0 0 127.0.0.1:53502 127.0.0.1:6666 TIME_WAIT -
tcp 0 0 127.0.0.1:53500 127.0.0.1:6666 TIME_WAIT
wait_time的存在原因如下2个:
那么如何减少TIME_WAIT过多占用资源呢?(待续)
大多数服务器端一般执行被动关闭,服务器不会进入TIME_WAIT状态。
服务端为了解决这个TIME_WAIT问题,可选择的方式有三种:
Ø 保证由客户端主动发起关闭(即做为B端)
Ø 关闭的时候使用RST的方式
Ø 对处于TIME_WAIT状态的TCP允许重用
从实际的dump抓包中发现,断开时一般情况只有3个包,A对B发送FIN包,这里少了B对A单独发送的ACK包。。但是有时又可以抓到此包。。然后B对A发送ACK+FIN包,A对B发送ACK包。。。因为如上文所述,如果在FIN_WAIT1的状态下收到了对方的同时带FIN标志和ACK标志的报文时,可以直接进入到TIME_WAIT的状态,无须经过FIN_WAIT2状态。
异常关闭:
比如在单线程的C/S种,当server A先后与X,Y连接,此时Y被阻塞,未能与X进行send和receive,但已经连通,这时候先释放Y,Y会向A发送FIN,然后A向Y发送ACK确认。然后再释放X,则此时X和A之间可能有3个包也可能有4个包(原因如上文)。。然后X进入TIME_WAIT。。然后A就成功与Y通信,但是Y已经断开进入了FIN_WAIT2状态,则A向Y发送ACK+FIN包,Y返回ACK确认,然后Y会进入TIME_WAIT...
但是假设Y进入FIN_WAIT2之后等待时间长一点,就会关闭Y进程,检测不到Y停在FIN_WAIT2状态,此时再让A去发送FIN+ACK。则Y无法返回ACK包,而是以RST包发送了一个seq,编号为SYN里的seq的编号+1.。。表示异常关闭,Y也不会出现在TIME_WAIT里。
tcpdump抓到的包如下:
12:27:24.479787 IP localhost.6666 > localhost.53506: Flags [F.], seq 1, ack 2, win 342, options [nop,nop,TS val 996218 ecr 933273], length 0
12:27:24.479798 IP localhost.53506 > localhost.6666: Flags [R], seq 3504376402, win 0, length 0