TCP提供了一种可靠、面向连接、字节流、传输层的服务,采用3次握手建立一个连接,采用4次挥手来关闭一个连接。
三次握手
三次握手为了确认双方接受能力和发送能力是否正常,并指定自己的初始化序列号为后面可靠性传输做准备;
-
SYN 连接请求报文段;
-
ACK 确认报文段;
-
seq 请求报文段第一个字节序列号;
-
ack 希望收到报文下一段报文字节序列号;
客户端/服务端无通信正常情况下都处理CLOSE状态;
-
客户端:主动发送请求报文信息,SYN=1,seq=x (指定序列初始值,为了可靠性传输,校验数据完整性)并将Closed状态更改为SYN-SEND状态;
-
服务端:接收到请求报文之后,回复客户端请求信息,ACK=1(代表确认收到请求),并SYN=1(请求准备连接中),seq = y, ack=x+1(期望客户端下一个报文端字节序列号是从x+1开始,前x位已经被服务端处理),并进入SYN-RECEIVED状态;
-
客户端:接受到服务端ACK之后(确认了服务端已经收到客户端的请求连接,并且服务端SYN=1代表服务请求跟客户端连接准备中),客户端回复ACK=1,seq=x+1, ack=y+1,客户端进入ESTABLISHED状态,随时可以接受数据,最后服务端接受到客户端ACK之后服务端进入ESTABLISHED状态;
SYN-SEND:发送连接请求之后的等待匹配的状态;
SYN-RECEIVED:收到连接请求并发送自己的连接请求,并等待请求确认;
ESTABLISHED:代表一个打开的连接,双方可以收发数据;
为什么需要三次握手?
三次握手的目的是建立可靠的通信信道,说到通讯,简单来说就是数据的发送与接收,而三次握手最主要的目的就是双方确认自己与对方的发送与接收是正常的。
第一次握手:客户端发送SYN,进入SYN-SEND状态,等待服务端SYN确认,即等待ACK消息,此时客户端不知道自己发送能力是否正常;
第二次握手:服务端回复了ACK,并带上自己的SYN,服务端进入SYN-RECEIVED状态,此时服务端不清楚自己是否发送正常,接受能力正常;
第三次握手:客户端收到ACK和SYN之后,客户端进入了ESTABLISHED状态,确认了自己发送能力正常,以及自己接受能力正常,此时还需要回复服务端ACK信息,服务端收到之后才能确认自己发送能力正常;
所以三次握手核心主要在第三次握手确认中,也是最短的握手要求,4次就多余,2次服务端就无法达到确认自自己收发能力正常与否;
三次握手是否可以携带数据?
第三次可以,因为客户端进去ESTABLISHED状态具备数据发送和接受能力,同时确认了服务端接受和发送能力正常;
SYN泛洪攻击
客户端和服务端在握手过程中,服务端进入SYN-RECEIVED状态的连接会进入半连接池,如果短时间内客户端伪造大量不同ip地址向服务端发送SYN请求建立连接,服务端短时间内需要不断回复大量的应答包(确认包),并且伪造ip不会响应服务端SYN请求(ACK确认),会触发服务器超时重新发送SYN包等待客户端确认,那么会长时间占用半连接池并消耗服务器大量内存和Cpu 。 简单来说就是攻击者对服务器发送非法大量的这种TCP连接,由于每一个都没法完成握手的机制,所以它就会消耗服务器的内存最后可能导致服务器死机,就无法正常工作了,更进一步说,如果这些半连接的握手请求是恶意程序发出,并且持续不断,那么就会导致服务端较长时间内丧失服务功能——这样就形成了DoS攻击,这种攻击方式就称为SYN泛洪攻击。(目前,Linux下默认会进行5次重发SYN-ACK包,重试的间隔时间从1s开始,下次的重试间隔时间是前一次的双倍,5次的重试时间间隔为1s, 2s, 4s, 8s, 16s, 总共31s, 称为指数退避,第5次发出后还要等32s才知道第5次也超时了,所以,总共需要 1s + 2s + 4s+ 8s+ 16s + 32s = 63s, TCP才会把断开这个连接。由于,SYN超时需要63秒,那么就给攻击者一个攻击服务器的机会,攻击者在短时间内发送大量的SYN包给Server(俗称SYN flood攻击),用于耗尽Server的SYN队列。对于应对SYN 过多的问题,linux提供了几个TCP参数:tcp_syncookies、tcp_synack_retries、tcp_max_syn_backlog、tcp_abort_on_overflow 来调整应对)。
握手过程意外终止或者握手丢失,如何处理?
服务端发送SYN-ACK数据包之后进入SYN-RECEIVED状态,此时与客户端的连接信息进入半连接池缓存,如果服务端长时间未能收到客户端的ACK应该包,那么会触发超时重传机制,服务端会重新向客户端发送SYN+ACK数据包请求确认, 重试次数会影响服务器超时时间长短, 再进行一段时间或者一定次数重传之后依然无法收到客户端ACK确认,那么服务端会从半连接池中释放该连接;
4次挥手释放连接
tcp连接释放需要4次握手流程,客户端和服务端都可以主动发起挥手动作;
-
FIN:终止连接请求
-
seq:请求报文段初始化序列号;
-
ACK:确认报文位;
-
ack:希望收到下一个报文段的字节序列号;
双方最开始都处于ESTABLISHED状态(正常收发数据),如果客户端请求断开流程如下;
-
客户端发送FIN=1报文(请求终止连接),报文初始化序列号seq=u,客户端停止发送数据,此时客户端进入FIN_WAIT1状态,等待服务端确认;
-
服务端收到客户端FIN之后,回复客户端ACK=1,seq=w,ack=u+1, 此时服务端进入CLOASE_WAIT状态(半关闭状态),服务端此时还能继续发送数据;
-
待数据发送完成之后,服务端发送FIN=1,ACK=1, seq=v , ack = u+1,请求终止连接,此时服务端不能发送数据给客户端,并进入LAST_ACK状态,等待确认释放连接;
-
客户端收到服务端的FIN=1终止连接之后,回复服务端ACK=1 ,seq=u+1, ack=v+1, 并进入了TIME_WAIT状态(2毫秒延迟,防止服务端没有收到ACK,客户端重新回复ACK报文),服务端收到ACK之后就进入CLOASE状态,2MSL过后客户端进入CLOASE状态,全连连接池释放该连接;
FIN_WAIT1:客户端处于中断连接请求等待确认状态;
CLOSED-WAIT:等待服务端数据发送完成请求中断连接;
FIN_WAIT2:等待服务端中断连接请求状态;
LAST-ACK: 等待服务器中断请求的回复确认;
TIME-WAIT : 等待足够的时间以确保服务端接收到连接中断请求的确认;
注意 :
这个时候由服务端到客户端的 TCP 连接并未释放掉,需要经过时间等待计时器设置的时间 2MSL(一个报文的来回时间) 后才会进入 CLOSED 状态(这样做的目的是确保服务端收到自己的 ACK 报文。如果服务端在规定时间内没有收到客户端发来的 ACK 报文的话,服务端会重新发送 FIN 报文给客户端,客户端再次收到 FIN 报文之后,就知道之前的 ACK 报文丢失了,然后再次发送 ACK 报文给服务端)。服务端收到 ACK 报文之后,就关闭连接了,处于 CLOSED 状态。
为什么要4次挥手?
由于TCP的半关闭特性,TCP在一端发起请求连接关闭之后任然可以接受另一端数据的能力,任何一方都可以数据传输之后发出等待释放的通知,待对方确认后进入半关闭,待另一方也没有再数据发送时候则发出连接释放通知,对方确认后就完成关闭了TCP连接;
也就是说两次握手只能释放一端到另一端的TCP连接,完全释放连接的话需要四次握手;