TCP三次握手与四次挥手

基本认识

TCP头格式

TCP 头格式

序列号:在建立连接时,由计算机生成的随机数作为初始值,通过SYN包传给接收端主机,每发送一次数据,就累加一次该「数据字节数」的大小,来解决网络包乱序的问题

确认应答号:指下次期望收到的数据序列号,发送端收到这个确认应答后可以认为在这个序号之前的数据都已经被正常接收,用来解决丢包问题

控制位

  • ACK:为1时,「确认应答」的字段变为有效,TCP规定除了最初建立连接时的SYN 包之外该位必须为1。
  • RST:为1时,表示TCP连接中出现异常,必须强制断开连接。
  • SYN:为1时,表示希望建立连接,并在其「序列号」的字段就行序列号初始值的设定。
  • FIN :为1时,表示之后不会再有数据发送,希望断开连接。当通信结束希望断开连接时,通信双方的主机之间相互交换FIN位位1的TCP段。

TCP层的存在,是因为IP层是「不可靠」的,他不能保证网络包的交付,网络包的交付,也不能保证网络包中数据的完整性性。

OSI 参考模型与 TCP/IP 的关系

为了保证网络数据包的可靠性,上层就需要TCP协议。

什么是TCP

TCP是面向连接的、可靠的、基于字节流的传输层通信协议。

  • 面向连接:一定是「一对一」才能连接,不像UDP协议可以一个主机同时向多个主机发送消息,也就是一对多是无法做到的。
  • 可靠的:无论网络链路中出现了怎样的变化,TCP都可以保证一个报文一定可以到达接收端。
  • 字节流:用户消息通过TCP传输协议时,消息可能会被操作系统分组成多个TCP报文,如果接收方的程序不知道「消息的边界」,是无法读出一个有效的用户消息。并且TCP报文是有序的,当前一个TCP报文没有收到的时候,即使它先收到后面的TCP报文,那么也不能扔给应用层去处理,而碰到重复的TCP报文会自动丢弃。

什么是TCP连接

简单来说,用来保证可靠性和流量控制维护的某些信息,这些信息的组合,包括Socket、序列号和窗口大小称为连接。

所以建立一个TCP连接需要客户端与服务器达成上述三个信息的共识。

  • Socket:由IP地址和端口号组成
  • 序列号:用来解决乱序问题
  • 窗口大小:用来做流量控制

如何唯一确定一个 TCP 连接呢?

通过TCP的四元组可以唯一的确定一个连接,四元组包括:源地址、源端口、目标地址和目标端口。

源地址和目标地址的字段(32位)是在IP头中,作用是通过IP协议发送报文给对方的主机。

源端口和目标端口的字段是在TCP头中,作用是告诉TCP协议应该把报文发送给哪一个进程。

UDP和TCP的区别

UDP不提供复杂的控制机制,利用IP提供面向「无连接」的通信服务。

UDP协议非常的简单,头部只有8个字节(64位),UDP的头部格式如下:

UDP 头部格式
  • 目标和源端口:主要告诉UDP协议应该把报文发送给那个进程。
  • 包长度:该字段保存了UDP头部的长度和数据的长度之和。
  • 校验和:校验和是为了提供可靠的UDP首部和数据而设计的,防止收到在网络传输受损的UDP包。

区别

  1. 连接

    • TCP是面向连接的传输层协议,传输数据前先要建立连接。
    • UDP是不需要连接的,可以直接开始传输数据。
  2. 服务对象

    • TCP是一对一的两点服务,即一条连接只有两个端点。
    • UDP支持一对一、一对多的交互通信。
  3. 可靠性

    • TCP是可靠交付数据的,数据库无差错、不丢失、不重复、按序到达。
    • UDP是尽最大努力交付,不保证数据的可靠的交付。
  4. 拥塞控制、流量控制

    • TCP有拥塞控制和流量控制机制,保证数据传输的安全性。
    • UDP则不同,即使网络非常拥堵了,也不会影响UDP的发送速率。
  5. 首部开销

    • TCP首部长度较长,会有一定的开销,首部在没有使用选项字段时是20个字节,如果使用了选择字段则会变得更长。-

    • UDP首部只有8个字节,并且是固定不变的,开销很小。

  6. 传输方式

    • TCP是流式传输,没有边界,但保证顺序和可靠性。

    • UDP是一个包一个包的发送,是有边界的,但可能会存在丢包和乱序。

  7. 分片不同

    • TCP的数据大小如果大于MSS的大小,会在传输层进行分片,目标主机收到之后,也同样会在传输层组装TCP数据包,如果中途叠了一个分片,只需要传输丢失的这一个分片就行。
    • UDP的数据大小如果大于MTU的大小,就会在IP层进行分片,目标主机接收到之后,在IP层组装完数据,接着再传给传输层。

TCP和UDP的应用场景

由于TCP是面向连接的,可以保证,数据额交付的可靠性,因此常用于:

  • FTP文件传输
  • HTTP/HTTPS

而UDP面向无连接,他可以随时发送数据,再加上UDP本身的处理既简单又高效,因此常用于:

  • 包总量较少的通信,如DNSSMTP
  • 视频、音频等多媒体通信
  • 广播通信

为什么UDP头部没有首部长度字段,而TCP头部有首部长度字段?

原因是TCP首部中的选项字段长度是可变的,而UDP首部是固定长度的,不会发生变化,所以使用UDP通信的双方都按照约定好的UDP首部长度来进行通信,不需要一个专门的字段来记录首部长度。

TCP连接建立

TCP三次握手的过程

TCP是面向连接的协议,所以使用TCP前必须先建立连接,而建立连接是通过三次握手来进行的,三次握手的过程如下:

TCP 三次握手

开始服务器和客户端都处于CLOSE状态,然后先是服务端主动开始监听某个端口,处于LISTEN状态

第一个报文—— SYN 报文

之后,客户端向服务器发送第一个报文,客户端随机初始化一个序列号(client_isn),将此序号至于TCP首部的「序号」字段,同时把SYN标志置为1,表示SYN报文。接着把第一个SYN报文发送给服务端,表示向服务端发起连接,该报文不包含应用层数据,之后客户端处于SYN-SNET状态。

第二个报文 —— SYN + ACK 报文

服务器收到客户端的SYN报文后,首先服务端也随机初始化自己的序号(server_isn),将次序号填入TCP首部的「序号」字段中,其次把TCP首部的「确认应答号」字段填入client-isn + 1,接着把SYNACK标志置为1.最后吧该报文发送给客户端,该报文也不包含应用层数据,之后服务端处于SYN-RCVD状态。

第三个报文 —— ACK 报文

客户端收到服务端报文后,还要向服务端回应最后一个应答报文,首先改应答报文TCP首部ACK标志位置为1,其次「确认应答号」字段填入server-isn + 1,最后吧报文发送给服务端,这次报文可以携带客户导服务器的数据,只有客户端处于ESTABLISHED状态。

服务器收到客户端的应答报文后,也进入ESTABLISHED状态。

从上面的过程可以发现,第三次握手是可以携带数据的,前两次握手不能携带数据

一旦完成了三次握手,双方都处于ESTABLISHED状态,此时连接就已经建立完成,客户端和服务端就可以相互发送数据了。

为什么是三次握手而不是两次、四次?

前面我们知道了TCP连接是:用于保证可靠性和流量控制维护的某些状态信息,这些信息的组合包括Socket、序列号和窗口大小

所以问题变成了为什么三次握手才可以初始化Socket、序列号和窗口大小并建立TCP连接。

从三个方面来说分别是:

  • 三次握手才可以阻止重复历史连接的初始化(主要原因)
  • 三次握手才可以同步双方的初始序列
  • 三次握手才可以避免资源浪费

避免历史连接

RFC 973中支出的TCP连接使用三次握手的主要原因:

The principle reason for the three-way handshake is to prevent old connection initiations from casing confusion.

简单来说,三次握手的首要原因是为了防止旧的重复连接初始化造成混乱。

考虑一个场景,,客户端先发送了 SYN(seq = 90) 报文,然后客户端宕机了,而且这个 SYN 报文还被网络阻塞了,服务端并没有收到,接着客户端重启后,又重新向服务端建立连接,发送了 SYN(seq = 100) 报文(注意不是重传 SYN,重传的 SYN 的序列号是一样的),下图为三次握手的序列图:

三次握手避免历史连接

而如果是两次握手就建立连接,那就回变成下面这样,被动接收方在第一次收到报文之后就会进入ESTABLISHED状态,并没有阻止掉历史连接,导致「被动发起方」建立了一个历史连接,又白白发送了数据,浪费了「被动发起方的资源」。

两次握手无法阻止历史连接

所以要解决这种现象,最好就是在「被动发起方」发送数据之前,就要组织掉历史连接,这样就不会造成资源的浪费,而三次握手就可以很好的解决这一问题。

同步双方初始序列号

TCP通信的双方,都必须维护一个「序列号」,序列号是可靠传输的一个关键因素,他的作用包括:

  • 接收方可以除去重复的数据;
  • 接收方可以根据数据包的序列号按序接收;
  • 可以标识发送出去的数据包中,那些是已经被对方接受的(通过ACK报文中的序列号知道);
四次握手与三次握手

四次握手也可以可靠的同步双方的初始化序号,但由于第二部和第三步就可以优化成一步,所以就成了「三次握手」。

而两次握手只保证了一方的初始序列号能被对方成功接收,没办法保证双方的初始序列号都能被确认接收。

避免资源浪费

如果只有「两次握手」当客户端的SYN请求连接在网络中阻塞,客户端没有收到ACK报文,就会重新发送SYN,由于没有第三次握手,服务器不清楚客户端是否收到了自己发送的建立连接的ACK确认信号,所以没接收到一个SYN就只能先主动建立一个连接,这在网络阻塞的时候会造成不必要的资源浪费。

两次握手会造成资源浪费

如果客户端的SYN阻塞了,重复发送多次SYN报文,那么服务器在收到请求后就会建立多个冗余的无效链接,造成资源的浪费。

小结

TCP建立连接时,通过三次握手能防止历史连接的建立,可以减少双方不必要的资源开销,可以帮助双方同步初始化序列号。序列号可以保证数据包不重复、不丢弃和按序传输。

不使用两次握手和四次握手的原因:

  • 两次握手:无法防止历史连接的建立,会造成双方资源的浪费,也无法可靠的同步双方序列号;
  • 四次握手:三次握手理论上已经最少可靠的建立连接,所以不需要使用更多的通信次数。

为什么每次建立TCP连接时,初始化序列都要求不一样

主要有两个方面的原因:

  • 为了防止历史报文被下一个相同四元组的连接接收;
  • 为了安全性,防止黑客伪造相同序列号的TCP报文被对方接收。

TCP断开连接

TCP四次挥手的过程

TCP断开连接的方式是通过四次挥手的方式,双方都可以主动断开连接,断开连接后朱继忠的资源将被释放,四次挥手的过程如下图:

客户端主动关闭连接 —— TCP 四次挥手

  • 客户端打算关闭连接,此时会发送一个TCP首部FIN标志位被置为1的报文,即FIN报文,之后客户端进入FIN_WAIT_1状态。
  • 服务端收到该报文后,就像客户端发送ACK应答报文,接着服务端进入CLOSE_WAIT状态。
  • 客户端收到服务端的ACK应答报文后,进入FIN_WAIT_2状态。
  • 等待服务端处理完数据后,也向客户端发送FIN报文,之后服务端进入LAST_ACK状态 。
  • 服务器收到 ACK应答报文之后,就进入CLOSE状态,至此服务端已完成连接的关闭。
  • 客户端经过2MSL后,自动进入CLOSE状态,客户端也完成了连接的关闭。

以上过程为主动关闭连接的过程,主动关闭才有TIME_WAIT状态。

为什么需要四次挥手

  • 关闭连接时,客户端向服务器发送FIN时,仅仅表示客户端不会再发送数据了,但是还可以接收数据。
  • 服务器收到客户端的FIN报文时,先回一个ACK应答报文,而服务端还可能有数据需要处理和发送,等服务端不要在发送数据时,才发送FIN报文给客户端,来表示同意现在关闭连接。

从上面过程可知,服务端通常需要等待完成数据的发送和处理,所以服务端的FINACK一般是分开发送的,因此需要四次挥手。

参考

小林coding-三次握手与四次挥手