tcp过程

来源:互联网 发布:网络综合布线图 编辑:程序博客网 时间:2024/06/17 01:14

TCP状态变迁图及状态说明


(先发出断开连接的进入TIME_WAIT状态)


(同时关闭,两者都进入TIME_WAIT状态)




用tcpdump -i lo 可以查看本地环路的通信情况。我这里的ACK的标志位显示的是“点”,即"."...下面的抓包过程中的flags[.]表示ACK包,flags[F.]表示FIN+ACK包。

用netstat -nap | grep port 可以监视与port端口相关的一些进程状态。

  1. 第一次握手:建立连接。客户端发送连接请求报文段,将SYN位置为1,Sequence Number为x;然后,客户端进入SYN_SEND状态,等待服务器的确认;
  2. 第二次握手:服务器收到SYN报文段。服务器收到客户端的SYN报文段,需要对这个SYN报文段进行确认,设置Acknowledgment Number为x+1(Sequence Number+1);同时,自己自己还要发送SYN请求信息,将SYN位置为1,Sequence Number为y;服务器端将上述所有信息放到一个报文段(即SYN+ACK报文段)中,一并发送给客户端,此时服务器进入SYN_RECV状态;
  3. 第三次握手:客户端收到服务器的SYN+ACK报文段。然后将Acknowledgment Number设置为1!!!!,向服务器发送ACK报文段,这个报文段发送完毕以后,客户端和服务器端都进入ESTABLISHED状态,完成TCP三次握手。

  • FIN_WAIT_1: 这个状态要好好解释一下,其实FIN_WAIT_1FIN_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


11:21:47.362249 IP localhost.53498 > localhost.6666: Flags [S], seq 1211615665, win 43690, options [mss 65495,sackOK,TS val 11503 ecr 0,nop,wscale 7], length 0
11:21:47.362264 IP localhost.6666 > localhost.53498: Flags [S.], seq 2202096765, ack 1211615666, win 43690, options [mss 65495,sackOK,TS val 11503 ecr 11503,nop,wscale 7], length 0
11:21:47.362274 IP localhost.53498 > localhost.6666: Flags [.], ack 1, win 342, options [nop,nop,TS val 11503 ecr 11503], 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          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个:

TCP的三次握手和四次“撒手”及头部的选项字段


那么如何减少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

有如下三个条件产生RST包(百度百科):
1. 建立连接的SYN到达某端口,但是该端口上没有正在监听的服务
如:IP为192.168.1.33的主机上并没有开启WEB服务(端口号为0x50),这时我们通过IE去访问192.168.1.33,通过Wireshark抓包,可以看到,对此SYN包的回复为RST。说明此服务器(即IP192.168.1.33)是存在的,不过其上并没有运行WEB Server(如apache)的程序
2. TCP想取消一个已有连接
基于什么样的情况才会取消一个已有的连接?
3. TCP接收到了一个根本不存在的的连接上的分节
我们知道,TCP在数据传输前,要通过三路握手(three-way handshake)建立连接,即连接建立起后,服务器和客户端都有一个关于此连接的描述,具体形式表现为套接口对,如果收到的某TCP分节,根据源 IP,源tcp port number,及目的IP,目的tcp port number在本地(指服务器或客户端)找不到相应的套接口对,TCP则认为在一个不存在的连接上收到了分节,说明此连接已错,要求重新建立连接,于是发出了RST的TCP包

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   -    
0 0
原创粉丝点击