TCP协议之三次握手、四次挥手

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接: https://blog.csdn.net/weixin_43949535/article/details/102492688

2019年10月24日12:06:10 首先在今天这么一个程序员的节日里,我希望天下热爱编程,热爱C++的兄弟和前辈们 身体健康,少掉头发;家庭美满,多涨工资。

最近找了也不是很多家公司,发现自己还是年轻 没有什么经验。虽然看着十分光鲜,实则漏洞很多 很多细节把握的不是很好。还是老句老话:苟利国家生死以,岂因祸福避趋之。漫漫清华路,且行且珍惜。

开始今天的学习!
注:关于Linux和计算机网络部分,知识点不成体系。纷繁零杂,故而将这两块的学习中的重要内容进行单独总结。也即:对重要的知识点进行一一处理解决,且两部分内容都放置在《Linux的学习心得和知识总结》中。最近找工作,面试本系列的知识点比C++ 以及数据结构的内容还要多。但是总体而言,知识点多 不难 但是很重要。


注:学习计算机网络和Linux内容的知识需要看的书有:

  1. 谢希仁的计算机网络
  2. OS精髓与设计原理
  3. Linux高性能服务器编程
  4. Linux程序设计第四版

学习的话,这仅仅是我本人学习时看过的书籍,书不在多,而在于精读。以上书籍很容易就可以在网络上找到,当然也可以在下面留言 向我索取,都是可以的。

好的 开始学习!!!
本小节内容:TCP协议的三次握手和四次挥手

背景知识

我们都知道 在TCP/IP体系的中:依次是 应用层、传输层、网络层和数据链路层。(注:同花顺的面试官还问到我 这个问题)。IP层可以实现两台主机之间的相互通信,但是这不是很严谨。因为我们都知道 真正进行通信的对象实体是两台主机上的应用进程,也即是一个主机A的应用进程pa同主机B的一个应用进程pb的交互数据信息。 IP协议可以实现把数据报送达至目的主机上,但是主机上的具体的应用进程尚未得到来自发送方的数据信息。而运输层提供端到端的用户进程通信(是主机上的应用进程)

传输层的两大常用协议:TCP和UDP。虽然双方都使用同一套的网络层(IP协议),但是两者向应用层提供的服务却是相差巨大。

通常而言:TCP提供面向连接的 可靠的 字节流服务;而UDP则是无连接的 不可靠的 用户数据报服务。

对UDP而言,在传送数据之前不需要进行 事先建立连接,而且是尽最大努力的交付(不保证可靠交付)。在发送完数据之后,也没有连接可释放且UDP的首部开销小 没有拥塞控制。 所以适合多媒体的通信、适合多播和广播、支持一对一 一对多 多对一 多对多的交互通信、广泛应用于简单的请求:应答请求以及快速递交比精确递交更为重要的场合。其对应的应用层的协议主要有DNS、TFTP、DHCP、SNMP、NFS等

TCP提供的是面向连接的可靠的服务,在传送数据之前需要进行 事先建立连接,且在发送完数据之后,也得连接释放。此外TCP的首部开销相对较大,也不可避免的增加了许多的开销 和应答确认 流量控制 拥塞控制等。其对应的应用层的协议主要有HTTP 、FTP、SMTP等。


TCP报文段的首部格式

TCP虽然是面向字节流服务的,但是TCP传送的数据单元却是报文段。一个TCP报文段分为首部和数据两部分,其首部的各个字段的作用体现了TCP的全部功能。因此为了掌握TCP的工作原理,必须要先掌握TCP首部各字段的作用。
在这里插入图片描述
如上图所示:
在这里插入图片描述
针对这个TCP头部,共5行 4*5=20,这是固定的,其后面的4n字节是根据所需所增加的选项部分。故而TCP首部的最小长度就是20字节。

  1. 源端口号和目的端口号:各2字节 16位。我们都知道在网络中 IP地址可以标识Internet中的不同的主机,而端口号(具有本地意义)则可以标识主机中的不同的应用进程。因此IP地址+端口号就可以确定某一个主机中的某一个应用进程了,以达到TCP的分用功能。
  2. 序号:4字节。因此序号的范围【0,2的32次方-1】。因为在TCP连接传输中,字节流中每一个字节都有序号(按顺序编号,且进行mod 2的32次方)。因为TCP是面向字节流的,整个要传送的字节流的起始序号必须在连接建立时确定。此序号字段值 就是指的是 本报文段所发送的数据的第一个字节的序号
  3. 确认号:4字节。指的是:期望收到对方下一个报文段的第一个数据字节的序号同时若是确认号=N,则表明到序号N-1为止的所有数据都已经全部正确收到。 例如下图所示:在这里插入图片描述
  4. 数据偏移:我喜欢直接称它为 首部长度。4位 由于首部中 存在长度未知的选项字段,所以说该数据偏移字段是非常有必要的。4位表示的数据大小在0到15,所以它就可以规定 整个首部长度为4*15=60字节,而TCP首部的最小长度就是20字节,所以其选项部分长度最多也就是40字节。
  5. 保留 6位。但是显然根据上图所示:已经增加了一些新的控制位。但是这里我们不做深入。
  6. 接下来的6位 控制位。非常重要。
    6.1 紧急URG:置为1 说明此报文段中有紧急数据需要尽快传送。(于是发送方TCP就把紧急数据插入到本报文段数据的最前面,而在紧急数据之后 仍是普通数据。这个需要和首部中的紧急指针字段配合使用。
    6.2 确认ACK:置为1时 说明确认号字段才是有效的。(为0时 确认号无效)TCP规定:在连接建立之后所有传送的报文段都必须把ACK置为1.
    6.3 推送PSH:在两个应用进程进行交互式的通信时,有时在一端的应用进程希望在键入一个命令后立即就能够收到对方的响应。在这种情况下:发送方TCP把PSH=1,并立即创建一个报文段发送出去。而接收方TCP在收到PSH=1的报文段,就尽快(就“推送”向前)交付接收应用程序。(而不必再等待整个缓存都写满了后再向上交付)
    6.4 复位RST:置为1 说明TCP连接中出现严重差错(如主机崩溃),必须释放连接,然后再重新建立运输连接。此外还可以用来拒绝 一个非法的报文段或拒绝打开一个连接。
    6.5 同步SYN:主要是用于TCP连接建立过程中。SYN=1 ACK=0表示这是一个连接请求报文段。若是对方同意建立连接,则会在响应报文中使用SYN=1 ACK=1.因此SYN=1,表示这是一个连接请求或连接接收的报文。
    6.6 终止FIN:置为1 说明发送方已完成数据传输,请求释放运输连接。
  7. 窗口:16 位,接收窗口的大小(接收端希望接收的字节数)。或者说:发送本报文段的一方的接收窗口的大小。这个值告诉对方:从本报文段首部中的确认号算起,接收方目前允许对方发送的数据量(以字节为单位)。之所以要有这个限制:接收方的数据缓存空间是有效的。总之:窗口值作为接收方让发送方设置其发送窗口的依据。 如下图所示:在这里插入图片描述
  8. 校验和:16位,校验的范围包括:报文首部、数据。类似于UDP的校验和计算:也是先在TCP报文段的前面加上12字节的伪首部。(TCP的协议号6,UDP的协议号为17)。在接收方收到此报文段后:仍要加上这个伪首部来计算校验和。在这里插入图片描述
  9. 紧急指针:16位,如果URG = 1(该紧急指针才有意义),该字段指示紧急数据的字节数(相对于序号的偏移:紧急数据的末尾在报文段中的位置),紧急数据在数据部分的最前面(紧急数据结束后就是普通数据)。注:窗口为0时,也可以发送紧急数据。
  10. 选项 :TCP报文的字段实现了TCP的功能,标识进程、对字节流拆分组装、差错控制、流量控制、建立和释放连接等。选项部分 长度可变,最大可达40字节。主要有:(最大报文段长度、窗口扩大、时间戳和选择确认等)在这里插入图片描述

TCP的运输连接管理

TCP是面向连接的协议,运输连接是用来传送TCP报文的。TCP运输连接的建立和释放是每一次面向连接的通信中必不可少的过程。所以,运输连接就有3个阶段:连接建立、数据传送和连接释放。 运输连接的管理就是使运输连接的建立和释放都能够正常的进行。

在TCP连接建立的过程中要解决的以下3个问题:在这里插入图片描述

三次握手

TCP建立连接的过程称为是握手
建立TCP连接,就是指建立一个TCP连接时,需要客户端和服务端总共发送3个TCP报文段以确认连接的建立。这个过程的开端:Linux的socket编程中,这一过程由客户端执行connect函数来触发,整个过程动态图如下所示:
在这里插入图片描述
分析如下:在这里插入图片描述
正如上图所示:最初的时候,客户端A和服务器B都是处于CLOSED状态。主动打开连接的为客户端,被动打开连接的是服务器。一开始 TCP服务器进程先创建传输控制块TCB,时刻准备接受客户进程的连接请求,此时服务器就进入了LISTEN(监听)状态(等待客户的连接请求,有 即作出响应)。

第一次握手:
A的TCP客户进程也是首先创建传输控制块TCB,然后向服务器B发出连接请求报文,这时报文首部中的同部位 SYN = 1,同时选择一个初始序列号 seq = x ,TCP规定,SYN报文段(即SYN=1的报文段)不能携带数据,但需要消耗掉一个序号。这时,TCP客户端进程进入了 SYN-SENT(同步已发送)状态。

第二次握手:
TCP服务器B收到请求报文段后,如果同意建立连接,则向A发出确认报文。在确认报文中应该设置ACK=1,SYN=1,确认号是ack = x + 1,同时也要为自己初始化一个序列号 seq = y, 这时,TCP服务器进程进入了SYN-RCVD(同步收到)状态。这个报文也不能携带数据,但是同样要消耗一个序号。

第三次握手:
TCP客户进程收到B的确认后,还要向服务器给出确认。确认报文的ACK=1,ack = y+1,自己的序列号 seq = x + 1, 此时,TCP连接建立,客户端进入ESTABLISHED(已建立连接)状态。TCP的标准规定,ACK报文段可以携带数据,但是如果不携带数据则不消耗序号。当服务器B收到客户端A的确认后也进入ESTABLISHED状态,此后双方就可以开始通信了。

这个时候就有两个经典的问题出来了:

  • 有没有四次握手
    有,如上图所示:B先给A发送的报文段 拆成两个:先发一个确认报文段(ACK=1 ack=x+1)再发送同步报文段(SYN=1 seq=y)。这个题没有什么难度,和三次握手效果一样。
  • 为什么TCP客户端A最后还要发送一次确认呢?这个我在面试时,也被问到了。
    答案很简单:这主要是防止已经失效的连接请求报文段突然又传送到了服务器B,从而产生错误。 假如我们使用的是两次握手建立连接,假设有这样一种场景,客户端A发送了第一个请求连接 但是并没有丢失,而是只是因为在网络的结点中滞留的时间太长了,由于TCP的客户端A迟迟没有收到确认报文,以为服务器B没有收到,此时重新向服务器B发送这条报文,此后客户端和服务器经过两次握手完成连接,传输数据,然后关闭连接。但是此前滞留的那一个请求连接,网络通畅了到达了服务器,其实这个报文本该是失效的,但是,两次握手的方式就会让客户端和服务器再次建立连接,这将导致不必要的错误和资源的浪费。所以说这里采用的是三次握手,就算是那一次失效的报文传送过来了,服务端接受到了那条失效报文并且回复了确认报文,但是客户端不会向B的确认发出确认。由于服务器B收不到确认,就知道客户端A并没有要求建立连接。

四次挥手

数据传输结束后,通信的双方都可释放连接。在最初的时候,客户端A和服务器B同时都是处于ESTABLISHED状态。然后客户端主动关闭,服务器被动关闭。
在这里插入图片描述
分析整个过程:在这里插入图片描述
第一次挥手:
客户端A的应用进程率先 向其TCP发出连接释放报文段,并且停止再发送数据,主动关闭TCP连接。该连接释放报文段首部的终止控制位FIN = 1,其序列号为seq = u(等于前面已经传送过来的数据的最后一个字节的序号加1)。此时,客户端A进入FIN-WAIT-1(终止等待1)状态,等待B的确认。 TCP规定,FIN报文段即使不携带数据,也要消耗掉一个序号。
第二次挥手:
服务器B收到连接释放报文后即发出确认报文,ACK = 1,ack = u + 1,并且设置自己的序列号seq = v(等于B前面已经传送过的数据的最后一个字节的序号+1),此时,服务端就进入了CLOSE-WAIT(关闭等待)状态。TCP服务器进程这时应该通知高层的应用进程,客户端向服务器的这个方向的连接就释放了,这时候TCP连接处于半关闭(half-close)状态,即客户端已经没有数据要发送了,但是服务器若发送数据,客户端依然要接受。也即:从B到A的这个方向的连接并未关闭,这个状态还要持续一段时间,也就是整个CLOSE-WAIT状态持续的时间。

客户端收到服务器的确认请求后,此时,客户端就进入FIN-WAIT-2(终止等待2)状态,等待服务器发送连接释放报文(但是在这之前还需要接受服务器B发送的最后的数据)。

第三次挥手:
服务器B将最后的数据发送完毕后,就向客户端发送连接释放报文,必须设置FIN = 1,ack = u + 1 (重复上一次已经发过的确认号)。因为在半关闭状态,服务器很有可能又发送了一些数据,假定此时的序列号为seq = w,此时,服务器就进入了LAST-ACK(最后确认)状态,等待客户端的确认。

第四次挥手:
客户端收到服务器的连接释放报文后,必须对此发出确认。在确认报文段中:ACK=1,ack=w+1,而自己的序列号是seq=u+1,然后客户端A就进入了TIME-WAIT(时间等待)状态。注意此时TCP连接还没有释放,必须经过2MSL(最长报文段寿命)的时间后,当客户端A撤销相应的传输控制块TCB后,才进入CLOSED状态。 而服务器只要收到了客户端发出的确认,立即进入CLOSED状态。 同样,撤销TCB后,就结束了这次的TCP连接。可以看到,服务器结束TCP连接的时间要比客户端早一些。

此处的几个经典问题:

  • 为什么客户端A在Time-WAIT状态还得等待2MSL的时间呢?
    其中的这个MSL(Maximum Segment Lifetime)是最长报文段寿命,就这样理解 所有的报文段都不可能存活时间超过它。原因如下:
    第一 为了保证客户端发送的最后一个ACK报文段能够到达服务器B,因为这个ACK报文有可能会在网络中丢失。因而使处于LAST-ACK状态的服务器已经发送了FIN + ACK报文请求断开了,可是客户端还没有给我回应,应该是服务器发送的请求断开报文它没有收到,于是服务器又会重新发送一次,而客户端就能在这个2MSL时间段内收到这个重传的报文,接着给出回应报文,并且会重启2MSL计时器。最后,A和B就正常进入close状态。若是A在TIME-WAIT状态下不等待一会儿时间,而是在发送完ACK报文段后立即释放连接。那么就无法收到B重传的FIN+ACK报文段,因此就无法再重传一次确认报文段,B就无法正常进入close状态了。
    第二 防止类似与“三次握手”中提到了的“已经失效的连接请求报文段”出现在本连接中。客户端A在发送完最后一个ACK报文段后,在这个2MSL时间中,就可以使本连接持续的时间内所产生的所有报文段都从网络中消失。这样就可以使下一个新的连接中不会出现旧的连接请求报文了。
  • 如果双方已经建立了连接,但是客户端突然出现故障了怎么办?
    在这种情况下,B就不能再收到A发来的数据,所以应当让B不要再白白等待下去。这时就需要使用TCP的保活计时器,显然,客户端如果出现故障,服务器不能一直等下去,白白浪费资源。服务器每收到一次客户的数据后都会重新设置这个保活计时器,时间通常是设置为2小时,若两小时还没有收到客户端的任何数据,服务器就会发送一个探测报文段,以后每隔75秒发送一次。若一连发送10个探测报文仍然没有客户的反应,服务器就认为客户端出了故障,接着就关闭连接。
  • 为什么建立连接是三次握手,关闭连接确是四次挥手呢?
    这个问题,同花顺的面试官也问到了我。
    首先建立连接的时候, 服务器在LISTEN状态下,收到建立连接请求的SYN报文后,把ACK和SYN放在一个报文里发送给客户端。而关闭连接时,服务器收到对方的FIN报文时,仅仅表示对方不再发送数据了但是还能接收数据,而自己也未必全部数据都发送给对方了,所以己方可以立即关闭,也可以发送一些数据给对方后,再发送FIN报文给对方来表示同意现在关闭连接,因此,己方ACK和FIN一般都会分开发送,从而导致多了一次。

TCP的有限状态机

在这里插入图片描述
上面这个乱糟糟的图描述了整个TCP连接过程中的所有TCP状态以及可能的状态转换。

注:这一部分内容详见《计算机网络》第七版 谢希仁著

猜你喜欢

转载自blog.csdn.net/weixin_43949535/article/details/102492688