一、三次握手

TCP协议的三次握手和四次挥手分别表示了TCP连接的建立和释放过程,在整个TCP协议是一个很重要的内容,同时也是面试时的常见考点。

趁着找工作的劲,使用socket+tcpdump分析了一下工作流程,socket客户端代可以在socket介绍及函数原语找到。服务端的代码可以在socket的select模型找到。

socket中各函数对应的TCP状态演示图:

三次握手的流程可以形容为(摘取自《TCP/IP协议:卷一》):

  1. 请求端(通常称为客户)发送一个SYN段指明客户打算连接的服务器的端口,以及初始序号(ISN),这个SYN段为报文段1。
  2. 服务器发回包含服务器的初始序号的SYN报文段(报文段2)作为应答。同时,将确认序号设置为客户的ISN加1以对客户的SYN报文段进行确认,一个SYN将占用一个序号。
  3. 客户必须将确认序号设置为服务器的ISN加1以对服务器的SYN报文段进行确认(报文段3)。

图片流程:

测试

开始tcpdump开始抓包:

tcpdump -i lo tcp port 9999 -S # 9999是服务端的端口

开启另外一个终端打开服务端:

./server 9999

再开启一个终端开启客户端:

./client 127.0.0.1 9999

此时tcpdump的输出为:

13:52:18.612060 IP localhost.34040 > localhost.9999: Flags [S], seq 3554397807, win 43690, options [mss 65495,sackOK,TS val 3432008521 ecr 0,nop,wscale 7], length 0
13:52:18.612082 IP localhost.9999 > localhost.34040: Flags [S.], seq 2489855324, ack 3554397808, win 43690, options [mss 65495,sackOK,TS val 3432008521 ecr 3432008521,nop,wscale 7], length 0
13:52:18.612109 IP localhost.34040 > localhost.9999: Flags [.], ack 2489855325, win 342, options [nop,nop,TS val 3432008521 ecr 3432008521], length 0

这三个数据包验证了三次握手的流程:

第一个可以看出是客户端33632发给服务端9999的数据包,是一个SYN数据包,seq2197027846

第二个数据包为服务端返回的ACK数据包,ack=2197027847,等于上一个数据包中客户端的seq+1

第三个数据包是客户端给服务端的,它也带了ack,并且等于上一个数据包中服务端发过来的seq+1

此时客户端和服务端的连接状态都到达了ESTABLISHED

> netstat -apn | grep 9999
tcp        0      0 0.0.0.0:9999            0.0.0.0:*               LISTEN      11507/server    
tcp        0      0 127.0.0.1:9999          127.0.0.1:34040         ESTABLISHED 11507/server    
tcp        0      0 127.0.0.1:34040         127.0.0.1:9999          ESTABLISHED 11508/client

二、四次挥手

四次挥手是由TCP的半关闭(half-close)造成的。既然一个TCP连接是全双工(即数据在两个方向上能同时传递),因此每个方向必须单独地进行关闭。这原则就是当一方完成它的数据发送任务后就能发送一个FIN来终止这个方向连接。当一端收到一个FIN,它必须通知应用层另一端几经终止了那个方向的数据传送。发送FIN通常是应用层进行关闭的结果。

收到一个FIN只意味着在这一方向上没有数据流动。一个TCP连接在收到一个FIN后仍能发送数据。而这对利用半关闭的应用来说是可能的,尽管在实际应用中只有很少的TCP应用程序这样做。

通过快捷键CTRL+C或者在客户端界面输入exit即可退出客户端,退出后的tcpdump输出:

13:57:46.959816 IP localhost.34040 > localhost.9999: Flags [F.], seq 3554397808, ack 2489855325, win 342, options [nop,nop,TS val 3432336261 ecr 3432008521], length 0
13:57:46.960901 IP localhost.9999 > localhost.34040: Flags [.], ack 3554397809, win 342, options [nop,nop,TS val 3432336261 ecr 3432336261], length 0
13:57:47.962734 IP localhost.9999 > localhost.34040: Flags [F.], seq 2489855325, ack 3554397809, win 342, options [nop,nop,TS val 3432337262 ecr 3432336261], length 0
13:57:47.962781 IP localhost.34040 > localhost.9999: Flags [.], ack 2489855326, win 342, options [nop,nop,TS val 3432337262 ecr 3432337262], length 0

四次挥手分析:

第一行客户端发给服务端,是一个FIN数据包,表示客户端要结束发送数据。

第二行是服务端的对上一条的响应,ack(3554397809)=seq(3554397808)+1

服务端在收到客户端的关闭消息后也关闭了连接,也发了一个FIN数据包,seq=2489855325

客户端收到服务端的关闭消息后也回了一个ack数据包,ack(2489855326)=seq(2489855325)+1

三、2MSL状态

2MSL状态是连接断开后需要继续等待的时间,客户端在收到服务端的FIN数据包之后,会进入2MSL的等待期,在这段时间内,任何来自服务端的数据都会被拒绝。这段时间存在的意义主要有以下几点:

  1. 客户端收到FIN后给服务端回ACK,如果此时数据没有到达服务端,服务端会尝试重连,浪费服务端资源。同时,如果立马又有一个程序占用了这个端口,收到服务端的重传FIN之后将会关闭。
  2. 服务端的数据包可能存在时延,一些数据包在FIN之后到达客户端,如果客户端关闭之后又起了一个程序,第二个程序将会收到来自前一个业务逻辑的数据,导致错误。
最后修改:2020 年 02 月 21 日
喜欢就给我点赞吧