详解TCP协议三次握手和四次挥手
TCP
建立连接和断开连接很重要,但是自己又经常忘记一些具体细节,回顾的时候要么到收藏夹里找文章,要么是百度/谷歌一波,偶尔一两次还好,次数多了略显麻烦,遂趁这次温故的机会,记录下来,方便自己也方便他人~
TCP报文格式
TCP
报文格式如下图所示:
其中有几个字段比较重要,在下面讲三次握手和四次挥手是会用到:
- 序号:
seq
序号,占32位
,用来标识从TCP
源端向目的端发送的字节流,发起方发送数据时对此进行标记; - 确认序号:
ack
序号,占32位
,只有ACK
标志位为1时,确认序号字段才有效; - 标志位:共
6
个,即URG
、ACK
、PSH
、RST
、SYN
、FIN
,每个标志位表示一个控制空能,具体如下:-
URG
:紧急指针(urgent pointer
),为1
时表示紧急指针有效,为0
则忽略紧急指针。紧急指针是一个正的偏移量,和序号字段中的值相加表示紧急数据最后一个字节的序号。TCP
的紧急方式是发送端向另一端发送紧急数据的一种方式; -
ACK
:确认序号,为1
时表示确认号有效,为0
表示报文中不含确认信息,忽略确认号字段; -
PSH
:PUSH
标志,为1
表示是带有PUSH
标志的数据,指示接收方在接收到该报文段以后,应尽快将这个报文段交给应用程序,而不是在缓冲区排队; -
RST
:重置/复位连接,用于重置由于主机崩溃或其他原因而出现错误的连接。或者用于拒绝非法的报文段和拒绝连接请求; -
SYN
:同步序号,用于建立连接过程; -
FIN
:FINISH
标志,释放连接,为1
时表示发送方已经没有数据发送了,即关闭本方数据流。
-
需要特别注意:
- 确认序号
ack
与标志位ACK
不同,不能混淆,看下面就会懂;- 确认方的确认序号
ack
等于发起方的序号seq
加1
,即ack=seq+1
,才能够两端匹配。
三次握手(建立TCP连接)
三次握手(Three-Way Handshake
)即建立TCP
连接,指建立一个TCP
连接时,需要客户端和服务端总共发送3
个包来确认连接的建立,整个过程大致如下:
- 第一次握手:
Client
将标志位SYN
置为1
,随机产生一个值seq=J
,并将该数据包发送给Server
,Client
进入SYN_SENT
状态,等待Server
确认。 - 第二次握手:
Server
收到数据包后由标志位SYN=1
知道Client
请求建立连接,Server
将标志位SYN
和ACK
都置为1
,ack=J+1
,随机产生一个值seq=K
,并将该数据包发送给Client
以确认连接请求,Server
进入SYN_RCVD
状态。 - 第三次握手:
Client
收到确认后,检查ack
是否为J+1
,ACK
是否为1
,如果正确则将标志位ACK
置为1
,ack=K+1
,并将该数据包发送给Server
,Server
检查ack
是否为K+1
,ACK
是否为1
,如果正确则连接建立成功,Client
和Server
进入ESTABLISHED
状态,完成三次握手,随后Client
与Server
之间可以开始传输数据了。
半连接(half-open connect)
在三次握手过程中,第二次握手,即Server
发送SYN-ACK
之后,收到Client
的ACK
之前的TCP
连接被称为半连接。
此时Server
会将第一次握手相关信息放入半连接队列(syns queue
)中,若收到第三次握手Client
发送的ACK
报文后,则将相关信息挪入全连接队列(accept queue
),否则会有限次数(超时机制)的重传SYN-ACK
。如果半连接队列或者全连接队列已满,则抛弃此次连接。
SYN攻击(半连接攻击):
恶意Client
在短时间内伪造大量不存在的IP
地址,并向Server
不断地发送SYN
包,Server
回复确认包,并等待Client
的确认,由于源地址是不存在的,因此,Server
需要不断重发直至超时,这些伪造的SYN
包将产时间占用未连接队列,导致正常的SYN
请求因为队列满而被丢弃,从而引起网络堵塞甚至系统瘫痪。
三次握手的原因
为了防止已失效的连接请求报文段突然又传送到了服务端,因而产生错误。
举例:
Client
发出的第一个连接请求报文段并没有丢失,而是在某个网络结点长时间的滞留了,以致延误到连接释放以后的某个时间才到达Server
。本来这是一个早已失效的报文段。但Server
收到此失效的连接请求报文段后,就误认为是Client
再次发出的一个新的连接请求。于是就向Client
发出确认报文段,同意建立连接。假设不采用“三次握手”,那么只要Server
发出确认,新的连接就建立了。由于现在Client
并没有发出建立连接的请求,因此不会理睬Server
的确认,也不会向Server
发送数据。但Server
却以为新的运输连接已经建立,并一直等待Client
发来数据。这样,Server
的很多资源就白白浪费掉了。采用“三次握手”的办法可以防止上述现象发生。例如刚才那种情况,Client
不会向Server
的确认发出确认。Server
由于收不到确认,就知道Client
并没有要求建立连接。
四次挥手(断开TCP连接)
四次挥手(Four-Way Wavehand
)即断开TCP
连接,指断开一个TCP
连接时,需要客户端和服务端总共发送4
个包以确认连接的断开。整个过程大致如下:
由于TCP
连接时全双工的,因此,每个方向都必须要单独进行关闭,这一原则是当一方完成数据发送任务后,发送一个FIN
来终止这一方向的连接,收到一个FIN
只是意味着这一方向上没有数据流动了,即不会再收到数据了,但是在这个TCP
连接上仍然能够发送数据,直到这一方向也发送了FIN
。首先进行关闭的一方将执行主动关闭,而另一方则执行被动关闭,上图描述的即是如此。
- 第一次挥手:
Client
发送一个FIN
,用来关闭Client
到Server
的数据传送,Client
进入FIN_WAIT_1
状态。 - 第二次挥手:
Server
收到FIN
后,发送一个ACK
给Client
,确认序号为收到序号seq+1
(与SYN
相同,一个FIN
占用一个序号),Server
进入CLOSE_WAIT
状态。 - 第三次挥手:
Server
发送一个FIN
,用来关闭Server
到Client
的数据传送,Server
进入LAST_ACK
状态。 - 第四次挥手:
Client
收到FIN
后,Client
进入TIME_WAIT
状态,接着发送一个ACK
给Server
,确认序号为收到序号seq+1
,同时等待2MSL
(报文最大生存时间)进入CLOSED
状态。Server
收到ACK
后进入CLOSED
状态,完成四次挥手。
四次挥手的原因
TCP
协议是一种面向连接的、可靠的、基于字节流的传输层全双工模式通信协议,这就意味着,当Client
发出FIN
报文段时,只是表示Client
已经没有数据要发送了,Client
告诉Server
,它的数据已经全部发送完毕了;但是,这个时候Client
还是可以接受来自Server
的数据;当Server
返回ACK
报文段时,表示它已经知道Client
没有数据发送了,但是Server
还是可以发送数据到Client
的;当Server
也发送了FIN
报文段时,这个时候才表示Server
也没有数据要发送了,就会告诉Client
,我也没有数据要发送了,之后彼此就会愉快的中断这次TCP
连接。
Client进入TIME_WAIT状态后等待2MSL(报文最大生存时间)才进入CLOSED状态的原因
- 保证
TCP
协议的全双工连接能够可靠关闭
如果Client
直接CLOSED
了,那么由于IP
协议的不可靠性或者是其它网络原因,导致Server没有收到Client
最后回复的ACK
。那么Server
就会在超时之后继续发送FIN
,此时由于Client
已经CLOSED
了,就找不到与重发的FIN
对应的连接,最后Server
就会收到RST
而不是ACK
,Server
就会以为是连接错误把问题报告给高层。这样的情况虽然不会造成数据丢失,但是却导致TCP
协议不符合可靠连接的要求。所以,Client
不是直接进入CLOSED,
而是要保持TIME_WAIT
,当再次收到FIN
的时候,能够保证对方收到ACK
,最后正确的关闭连接。
- 保证这次连接的重复数据段从网络中消失
如果Client
直接CLOSED
,然后又再向Server
发起一个新连接,我们不能保证这个新连接与刚关闭的连接的端口号是不同的。也就是说有可能新连接和老连接的端口号是相同的。一般来说不会发生什么问题,但是还是有特殊情况出现:假设新连接和已经关闭的老连接端口号是一样的,如果前一次连接的某些数据仍然滞留在网络中,这些延迟数据在建立新连接之后才到达Server
,由于新连接和老连接的端口号是一样的,又因为TCP
协议判断不同连接的依据是socket pair
,于是,TCP
协议就认为那个延迟的数据是属于新连接的,这样就和真正的新连接的数据包发生混淆了。所以TCP
连接还要在TIME_WAIT
状态等待2MSL
,这样可以保证本次连接的所有数据都从网络中消失。