三次握手和四次挥手是各个公司常见的考点,也具备必定的水平区分度,也被一些面试官做为热身题。不少小伙伴说这个问题刚开始回答的挺好,可是后面越回答越冒冷汗,最后就歇菜了。面试
见过比较典型的面试场景是这样的:编程
面试官:请介绍下三次握手bash
求职者:第一次握手就是客户端给服务器端发送一个报文,第二次就是服务器收到报文以后,会应答一个报文给客户端,第三次握手就是客户端收到报文后再给服务器发送一个报文,三次握手就成功了。服务器
面试官:而后呢?cookie
求职者:这就是三次握手的过程,很简单的。网络
面试官:。。。。。。并发
(番外篇:一首凉凉送给你)socket
记住猿人谷一句话:面试时越简单的问题,通常就是隐藏着比较大的坑,通常都是须要将问题扩展的。上面求职者的回答不对吗?固然对,但距离面试官的指望可能还有点距离。spa
但愿你们能带着以下问题进行阅读,收获会更大。3d
三次握手(Three-way Handshake)其实就是指创建一个TCP链接时,须要客户端和服务器总共发送3个包。进行三次握手的主要做用就是为了确认双方的接收能力和发送能力是否正常、指定本身的初始化序列号为后面的可靠性传送作准备。实质上其实就是链接服务器指定端口,创建TCP链接,并同步链接双方的序列号和确认号,交换TCP窗口大小
信息。
刚开始客户端处于 Closed 的状态,服务端处于 Listen 状态。 进行三次握手:
第一次握手:客户端给服务端发一个 SYN 报文,并指明客户端的初始化序列号 ISN(c)。此时客户端处于 SYN_SEND
状态。
首部的同步位SYN=1,初始序号seq=x,SYN=1的报文段不能携带数据,但要消耗掉一个序号。
第二次握手:服务器收到客户端的 SYN 报文以后,会以本身的 SYN 报文做为应答,而且也是指定了本身的初始化序列号 ISN(s)。同时会把客户端的 ISN + 1 做为ACK 的值,表示本身已经收到了客户端的 SYN,此时服务器处于 SYN_REVD
的状态。
在确认报文段中SYN=1,ACK=1,确认号ack=x+1,初始序号seq=y。
第三次握手:客户端收到 SYN 报文以后,会发送一个 ACK 报文,固然,也是同样把服务器的 ISN + 1 做为 ACK 的值,表示已经收到了服务端的 SYN 报文,此时客户端处于 ESTABLISHED
状态。服务器收到 ACK 报文以后,也处于 ESTABLISHED
状态,此时,双方已创建起了链接。
确认报文段ACK=1,确认号ack=y+1,序号seq=x+1(初始为seq=x,第二个报文段因此要+1),ACK报文段能够携带数据,不携带数据则不消耗序号。
发送第一个SYN的一端将执行主动打开(active open),接收这个SYN并发回下一个SYN的另外一端执行被动打开(passive open)。
在socket编程中,客户端执行connect()时,将触发三次握手。
弄清这个问题,咱们须要先弄明白三次握手的目的是什么,能不能只用两次握手来达到一样的目的。
所以,须要三次握手才能确认双方的接收与发送能力是否正常。
试想若是是用两次握手,则会出现下面这种状况:
如客户端发出链接请求,但因链接请求报文丢失而未收到确认,因而客户端再重传一次链接请求。后来收到了确认,创建了链接。数据传输完毕后,就释放了链接,客户端共发出了两个链接请求报文段,其中第一个丢失,第二个到达了服务端,可是第一个丢失的报文段只是在某些网络结点长时间滞留了,延误到链接释放之后的某个时间才到达服务端,此时服务端误认为客户端又发出一次新的链接请求,因而就向客户端发出确认报文段,赞成创建链接,不采用三次握手,只要服务端发出确认,就创建新的链接了,此时客户端忽略服务端发来的确认,也不发送数据,则服务端一致等待客户端发送数据,浪费资源。
服务器第一次收到客户端的 SYN 以后,就会处于 SYN_RCVD 状态,此时双方尚未彻底创建其链接,服务器会把此种状态下请求链接放在一个队列里,咱们把这种队列称之为半链接队列。
固然还有一个全链接队列,就是已经完成三次握手,创建起链接的就会放在全链接队列中。若是队列满了就有可能会出现丢包现象。
这里在补充一点关于SYN-ACK 重传次数的问题: 服务器发送完SYN-ACK包,若是未收到客户确认包,服务器进行首次重传,等待一段时间仍未收到客户确认包,进行第二次重传。若是重传次数超过系统规定的最大重传次数,系统将该链接信息从半链接队列中删除。 注意,每次重传等待的时间不必定相同,通常会是指数增加,例如间隔时间为 1s,2s,4s,8s......
当一端为创建链接而发送它的SYN时,它为链接选择一个初始序号。ISN随时间而变化,所以每一个链接都将具备不一样的ISN。ISN能够看做是一个32比特的计数器,每4ms加1 。这样选择序号的目的在于防止在网络中被延迟的分组在之后又被传送,而致使某个链接的一方对它作错误的解释。
三次握手的其中一个重要功能是客户端和服务端交换 ISN(Initial Sequence Number),以便让对方知道接下来接收数据的时候如何按序列号组装数据。若是 ISN 是固定的,攻击者很容易猜出后续的确认号,所以 ISN 是动态生成的。
其实第三次握手的时候,是能够携带数据的。可是,第一次、第二次握手不能够携带数据
为何这样呢?你们能够想一个问题,假如第一次握手能够携带数据的话,若是有人要恶意攻击服务器,那他每次都在第一次握手中的 SYN 报文中放入大量的数据。由于攻击者根本就不理服务器的接收、发送能力是否正常,而后疯狂着重复发 SYN 报文的话,这会让服务器花费不少时间、内存空间来接收这些报文。
也就是说,第一次握手不能够放数据,其中一个简单的缘由就是会让服务器更加容易受到攻击了。而对于第三次的话,此时客户端已经处于 ESTABLISHED 状态。对于客户端来讲,他已经创建起链接了,而且也已经知道服务器的接收、发送能力是正常的了,因此能携带数据也没啥毛病。
服务器端的资源分配是在二次握手时分配的,而客户端的资源是在完成三次握手时分配的,因此服务器容易受到SYN洪泛攻击。SYN攻击就是Client在短期内伪造大量不存在的IP地址,并向Server不断地发送SYN包,Server则回复确认包,并等待Client确认,因为源地址不存在,所以Server须要不断重发直至超时,这些伪造的SYN包将长时间占用未链接队列,致使正常的SYN请求由于队列满而被丢弃,从而引发网络拥塞甚至系统瘫痪。SYN 攻击是一种典型的 DoS/DDoS 攻击。
检测 SYN 攻击很是的方便,当你在服务器上看到大量的半链接状态时,特别是源IP地址是随机的,基本上能够判定这是一次SYN攻击。在 Linux/Unix 上可使用系统自带的 netstats 命令来检测 SYN 攻击。
netstat -n -p TCP | grep SYN_RECV
复制代码
常见的防护 SYN 攻击的方法有以下几种:
创建一个链接须要三次握手,而终止一个链接要通过四次挥手(也有将四次挥手叫作四次握手的)。这由TCP的半关闭(half-close)形成的。所谓的半关闭,其实就是TCP提供了链接的一端在结束它的发送后还能接收来自另外一端数据的能力。
TCP 的链接的拆除须要发送四个包,所以称为四次挥手(Four-way handshake),客户端或服务器都可主动发起挥手动做。
刚开始双方都处于 ESTABLISHED 状态,假如是客户端先发起关闭请求。四次挥手的过程以下:
FIN_WAIT1
状态。 即发出链接释放报文段(FIN=1,序号seq=u),并中止再发送数据,主动关闭TCP链接,进入FIN_WAIT1(终止等待1)状态,等待服务端的确认。CLOSE_WAIT
状态。 即服务端收到链接释放报文段后即发出确认报文段(ACK=1,确认号ack=u+1,序号seq=v),服务端进入CLOSE_WAIT(关闭等待)状态,此时的TCP处于半关闭状态,客户端到服务端的链接释放。客户端收到服务端的确认后,进入FIN_WAIT2(终止等待2)状态,等待服务端发出的链接释放报文段。LAST_ACK
的状态。 即服务端没有要向客户端发出的数据,服务端发出链接释放报文段(FIN=1,ACK=1,序号seq=w,确认号ack=u+1),服务端进入LAST_ACK(最后确认)状态,等待客户端的确认。TIME_WAIT
状态。须要过一阵子以确保服务端收到本身的 ACK 报文以后才会进入 CLOSED 状态,服务端收到 ACK 报文以后,就处于关闭链接了,处于 CLOSED
状态。 即客户端收到服务端的链接释放报文段后,对此发出确认报文段(ACK=1,seq=u+1,ack=w+1),客户端进入TIME_WAIT(时间等待)状态。此时TCP未释放掉,须要通过时间等待计时器设置的时间2MSL后,客户端才进入CLOSED状态。收到一个FIN只意味着在这一方向上没有数据流动。客户端执行主动关闭并进入TIME_WAIT是正常的,服务端一般执行被动关闭,不会进入TIME_WAIT状态。
在socket编程中,任何一方执行close()操做便可产生挥手操做。
由于当服务端收到客户端的SYN链接请求报文后,能够直接发送SYN+ACK报文。其中ACK报文是用来应答的,SYN报文是用来同步的。可是关闭链接时,当服务端收到FIN报文时,极可能并不会当即关闭SOCKET,因此只能先回复一个ACK报文,告诉客户端,"你发的FIN报文我收到了"。只有等到我服务端全部的报文都发送完了,我才能发送FIN报文,所以不能一块儿发送。故须要四次挥手。
TIME_WAIT状态也成为2MSL等待状态。每一个具体TCP实现必须选择一个报文段最大生存时间MSL(Maximum Segment Lifetime),它是任何报文段被丢弃前在网络内的最长时间。这个时间是有限的,由于TCP报文段以IP数据报在网络内传输,而IP数据报则有限制其生存时间的TTL字段。
对一个具体实现所给定的MSL值,处理的原则是:当TCP执行一个主动关闭,并发回最后一个ACK,该链接必须在TIME_WAIT状态停留的时间为2倍的MSL。这样可以让TCP再次发送最后的ACK以防这个ACK丢失(另外一端超时并重发最后的FIN)。
这种2MSL等待的另外一个结果是这个TCP链接在2MSL等待期间,定义这个链接的插口(客户的IP地址和端口号,服务器的IP地址和端口号)不能再被使用。这个链接只能在2MSL结束后才能再被使用。
MSL是Maximum Segment Lifetime的英文缩写,可译为“最长报文段寿命”,它是任何报文在网络上存在的最长时间,超过这个时间报文将被丢弃。
为了保证客户端发送的最后一个ACK报文段可以到达服务器。由于这个ACK有可能丢失,从而致使处在LAST-ACK状态的服务器收不到对FIN-ACK的确认报文。服务器会超时重传这个FIN-ACK,接着客户端再重传一次确认,从新启动时间等待计时器。最后客户端和服务器都能正常的关闭。假设客户端不等待2MSL,而是在发送完ACK以后直接释放关闭,一但这个ACK丢失的话,服务器就没法正常的进入关闭链接状态。
理论上,四个报文都发送完毕,就能够直接进入CLOSE状态了,可是可能网络是不可靠的,有可能最后一个ACK丢失。因此TIME_WAIT状态就是用来重发可能丢失的ACK报文。
《TCP/IP详解 卷1:协议》有一张TCP状态变迁图,很具备表明性,有助于你们理解三次握手和四次挥手的状态变化。以下图所示,粗的实线箭头表示正常的客户端状态变迁,粗的虚线箭头表示正常的服务器状态变迁。
之后面试官再问你三次握手和四次挥手,直接把这一篇文章丢给他就能够了,他想问的都在这里。
参考:《TCP/IP详解 卷1:协议》