TCP的可靠机制

TCP的可靠机制

前言

要了解TCP的可靠机制,我们必须要先熟悉TCP的报文,在这篇文章中有详细介绍TCP的报文 :

并且确认应答机制也在该文章中提到,所以这篇文章就不会再介绍确认应答了。

超时重传

我们都知道,报文在网络中传输是有可能丢包的。如果丢包了,那么TCP又该如何处理?

首先我们要分为2种情况,1种情况是数据丢了,一种情况是服务器发的应答丢了。

情况1.数据丢了

在这里插入图片描述

这个问题很好解决,因为TCP有确认应答机制。收到了一条数据那么就必须要发送一个应答告诉对方收到了这条报文。如果没有收到,那么发送数据发会认为自己的数据并没有被收到,此时间隔一段时间(超时时间)进行报文重传。

情况2.应答丢了

这种情况就是数据已经到达了对端,但是对端给的应答丢了。

在这里插入图片描述

这情况发送数据方依旧在超时时间后重新发送报文。因为无论是应答丢了还是数据丢了,发送方是不知道的。而当应答丢了的时候,又发送了一则数据。那么对端就收到了重复的报文,此时对端会通过32位序列号对报文进行排序,如果序列号相同的则会进行去重。

超时时间

知道了在数据或者应答丢失的情况下,发送方会隔一段时间进行重传。 那么这 一段时间 是多久呢?

如果超时时间设置太短,那么可能会发送重复的包。

如果超时时间设置太长,那么又可能造成效率问题。

并且网络中的环境也是即使变化的。

所以TCP为了保证无论在任何坏境下都能比较高性能的通信,会动态计算这个最大超时时间。

在Linux中,超时以500ms为一个单位进行控制,每次判定超时重发的超时时间都是以500ms的整数倍。

第一次重发的时间是500ms ,如果第一次重发后没有得到回应,那么第二次就会第 2 * 500ms进行重发。第三次则是 4 * 500ms… 直到到达一定次数时,TCP认为网络或者对端主机出现异常,强制关闭连接。

连接管理

TCP是面向连接的,TCP每次与一个客户端连接成功,那么就会创建一个连接。那么这个连接要不要管理?要!怎么管理?先描述,再组织。创建一个描述连接的结构体,由操作系统进行管理。也就是每一个连接都有自己对应的数据结构!每一个连接都占用着操作系统的一份资源!

上面说的连接建立成功之后的情况,那么如何建立连接?

三次握手

在正常情况下,TCP连接是要经过三次握手的过程。断开时要经历四次挥手过程。而三次握手过程必然是客户端先发起的,不存在有服务器去连接客户端的情况。你好比你访问了一个页面,那么访问请求肯定是先从你的浏览器发起的。因为服务器根本不知道你的IP和端口,只有你先发送报文过后,服务器才能获得你的IP + 端口。服务器和客户端是1 : N 的关系。

三次握手过程

1.客户端向服务器发送带有SYN的报文,并自己处于SYN_SENT状态

2.服务器收到了客户端发送的SYN报文,此时服务器处于SYN_RCVD状态,并向客户端发送SYN+ACK

3.客户端收到服务器发送的SYN+ACK报文,此时客户端建立连接成功,此时客户端处于ESTABUSHED状态,并向服务器发送ACK

4.服务器收到客户端建立连接成功后发送的ACK报文,知道了客户端建立连接成功,此时服务器处于ESTABUSHED状态

在这里插入图片描述

当到达第四步的时候!客户端与服务器之间的连接就已经建立好了!而accept函数是从底层获取连接,而不是建立连接的函数!

为什么是三次握手,而不能是一次,两次,或者四次?

先来说为什么不能是一次握手

如果是一次握手的话,客户端只要发送连接,服务器就认为连接成功了。那么客户端是不是可以同时发送大量的SYN连接请求,而服务器每收到一个连接请求就建立一个连接。因为每建立一个连接就要消耗一份系统资源,而客户端那边发起连接请求几乎没有成本。所以服务器最终会不会因为连接请求过多而导致崩溃呢?答案是会的!

而同时发送大量SYN连接请求这种行为,也被称为SYN洪水攻击

为什么不能是2次握手?

2次握手和1次握手的本质是一样的。只要客户端发送SYN,服务器就认为已经连接成功了!至于ACK,客户端压根可以不关心!2次握手照样可以通过SYN洪水攻击让服务器产生大量的连接最后导致服务器崩溃。而客户端这边几乎没有成本,因为只是仅仅发送一个SYN报文而已。

在这里插入图片描述

三次握手为什么又可以了呢?

一次握手和2次握手的本质是不清楚服务器和客户端的收发能力!

一次握手的时候,服务器知道客户端有发送数据的能力和自己有收数据的能力,但不知道自己发送数据的能力和客户端接收数据的能力

二次握手客户端知道服务器有收数据和发送数据的能力,服务器知道自己有收数据的能力,但不知道自己有发数据的能力

而三次握手,服务器和客户端都自己彼此的收数据和发送数据的能力。

在这里插入图片描述

并且我们可以看到,客户端在收到服务器发来的ACK+SYN后,就已经建立了连接!此时客户端再给服务器发送ACK,服务器收到ACK才开始建立连接。 这就意味着,客户端在服务器之前建立连接!也就说明了,客户端建立连接是有成本的!这样一来,客户端和服务器就都要维护等量的连接,一旦你客户端连接过多,对端也可以进行相应的判断处理。

TCP能保证安全吗?

很遗憾,不能。三次握手只能让一台主机和服务器承受同等的连接压力。但无法抑制多台主机的同时访问。假设有1W台主机,同一时间给你的2核2G服务器发送连接请求。那么你的服务器就同时来了1W个连接,最后因为超负荷饮恨被操作系统KILL掉。而这种攻击手段想必大家都听过,就是DDOS攻击

为什么不是四次握手?

三次握手可以做到的事情,为什么要四次握手呢?多一次握手就相当于要多发一条报文,而全世界那么多人每天都在使用网络。无论是四次握手,五次还是六次,都是对网络资源的一种浪费。就好比有两家小卖铺摆在你面前,同一样东西,一家只要1块钱,另一家要10块钱。肯定选1块钱的那家啊。人傻钱多当我没说=。=

四次挥手

建立连接时有三次握手,那么关闭连接也会有四次挥手(你拔电脑电源让电脑秒关机不算)。而四次挥手的过程和三次握手类似。只不过三次握手必须由客户端发起,而四次挥手无论是客户端还是服务器都可以发起。所以下面我们假设是客户端发起四次挥手。

四次挥手过程

1. 客户端关闭连接(close fd),状态变成FIN_WAIT_1 ,并向服务器发送携带FIN被置为1的报文

2. 服务器收到客户端的FIN报文,状态变为CLOSE_WAIT,并向客户端发送ACK确认。

3. 客户端收到ACK后,状态变为FIN_WAIT_2

4. 服务器端Close连接后,向客户端再次发送FIN报文,并状态被置为LAST_ACK

5. 客户端收到服务器发送的FIN,进入TIME_WAIT状态。并向服务器发送ACK

6. 服务器收到ACK,四次挥手过程结束

在这里插入图片描述

当我们的客户端断开连接时,也就是调用了close(fd),那么就会向服务器发送FIN,并把自己置为FIN_WAIT_1。此时的服务器处于CLOSE_WAIT状态。

CLOSE_WAIT

如果我们的服务器不进行close(fd)关闭文件描述符,那么就会造成该连接一直处于CLOSE_WAIT状态而无法释放。而客户端也因为没有收到服务器发来的FIN,一直处于FIN_WAIT_2状态。直到超时被关闭。

在这里插入图片描述

TIME_WAIT

当发起断开连接的那一端收到对端发来的FIN时,则会向对端发送一条ACK。自己处于TIME_WAIT状态,一般TIME_WAIT状态会持续2MSL(报文最大生存时间)。因为此时关闭连接端向对端发送的ACK是有可能丢包的,所以需要处于TIME_WAIT状态一段时间。如果ACK报文丢包了,则会进行重传。

在这里插入图片描述

所以这也就可以解释了,为什么我的服务器有时候不能立即重启。因为当服务器主动断开连接时,如果此时有客户端还没有关闭,那么服务器会处于TIME_WAIT状态,此时重启就会绑定出错,我们可以用地址复用的方法来解决。

滑动窗口

如果我们发送数据,在没有收到应答之前,是可能出现丢包的情况,如果丢包,就要超时重传。为了支持超时重传,我们是不是要把数据保存起来?保存在哪里呢? 保存在TCP的发送缓冲区

**这个发送缓冲区至少被分成三个区域,第一段区域是已经发送&&收到应答的数据,第二段区域是已经发送,但还没有收到应答的区域。第三段是数据尚未发送的区域。**当然还有第四段,空区域,新发送的数据会被拷贝到这段区域。

在这里插入图片描述

而中间那段已经发送但没有收到应答的区域,被称之为滑动窗口

1.如何看待滑动窗口?

发送缓冲区的本质就是一个数组,而滑动窗口则代表的一段区间,我们可以用win_start和win_end两个指针来表示这段区间。在这里插入图片描述

2.滑动窗口的开始大小是怎么设定的?之后怎么变化?

很简单,我们上面说了滑动窗口是win_start - win_end的区间。那么我们开始就把 win_start = 0,win_end = win_start + tcp_win(16位窗口大小的值)。之后无论滑动窗口怎么滑动,都必须保证在对方的接受能力范围之内。

**窗口的大小就等于对方的接受能力,也就是tcp_win16位窗口大小字段(不考虑网络拥塞的情况下)。 **

3.窗口一定会向右滑动吗?会向左滑动吗?

首先,滑动窗口左边的内容是已经发送并且收到应答了的,再次发送没有意义,所以滑动窗口不会向左滑动

滑动窗口大部分情况下是会向右滑动的,但是如果对方的接收能力为0呢?无法往对方发送数据,所以此时的滑动

窗口是不变的。所以滑动窗口可能向右滑动,也可能保持不变

4. 滑动窗口会一直不变吗?会变大吗?会变小吗?为什么?

滑动窗口可能会不变,但不可能一直不变!

会变大吗? 会! 当对端的上层突然把缓冲区数字拿走时,对端的接收能力大大提升,当发送端重新获得对方的窗口大小时。滑动窗口也会跟着变化,因为对方的接受能力变强,所以发送端的滑动窗口也会变大!

会变小吗?会!当对端的上层迟迟不拿数据,而发送端不断的发数据,那么win_start就会越来越大,而win_end却没有变,这也就意味滑动窗口在慢慢变小。直到最后对端的缓冲区被打满了,发送端的滑动窗口也会变为0。

5…收到应答确认的时候,如果不是最左侧发送的报文的确认,而是中间的,结尾的怎么办?要滑动吗?

这种时候我们要分两种情况,一种是应答丢了,数据没丢,一种是数据丢了。

应答丢了,数据没丢

如果是应答丢了,数据没丢的时候,没关系。因为TCP有32位确认序号。即使的前面的应答丢了,但是后面的应答的32位确认序号填的是后面数据的,那么发送数据方就知道确认序号之前的数据已经全部被对方收到了。

在这里插入图片描述

所以应答丢的情况,滑动窗口会照样滑动。

数据丢了

TCP是有32位序号的,在收到一批报文后会对报文进行排序。如果中间出现丢包的情况,那么确认序号就不能填收到的,而是丢包之前的那一个。假设现在收到4个报文 0 - 1000 , 1001 - 2000 , 3001 - 4000,而在对报文进行排序时发现中间的 2001 - 3000丢了,那么 3000之后的报文应答的确认序号只能填 2001 ,当对端连续收到3个2001时,就知道自己 2001 - 3000的报文丢包了。所以就会进行重传。

在这里插入图片描述

当收到好几个重复的确认序号时,发送端就知道自己之前的报文丢包了,所以会进行重传机制。而滑动窗口也只是滑到2001的位置。并且重新发送数据。

6. 滑动窗口会一直向后滑动吗?如果空间不够了怎么办?

从物理结构上看,滑动窗口是一个数组。但是从逻辑结构上看,滑动窗口是一个环形结构。我们可以通过 %数组的长度,来实现一个环形结构的数组。

流量控制

流量控制其实在上篇文章介绍16位窗口大小就已经提到过,通过16位窗口大小知道对方的接收能力。但这还不够,因为我们考虑到了对方的接收能力,但却没有考虑的网络的接收能力。如果网络当前坏境较差,发送大量数据会加重网络环境。那么此时只能按照网络能接收的能力来,而这一块我们会在下面拥塞控制中得到。所以滑动窗口的大小 = Min(对方接收能力,网络接收能力)

拥塞控制

前面我们一直在说客户端与服务端之间的可靠性。可是你有没有想过,无论是哪端发送数据,数据都是要在网络中传输的。如果网络出现问题了呢?如果网络因为大量报文而造成网络拥塞了呢?那么TCP也要有一种机制来保证在网络中传输的可靠性,这种机制就是拥塞控制

TCP如何判断网络拥塞

我们都了解当报文丢失时会触发超时重传。但这是少量报文丢失的情况,如果大量报文丢失的情况呢?这时候也要进行重传吗?答案是绝对不可行!因为此时网络可能已经出现问题了!本来就因为大量数据而造成网络拥塞,而你此时又重传大量的报文。要知道,全世界那么多主机,大部分都是使用的TCP协议。也就是说如果每台主机都去重传大量的报文,那么就会造成网络瘫痪。

举个例子:

当你们学校考试,假如考的是数据结构这门课。如果全班50人只有1-2个人挂科了,那么你会觉得这种情况是正常的。而如果只有1 - 2 个人没挂科,那么你们一定会认为这一定是老师的问题,老师出的题太难太偏。

所以TCP判断网络拥塞也是这样一个道理,当你发了1000个报文,丢失了3个报文。那么会认为是正常的丢包,进行超时重传。如果丢失的是999个报文,这时候就绝对不能进行重传了!因为这种情况肯定是网络发生拥塞,要进行拥塞控制。

TCP的慢启动机制

慢启动机制就是先发送少量数据探探路,摸清当前的网络状态,再决定按照多大的速度传输数据。

  • 引入一个概念,拥塞窗口(cwnd)
  • 发送刚开始的时候,拥塞窗口大小为1
  • 每收到一个ACK应答,拥塞窗口 + 1。相当于每次发送的报文 * 2
  • 每次发送数据包的时候,将拥塞窗口和对端发来的窗口大小作比较,取较小值作为实际发送的窗口

像上面这样的拥塞窗口速度,是指数级别的,刚开始很慢,但后面会非常快。所以我们要引入一个慢启动的阈值 ,这个阈值被称为ssthresh。

在这里插入图片描述

TCP刚开始启动的时候,阈值为滑动窗口的最大值。拥塞窗口则为1,此时拥塞窗口呈指数级增长,直到增长的阈值时呈线性增长。在线性增长过程中如果遇到了网络拥塞,则会进行乘法减小。把阈值更改为遇到网络拥塞时的一半,拥塞窗口从1开始继续慢启动。重复上面的步骤。

遇到网络拥塞时,是不能发送太多数据的,所以从1开始发送数据进行慢启动,阈值之前呈指数增长,阈值之后呈线性增长。前期的指数增长速度其实是比较慢的,可以有效的缓解网络的压力。而中后期网络恢复,又可以快速的恢复通信。因为指数增长后期非常的快,所以引入了阈值,超过阈值则进行线性增长。

当然,如果网络非常的好,而发送的数据又不多的话。拥塞窗口会一直缓慢增长,但这并不是大问题。因为实际的发送窗口是对端窗口大小和拥塞窗口的较小值。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://xiahunao.cn/news/3280863.html

如若内容造成侵权/违法违规/事实不符,请联系瞎胡闹网进行投诉反馈,一经查实,立即删除!

相关文章

详解Qt 之QMdiArea 和 QMdiSubWindow

文章目录 前言QMdiArea概念作用为什么需要 QMdiAreaQMdiArea 的主要函数和成员函数列表 QMdiSubWindow概念作用为什么需要 QMdiSubWindowQMdiSubWindow 的主要函数和成员函数列表 示例代码 更多用法... 总结 前言 在复杂的应用程序中,尤其是那些需要同时管理多个子…

RabbitMQ快速入门(MQ的概念、安装RabbitMQ、在 SpringBoot 项目中集成 RabbitMQ )

文章目录 1. 补充知识:同步通讯和异步通讯1.1 同步通讯1.2 异步通讯 2. 同步调用的缺点2.1 业务耦合2.2 性能较差2.3 级联失败 3. 什么情况下使用同步调用4. 异步调用5. 异步调用的优点和缺点5.1 异步调用的优点5.1.1 解除耦合,拓展性强5.1.2 无需等待&a…

SQL必知必会

SQL必知必会 一些SQL知识,出自极客时间陈旸老师《SQL必知必会》 https://time.geekbang.org/column/intro/100029501 基础 视图 视图作为一张虚拟表,帮我们封装了底层与数据表的接口。它相当于是一张表或多张表的数据结果集。视图的这一特点&#x…

DMB,DSB,ISB三个指令区别

此部分说明三个指令的具体区别(在指令流水线上说明),这三个指令主要目的在于确保程序在多处理器环境下的稳定性和一致性,避免由于指令乱序和内存操作重排引起的不可预测行为 一个简化的流水线,包含以下阶段&#xff1…

【git】git常用命令提交规范

Git 是程序员工作中不可或缺的版本控制工具,以下是一些优化后的常用 Git 命令列表,旨在帮助你更高效地使用 Git 进行版本控制。 基础操作 拉取代码 git clone xxx.git创建分支 git branch dev切换分支 git checkout dev # 或者 git switch dev创建并切换…

Mirror学习笔记(一) 简介

文章目录 一、常规学习:Mirror核心功能有服务器和主机 二、时间戳批处理时间戳 三、TCP和UDP四、CCU(同时在线人数)五、SyncDirection(同步方向)六、RTT(往返时间)七、Connection Quality(连接质量)八、Lag Compensati…

Android mLruProcesses的分布结构

AMS中的进程管理 final ArrayList<ProcessRecord> mLruProcesses new ArrayList<ProcessRecord>(); 在AMS的内部属性中使用mLruProcesses集合保存所有的进程信息&#xff0c;AMS将所有进程按照优先级从低到高的顺序保存着对应的ProcessRecord信息&#xff0c;即排…

25、Python之面向对象:私有属性是掩耳盗铃还是恰到好处

引言 声明&#xff0c;今天的文章中没有一行Python代码&#xff0c;更多的是对编程语言设计理念的思考。 上一篇文章中介绍了关于Python面向对象封装特性的私有属性的相关内容&#xff0c;提到了Python中关于私有属性的实现是通过“名称混淆”的方式来实现的&#xff0c;我们…

【Python体验】第五天:目录搜索、数据爬虫(评论区里写作业)

文章目录 目录搜索 os、shutil库数据爬虫 request、re作业&#xff1a;爬取案例的top250电影的关键信息&#xff08;名称、类型、日期&#xff09;&#xff0c;并保存在表格中 目录搜索 os、shutil库 os 模块提供了非常丰富的方法用来处理文件和目录。 os.listdir(path)&#x…

连环画:80、90后的童年记忆与副业项目的AI新玩法

在那个纯真的年代&#xff0c;当80、90后的孩子们还在为学业忙碌之余&#xff0c;一种名为连环画的读物成为了他们心中难以磨灭的记忆。 这些由一幅幅精美插图串联起来的故事&#xff0c;不仅满足了他们对知识的渴望&#xff0c;更在无形中丰富了他们的想象力和审美能力。在那…

智云-一个抓取web流量的轻量级蜜罐

智云-一个抓取web流量的轻量级蜜罐 安装环境要求 apache php7.4 mysql8 github地址 https://github.com/xiaoxiaoranxxx/POT-ZHIYUN 系统演示

Xilinx FPGA:vivado SPI实现FLASH通信

一、实验要求 要求使用SPI协议实现对flash芯片的页编程、读操作、页擦除等功能。 二、模块划分 大概的时序图&#xff1a; 三、程序设计 &#xff08;1&#xff09;接收端模块 timescale 1ns / 1ps module uart_rx(input sys_clk ,input …

ShardingSphere实战(2)- 水平分表

项目环境&#xff1a; JDK11 MySQL 8.0.30 Springboot 2.7.4 Mybatis ShardingSphere HikariCP 连接池 一、Maven 依赖 <parent><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-parent</artifactId><versi…

C++ | string

前言 本篇博客讲解c中的string类的使用(常用接口) &#x1f493; 个人主页&#xff1a;普通young man-CSDN博客 ⏩ 文章专栏&#xff1a;C_普通young man的博客-CSDN博客 ⏩ 本人giee:普通小青年 (pu-tong-young-man) - Gitee.com 若有问题 评论区见&#x1f4dd; &#x1f389…

Redis持久化之RDB和AOF详解

持久化是确保 Redis 数据在服务器重启或崩溃时不丢失的关键功能。由于 Redis 是基于内存的数据库&#xff0c;如果不进行持久化&#xff0c;所有数据都存在于内存中&#xff0c;一旦服务器进程退出&#xff0c;内存中的数据就会丢失。持久化机制可以将 Redis 的数据库状态保存到…

C# Unity 面向对象补全计划 之 访问修饰符

本文仅作学习笔记与交流&#xff0c;不作任何商业用途&#xff0c;作者能力有限&#xff0c;如有不足还请斧正 本系列旨在通过补全学习之后&#xff0c;给出任意类图都能实现并做到逻辑上严丝合缝

vue3项目结构梳理:

总览 1.vscode文件&#xff1a; 通常用于存放Visual Studio Code编辑器的插件的配置 2.node_moudles文件夹&#xff1a; 这个文件夹包含了项目所需的所有npm依赖包。&#xff08;需要在根目录下执行npm i命令安装这个文件夹&#xff09; 或者在项目根目录&#xff08;packa…

postgresql密码复杂度验证和有效期

前言 为了数据库安全以及应对等保测评等要求&#xff0c;我们需要设置密码复杂度。我们通过passwordcheck模块实现复杂度检测功能。 启用密码复杂度验证 找到自己安装pg库的配置文件目录&#xff0c;修改postgresql.conf vim postgresql.conf修改如下内容 shared_preload_…

中国十大顶级哲学家,全球公认的伟大思想家颜廷利:人类为何拥有臀部

人类为何拥有臀部&#xff1f;若众生皆无此部位&#xff0c;又如何能寻得一处真正属于自己的“座位”&#xff1f;在博大精深的中国传统文化中&#xff0c;汉字“座”与“坐”均蕴含“土”字元素。在易经的智慧里&#xff0c;作为五行之一的“土”&#xff0c;象征着人类社会的…

MySQL8--用户与权限管理

原文网址&#xff1a;MySQL8--用户与权限管理_IT利刃出鞘的博客-CSDN博客 简介 说明 本文介绍MySQL8的用户与权限的管理&#xff0c;包括&#xff1a;用户的创建与删除、授权与撤销权限等。 为什么要管理用户与权限&#xff1f; 目的是保证数据库的安全性&#xff0c;只授…