网络原理-TCP协议

回顾上文

上一篇博客中详细描述了UDP的报文格式及特点

  • UDP报头 + UDP载荷(应用层数据报),源端口,目的端口,报文长度,校验和
  • 无连接,不可靠传输,面向数据报,全双工

这一篇博客来描述TCP的特点及报文格式

TCP的特点

其中可靠传输这样的特性,在代码中无法直观的认识到,但是这个特性其实是TCP最核心的部分

TCP报文格式

  • 16位目的端口号,作为传输层协议,首先就需要能够表示端口号是啥
  • 4位首部长度指的是报头的长度,不是总TCP报文的长度,长度是变长的,从协议的层次上是没有限制的
  • 选项:如果选项完全没有,TCP报头长度就是20字节;如果选项拉满,TCP报头最长时为60字节
  • 保留(6位):UDP报文长度使用2个字节表示,太小了,能否扩展,类似于UDP报文长度的扩展问题,万一某一天,TCP需要扩展一些新的功能,此时就可以使用这个保留位来表示了
  • 保留位:,TCP的灵魂
  • 校验和:此处的校验和和UDP的校验和是一个东西(checksum)

TCP协议10个比较核心的机制

确认应答机制

TCP协议要解决一个很重要的问题,可靠传输,所谓的可靠传输,不是说,你发送方能够100%的把数据发送给接收方,但是会尽可能,尤其是让发送方知道,接收方是否收到

通过AI可以得知

TCP(传输控制协议)是一种面向连接的、可靠的、基于字节流的传输层通信协议确认应答机制是TCP确保数据可靠传输的重要特性之一下面通过一个例子来描述TCP协议中的确认应答机制:

假设有两个主机,主机A(发送方)和主机B(接收方),它们通过TCP协议进行通信

连接建立:首先,主机A向主机B发起一个连接请求(SYN),主机B收到后响应一个确认报文(SYN-ACK),然后主机A再发送一个确认报文(ACK)来确认连接请求成功这样,一个TCP连接就建立起来了


数据发送:

  • 主机A准备发送数据给主机B在发送数据之前,主机A会随机生成一个初始序列号(ISN),用于标识发送的数据字节流

确认应答:

  •    - 主机A发送第一个数据包给主机B,数据包中包含序列号(seq)和数据内容
  •    - 主机B收到数据包后,会生成一个确认序列号(ack),这个序列号是主机B期望从主机A接收的下一个字节的数据的序列号例如,如果主机B正确接收了序列号为100的数据包,那么它期望下一个数据包的序列号是101,所以它会发送一个ack=101的确认报文回主机A
  •    - 主机A收到确认报文后,知道主机B已经成功接收到了序列号为100的数据包
  • 数据传输:主机A继续发送更多的数据包,每个数据包都包含一个序列号,主机B对每个收到的数据包进行确认应答

丢包与重传:

  •    - 如果主机B没有及时收到主机A发送的数据包(例如,因为数据包在网络中丢失),它将不会发送对应的确认应答
  •    - 主机A在发送数据包后会启动一个定时器等待确认应答如果在超时时间内没有收到确认应答,主机A将重传该数据包

数据接收与顺序:

  •    - 如果主机B收到的数据包序列号不是它期望的序列号(例如,它收到了序列号为102的数据包,但是它期望的是101),它会发送一个重复的确认应答(ack=101),告诉主机A跳过的数据包尚未到达
  •    - 主机A收到重复的确认应答后,会重新发送丢失的数据包

连接终止:

  • 数据传输完成后,任一主机都可以发起连接终止这通常通过发送带有FIN标志位的数据包来实现,对方收到后会发送ACK确认,并也可以发送FIN标志位的数据包来关闭另一端的数据传输最终,双方都发送了FIN和ACK之后,连接被关闭

通过这个确认应答机制,TCP确保了数据的可靠传输,即使在网络环境不稳定的情况下也能保证数据的完整性和顺序性

来举一个比较简单的栗子:在麻辣烫店里点了A和B,接下来你问做好了没,老板要问你哪一个做好了没,此时如果你是按照顺序来问的话,那么老板就会回答说A还没做好,此时你发给老板的信息就是一个ACK,老板给你的就是一个响应,也是一个ACK

实际真正的TCP的序号不是按照“一条两条”的方式来编号的,而是按照“字节”来编号的,每个字节都会分配一个序号

此处报头写的序号的数值就是载荷部分第一个字节的序号(由于序号连续递增特点,只需要知道第一个字节的序号,后续每个字节的序号就都知道了),虽然TCP序号是连续递增的,不一定是从0或者1开始跌(具体如何开始,为何这么设定,后面“连接管理”再详细解释)

此处应答报文确认序号是1001,理解成:

  • 对于B来说,<1001的数据都已经确认收到了
  • B再向A索要从1001开始的数据

TCP报头自身不表示TCP长度,TCP载荷的长度是要结合IP协议报头来算出

发送方发的数据报,只是“序号字段”有效,确认序号字段是无效的,接收方返回的ACK,只是“确认序号”有效,序号字段是无效的

上述序号的机制,都可以理解成是TCP定义的“规则”,既然是规则就有很多定义的方式,但是最初设计tcp的大佬们选择了当前这套规则,TCP最核心的功能,就是可靠传输,可靠传输之所以能达成,主要就是依靠“确认应答”机制,这里是通过应答报文来通知发送方,一切顺利的情况

Question:如果出现丢包的情况下呢?


Answer:丢包客观存在,无法预测,网络环境,本身就是不可靠的,但是TCP就是要在不可靠的环境中,构造出“可靠的通信方式”


超时重传

这个特性就是用来应对网络出现丢包情况,策略,正常情况下,TCP是通过确认应答来知道数据是否被对端收到了

如果B没有收到A的数据,此时B不可能给出任何应答,A就可以根据“是否收到了ACK”来区分是否出现丢包,A从发送出数据之后,到正常收到ACK,肯定也会经历一些时间的,A就会进行一定的等待,如果等待时间超过了某个阈值(超时),还没有收到ACK,此时就可以认为出现丢包了


此时发现丢包,就要进行重传,把刚才发的数据再传输一遍


如果是ACK丢了呢?数据报顺利到达,但是返回的ACK丢了呢?


站在A的角度,A无法区分是数据丢了,还是ACK丢了,A能看到的都是没有收到ACK,A做的事情只能是出发重传,很明显,B此时就收到了两份重复的数据,上述情况本身很危险,对于转账这样的操作,触发重传就引起重复转账


TCP接收方这边,会针对收到的数据进行去重,会按照序号,来进行去重


TCP层次上,重复传输/重复接受,无所谓,一定要保证应用层(应用程序,read,读数据的时候)不能读到重复的数据,这些都是应用程序代码中的逻辑,无论TCP底层重传多少次,内核中始终会确保,应用层读到的数据就只有一份,此时就可以确保,应用程序不会因为TCP重传引起BUG,核心机制为去重


接收方,操作系统内核中,存在一个数据结构,“接收缓冲区”->类似于PriorityBlockingQueue

  • 调用accept
  • 拿到clientSocket
  • 拿到InputStream
  • 进行read

B收到数据之后,层层分用,分用到TCP这一层的时候就会有一个阻塞队列数据结构,把收到的结构放到阻塞队列中了,放的过程中,就会根据当前数据的序号,在队列中进行判断,判定这个数据是否在队列中存在(或者曾经在队列中存在过->队列中的数据,会被应用程序读取走),只要存在过,这个新的数据就不会进入队列,而是直接丢弃(此处的去重只是针对超时重传,特殊情况下展开的,和你正常传输数据是不相关的)


接受缓冲区,除了去重之外,还有一个很重要的功能,针对收到的数据进行排序,网络传输,会后发先至,很多时候,希望咱们发出去的数据能够有序的到达接收方,A按照1234这样的顺序write,B这边read的时候,也是按照1234read出来,虽然网络传输的中间过程是后发先至,是乱序的,但是不要紧在接收缓冲区里,会对收到的数据先排个序,让序号小的在前头,序号大的在后头,并且数据和数据之间的序号始终都是连续的

丢包,本身是一个概率事件,假设,网络丢包是10%,一次传输数据丢失了,概率是10%,触发一次重传,重传也丢失了,概率10% * 10% = 1%,99%的概率是至少能传输成功一次,随着重传的次数的增加,这个概率还会进一步变大,反之,如果连续重传几次,都丢包,说明当前本身丢包的概率就非常大了

Question:超时重传,超时时间,是多少呢?


Answer:注意这个时间不是固定数值,而是会动态变化,随着重传轮次的增加,会变得越来越长,假设第一次传输数据,等待50ms,就出发重传,重传之后,等待100ms,但如果还是收不到ack,再等待,再等待150ms,超时时间间隔会越来越长(不一定是线性增长,具体怎么增长发,取决于系统的具体实现),换而言之,重传的频率越来越低

啥时候会重传多次?大概率就是网络出现严重故障了,接下来重传,能成功的可能性比较低了,如果你要是重传的越来越快,非但得不到好的结果,而是浪费很多系统资源,疗伤的时间周期会变长

如果网络上确实出现严重故障了,重传若干次,还是不成功,达到一定次数阈值,就会尝试“重置连接”,触发一个“复位报文”尝试”重置连接“,充值就是通信双方清空之前tcp传输过程中的中间状态(比如把接收缓冲区里的数据都丢掉),网络出现严重故障,RST报文也是无法顺利完成的,此时重置也失败,就只能断开连接了(释放掉,保存对端信息的数据结构)

超时重传,是确认应答的重要补充,TCP可靠传输,全靠确认应答和超时重传这俩机制支撑着

连接管理

建立连接的流程:三次握手

建立连接,就是通信双方,各自保存对端的信息,具体完成上述过程,需要经过三次网络交互

三次握手的第一次,一定是客户端先发起的,谁先发起,谁就是客户端,如果是“服务器”先发起,它就不应该叫服务器了,而应该叫做“客户端”,SYN为RST报文中的一位,为1代表为SYN,这个数据报不携带任何业务数据,载荷部分是空着的,只有TCP报头(synchronize同步,前三个字母,TCP中的同步是希望服务器和客户端之间达成某种有关联的状态)

而第二次发送SYN+ACK则是RST中这两位设置为1,每个tcp数据报,都会经理层层封装和分用

为啥是各自给对方发送syn?


是为了相互之间确认关系,而不是单方面确认连接,如果单方面连接没有回应,这算啥?

三次握手的时候,相当于,双方各自让对方保存自己的信息,得是两边都把对方的信息保存好,连接才算是建立完成

三次握手的意义,以及三次握手解决了什么问题?

三次握手,相当于“投石问路”,在正式传输业务数据之前,先确认一下通信链路是否畅通,也相当于TCP可靠传输的一种保证方式(这里是辅助的机制,核心还是确认应答和超时重传,经典问题:TCP如何保证可靠传输?千万不能回答“三次握手”)

通过三次握手,来确认通信双方,发送能力和接受能力都是正常的,进行三次握手,本质上就是完成上述确认的过程

三次握手可否变成两次握手呢?

不行的!服务器这边对于“发送能力,接受能力”的认知是不完整的,,需要第三次交互,把客户端掌握的完整情报,告知给服务器


三次握手可否变成四次握手呢?

行,但是没必要,拆开中间的这次交互,虽然不影响tcp的正常功能,但是性能会有所损失


谈谈TCP建立连接的流程

三次握手过程中,还需要协商一些必要的参数,通信双方共同商量一下,有的参数,不是单方面就能确认的,需要双方共同确定出来,TCP通信时使用的序号,就是协商出来的,序号往往不是从1/0开始的,而是从三次握手的时候,通信双方协商出一个数字,第一次连接和第二次连接协商出来的其实序号,往往差异很大

通信双方,先建立连接,进行若干次通信,断开连接,过一会,又建立连接,又进行若干通信,此时可以避免一个问题:前朝的剑斩后朝的官

TCP状态

  • listen:是服务器出现的状态,当服务器绑定端口成功之后,就会进入到listen状态,此时意味着,就随时可以有客户端连接上来了
  • established:建立完成,可以随时进行后续通信了
  • close_wait:被动一方进入的状态,等待代码调用close,代码里调用close越及时,这里的状态就越不容易看到

断开连接的流程:四次挥手

三次握手,一定是客户端发起第一次请求,四次挥手,则不一定,服务器和客户端都可以主动发起;类似于谈恋爱的时候,一般都是男生追女生,但是分手的时候,可能是男生主动提出分手,也可能女生提出

此处以客户端主动提出为例,客户端发出FIN(断开连接),服务器说我也断开连接(ACK),并且结束(FIN),客户端说收到(ACK)

Question:四次挥手,中间两次能否主动合并?如能?


Answer:有时候能合并,有时候不能合并,常规情况下是不能合并,特殊情况下能合并,为啥不能合并?三次握手过程中,syn和ack都是内核自动控制发送的(发送实际其实是内核控制的,同一时机)

服务器收到syn之后立即返回ack,也立即返回syn,同时进行可以合并

这里ACK是收到FIN就立即返回(内核控制的),应用程序代码中,调用close的时候,才会触发的(应用程序控制的),由于这两个数据触发是不同的时机,因此就难以进行合并,在特殊情况下,上述两个数据可以合并,TCP还有一个机制,延时应答(要回复ACK,但不是马上,而是稍等一会),这个情况下,就可以合并,终究,上述合并的情况属于”特殊情况“,对”一般情况“是不能合并的,所以,最终还是把断开连接,称为”四次挥手“

有的时候,可能会在服务器这边见到大量的close_wait或者close的调用不够及时,说明代码大概率有bug了,代码很可能忘记调用close了

四次挥手的重要状态:

  • close_wait:等待应用程序调用close方法,但是如果程序出现问题,close没有及时调用或者压根忘记调用就可能使机器上出现大量的close_wait状态
  • time_wait:存在的意义,就是为了应对最后一个ACK丢包这样的场景
  • 客户端再收到服务器返回的FIN之后,不能立即释放TCP连接(如果立即释放了,后续一旦对端重传了FIN,此时客户端就无法应对了,无法返回ack了),因此客户端这边就需要一个特殊的状态time_wait状态来等待可能到达的FIN重传的数据
  • time_wait状态不是持续的,而是有一定时间的,在一定时间之内,如果没有收到重传的FIN,潜台词就是最后一个ACK已经被对方收到了,就不会重传FIN,此时time_wait就可以释放了
  • close_wait不一定是服务器处于的状态(被动接受的一方)
  • time_wait也不一定是客户端处于的状态(主动发起的一方)

四次挥手过程只是通知说,我马上要删除你了,真正执行删除,是要在四次挥手完成之后

Question:如果发现服务器端出现大量的time_wait如何处理?


Answer:出现大量time_wait,说明服务器这边触发大量的主动断开TCP连接的操作,这个操作对于服务器来说,很可能是不科学的,一般都是客户端主动断开连接

滑动窗口

确认应答,超时重传,连接管理都是为了保证可靠传输,TCP除了保证可靠传输之外,也希望能够尽早高效的完成数据传输,滑动窗口就是一种提高传输效率的机制

下图为没有滑动窗口的数据传输过程

下图为引入滑动窗口之后的效果,把一条一条发送转变为批量发送,批量发送就把等待时间重叠了,虽然是批量发送,还是要等待一会ACK(也不能完全不等,一直往后发,可靠性就难以保证)

发送方收到3001,意思就是<3001的数据,都已经确认收到了,此时1001-2000,2001-3000这两个数据都得到了确认,这个时候,滑动窗口,可以直接往后挑两个格子

滑动窗口中,如果出现丢包咋办?仍然要保证可靠性

数据包已经抵达,ACK被丢了

不需要做任何处理,也是没有问题的

2001告诉发送方,1-2000的数据都是收到了,此时2001涵盖了1001的效果,1-5000都已经到了,前面的3001-4000,4001-5000也得到了确认,只要不是ACK全丢了,哪怕丢了50%

数据包就直接丢了

注意观察每个ack确认序号的变化,虽然主机A仍然在继续给B往后发送,接下来的每个数据,B都是再向A索要1001这个数据包,A连续收到若干个1001这样的ack,明白了原来是1001-2000这个数据丢了

上述针对丢包的处理,整个过程都是很高效的:

  • 对于ack丢包,不做任何处理
  • 对于数据丢失,只需要把缺失的数据重传即可,其他的数据不必重传

不等待ack,批量发送数据,这个过程就是称为“窗口大小”,上图中批量发送了四组数据,等待着四组数据的ack,收到了2001ack,此时这个1001-2000的数据就得到了确认,标记成灰色了,接下来,当然要继续等待2001-5000的ack,与此同时,也发送新的数据,5001-6000(此时要等待ack数据还是四组数据)

滑动窗口中,批量发送4组数据之后,不是等到4个ack都回来才继续发送新的数据而是收到一个ack,就往后发一个新的

握手和滑动窗口的区别就是,握手tcp数据包没有载荷,不代表任何业务的意义

快速重传(滑动窗口下,搭配的丢包处理机制),超时重传和快速重传相当于两种不同的重传机制,是否是冲突的呢?

当然不冲突,不同情况下,采取的重传策略,快速重传相当于超时重传在滑动窗口下的特殊变种

如果你tcp传输的数据比较少,不频繁,此时就不会触发滑动窗口,得是你短时间传输大量的数据,此时才能触发滑动窗口;仍然按照超时重传方式来解决丢包问题,此时才触发快速重传,按照ack反馈的次数来解决丢包问题

重要结论:滑动窗口说是提升效率的机制,更准确的说,是“亡羊补牢”的机制,TCP为了保证可靠性,牺牲了很多效率的,引入滑动窗口是让效率上的牺牲变少一些,但是仍然是存在牺牲的,再如何滑动窗口,速度不可能比UDP这种没有可靠机制的协议更快

流量控制

滑动窗口涉及到关键概念,窗口大小,批量发送多少数据,数据量,就称为“窗口大小”就属于是不需要等待ack,窗口大小是可变的,可以通过窗口大小,来控制发送方的发送速度,窗口越大,单位时间发的数据就越多,效率就越高;窗口越小,单位时间发的数据就越少

通常情况下,肯定是希望,尽可能高效的传输,但是高效前提一定是可靠性,如果发送速度太快,接收方处理不过来,此时还是麻烦事(还可能会引起丢包->不仅仅是交换机路由器会丢包,接收方这个电脑,也是可能会丢包的)

更合理的做法,应该是接收方给就要告诉发送方,你发慢一点,我顶不住了,这样的机制就称为“流量控制”,接收方根据自身的处理能力,反向制约发送方的速度,使双方达成一个“平衡”

Question:具体如何,衡量接收方的处理速度呢?


Answer:接收方,有一个接收缓冲区(阻塞队列)

以空闲空间大小作为发送方发送数据的窗口的大小,把这个数据发送给发送数据方,接收方会给发送方返回ack,在ack报文中,在TCP报头里,指定一个字段,表示上述的空闲空间大小

窗口大小只会在ack报文中生效,含义就是接收方 接收缓冲区空闲空间大小,是否意味着,此处的窗口大小,最大就是64KB,不是!!选项中包含特殊属性(窗口扩展因子)

此时发送方就可以按照上述窗口大小,决定下一轮数据发送的窗口大小了

初始情况下,接受缓冲区是4000,这个过程没考虑B的应用程序消费数据的过程,其实实际上,B的应用程序也在不停的消费数据使剩余空间变大,一旦满了,就会告诉发送方暂停发送,如果A暂停发送,B也就不会发对于的ack了,此时B缓冲区发生改变(应用程序消费了一部分数据,B如何告诉A?)此时就会周期性发送“窗口探测报文”,也是不携带业务数据(载荷)TCP数据包,主要的目的就是为了触发ack,从而知道,B这边的缓冲区的情况

上述过程非常类似于,小学的经典问题“蓄水池问题”,蓄水池就是接受缓冲区

水位的变化,就和这两边的速度密切相关,此处更复杂的是,要随时根据水位,调整发送方的速度

拥塞控制

和流量控制类似,都是和滑动窗口搭配的机制,流量控制是,站在接收方的角度,影响发送方的速度

链路上的任何一个节点,性能瓶颈都会制约发送方的发送速度,流量控制的时候,很容易定量的来衡量,接收缓冲区剩余空间大小,用这个作为发送窗口大小,考虑中间节点就复杂了,中间有多少设备?每次走的路径,都可能不一样,每个设备处理能力,繁忙程度都不一样

任凭你中间结构多复杂,TCP都把他们视为一个整体,然后通过“实验”的方式来找到一个合适的窗口大小(发送速度),刚开始,按照小的速度,小的窗口来发送数据,如果没有出现丢包(说明中间链路非常畅通),就可以增加速度,增加窗口大小,继续增加速度,此时某个设备达到瓶颈,出现丢包了,此时发送方立即减小窗口大小,继续发送,看是否还丢包,如果不丢包,再继续尝试加,如果丢包,就继续尝试减(反复再丢包的边缘,疯狂试探),这个就能找到一个合适的窗口大小的值,就可以不丢包,并且还能以比较快的速度完成传输,由于网络是复杂多变的,按照上述方式动态调整,随时适应网络中的变化

上述过程称为“拥塞控制”,拥塞控制,拥塞窗口,大小动态变化,具体是咋变的?是否有规律

  • 刚开始以比较小的窗口来传输数据(主要是因为刚开始不知道网络是否拥堵,先试试看)
  • 按照指数方式扩大窗口(这里的“慢”说的是刚开始窗口大小比较小,传输速度慢,而不是窗口大小的变化速度慢(指数增长非常快的))
  • 指数增长过程中,达到某个阈值,就要变成线性增长
  • 线性增长也是增长,发送速度越来越快,增长到一定程度,就会出现丢包,此时,发送方大概就摸到了网络大概的能力是在啥样的水平,此时就会立即把窗口变小(发送速度减下去)
  • 缩小有两种方式
  1. 直接缩到底,回到了最初慢启动的时候,接下来指数增长,线性增长
  2. 缩到出现丢包时窗口一半这样的位置,接下来线性增长

延时应答

ack不会立即返回,而是稍等一会再返回,为啥要延时,核心目的,提升传输的效率,决定传输速度最关键的因素,窗口大小,在能够承受的前提下,尽可能的提高窗口大小,通过延时,就可以使窗口大小得到提升,延时就是给应用程序,腾出来更多的消费空间

总量5KB,收到1KB数据,还剩下4KB,如果立即返回ack,ack中的窗口大小就是4KB,如果延时一会再返回ack?在延时的时间里,应用程序和可能就把刚才的数据消费了一部分(应用程序读了512个字节的数据)接收缓冲区还剩下512字节了,此时返回的窗口大小4.5KB,这里的变大其实也是和应用程序的处理能力直接相关的,合理范围内的变大

此处延时时间,有两种方式

  • 按照一定的时间来指定延时
  • 按照收到的数据量

捎带应答

建立在延时应答的基础之上,提升效率的机制

日常开发中,客户端服务器之间的通信,经常是“一问一答”这样的模型

正常来说,ack是内核收到请求,自动返回的,响应数据,应用程序代码执行一系列逻辑之后返回的,由于延时应答的存在,ack不一定立即返回,在ack稍等一会的时候正好就要返回响应数据,此时就在响应数据中,TCP中的ack这一位置上,把确认序号/窗口大小都设置上

面向字节流,粘包问题

在字节流读写数据的场景中,会涉及到一个非常关键的问题:粘包问题

假设服务器是一个翻译服务器,就需要把英文转成中文,服务器调用read来读取请求,由于字节流的特点,读的时候咋读都行,一次可以读一个字节,也可以一次读若干个字节,此时服务器无法区分,从哪里到哪里是一个完整的单词

此处粘的是,应用层的数据包,主要是需要区分,从哪里到哪里是一个完整的应用层数据包,明确包之间的界限

  • 使用分隔符,定义任意字符都可以,只要字符在请求数据中是不存在的
  • 约定包的长度

json,xml,yml都属于是基于分隔符的方式来进行区分的,protobuffer按照长度的方式来区分,http即会用到长度,也会用到分隔符

粘包问题不仅仅是TCP问题,读写文件也会涉及到

异常情况

其中某一个进程崩溃了

进程崩溃也好,正常结束也好,操作系统,都能够回收释放对应的PCB,可以释放里面的文件描述符表也就相当于调用close.

此时仍然会正常和对方进行四次挥手操作,之前写的客户端服务器代码,客户端如何退出?就是强制杀死进程(和进程崩溃本质是一样的)

四次挥手,能挥完,虽然进程不在,操作系统仍然管理着tcp的连接可以顺利和对方挥完

某个主机被关机(正常流程的关机)

对于这种正常流程的关机,操作系统会先尝试强制结束所有的用户进程,然后再进入关机流程,这个过程也会和上面一样,结束进程之后,进行四次挥手,下面这个情况,可能挥完了,也可能没挥完,主动触发FIN了,之后的流程没走完,系统就已经关机了,A和B建立TCP连接,A这边关机了,A关机之前,告诉B,FIN,B这边收到了 FIN, B返回 ACK,代码进入下一阶段流程,准备发送 FIN,此时如果A已经关机了,意味着B接下里的FIN就会反复重传几次,此时B反复重传几次,没有ACK,还是会继续吧A给删掉了,A都已经关机了,之前保存的B的消息,自然也没了(内存)

某个主机电源掉电

A和B通信,A突然掉电了,A无法做出任何反应,就无了,B还傻傻的以为A存在呢

  • B是发送数据方:
  • B写下来发的数据,都不会有ack了,B就会触发超时重传,重传几次之后,发送复位报文(RST),RST也没有响应,B就会单方面删除保存A的信息
  • B是接收方:
  • 接受方无法知道对方啥时候给我发数据,当A沉默了之后,B也不知道A是暂时暂停一会还是A挂了,B在一定时间之内没收到A的数据之后,就会触发心跳包(有周期,没有心跳就挂了)

心跳包就可以认为是一个没有载荷的数据包,只是为了触发ack,B给A发了一个心跳包,如果A正常,A就会回应ACK,如果A挂了,B不会受到任何回应,连续发了若干次,A都没有回应,这个时候B就认为A挂了,于是单方面释放连接


TCP虽然内置了心跳包,但是这个心跳包,周期比较长,指望通过这个心跳发现对端挂了,往往需要分钟级别这样的时间,在实际开发中,经常会实现应用层的心跳包,用更高频率,更短周期,一旦某个设备挂了,就可以更快速的发现问题

网线断开

本质上就是第三种情况,A和B之间建立TCP连接,A和B之间的网线断开了,比如A是发送方,B是接受方,A的角度,就会触发超时重传,触发RST,单方面删除信息;B的角度,就会触发心跳包,对方无响应,单方面删除信息

总结:

TCP的10个主要特性

  1. 确认应答
  2. 超时重传
  3. 连接管理
  4. 滑动窗口
  5. 流量控制
  6. 拥塞控制
  7. 延时应答
  8. 捎带应答
  9. 粘包问题
  10. 异常情况
上一篇:鸿蒙NEXT开发-自定义构建函数(基于最新api12稳定版)


下一篇:Token: 数据库、存储系统和API安全的应用