• TCP协议的三次握手和四次挥手


    完整的TCP内容,请参考RFC 9293
    TCP协议为应用提供可靠的、有序的的字节流服务。TCP是面向连接的,提供了全双工的通信。TCP使用端口号来识别应用程序服务并在主机之间复用不同的流。

    TCP header格式

    TCP header也像IP header一样,在header中提供了一些专门用于TCP的信息,TCP header之后就是用户数据,TCP段的组成如下:请添加图片描述
    Source Port(源端口号):大小为16个比特位,两个字节。
    Destination Port(目的端口号):大小为16个比特位,两个字节。
    Sequence Number(序列号):大小为32个比特位,4个字节。在这个TCP段中,数据部分中(上图的Data部分),第一个字节(8比特)的序列号,如果标志位SYN有设置,那么这个序列号就是源端(发送这个TCP段的端)选定的初始序列号(ISN,initial sequence number ),Data部分的第一个字节的序列号就是ISN+1. 检测是否丢包就要参考这个序列号。
    Acknowledgment Number(确认号):大小为32个比特位,4个字节。如果标志位ACK有设置,那么这个字段的值就是该TCP段的发送者期待收到的下一个序列号的值。一旦,TCP连接建立好后,这个字段在每次有TCP传送时都要设置。
    Data Offset(数据偏移量): 大小为4比特。它是TCP header中32比特位的个数。它表示Data在TCP段中什么位置开始。从上图我们可知,TCP header至少有5个32位字,即TCP header的大小到少为20个字节。由于Data Offset只有4比特位,最大值为15,即TCP header最多只能有15个32位字,即60个字节。除去固定的20字节,Options部分最多只有10个32位字,即40个字节大小。

    Rsrvd(Reserved,保留):大小为4比特位,用于日后扩展的控制位,在一般的TCP段中,该字段都是0,即使收到这个字段的值,都要忽略。除非这些控制位在发送端主机和接收端主机都有了相应的实现。

    控制位:这就是我们通常讲的标志位,目前一共有8个,每个大小为1比特位。

    • CWR: 拥塞窗口减小,该标志由发送主机设置,表示它收到设置了ECE标志的TCP段,并在拥塞控制机制中已响应。
    • ECE:ECN-Echo.ECN代表Explicit Congestion Notification显式拥塞通知,ECN允许在不丢弃数据包的情况下对网络拥塞进行端到端通知。这个控制位有两个作用,一,如果SYN控制位有设置,即SYN=1,表示TCP对端能够支持ECN。二,如果SYN=0,则表示在正常传输期间,在IP首部中收到了一个设置了拥塞经验标志(ECN-11)的分组。对TCP发送端来说,这是一个网络拥塞(或即将发生的拥塞)的指示。
    • URG:这个控制位有设置,即为1时,表示Urgent pointer字段是有用的。
    • ACK:这个控制位有设置,即为1时,表示Acknowledgment Number字段是有用的。在客户端发送初始SYN包后,所有的数据包都应用设置这个控制位。
    • PSH:这个控制位有设置,即为1时,就是要求把缓冲的数据推送到接收应用中。
    • RST:重置TCP连接
    • SYN:同步序列号,只有从每一端发送出来的第一个数据包需要设置这个控制位。
    • FIN:发送者没有更多数据要发送,需要关闭连接时发送,它在发送者最后一个数据包中设置。

    Window(窗口):大小为16位。它表示发送这个TCP段的发送方愿意接收的数据大小,即可以接收以8比特为一个单位的个数。它是从acknowledgment number字段开始计起。如acknowledgment number字段值是2000,而Window是100,表示当前发送方对于下次收到的数据,只愿意接收从序列号2000开始往后100个8比特位数据。当使用窗口缩放扩展时,Window值会发生偏移。
    Checksum(校验和):大小为16位。用于错误检测。
    Urgent Pointer(紧急指针):大小为16位。该字段以正偏移量从该段的序列号传达紧急指针的当前值。紧急指针指向紧急数据后面的八位序列号。该字段仅在设置了URG控制位的段中解释。
    Options(可选项):请参考其他资料。
    Data(数据):就是需要TCP传送的用户数据。

    以上就是TCP段的内容。

    三次握手

    TCP连接在我接触过的典型应用里,有一端是被动打开的,这一端通常是一个服务端,被动打开意味着进程需要监听来自远程TCP端发起的连接请求。这个服务端的进程会绑定到一个特定的端口并监听这个端口。远程TCP端主动发起来连接请求的方式,我们称为主动打开。理论上两端都既可以是服务端也可以是客户端,甚至两都是。一个基本要求就是它们被寻找到,如有固定的IP(可路由到的)和端口。

    TCP连接是通过一对socket来定义的。对于每一个连接都有一个发送的序列号和一个接收序列号。为了建立或初始一个连接,两个TCP端必须同步彼此的初始序列号。这将通过交换用于建立连接用的TCP段来完成的。
    这必须要有一个机制来生成初始序列号和在连接两端的TCP端同步这些序列号。同步的过程要求连接的每一端都要向对方发送自己的初始序列号,并从对端收到对发送初始序列号的确认,所以连接的每一端在接收到对方发来的SYN后,都要对它的序列号进行确认响应。

    序列号有什么用呢?通过TCP传输的每个八位字节数据都有一个序列号,因为每个八位字节是有序的,所以每个八位字节都可以被确认。采用累积方式的确认机制,所以一个序列号A的确认,表示A前面所有的八位字节已收到,但不包括A本身。在一个TCP段中八位字节数据的编码方案是这样的:紧跟在TCP header后的第一个数据八位字节是最低编号,其后的八位字节是连续编号。但有一点有要记住,真实的序列号空间是有限,即使它很大。序列号的空间是0到2的32次方,因为序列号的空间是有限的,所以处理序列号的算术都必须模2的32次方。当出现重复传输数据时,这个机制会让检测重复的工作变得简单。TCP端将会收到作为对它发送数据的响应的确认,每个TCP端都会保留一个重传队列,如果重传队列里的一个TCP段它的序列号和长度的和小于或等于传入的TCP段里确认值,那么它就可以被完全确认了。

    为了正确地同步TCP连接两端的初始序列号,我们通过三次握手来建立连接,以此来完成初始序列号的同步,在连接没有建立之前都不通过传输任何数据,这就是为什么说TCP是面向连接的原因。

    SYN和FIN控制位只用在连接打开和关闭过程中。一个TCP连接是通过一对socket来定义的。这个TCP连接也是可以重用的。一个连接的新实例就是一个新的连接。我们不希望发生这样的情况,在一个新的连接实例中,收到上一个连接实例发来的TCP段数据。那么该如何识别收到的TCP段是否是旧的呢?TIME-WAIT这个状态会限制连接的重用,同时初始序列号的选择也可以防止传入的数据包与对应的连接出现歧义。这些措施都有效地保证了传送的数据是正确的,有效地防止来自非当前连接的数据被传送的应用程序。在建立一个新的TCP连接时,初始序列号生成器就会用来选择一个新的32位初始序列号(ISN)。这个生成器的生成算法很关键,否则被被黑客轻易地预测或猜到ISN的值,那么就容易给应用程序带来安全方面的问题。

    对于每一个连接都有一个发送序列号和一个接收序列号,初始发送序列号由数据发送的TCP端来选择,初始接收序列号则是在连接建立过程中获悉。对于个连接的建立或初始化,两个TCP端都必须同步彼此的初始序列号,这通过交换附带控制位”SYN“和初始序列号的TCP段来完成。这个同步过程要每一端都要发送各自的初始序列号和接收来自对端的发送序列号的确认。每一边都会收到对端TCP端的初始序列号,并在收到后发回确认。具体过程如:

    1) A --> B  SYN my sequence number is X
    2) A <-- B  ACK your sequence number is X
    3) A <-- B  SYN my sequence number is Y
    4) A --> B  ACK your sequence number is Y
    
    • 1
    • 2
    • 3
    • 4

    因为第二、三步可以合并到一个TCP段中,因此就有了所谓三次握手的说法。三次握手对于TCP连接的建立是很重要的,主要的原因是序列号并没有绑定到网络上某个全局时钟,也就是说没有一个全局的时钟来帮忙生成序列号。序列号的生成必须依赖TCP端不同机制来选择初始序列号。

    所以三次握手就是用来解决这个初始序列号的问题的机制,这个过程是由一个TCP端发起,另一个TCP端响应。
    请添加图片描述

    第二行:TCP端A发送SYN段给TCP端B,表示TCP端A将使用以100开始的序列号。
    第三行:TCP端B发送SYN和ACK两个控制位的TCP段给TCP端A ,其中发送的ACK是对它收到的TCP端A发的SYN的确认,而发送的SYN则是告诉TCP端A,它将使用以300开始的序列号。确认字段的序列号表示TCP端B期待下一次TCP端A发给的序列号是101.
    第四行:TCP端A对收到的TCP端B发的SYN的确认,TCP端A会发送一个空的含有ACK控制位的TCP段对TCP端B发的SYN进行确认。确认号是301,表示TCP端A期望下次收到TCP端发送的序列号是301.序列号是101.
    第五行:TCP端A发送了一些数据,序列号是101,这个序列号与第四行的一样,因为ACK是不占用序列号空间的。

    下面通过下面的图再来看看三次握手的过程:

    1. SYN:主动打开是由客户端主动向服务端发送一个SYN来实现的,客户端将TCP段中的序列号设置为一个随机值X,这是第一次握手。
    2. SYN-ACK:服务端回复SYN-ACK,回复的TCP段中的确认号就是收到客户端的序列号加1,即X+1,回复的TCP段中的序列号是服务端选择的一个随机数Y,这是第二次握手。
    3. ACK:最后,客户端向服务端发回一个ACK,发回的TCP段中的确认号为接收到的序列号加1,即Y+1,序列号为接收到的确认值,即X+1, 这是第三次握手。

    请添加图片描述

    第1,2步建立并确认了从客户端到服务端方向的序列号,第2,3步建立并确认了从服务端到客户端方向的序列号。至此,客户端和服务端都收到了确认,并建立了全双工通信。请求~确认这种沟通模式在TCP中很常见,正因如此,要确定两个方向上的序列号,至少就需要三次的握手来实现。它们握手的最终目的就是要确定两个方向上的序列号。

    我们现在所涉及到都是在正常情况下,实际,在TCP的实际还要考虑一些异常的情况。在这里不做介绍。

    TCP的状态机

    一个连接过程会经历一系列的状态:LISTEN,SYN-SENT,SYN-RECEIVED,ESTABLISHED,FIN-WAIT1,FIN-WAIT2,CLOSE-WAIT,CLOSING,LAST-ACK,TIME-WAIT,还有一个虚拟的状态CLOSED,之所以说它是虚拟,是因为它表示的是没有连接。

    LISTEN:表示等待远程TCP端或端口的连接请求
    SYN-SENT:表示在发送了一个连接请求后,等待一个相匹配的连接请求
    SYN-RECEIVED:表示在收到和发送了一个连接请求后等待一个确认连接请求确认
    ESTABLISHED:表示一个连接已建立,这是连接的数据传输阶段的正常状态。
    FIN-WAIT1:等待一个来自远程TCP端的连接终止请求,或者一个前面发送的连接终止请求的确认。
    FIN-WAIT2: 表示等待一个来自远程的TCP端的连接终止请求。
    CLOSE-WAIT:表示等待来自本地用户的连接终止请求
    CLOSING:表示等待一个来自远程TCP端连接终止请求确认
    LAST-ACK:表示等待一个先前发到远程TCP端的连接终止请求的确认(这个发送到远程TCP端的终止请求已经包含了一个来自远程TCP端发的终止请求的确认。)
    TIME-WAIT:表示等待足够的时间确保远程TCP端收到它的连接终止请求的确认,避免新的连接受到前一个连接的延迟TCP段的影响。
    CLOSED:表示没有连接

    TCP连接过程从一个状态到另一个状态以响应事件。这些事件可以是用户的调用、超时,OPEN, SEND, RECEIVE,CLOSE,ABORT,STATUS,传入的TCP段,尤其是那些包含SYN、ACK、RST、FIN控制位的TCP段。

    关闭TCP连接

    其实关闭TCP连接有很多种情况,比三次握手要复杂。这里只说它正常关闭的情况,参考:RFC 9293
    关闭TCP连接的情况会比建立TCP连接的过程要复杂一些。TCP连接的一端选择关闭后可能还会继续接收数据,直到它被告知,它的对端已经关闭了。在TCP的实现中,用户会继续从它们关闭的TCP连接中继续读取发送过来的数据,直到没有数据为止。TCP的实现里会在连接关闭前把所要已缓存起来要发送的数据全部发送出去。所以发起关闭的TCP端不能马上就关闭,它必须等待直到连接成功关闭,这样一来也能知道所有要接收的数据在目的端已被全部接收。我们的应用程序要告诉TCP的实现去关闭连接,远端的TCP端也会初始化一个FIN控制信号,具体情况如下,这些都是正常情况下发生的过程:
    请添加图片描述
    请添加图片描述
    Tips: Maximum Segment Lifetime (MSL)

    这就是常说的TCP四次握手或四次挥手。

    1)TCP Peer A 初始Close
    它把含FIN控制位的TCP段组装好,然后放在输出TCP段的队列中。此时TCP实现不再接收任何数据的TCP段,简单地说,它不再发送用户数据。之后TCP Peer A进入FIN-WAIT-1状态。在这个状态下,但是它仍然能够继续接收TCP Peer B发来的数据。所有在FIN之前包括FIN的TCP段,如果没有被确认,都会被重传。

    (如果其他TCP端已经对收到的FIN的进行了确认并发送了自己FIN,那么前面最先发送FIN的TCP段,就可以对发来FIN进行确认。这里要注意,接收到FIN的TCP端会单独对FIN进行ACK确认,并不会在确认的同时发送自己的FIN。虽然有人说将来在考虑将其合成一步,但目前还是分开的,我觉得是有道理,因为它可能还有数据没有发完,它需要在发完后,再关闭自己)

    2)TCP Peer B从网络上收到FIN
    TCP Peer B收到FIN,可以对它进行进行ACK确认,告诉其他TCP端连接正在关闭(注意是正在关闭,不是马上关闭)。TCP Peer B会在发送完剩余的数据后,向其他TCP端发送FIN。接下来,TCP Peer B就会等待对它发送FIN的确认。一旦,TCP Peer B收到对它发的FIN的确认就关闭连接。如果TCP Peer B迟迟收不到对它发送的FIN的确认,那么就会等用户超时后,终止连接。其他TCP端会被告知这一变化。

    同时关闭连接的情况如下:

    请添加图片描述
    在TCP Peer A和B交换完彼此的FIN并都得到对方的确认,就可以同时关闭TCP连接。当所有在先于FIN 收到的TCP段都被处理和确认后,每个TCP端就可以对它已经收到的FIN进行ACK确认。然后,同时关闭连接。如果收到确认,就会等待用户超时强制关闭。

    总结
    TCP协议的知识很多,这里只是根据个人兴趣摘录部分出来。

    我们的TCP的设计要求就是能够发现网络拥塞并及时调整发送带率,并且是可靠的。TCP在IP(Internet Protocol )中被广泛使用,文件传输(文件传输协议FTP)、邮件、万维网等都有广泛的使用。 TCP(Transmission Control Protocol)协议是一种在计算机网络里使用的面向连接的协议,在两个应用之间的开始通信前要建立一个连接。TCP的可靠性要求它能够发现哪些数据对方没有收到,然后重新传送这部分数据,确保对方最后一定能够获得完整的有序的数据。在TCP传送数据时,它会把数据拆解成一个一个小的数据段,然后把它们通过网络传送到对端的应用,对端的TCP会在收到数据后,按顺序把它们组装起来,再向上传送给应用。TCP有相应的机制发现的传送的数据中可能出现的错误,TCP还有相应的机制进行数据流控制和拥塞控制。

    在大多的开发者中一般都不太会接触TCP的实现,一般都是通过使用Socket 、HTTP或其他协议间接地使用TCP。Socket套接字很好地简单化了我们使用TCP的过程。

    根据TCP提供的功能,我们可以知当我们对数据有可靠性要求时,TCP协议就应该成为我们的首先。因为TCP的功能,会使用它可能会有较长的延迟, 对那些要及时传送的信息如视频直播,IP实时语音,TCP似乎会带来一些不好的体验,因此这类对及时性有要求的应用,通常会选择UDP。

    我们都知道在互联网模型中,TCP是属于传输层的协议。那么一个应用发送数据给远程的应用是不需要知道特定的机制是如何工作的,比如IP分片,传输介质的最大传输单元等,在传输层,TCP处理所有的握手和传输细节,然后向上应用层提供一个网络连接的抽象层,典型的代表就是Socket套接字。

    关于这个协议,后续我们再分享更多。初学者不太建议看,对互联网模型有了解最适合看的。

  • 相关阅读:
    艾美捷C1q天然蛋白中英文说明书
    【Verilog刷题篇】硬件工程师从0到入门3|组合逻辑复习+时序逻辑入门
    springboot:整合mybatis-plus
    Bootstrap中的栅格系统
    VSCode使用简介
    LVGL_文件系统FS
    并网逆变器学习笔记1---虚拟同步机控制(VSG)
    Redisson 框架中的分布式锁
    帝国cms如何隐藏指定信息不在列表页显示
    蓝桥杯每日一题2023.11.15
  • 原文地址:https://blog.csdn.net/weixin_40763897/article/details/130666326