TCP三次握手

TCP三次握手和四次挥手是面试题的热门考点,它们分别对应TCP的连接和释放过程,今天我们先来认识一下TCP三次握手过程,以及是否可以使用“两报文握手”建立连接?。

1、TCP是什么?

TCP是面向连接的协议,它基于运输连接来传送TCP报文段,TCP运输连接的建立和释放,是每一次面向连接的通信中必不可少的过程。

TCP运输连接有以下三个阶段:

  • 建立TCP连接,也就是通过三报文握手来建立TCP连接。
  • 数据传送,也就是基于已建立的TCP连接进行可靠的数据传输。
  • 释放连接,也就是在数据传输结束后,还要通过四报文挥手来释放TCP连接。

TCP的运输连接管理就是使运输连接的建立和释放都能正常的进行。

图解TCP三次握手和TCP四次挥手TCP四次挥手_服务器

2、TCP首部格式

 

图解TCP三次握手和TCP四次挥手TCP四次挥手_服务器_02

源端口: 占16比特,写入源端口号,用来 标识发送该TCP报文段的应用进程。

目的端口: 占16比特,写入目的端口号,用来标识接收该TCP报文段的应用进程。

序号: 占32比特,取值范围[0,2^32-1],序号增加到最后一个后,下一个序号就又回到0。指出本TCP报文段数据载荷的第一个字节的序号。

确认号: 占32比特,取值范围[0,2^32-1],确认号增加到最后一个后,下一个确认号就又回到0。指出期望收到对方下一个TCP报文段的数据载荷的第一个字节的序号,同时也是对之前收到的所有数据的确认。若确认号=n,则表明到序号n-1为止的所有数据都已正确接收,期望接收序号为n的数据。

确认标志位ACK: 取值为1时确认号字段才有效;取值为0时确认号字段无效。TCP规定,在连接建立后所有传送的TCP报文段都必须把ACK置1。

数据偏移: 占4比特,并以4字节为单位。用来指出TCP报文段的数据载荷部分的起始处距离TCP报文段的起始处有多远。这个字段实际上是指出了TCP报文段的首部长度。

窗口: 占16比特,以字节为单位。指出发送本报文段的一方的接收窗。

同步标志位SYN: 在TCP连接建立时用来同步序号。终止标志位FIN: 用来释放TCP连接。复位标志位RST: 用来复位TCP连接。

推送标志位PSH: 接收方的TCP收到该标志位为1的报文段会尽快上交应用进程,而不必等到接收缓存都填满后再向上交付。

校验和: 占16比特,检查范围包括TCP报文段的首部和数据载荷两部分。在计算校验和时,要在TCP报文段的前面加上12字节的伪首部。

紧急指针: 占16比特,以字节为单位,用来指明紧急数据的长度。

填充: 由于选项的长度可变,因此使用填充来 确保报文段首部能被4整除,(因为数据偏移字段,也就是首部长度字段,是以4字节为单位的)。

3、TCP的连接建立

TCP 建立连接的过程叫做握手,握手需要在客户和服务器之间交换三个TCP 报文段,称之为三报文握手,采用三报文握手主要是为了防止已失效的连接请求报文段突然又传送到了,因而产生错误。

TCP的连接建立要解决以下三个问题:

  • 1、使TCP双方能够确知对方的存在 。
  • 2、使TCP双方能够协商一些参数( 最大窗口值是否使用窗口扩大选项和时间戳选项,以及服务质量等)。
  • 3、使TCP双方能够对运输实体资源(例如缓存大小连接表中的项目等)进行分配。

4、三次握手图文详解

这是两台要基于TCP进行通信的主机:

图解TCP三次握手和TCP四次挥手TCP四次挥手_服务器_03

  • 主动发起TCP连接建立称为TCP客户(client)。
  • 被动等待TCP连接建立的应用进程称为TCP服务器(server)。

我们可以将TCP建立连接的过程比喻为”握手“,“握手”需要在TCP客户端和服务器之间交换三个TCP报文段。

最初两端的TCP进程都处于关闭状态。

图解TCP三次握手和TCP四次挥手TCP四次挥手_客户端_04


一开始,TCP服务器进程首先创建传输控制块,用来存储TCP连接中的一些重要信息。 例如TCP连接表、指向发送和接收缓存的指针、指向重传队列的指针,当前的发送和接收序号等。之后就准备接受TCP客户进程的连接请求, 此时TCP服务器进程就要进入监听状态等待TCP客户进程的连接请求。 

图解TCP三次握手和TCP四次挥手TCP四次挥手_客户端_05

 TCP客户进程也是首先创建传输控制块,然后再打算建立。 TCP服务器进程是被动等待来自TCP客户端进程的连接请求,因此称为被动打开连接。

图解TCP三次握手和TCP四次挥手TCP四次挥手_tcp/ip_06


TCP连接时向TCP服务器进程发送TCP连接请求报文段,并进入同步已发送状态。

  • TCP 连接请求报文段首部中的同步位SYN被设置为1,,表明这是一个tcp连接请求报文段。
  • 序号字段seq被设置了一个初始值x作为TCP客户进程所选择的初始序号。

由于TCP连接建立是由TCP客户进程主动发起的,因此称为主动打开连接。 请注意TCP规定SYN被设置为1的报文段不能携带数据但要消耗掉一个序号。

图解TCP三次握手和TCP四次挥手TCP四次挥手_客户端_07


TCP服务器进程收到TCP连接请求报文段后,如果同意建立连接,则向TCP客户进程发送TCP连接请求确认报文段,并进入同步已接收状态。

  • 该报文段首部中的同步位SYN和确认位ACK 都设置为1,表明这是一个TCP连接请求。
  • 序号字段seq被设置了一个初始值y,作为TCP服务器进程所选择的初始序号。
  • 确认号字段ack的值被设置成了x+1,这是对TCP客户进程所选择的初始序号seq的确认。

请注意这个报文段也不能携带数据,因为它是SYN被设置为一的报文段但同样要消耗掉一个序号。

图解TCP三次握手和TCP四次挥手TCP四次挥手_服务器_08


TCP客户进程收到TCP连接请求确认报文段后,还要向TCP服务器进程发送一个普通的TCP 确认报文段并进入连接已建立状态。

  • 该报文段首部中的确认位ACK被设置为1,表明这是一个普通的TCP确认报文段 。
  • 序号字段seq 被设置为x+1,这是因为TCP客户进程发送的第一个TCP报文段的序号为x,并且不携带数据,因此第二个报文段的序号为x +1。
  • 确认号字段ack被设置为y + 1,这是对TCP服务器进程所选择的初始序号的确认。

请注意TCP规定,普通的TCP确认报文段可以携带数据。但如果不携带数据则不消耗序号,在这种情况下所发送的下一个数据报文段的序号仍是x + 1。

图解TCP三次握手和TCP四次挥手TCP四次挥手_数据_09


TCP服务器进程收到该确认报文段后也进入连接已建立状态,现在TCP双方都进入了连接已建立状态,他们可以基于已建立好的TCP连接进行可靠的数据传输了。

5、三次握手文字总结

三次握手是 TCP 连接的建立过程。在握手之前,主动打开连接的客户端结束 CLOSE 阶段,被动打开的服务器也结束 CLOSE 阶段,并进入 LISTEN 阶段。随后进入三次握手阶段:

① 首先客户端向服务器发送一个 SYN 包,并等待服务器确认,其中:

  • 标志位为 SYN,表示请求建立连接;
  • 序号为 Seq = x(x 一般取随机数);
  • 随后客户端进入 SYN-SENT 阶段。

② 服务器接收到客户端发来的 SYN 包后,对该包进行确认后结束 LISTEN 阶段,并返回一段 TCP 报文,其中:

  • 标志位为 SYN 和 ACK,表示确认客户端的报文 Seq 序号有效,服务器能正常接收客户端发送的数据,并同意创建新连接;
  • 序号为 Seq = y;
  • 确认号为 Ack = x + 1,表示收到客户端的序号 Seq 并将其值加 1 作为自己确认号 Ack 的值,随后服务器端进入 SYN-RECV 阶段。

③ 客户端接收到发送的 SYN + ACK 包后,明确了从客户端到服务器的数据传输是正常的,从而结束 SYN-SENT 阶段。并返回最后一段报文。其中:

  • 标志位为 ACK,表示确认收到服务器端同意连接的信号;
  • 序号为 Seq = x + 1,表示收到服务器端的确认号 Ack,并将其值作为自己的序号值;
  • 确认号为 Ack= y + 1,表示收到服务器端序号 seq,并将其值加 1 作为自己的确认号 Ack 的值。
  • 随后客户端进入 ESTABLISHED。

当服务器端收到来自客户端确认收到服务器数据的报文后,得知从服务器到客户端的数据传输是正常的,从而结束 SYN-RECV 阶段,进入 ESTABLISHED 阶段,从而完成三次握手。

5、是否可以使用“两报文握手”建立连接?

为什么TCP客户进程最后还要发送一个普通的TCP确认报文段?

考虑这样一种情况,TCP客户进程发出一个TCP连接请求报文段,但该报文段在某些网络节点长时间滞留了,这必然会造成该报文段的超时重传。

假设重传的报文段被TCP服务器进程正常接收,TCP服务器进程给TCP客户进程发送一个TCP连接请求确认报文段,并进入连接已建立状态。

图解TCP三次握手和TCP四次挥手TCP四次挥手_数据_10


请注意,由于我们改为两报文握手,因此TCP服务器进程发送完TCP连接请求确认报文段后,进入的是连接已建立状态,而不像三报文握手那样进入同步已接收状态,TCP服务器进程并等待TCP客户进程发来针对TCP连接请求确认报文段的普通确认报文段。TCP客户进程收到TCP连接请求确认报文段后进入TCP连接已建立状态,但不会给TCP服务器进程发送针对该报文段的普通确认报文段。

现在,TCP双方都处于连接已建立状态,他们可以相互传输数据,之后可以通过四报文挥手来释放连接,TCP双方都进入了关闭状态。

图解TCP三次握手和TCP四次挥手TCP四次挥手_数据_11


一段时间后,之前滞留在网络中的那个失效的TCP连接请求报文段到达了TCP服务器进程,TCP 服务器进程会误认为这是TCP客户进程又发起了一个新的TCP连接请求,于是给TCP客户进程发送TCP连接请求确认报文段并进入连接已建立状态。

该报文段到达TCP客户进程,由于TCP客户进程并没有发起新的TCP连接请求,并且处于关闭状态,因此不会理会该报文段。

但TCP服务器进程已进入了连接已建立状态,他认为新的TCP连接已建立好了,并一直等待TCP客户进程发来数据。这将白白浪费TCP服务器进程所在主机的很多资源。

图解TCP三次握手和TCP四次挥手TCP四次挥手_数据_12


综上所述,采用三报文握手,而不是两报文握手来建立TCP连接,是为了防止已失效的连接请求报文段突然又传送到了TCP服务器进程因而导致错误。

  6、两次握手文字总结

三次握手的主要目的是确认自己和对方的发送和接收都是正常的,从而保证了双方能够进行可靠通信。若采用两次握手,当第二次握手后就建立连接的话,此时客户端知道服务器能够正常接收到自己发送的数据,而服务器并不知道客户端是否能够收到自己发送的数据。

我们知道网络往往是非理想状态的(存在丢包和延迟),当客户端发起创建连接的请求时,如果服务器直接创建了这个连接并返回包含 SYN、ACK 和 Seq 等内容的数据包给客户端,这个数据包因为网络传输的原因丢失了,丢失之后客户端就一直接收不到返回的数据包。由于客户端可能设置了一个超时时间,一段时间后就关闭了连接建立的请求,再重新发起新的请求,而服务器端是不知道的,如果没有第三次握手告诉服务器客户端能否收到服务器传输的数据的话,服务器端的端口就会一直开着,等到客户端因超时重新发出请求时,服务器就会重新开启一个端口连接。长此以往, 这样的端口越来越多,就会造成服务器开销的浪费。


TCP四次挥手

上面的文章中,我们详细讲解了三次握手的过程,今天我们继续深入学习TCP四次挥手,以及相关面试问题解答。

1、TCP的连接释放

  • TCP 连接释放过程比较复杂。
  • 数据传输结束后,通信的双方都可释放连接。
  • TCP 连接释放过程是 四报文挥手。

2、TCP通过“四报文挥手”来释放连接

  • TCP 连接的建立 采用客户服务器方式。
  • 主动发起连接建立的应用进程叫做 TCP客户 (client)。
  • 被动等待连接建立的应用进程叫做 TCP服务器 (server)。
  • 任何一方都可以在数据传送结束后发出连接释放的通知。

3、四次挥手图文详解

数据传输结束后,TCP通信双方者都可以释放连接, 现在TCP客户进程和TCP服务器进程都处于连接已建立(ESTABLISHED)状态

图解TCP三次握手和TCP四次挥手TCP四次挥手_服务器_13


假设使用TCP客户进程的应用进程通知其主动关闭TCP连接。TCP客户进程会发送TCP连接释放报文段,并进入终止等待1(FIN-WAIT-1)状态。

 TCP连接释放报文段首部中:

  • 终止位FIN和确认为ACK的值都被设置为1,表明这是一个TCP连接释放报文段,同时也对之前收到的报文段进行确认。
  • 序号seq字段的值设置为u,它等于TCP客户进程之前已传送过的数据的最后一个字节的序号加1。
  • 确认号ack字段的值设置为v,它等于TCP客户进程之前已收到的、数据的最后一个字节的序号加1。

请注意: TCP规定终止位FIN等于1的报文段即使不携带数据,也要消耗掉一个序号。

图解TCP三次握手和TCP四次挥手TCP四次挥手_tcp/ip_14


TCP服务器进程收到TCP连接释放报文段后,会发送一个普通的TCP确认报文段并进入关闭等待(CLOSE-WAIT)状态。

TCP确认释放报文段首部中:

  • 确认位ACK的值被设置为1,表明这是一个普通的TCP确认报文段。
  • 序号seq字段的值设置为v,它等于TCP服务器进程之前已传送过的数据的最后一个字节的序号加1,这也与之前收到的TCP连接释放报文段中的确认号匹配。
  • 确认号ack字段的值设置为u+1,这是对TCP连接释放报文段的确认。

图解TCP三次握手和TCP四次挥手TCP四次挥手_tcp/ip_15

 TCP服务器进程应该通知高层应用进程,TCP客户进程要断开与自己的TCP连接。此时,从TCP客户进程到TCP服务器进程这个方向的连接就释放了。这时的TCP连接属于半关闭状态,也就是TCP客户进程已经没有数据要发送了。但如果TCP服务器进程还有数据要发送,TCP客户进程仍要接收,也就是说从TCP服务器进程到TCP客户进程这个方向的连接并未关闭,这个状态可能要持续一段时间

图解TCP三次握手和TCP四次挥手TCP四次挥手_客户端_16


TCP客户进程收到TCP确认报文段后就进入终止等待2(FIN-WAIT-2)状态,等待TCP服务器进程发出的TCP连接释放报文段。 若使用TCP服务器进程的应用进程已经没有数据要发送了,应用进程就通知其TCP服务器进程释放连接。由于TCP连接释放是由TCP客户进程主动发起的,因此TCP服务器进程对TCP连接的释放称为被动关闭连接。 

图解TCP三次握手和TCP四次挥手TCP四次挥手_数据_17

TCP服务器进程发送TCP连接释放报文段并进入最后确认(LAST-ACK)状态。

TCP连接释放报文段首部中:

  • 终止位FIN和确认位ACK的值都被设置为1,表明这是一个TCP连接释放报文段,同时也对之前收到的报文段进行确认。
  • 假定序号seq字段的值为w,这是因为在半关闭状态下,TCP服务器进程可能又发送了一些数据。
  • 确认号ack字段的值为u+1,这是对之前收到的TCP连接释放报文段的重复确认。

图解TCP三次握手和TCP四次挥手TCP四次挥手_数据_18

 TCP客户进程收到TCP连接释放报文段后,必须针对该报文段发送普通的TCP确认报文段,之后进入时间等待(TIME-WAIT)状态。

TCP确认报文段首部中:

  • 确认为ACK的值被设置为1,表明这是一个普通的TCP确认报文段。
  • 序号seq字段的值设置为u+1,这是因为TCP客户进程之前发送的TCP连接释放报文段虽然不携带数据,但要消耗掉一个序号。
  • 确认号ack字段的值设置为w+1,这是对所收到的TCP连接释放报文段的确认。

TCP服务器进程收到该报文段后就进入关闭状态,而TCP客户进程还要进过2MSL后才能进入关闭状态。 ( MSL(Maximum Segment Lifetime)意思是最长报文段寿命,RFC793建议为2分钟。)

图解TCP三次握手和TCP四次挥手TCP四次挥手_客户端_19

4、四次挥手文字总结

四次挥手即 TCP 连接的释放,这里假设客户端主动释放连接。在挥手之前主动释放连接的客户端结束ESTABLISHED 阶段,随后开始四次挥手: 

① 首先客户端向服务器发送一段 TCP 报文表明其想要释放 TCP 连接,其中:

  • 标记位为 FIN,表示请求释放连接;
  • 序号为 Seq = u;
  • 随后客户端进入 FIN-WAIT-1 阶段,即半关闭阶段,并且停止向服务端发送通信数据。

② 服务器接收到客户端请求断开连接的 FIN 报文后,结束 ESTABLISHED 阶段,进入 CLOSE-WAIT 阶段并返回一段 TCP 报文,其中:

  • 标记位为 ACK,表示接收到客户端释放连接的请求;
  • 序号为 Seq = v;
  • 确认号为 Ack = u + 1,表示是在收到客户端报文的基础上,将其序号值加 1 作为本段报文确认号Ack 的值;
  • 随后服务器开始准备释放服务器端到客户端方向上的连接。

客户端收到服务器发送过来的 TCP 报文后,确认服务器已经收到了客户端连接释放的请求,随后客户端结束 FIN-WAIT-1 阶段,进入 FIN-WAIT-2 阶段。

③ 服务器端在发出 ACK 确认报文后,服务器端会将遗留的待传数据传送给客户端,待传输完成后即经 过 CLOSE-WAIT 阶段,便做好了释放服务器端到客户端的连接准备,再次向客户端发出一段 TCP 报文, 其中:

  • 标记位为 FIN 和 ACK,表示已经准备好释放连接了;
  • 序号为 Seq = w;
  • 确认号 Ack = u + 1,表示是在收到客户端报文的基础上,将其序号 Seq 的值加 1 作为本段报文确认号 Ack 的值。

随后服务器端结束 CLOSE-WAIT 阶段,进入 LAST-ACK 阶段。并且停止向客户端发送数据。

④ 客户端收到从服务器发来的 TCP 报文,确认了服务器已经做好释放连接的准备,于是结束 FIN-WAIT-2 阶段,进入 TIME-WAIT 阶段,并向服务器发送一段报文,其中:

  • 标记位为 ACK,表示接收到服务器准备好释放连接的信号;
  • 序号为 Seq= u + 1,表示是在已收到服务器报文的基础上,将其确认号 Ack 值作为本段序号的值;
  • 确认号为 Ack= w + 1,表示是在收到了服务器报文的基础上,将其序号 Seq 的值作为本段报文确认号的值。

随后客户端开始在 TIME-WAIT 阶段等待 2 MSL。服务器端收到从客户端发出的 TCP 报文之后结束LAST-ACK 阶段,进入 CLOSED 阶段。由此正式确认关闭服务器端到客户端方向上的连接。客户端等待完 2 MSL 之后,结束 TIME-WAIT 阶段,进入 CLOSED 阶段,由此完成「四次挥手」。

5、相关面试问题

1、TCP客户进程在发送完最后一个确认报文后,为什么不直接进入关闭状态?而是要进入时间等待状态,2MSL后才进入关闭状态,这是否有必要呢?

来看这种状况,TCP服务器进程发送TCP连接释放报文段后进入最后确认状态。TCP客户进程收到该报文段后,发送普通的TCP确认报文段,并进入关闭状态而不是时间等待状态。然后,该TCP确认报文段丢失了,这必然会造成TCP服务器进程对之前所发送的TCP连接释放报文段的超时重传,并仍处于最后确认状态

图解TCP三次握手和TCP四次挥手TCP四次挥手_服务器_20


重传的TCP连接释放报文段到达TCP客户进程。由于TCP客户进程属于关闭状态,因此不理睬该报文段。这必然会造成TCP服务器进程反复重传TCP连接释放报文段, 并一直处于最后确认状态而无法进入关闭状态。 

图解TCP三次握手和TCP四次挥手TCP四次挥手_客户端_21

因此时间等待状态以及处于该状态2MSL时长,可以确保TCP服务器进程可以收到最后一个TCP确认报文段而进入关闭状态。

另外,TCP客户进程在发送完最后一个TCP确认报文段后,在经过2MSL时长,就可以使本次连接持续时间内所产生的所有报文段都从网络中消失,这样就可以使下一个新的TCP连接中,不会出现旧连接中的报文段。

2、为什么要四次挥手?

释放 TCP 连接时之所以需要四次挥手,是因为 FIN 释放连接报文和 ACK 确认接收报文是分别在两次握手中传输的。 当主动方在数据传送结束后发出连接释放的通知,由于被动方可能还有必要的数据要处理,所以会先返回 ACK 确认收到报文。当被动方也没有数据再发送的时候,则发出连接释放通知,对方确认后才完全关闭TCP连接。

3、CLOSE-WAIT 和TIME-WAIT 的状态和意义

在服务器收到客户端关闭连接的请求并告诉客户端自己已经成功收到了该请求之后,服务器进入了CLOSE-WAIT 状态,然而此时有可能服务端还有一些数据没有传输完成,因此不能立即关闭连接,而CLOSE-WAIT 状态就是为了保证服务器在关闭连接之前将待发送的数据发送完成。

TIME-WAIT 发生在第四次挥手,当客户端向服务端发送 ACK 确认报文后进入该状态,若取消该状态,即客户端在收到服务端的 FIN 报文后立即关闭连接,此时服务端相应的端口并没有关闭,若客户端在相同的端口立即建立新的连接,则有可能接收到上一次连接中残留的数据包,可能会导致不可预料的异常出现。

4、Time_wait过多有什么危害?

内存资源占用、端口资源占用(一个TCP连接至少消耗一个端口),每端口,无法建立新连接。
服务器资源受限:服务器监听一个端口,会把连接丢给线程处理,可以继续监听端口,但是线程池处理不了那么多连接。

5、Time_wait状态过多的优化?

什么时候产生: 首先调用close()发起主动关闭的一方,再发送最后一个ACK之后。

为何产生: 确保最后一个ACK到达,保证TCP全双工连接可靠释放;使旧的数据包过期消失。什么时候会产生大量Time_wait:当请求量比较大的时候,而且所有的请求都是短连接的时候。

如何避免: 多IP增加随机端口;内核参数调优(服务器设置SO_REUSEADDR套接字选项来通知内核,如果端口忙,但TCP连接位于TIME_WAIT状态时可以重用端口)﹔使用长连接(Connection: keep-alive) 、 Linux参数net.ipv4.tcp_tw_reuse和 tcp_timestamps开启,复用处于TIME_WAIT的socket为新的连接所用。