我们退出 telnet 命令后,TCP 将关闭连接。于此同时,我们通过 tcpdump 也观察到 TCP 关闭连接的通信过程。本节,我们继续深入研究 TCP 关闭连接的通信细节。
 
ASP站长网上节实验中的通信过程,已经被抓包并保存起来,我们直接用 tcpdump 命令将其打开( tcp.pcap ):
 
root@client [ ~ ]  ➜ tcpdump -nr tcp.pcap
reading from file tcp.pcap, link-type LINUX_SLL (Linux cooked v1)
17:31:57.624391 ARP, Request who-has 10.0.0.2 tell 10.0.0.3, length 28
17:31:57.624439 ARP, Reply 10.0.0.2 is-at 0e:bd:60:1c:69:9d, length 28
17:31:57.624450 IP 10.0.0.3.55692 > 10.0.0.2.22: Flags [S], seq 386101196, win 29200, options [mss 1460,sackOK,TS val 811948031 ecr 0,nop,wscale 7], length 0
17:31:57.624495 IP 10.0.0.2.22 > 10.0.0.3.55692: Flags [S.], seq 1155103769, ack 386101197, win 28960, options [mss 1460,sackOK,TS val 3541712191 ecr 811948031,nop,wscale 7], length 0
17:31:57.624522 IP 10.0.0.3.55692 > 10.0.0.2.22: Flags [.], ack 1, win 229, options [nop,nop,TS val 811948031 ecr 3541712191], length 0
17:31:57.635739 IP 10.0.0.2.22 > 10.0.0.3.55692: Flags [P.], seq 1:42, ack 1, win 227, options [nop,nop,TS val 3541712202 ecr 811948031], length 41
17:31:57.635778 IP 10.0.0.3.55692 > 10.0.0.2.22: Flags [.], ack 42, win 229, options [nop,nop,TS val 811948042 ecr 3541712202], length 0
17:31:59.808411 IP 10.0.0.3.55692 > 10.0.0.2.22: Flags [F.], seq 1, ack 42, win 229, options [nop,nop,TS val 811950215 ecr 3541712202], length 0
17:31:59.809175 IP 10.0.0.2.22 > 10.0.0.3.55692: Flags [.], ack 2, win 227, options [nop,nop,TS val 3541714376 ecr 811950215], length 0
17:31:59.809464 IP 10.0.0.2.22 > 10.0.0.3.55692: Flags [F.], seq 42, ack 2, win 227, options [nop,nop,TS val 3541714376 ecr 811950215], length 0
17:31:59.809483 IP 10.0.0.3.55692 > 10.0.0.2.22: Flags [.], ack 43, win 229, options [nop,nop,TS val 811950216 ecr 3541714376], length 0
很显然,最后四个包就是四次挥手关闭连接的过程:
 
 
 
当我们按下 Ctrl-D 退出 telnet 命令时,客户机向服务器发出一个 FIN 包。这是一个设置了 FIN 标志位的 TCP 分组,它告诉服务器,客户机这端的数据已经发完,准备关闭连接:
 
 
 
服务器收到 FIN 包后,将回复一个 ACK 进行确认。注意到,确认号在 FIN 包序号的基础上加一,因为 FIN 也要占用一个序号,跟 SYN 一样。
 
于此同时,服务器将连接读端关闭的情况通知上层应用程序—— SSH 服务进程。至此,TCP 连接中从客户机到服务器的传输方向已经关闭,连接处于 半关闭 状态。
 
 
 
上图灰色部分就是其中已关闭的传输方向,它对客户机来说是 写端 ,对服务器来说是 读端 。另一个方向的数据传输仍可正常进行,因此服务器可以继续发送数据(写端),客户机也可以接收服务器发来的数据(读端)。
 
SSH 服务进程获悉客户机关闭连接后,便准备结束服务并关闭连接。如果这时它还有数据没发完,仍可通过半开连接发往客户机。等所有数据都发送完毕,服务器同样发送 FIN 分组,告诉客户端连接关闭。
 
一个 TCP 连接包含两个方向的传输通道,因此需要两对 FIN/ACK 分组,各自负责关闭对应的方向。因此,这两对 FIN/ACK 交互也被形象地称为 四次挥手 。
 
状态变迁
TCP 建立连接需要三次握手,关闭连接需要四次挥手,步骤相对繁琐。这意味着一个 TCP 连接应该有很多中间状态,接下来我们深入研究一下:
 
 
 
上图是 TCP 连接全生命周期时序图,左边是客户端的时间轴,右边是服务端的时间轴。时间轴上的不同颜色,则分别表示客户端和服务端连接所处的状态:
 
客户端发出 SYN 分组,连接进入 SYN_SENT 状态;
服务端收到客户端发来的 SYN 分组,它回复 SYN/ACK 分组,连接进入 SYN_RECV 状态;
客户端收到服务器的 SYN/ACK 分组,它回复 ACK 分组,连接进入 ESTABLISHED 状态;
服务器收到客户端的 ACK 分组,服务端连接也进入 ESTABLISHED 状态;
当连接处于 ESTABLISHED 状态时,客户端和服务端可以互相传输数据;
时序图中间的数据分组及其后的 ACK 分组,为实验中 SSH 服务向客户机返回自己的版本信息(这部分数据被 telnet 命令直接输出到屏幕中);
客户端准备退出时,它通过 FIN 分组通知服务端,连接进入 FIN_WAIT1 状态;
服务器收到客户端发来的 FIN 分组,它回复 ACK 分组进行确认,连接进入 CLOSE_WAIT 状态;
客户端收到服务器发来的 ACK 分组,连接进入 FIN_WAIT2 状态;
这时连接处于半关闭状态,服务器仍可以向客户端发送数据;
服务器发完剩余数据后,向客户端发送 FIN 分组,通知客户端关闭连接,服务端连接便进入 LAST_ACK 状态;
客户端收到服务器发来的 FIN 分组,回复 ACK 分组进行确认,客户端连接进行 TIME_WAIT 状态;
服务端收到 ACK 分组后,连接彻底关闭;
由于最后一个 ACK 分组可能会丢,客户端必须在 TIME_WAIT 状态等待一段时间,以便对服务器重传的 FIN 分组进行确认;

dawei

【声明】:九江站长网内容转载自互联网,其相关言论仅代表作者个人观点绝非权威,不代表本站立场。如您发现内容存在版权问题,请提交相关链接至邮箱:bqsm@foxmail.com,我们将及时予以处理。