TCP 的三次握手和四次挥手,可以说是老生常谈的经典问题了,通常也作为各大公司常见的面试考题,具有一定的水平区分度。看似是简单的面试问题,如果你的回答不符合面试官期待的水准,有可能就直接凉凉了。
本文会围绕,三次握手和四次挥手相关的一些列核心问题,分享如何更准确的回答和应对常见的面试问题,以后面对再刁钻的面试官,你都可以随意地跟他扯皮了。
面试TCP的意义
我想要先说明一个重要问题,到底面试 TCP 的意义何在?
经常会听到这样抱怨:我是做业务程序开发的,面试官竟然问我 TCP 三次握手、TCP 拥塞控制的问题,还问的这么细致?
这些同学会觉着面试官是闲的淡疼,或是故意刁难候选人,更有同学认为面试官是为了防止自己技术退步拿来练手的,这种想法我也是醉了。
当然,不同人对此可能会有不同的想法,但我们技术人应该以积极的心态来理解和面对这个问题,在我看来面试 TCP 有重要的意义:
1. 从面试官的角度,可以快速考察候选人对基础知识的掌握程度,以及候选人对待技术的那种知其所以然的态度。
2. 从求职者的角度,即使工作内容中没有直接用到 TCP 协议,但在遇到网络故障,调试和分析问题时,熟悉 TCP 显得十分重要,要不抓包都看不懂。
3. 从学习的角度,我们可以学习 TCP 的设计理念,比如 TCP 重传、拥塞控制,以及如何在性能和原理之间做权衡和取舍的,举一反三,将这些原理细节应用到我们平时的软件设计上,也是一种思维上的学习成长。
4. 如果想要调整 TCP 参数来提升传输速度,可服务器上相关的系统参数有几十个,究竟该怎么调整呢?
5. 在服务器本地的 TCP 连接状态出现了类似 fin_wait
、time_wait
,该怎么解决,是什么原因引起的?如果不懂 TCP,即使别人告诉你解决方案,你也不能够真正理解的。
所以,我们非常有必要认真学习 TCP 协议,对 TCP 熟悉程度,在某种意义上也是你与别人拉开距离的重要标识。
TCP 基础
这里先帮小伙伴们熟悉和回顾下 TCP 的基本概念,以至于能够更好的理解文章后边的内容。
TCP 其实是非常复杂的协议,我们先聊一些基础的。我们知道 TCP 是一种可靠的协议,它主要通过解决这几个问题来实现可靠性的,分别是:乱序、丢包重传、流控、拥塞控制。通过从图中报文格式的字段,也能够简单了解到 TCP 的相关概念。
- TCP 在网络 OSI 七层模型中的第四层,TCP 包是没有 IP 地址的,但有源端口和目的端口,用来标识通信的进程。
Sequence Number
是记录包的序号,TCP 会按照报文字节进行编号,它是用来解决包在网络中乱序的问题。Acknowledgement Number
确认序列号,是用于向发送方确认已经收到了哪些包,用来解决不丢包的问题。Windows
也叫Advertised-Windows
,也就是著名的滑动窗口,主要是用来解决流控的。TCP Flag
就是包的类型,主要是用于操控 TCP 状态机的。
三次握手
三次握手是各个公司常见的面试考点。以过来人经验来讲,虽然该问题看似简单,但你还真不一定能够回答的好。
见过比较典型面试问答场景:
面试官:请描述一下三次握手的过程吧
求职者:第一次客户端给服务端发送一个报文,第二次是服务器收到包之后,也给客户端应答一个报文,第三次是客户端再给服务器发送一个回复报文,TCP 三次握手成功。
面试官:还有吗?
求职者:说完了哈,这就是三次握手,很简单的 面试官:嗯,我没什么问的了,你还有什么问题吗?
这时求职者紧张的心终于平静了,因为面试官没有深入下去的意思,继续问下去可能也不懂,皆大欢喜!当然本次面试基本上也就 game over
了。
求职者回答的不正确么?正确,但是回答的过于简单,离面试官的期望的答案还有一定的距离,我们该怎么回答呢?
TCP 三次握手,其实就是建立一个 TCP 连接,客户端与服务器交互需要 3 个数据包。握手的主要作用就是为了确认双方的接收和发送能力是否正常,初始序列号,交换窗口大小以及 MSS 等信息。
- 第一次握手:客户端发送
SYN
报文,并进入SYN_SENT
状态,等待服务器的确认; - 第二次握手:服务器收到
SYN
报文,需要给客户端发送ACK
确认报文,同时服务器也要向客户端发送一个SYN
报文,所以也就是向客户端发送SYN + ACK
报文,此时服务器进入SYN_RCVD
状态; - 第三次握手:客户端收到
SYN + ACK
报文,向服务器发送确认包,客户端进入ESTABLISHED
状态。待服务器收到客户端发送的ACK
包也会进入ESTABLISHED
状态,完成三次握手。
我们回答时,可以先简单概述 TCP 过程,然后三次握手具体描述时,需要说明状态的基本转换。
TCP 三次握手,其实就是 TCP 应用在发送数据前,通过 TCP 协议跟通信对方协商好连接信息,建立起TCP的连接关系。
我们需要知道,TCP 连接并非是在通信设备两端之间建立信号隧道,而本质上就是双方各自维护所需的状态状态,以达到 TCP 连接的效果。所以 TCP 状态机是 TCP 的核心内容,学习 TCP 一定要搞懂这些状态机之间的转换。
二次握手可以吗
问:为什么 TCP 采用三次握手,二次握手可以吗?
我们可以从几个方面来解释:
(一)确认双方的收发能力
TCP 建立连接之前,需要确认客户端与服务器双方的收包和发包的能力。
1. 第一次握手:客户端发送网络包,服务端收到了。这样服务端就能得出结论:客户端的发送能力、服务端的接收能力是正常的。
2. 第二次握手:服务端发包,客户端收到了。这样客户端就能得出结论:服务端的接收、发送能力,客户端的接收、发送能力是正常的。不过此时服务器并不能确认客户端的接收能力是否正常。
3. 第三次握手:客户端发包,服务端收到了。这样服务端就能得出结论:客户端的接收、发送能力正常,服务器自己的发送、接收能力也正常。
所以,只有三次握手才能确认双方的接收与发送能力是否正常。
(二)序列号可靠同步
如果是两次握手,服务端无法确定客户端是否已经接收到了自己发送的初始序列号,如果第二次握手报文丢失,那么客户端就无法知道服务端的初始序列号,那 TCP 的可靠性就无从谈起。
(三)阻止重复历史连接的初始化
客户端由于某种原因发送了两个不同序号的 SYN
包,我们知道网络环境是复杂的,旧的数据包有可能先到达服务器。如果是两次握手,服务器收到旧的 SYN
就会立刻建立连接,那么会造成网络异常。
如果是三次握手,服务器需要回复 SYN+ACK
包,客户端会对比应答的序号,如果发现是旧的报文,就会给服务器发 RST
报文,直到正常的 SYN
到达服务器后才正常建立连接。
所以三次握手才有足够的上下文信息来判断当前连接是否是历史连接。
(四)安全问题
我们知道 TCP 新建连接时,内核会为连接分配一系列的内存资源,如果采用两次握手,就建立连接,那会放大 DDOS 攻击的。
TCP 作为一种可靠传输控制协议,其核心思想:既要保证数据可靠传输,又要提高传输的效率,而三次握手恰好可以满足以上两方面的需求!
初始序列号(ISN)
问:ISN 代表什么?意义何在?ISN 是固定不变的吗?ISN为何要动态随机?
ISN 是什么?
答:ISN
全称是 Initial Sequence Number
,是 TCP 发送方的字节数据编号的原点,告诉对方我要开始发送数据的初始化序列号
ISN 是固定不变的吗?
答:ISN 如果是固定的,攻击者很容易猜出后续的确认序号,为了安全起见,避免被第三方猜到从而发送伪造的 RST
报文,因此 ISN 是动态生成的
半连接队列
什么是半连接队列?
答:服务器第一次收到客户端的 SYN
之后,就会处于 SYN_RCVD
状态,此时双方还没有完全建立连接。服务器会把这种状态下请求连接放在一个队列里,我们把这种队列称之为半连接队列。
当然还有一个全连接队列,就是已经完成三次握手,建立起连接的就会放在全连接队列中。如果队列满了就有可能会出现丢包现象。
三次握手可以携带数据吗?
问:三次握手过程中,可以携带数据吗?
答:第一次、第二次握手不可以携带数据,而第三次握手是可以携带数据的。
我们可以思考一个问题,假如第一次握手可以携带数据的话,如果有人要恶意攻击服务器,那他每次都在第一次握手中的 SYN 报文中放入大量的数据,疯狂着重复发 SYN 报文,这会让服务器花费大量的内存空间来缓存这些报文,这样服务器就更容易被攻击了。
对于第三次握手,此时客户端已经处于连接状态,他已经知道服务器的接收、发送能力是正常的了,所以可以携带数据是情理之中。
TCP 四次挥手
当我们的应用程序不需要数据通信了,就会发起断开 TCP 连接。建立一个连接需要三次握手,而终止一个连接需要经过四次挥手。
- 第一次挥手。客户端发起
FIN
包(FIN = 1),客户端进入FIN_WAIT_1
状态。TCP 规定,即使FIN
包不携带数据,也要消耗一个序号。 - 第二次挥手。服务器端收到
FIN
包,发出确认包ACK
(ack = u + 1),并带上自己的序号 seq=v,服务器端进入了CLOSE_WAIT
状态。这个时候客户端已经没有数据要发送了,不过服务器端有数据发送的话,客户端依然需要接收。客户端接收到服务器端发送的ACK
后,进入了FIN_WAIT_2
状态。 - 第三次挥手。服务器端数据发送完毕后,向客户端发送
FIN
包(seq=w ack=u+1),半连接状态下服务器可能又发送了一些数据,假设发送 seq 为 w。服务器此时进入了LAST_ACK
状态。 - 第四次挥手。客户端收到服务器的
FIN
包后,发出确认包(ACK=1,ack=w+1),此时客户端就进入了TIME_WAIT
状态。注意此时 TCP 连接还没有释放,必须经过2*MSL
后,才进入CLOSED
状态。而服务器端收到客户端的确认包ACK
后就进入了CLOSED
状态,可以看出服务器端结束 TCP 连接的时间要比客户端早一些。
问:为什么建立连接握手三次,关闭连接时需要是四次呢?
答:其实在 TCP 握手的时候,接收端发送 SYN+ACK
的包是将一个 ACK
和一个 SYN
合并到一个包中,所以减少了一次包的发送,三次完成握手。
对于四次挥手,因为 TCP 是全双工通信,在主动关闭方发送 FIN 包后,接收端可能还要发送数据,不能立即关闭服务器端到客户端的数据通道,所以也就不能将服务器端的 FIN
包与对客户端的 ACK
包合并发送,只能先确认 ACK
,然后服务器待无需发送数据时再发送 FIN
包,所以四次挥手时必须是四次数据包的交互。
问:为什么TIME_WAIT 状态需要经过 2MSL 才能返回到 CLOSE 状态?
答:MSL
指的是报文在网络中最大生存时间。在客户端发送对服务器端的 FIN
的确认包 ACK
后,这个 ACK
包是有可能不可达的,服务器端如果收不到 ACK
的话需要重新发送 FIN
包。
所以客户端发送 ACK
后需要留出 2MSL
时间(ACK 到达服务器 + 服务器发送 FIN 重传包,一来一回)等待确认服务器端确实收到了 ACK 包。
也就是说客户端如果等待 2MSL
时间也没有收到服务器端的重传包 FIN
,说明可以确认服务器已经收到客户端发送的 ACK
。
还有第 2 个理由,避免新旧连接混淆。
在客户端发送完最后一个 ACK
报文段后,在经过 2MSL
时间,就可以使本连接持续的时间内所产生的所有报文都从网络中消失,使下一个新的连接中不会出现这种旧的连接请求报文。
你要知道,有些自作主张的路由器会缓存 IP 数据包,如果连接重用了,那么这些延迟收到的包就有可能会跟新连接混在一起。
总结
本篇文章以 TCP 三次握手和四次挥手这个经典问题为主题,初步窥探了 TCP 协议的入门知识点,后边会有一系列的文章,来分享 TCP 协议相关的方方面面,如果感兴趣请关注我,我们一起把 TCP 协议彻底搞透彻了。
最后,帮大家总结一下 TCP 的核心知识点。我们知道 TCP 协议是可靠的,它主要是通过解决如下几个问题来保证可靠性的:
- 乱序
- 丢包
- 流控
- 拥塞控制
TCP 是一个巨复杂的协议,基本上 TCP 涉及的所有内容都是围绕解决这几个问题的,请务必时刻认真牢记。
推荐阅读: