三次握手
- 第一次握手:建立连接,客户端发送请求报文,将SYN = 1,seq =x,客户端进入SYN_SEND状态。
- 第二次握手:服务器收到SYN报文段,需要对这个SYN报文段以确认,设置ACK = 1,SYN = 1,seq = y,ack = x + 1。服务器进入SYN_RECV状态。
- 第三次握手:客户端收到服务器的SYN+ACK报文,将ACK = 1,seq = x + 1,ack = y + 1。客户机和服务器都进入ESTABLISHED状态
注:
- SYN为同步位:仅当SYN=1是一个请求报文。
- ACK为确认位:仅当ACK=1时确认号字段有效。TCP规定,在TCP链接建立后,所有的传送的报文段都需要将ACK置为1
- 值得注意的是,服务器端的资源在第二次握手时分配,而第三次握手时,客户端才会为其分配资源,这样一来服务器容易遭受到SYN洪泛攻击。
四次挥手(以用户主机主动发起关闭为例)
- 第一次挥手:主机设置seq=u,FIN=1。其中的u是之前发送的最后一个数据的序号+1,发送给对方,不论是谁主动要求关闭,都需要向对方发送第一次挥手。此时的主机进入FIN—WAIT-1状态
- 第二次挥手:服务器收到连接释放报文段后发出确认:ack=u+1,ACK=1,seq=v,同样地,v也是该机器之前发送的最后一个数据序号+1。此时的客户机进入CLOSE—WAIT状态(主机在接收到第二次挥手后进入FIN-WAIT-2状态),此时客户机到主机的链接就被释放了。
- 第三次挥手:服务器发送:FIN=1,ACK=1,seq=w,ack=u+1。服务器进入LAST-ACK状态,主机收到后进入TIME-WAIT 状态
- 第四次挥手:主机收到来自服务器的FIN报文后,确认没有要发送的数据后,使用FIN=1,seq=u+1,ack=w+1确认,此时的TCP还没有关闭!需要经历计时器设置的2MSL后才能正式关闭链接。
注:
- 客户端和服务器都可以主动终止服务。
- ACK和ack并不是一个东西,ACK表示确认位,而ack则是确认的具体序号。
- 在第二次挥手后,第三次挥手前,主动发起关闭TCP连接的请求方(用户主机)的连接已经被释放了,而此时,另外一条从服务器到主机的连接仍可用,此时服务器仍能向主机发送请求。
- 在第四次挥手完成后,(注3)中的连接需要经过计时器设置的2MSL后才会关闭。
一些异常处理
情况一:三次握手中第一次握手的[SYN]数据包丢失:
这种情况下服务器根本不知道客户机发送了数据包,自然无法发送应答,无法进行第二次握手。应对策略是客户端根据超时重传机制来重传请求。
情况二:三次握手中第二次握手的[SYN、ACK]数据包丢失了
- 就客户端的角度来说:认为自己的请求没有到达服务器,即按照情况一进行重传。
- 就服务器的角度来说,系统没有收到客户端发来的[ACK]包,也会触发重传。
情况三:三次握手中的第三次握手的[ACK]包丢了
- 这是一种比较特殊的情况,在这种情况下,客户端已经建立好连接了,但是服务端因为收不到「ACK」会走重传机制,于是服务端处于SYN-RCVD状态下。
- 在大多数情况下, 客户端进入ESTABLISHED状态后,会认为链接已经建立,会立即发送数据。一旦服务器在接收到数据包时,查看内部的ACK置位就可以知道:之前的数据已经被确认。
例如:第二次握手服务器发送了:SYN=1、ACK=1、seq=100、ack=66,而客户端发送的第三次握手数据为:ACK=1、seq=66、ack=101。但是在途中,这个数据包丢失了。此时客户端已经建立了连接,处于ESTABLISHED状态,服务器还在等待这个[ACK]包。但是客户端既然认为自己已经建立了链接,自然而然开始发送数据,TCP规定,在TCP连接建立后所有发送的报文段必须将ACK置为1。由此服务器在正常接收到第一个数据包的时候,会发现这个ACK为1的数据包,于是服务器会正常地进入ESTABLISHED状态,完成连接。
情况四:SYN洪泛攻击
即客户端是恶意的,在发送「SYN」包后,并收到「SYN,ACK」后就不回复了,那么服务端此时处于一种半连接的状态,虽然服务端会通过 tcp_synack_retries 配置重试的次数,不会无限等待下去,但是这也是有一个时间周期的。如果短时间内存在大量的这种恶意连接,对服务端来说压力就会很大,这就是所谓的 SYN FLOOD 攻击。
情况五:断开连接的 FIN 包丢了(四次挥手中的第一次)
客户端率先发的「FIN」包丢了,或者没有收到对端的「ACK」回复,则会触发超时重传,直到触发重传的次数,直接关闭连接。 对于服务端而言,如果客户端发来的「FIN」没有收到,同时客户机没有发送任何数据,也就没有任何感知。会在一段时间后,也关闭连接。
情况六:服务端第一次回复的 ACK 丢了(四次挥手中的第二次)
- 此时因为客户端没有收到「ACK」应答,会尝试重传之前的「FIN」请求,服务端收到后,又会立即再重传「ACK」。而此时服务端已经进入 CLOSED-WAIT状态,开始做断开连接前的准备工作。当准备好之后,会回复「FIN,ACK」,注意这个消息是携带了之前「ACK」的响应序号的。只要这个消息没丢,客户端可以凭借「FIN,ACK」包中的响应序号,直接从 FIN-WAIT-1 状态,进入 TIME-WAIT 状态,开始长达 2MSL 的等待。 也就是说,第三次挥手没有丢失,第二次挥手丢失了,并不影响客户机、服务器关闭TCP连接。
情况七:客户端最后回复的 ACK 丢了(四次挥手中的第二次)
客户端在回复「ACK」后,会进入 TIME-WAIT 状态,开始长达 2MSL 的等待,服务端因为没有收到「ACK」的回复,会重试一段时间,直到服务端重试超时后主动断开。或者等待新的客户端接入后,收到服务端重试的「FIN」消息后,回复「RST」消息,在收到「RST」消息后,复位服务端的状态。
情况八:客户端收到 ACK 后,服务端跑路了
客户端在收到「ACK」后,进入了 FIN-WAIT-2 状态,等待服务端发来的「FIN」包,而如果服务端跑路了,这个包永远都等不到。在 TCP 协议中,是没有对这个状态的处理机制的。但是协议不管,系统来凑,操作系统会接管这个状态,例如在 Linux 下,就可以通过 tcp_fin_timeout 参数,来对这个状态设定一个超时时间。需要注意的是,当超过 tcp_fin_timeout 的限制后,状态并不是切换到 TIME_WAIT,而是直接进入 CLOSED 状态。
情况九:客户端收到 ACK 后,客户端自己跑路了
客户端收到「ACK」后直接跑路,服务端后续在发送的「FIN,ACK」就没有接收端,也就不会得到回复,会不断的走 TCP 的超时重试的机制,此时服务端处于 LAST-ACK 状态。那就要分 2 种情况分析:
- 在超过一定时间后,服务端主动断开。
- 收到「RST」后,主动断开连接。
「RST」消息是一种重置消息,表示当前错误了,应该回到初始的状态。如果客户端跑路后有新的客户端接入,会在此发送「SYN」以期望建立连接,此时这个「SYN」将被忽略,并直接回复「FIN,ACK」消息,新客户端在收到「FIN」消息后是不会认的,并且会回复一个「RST」消息。
一些常见的问题
如何在UDP上层实现可靠数据传输
UDP传输在可靠数据传输方面可能出现问题的地方:
- 无确认、应答机制,发送的双方不知道数据是否已经正常交付给对方;
- 无缓存机制,不能在确认出错后及时地重发。
- 无法对整个网络环境进行拥塞控制和处理;
解决方案
在TCP协议中,面对字节流,每次传输数据都会对传输的单元进行编号,以一个数据块即为一个序号。在接收方收到序号为100的数据时,将会发送ack=101,这一ack有两个含义,分别是对100号数据进行确认、对101号数据表示请求。 而在UDP协议中,UDP面向一个个的UDP数据报,我们需要在传输层的上层,即应用层对UDP的数据报进行传输的确认,增加seq/ack机制,保障数据发送到对端。 在端到端发送数据时,随机生成一个seq=x,然后每一片按数据大小分配seq,数据到达接收端后放入缓存,并发送一个ack=x + 1的包,表示数据已经收到,而发送端收到ack包时,删除缓冲区对应的数据。同时发送端设置定时器,定时检查任务是否需要进行超时重传。
为什么握手是三次而挥手是四次。(第二次挥手的作用是什么?)
因为当Server端收到Client端的SYN连接请求报文后,可以直接发送SYN+ACK报文。其中ACK报文是用来应答的,SYN报文是用来同步的。但是关闭连接时,当Server端收到FIN报文时,很可能并不会立即关闭SOCKET,所以只能先回复一个ACK报文,告诉Client端,"你发的FIN报文我收到了"。只有等到我Server端所有的报文都发送完了,我才能发送FIN报文,因此不能一起发送。故需要四步握手。
为什么在第三次握手后需要经过一个TIME—WAIT状态,设置一个2MSL定时器才能关闭连接?
假象网络是不可靠的,有可以最后一个ACK丢失。所以TIME—WAIT状态就是用来重发可能丢失的ACK报文。在Client发送出最后的ACK回复,但该ACK可能丢失。Server如果没有收到ACK,将不断重复发送FIN片段。所以Client不能立即关闭,它必须确认Server接收到了该ACK。Client会在发送出ACK之后进入到TIME-WAIT状态。Client会设置一个计时器,等待2MSL的时间。如果在该时间内再次收到FIN,那么Client会重发ACK并再次等待2MSL。所谓的2MSL是两倍的MSL(Maximum Segment Lifetime)。MSL指一个片段在网络中最大的存活时间,2MSL就是一个发送和一个回复所需的最大时间。如果直到2MSL,Client都没有再次收到FIN,那么Client推断ACK已经被成功接收,则结束TCP连接。
三次握手改成两次握手会出现什么问题?
3次握手完成两个重要的功能,既要双方做好发送数据的准备工作(双方都知道彼此已准备好),也要允许双方就初始序列号进行协商,这个序列号在握手过程中被发送和确认。 假设只有请求和应答两次握手,那么当应答分组丢失时,用户端将不知道服务器是否已经准备好,用户端认为连接尚未建立成功,而服务端则认为自己的[ACK]分组已经发送成功了,这样一来客户端将忽略任何来自服务器端的分组;同时,服务端将会不断地超时重发。形成死锁。