当前位置: 首页 > news >正文

TCP 的演化史-fast retransmit/recovery

工作原因要对一个 newreno 实现增加 sack 支持。尝试写了 3 天 C++,同时一遍又一遍梳理 sack 标准演进。这些东西我早就了解,但涉及落地写实现,就得不断抠细节,试图写一个完备的实现。

这事有更简单的方法。根本没必要完全实现 RFC,目标是高吞吐,而不是实现标准 TCP,因此只要保证最宽容的可用性,剩下的交给现实。我们做 cc 时,为提高性能,何尝不是这里 cwnd += 10,那边 cwnd += whatever(sk),异曲同工。

前面两周写了一些关于 TCP 演进的文字,无论怎样,这是个好机会以一个实例来展示演进过程中的方法论。捕捉其中关于 sack 的两个细节,降窗和重传,再写一些文字。

TCP 丢包后进入 fast retransmit/recovery 后涉及如何降窗和如何重传两件事,TCP 经过 40 多年的演化,关于这两件事铸建了 4 个里程碑。

最初的 TCP 只有 rto,没有 fast retransmit,此即 TCP tahoe,只要有丢包,即将 cwnd = 1,重新开始慢启动。第 1 个里程碑是 TCP reno 和 TCP newreno,二者一脉相承。

reno 引入了 fast retransmit,但有很多问题,newreno 解决了这些问题。这部分参见 Wiki。seastar 只实现了 RFC6582 定义的 newreno,非常原始,但基本上这就是一个最小能用的 TCP 实现。

从第 2 个里程碑开始,TCP 开始起飞。我的工作也就是尽量追着这个尾迹跟着飞,在必要时加入一些自己的 trick,或觉得标准实现太麻烦时偷一下懒裁剪掉些东西。

引入 fast retransmit 时 TCP 还不支持 sack,收到 3 个 dupack 即进入 fast retransmit/fast recovery,但只留下一个未被 ack 的 hole,hole 后面的情况什么也看不到,没有任何启发式手段让 sender 猜测哪些 seg 丢了。

降窗如 范雅各布森 所述,直接将 cwnd = ssthresh = cwnd / 2。RFC5681 不允许超过一半的 seg 在途(这似乎是在迎合或确认 ssthresh = cwnd/2 这件事,详见:雅各布森管道):

until all lost segments in the window of data in question are repaired, the number of segments transmitted in each RTT MUST be no more than half the number of outstanding segments when the loss was detected.

这显然降低了带宽利用率,但这时没有任何信息指示如何做得更好(每发送一个 seg 只能至多带回 1 个 ack),解决这个问题的条件尚未具备。

关于重传,除了一个 hole 没有任何辅助信息,只能从 una 开始每次重传 1 个往前挪 nua,没有任何别的办法。如下图所示,整个图景相当于一个 seq space 构成的一维世界,一 hole 以障目:
在这里插入图片描述
随着 sack 被引入,降窗和重传逻辑均起了大变化。我们关注的是这个变化是如何如丝般顺滑而一气呵成的,以便这个方法论能指示 TCP(or any others) 未来的演化方向。

第 3 个里程碑来自 sack 被引入后。详见 RFC6675。 sack 反馈了更加丰富的信息,sender 有了绕到 hole 后面查看究竟的途径,这相当于引入了额外的维度,将 seq space 直立了起来,仰起头就能看到 seq space 的细节:
在这里插入图片描述
TCP 将 seq space 抽象成一个 scoreboard,该 scoreboard 上对 seq space 的每一个 seg 区分对待,可 mark sacked,mark lost,mark reordering,针对 lost seg 进行重传并 mark retrans,inflight 从而可精确获取:

inflight = snd_nxt - snd_una + snd_retrans - snd_lost - snd_sacked

基于守恒律,于是 cwnd = inflight 更加精确。

二维世界丰富的多的信息指示 sender 做出更好的决策,不再每次 retransmit 一个 seg,TCP 重传算法第一次统一处理重传 seg 以及新 seg,它们统一受 cwnd 限制:cwnd - inflight > 0。

重传逻辑将 seg 分三个优先级,mark lost 的 seg 被优先传输,因为它们最容易补 hole 而推进 una,新 seg 第二优先,它们可能带回新的 sack,推进 mark lost,背后的考虑是尽量延后 mark lost,而剩余的最后重传:
在这里插入图片描述
下面再看降窗逻辑。

重传 seg 和新 seg 在 fast retransmit/recovery 状态被统一安排,一个 seg 带回的 sack 可导致大量 mark lost,参考前面 inflight 公式,大量 seg 由于 mark lost 被清出 pipe,inflight 急剧减少而腾出大量 pipe 空间。cwnd 一定时,后果便是 burst(可 burst 一个 cwnd = ssthresh 的数据)。而 burst 会加剧网络拥塞而丢包,形成正反馈。

TCP 第一次以 ack 携带信息(sack)而非 ack 本身来计数,引入了 scoreboard 细化了 seq space,但处理降窗的方式却没有同步跟进处理这个 burst 问题。

有两个更平滑降窗的方案,第一个是 Linux TCP 的 rate-halving 算法。rate-halving 思路很简单,不再一下子将 cwnd 折半,而是每收到 2 个 ack 将 cwnd 减 1,这样收到一个 cwnd 的 ack 后,cwnd 即原来的一半了。

但彼时 cc 已模块化,丢包降窗的目标可自定义,比如 cubic 将 cwnd 降为 0.7*cwnd,而 rate-halving 写死了比例 0.5,且未考虑 sack 计数而精度不够(sack 大大提高了判断精度,rate-halving 却没有利用任何 inflight 信息)。 后来 Google 提出了 prr 算法,将 cwnd 精确平滑收敛到 ssthresh = (1 - beta)*cwnd。

思路和 rate-halving 类似,只是 prr 基于 scoreboard 精确计算 fast retransmit/recovery 期间被 ack/sack 的数据 prr_delivered 和实际发送的数据 prr_out 以获得收益。按照正常的 seg 守恒律:

cnt = prr_delivered - prr_out

结果应为 0,意思是 prr_delivered 被确认之后才能兑换等量的 prr_out 而发出,按比例缩放这个守恒律即可:

cnt = (1 - beta)*prr_delivered - prr_out

以 beta = 0.3 为例,意思是 0.7 个单位的 prr_delivered 被确认后兑换 一个单位的 prr_out,为了满足守恒律使结果为 0,prr_out 也要进行 0.7 缩放,因此必须从 cwnd 中扣除这个差异:

cwnd = inflight - |cnt|

由此在一个 rtt 内,sender 缓慢地,平滑地,等比例地将 cwnd 降到 (1-beta)*cwnd。

降窗逻辑看起来很 perfect。

让我们回看引入 sack 后的第 3 个里程碑的重传逻辑,看它有什么问题不能解决,并且该问题还必须被解决。

随着带宽资源的增加,传输协议对带宽利用率有所期待,而不再仅仅满足于保证可用性的 AIMD 算法。需实时测量的 rate-based cc 在这个背景下被设计。

与 cwnd-based cc(cwnd 配额用尽即不可再发送) 不同,实时测量考虑 delivery rate 反馈而非仅仅 ack 时钟,需要 sender 有持续数据流可发送,即使 rwnd 憋死(矢量滑动 rwnd 导致,我对此有单边解法),在收不到重传 seg 的 sack 后,要再次重传该 seg(这在某种意义上确实可以取消 rto 了,但这是后话)。

RFC6675 标准 sack 重传机制涉及两个细节,一个是 reordering 更新,一个是依赖前者的 mark lost,先看 reordering 更新:
在这里插入图片描述
reordering 根据 sack 的布局和批次不断被更新,关于 reordering 的细节,详见早期的一篇分析:TCP 的乱序&丢包判断。

基于此,再看 mark lost:
在这里插入图片描述
这两个机制揉在一起非常复杂。

RFC6675 的算法显然无法满足多次重传相同 seg,因为 sender 的 scoreboard 没有信息区分多次 mark lost 被重传的 seg,这就无法在 mark lost 和 mark reordering 之间做判断:
在这里插入图片描述
每个 seg 只能 mark lost 一次,如果重传丢了,只能 rto。

第 4 个里程碑就来了。

在 sack 引入的额外维度后,一维 seq space 展成了二维,sender 可以从额外的维度绕过 hole 看到信息量更大的 scoreboard。寻着同样的思路,再加一个维度,为传输加上时间轴,这就是 rack:
在这里插入图片描述
两根坐标轴就搞定了所有事情,不再需要额外的两个过程,虽然也依然会有由于 reordering 对 rack window 所做的调整,但相比更新 reordering 值本身的逻辑就太简单和直观了。

看一下清爽的 rack 全景:
在这里插入图片描述
sender 维护一个统一安排 mark lost 重传 seg 和新 seg 的按发送时间 fifo 队列。每一个 hole 只要在一定时间(比它后发送的都被 ack/sack,而它在此一定时间后仍是 hole)内没被 ack/sack 可以重新被 mark lost,与其是否被重传过以及重传过几次无关。每次传输都会将时间戳打入,以区别传输轮次。

就这样,rack 引入一个时间序解决了 reordering 和 mark lost 歧义的问题。

rack 靠时间驱动,即时间序,而此前的方法则是在 seq space 上根据字节序关系靠启发(且看 Linux TCP 的 update scoreboard)驱动,再往前,则连启发都没得启发了,因为根本没信息。过程的发展非常柔滑,值得再次总结。

一维世界,一个 hole 就堵住了,sack 相当于二维平面,sender 可绕到 hole 后看情况,顺着往后,用时间编码发送顺序,就是 rack,sender 在一个三维看板看到的信息更详细,从而可做出更精确合理的判断。

接下来的事情尚未发生(或者发生了一点点),但按照上面的推理逻辑,它应该会发送。

rack 就像一架引擎,只要 ack 时钟不断,在丢包时亦始终有 seg 被发送,这些 seg 将带回驱动引擎的进一步的 ack 流,该策略非常适合高速网络,发送引擎不再区分 seg 类型,与 scoreboard 解耦,rack 将代替任何基于重复 ack/sack 的启发算法 mark lost,发送引擎维持高速运转,源源不断提供数据用于实时测量。

BBR 即使用 rack 作为自己的丢包探测驱动。

随着带宽资源进一步丰富,类似 BBR 的算法未竞全功。由于 TCP 没有打包直接传输 seq space 字节流,导致无边界确认处理非常复杂,由此带来了 undo 歧义和 ack 歧义问题,不必要重传,不准确的 rtt 测量和 delivery rate/cwnd 计算都是其影响。这些复杂且不精确的处理是高带宽利用率的绊脚石。

问题根源在于,正向传输的 seg 是 seq space 的矢量字节流,而 ack/sack 指导 cwnd 更新的只有标量 account,丢失了 seg 传输顺序。换句话说,sender 发送的 seg 是按序的,而 sender 接收到的 ack/sack 却没有信息可还原对应 seg 的顺序。

按以往惯例,既然 rack 已将时间序编码到了 sender 本地,只需将该时间序同时编码到 seg 本身,这样 seg 将引导 ack 反馈更丰富的 receiver 端信息,该信息与 sender 的本地传输时间序进行比对,就什么都有了。过年期间,我曾想了一个方法,详见:重新设计 TCP。下面是对该文字的一点前置分析。

如果 seg 携带 1 bit 额外信息,就能区分一次重传和原始 seg,携带 2 bit 能区分 3 次重传和原始 seg,以此类推,携带 32 bit 能区分 4G 次传输(1 次原始 seg 和 4G-1 次重传),为将每次传输识别为一个不允许切割的整体,需将 seq space 的字节流按序打包进固定长度的 packet,以 packet 为单位传输并针对 packet 确认,为每一个 packet 打标递增的 packet id 作为序列号。

这想法是个自然而然的过程。逻辑反而更简单明,加入一个 packet id 便清除了启发式判断。

可 TCP 没地方编码 packet id 了,况且 TCP 的标准处理流程无法要求不切割,打包过程很难合并入 TCP。但 QUIC 接力了。虽然 QUIC 未必是 TCP 后继,但它在 sack,rack,重传等方面的处理方式,正是针对 TCP 的 bugfix,整个过程一脉相承,可理解。

后面的路怎么走我不知道,也不预测,但俯拾皆是的肯定依然一地鸡毛,而且如果它确实发生了,将它与前面的事情连起来,必将在同一条线上。

浙江温州皮鞋湿,下雨进水不会胖。

相关文章:

TCP 的演化史-fast retransmit/recovery

工作原因要对一个 newreno 实现增加 sack 支持。尝试写了 3 天 C,同时一遍又一遍梳理 sack 标准演进。这些东西我早就了解,但涉及落地写实现,就得不断抠细节,试图写一个完备的实现。 这事有更简单的方法。根本没必要完全实现 RFC…...

CSS基础选择器,你认识多少?

前言在上一文初识CSS中,我们了解到了其格式:选择器{ }在初步尝试使用时,我们笼统的直接输入了p { }以选择p标签来对其操作,而这一章节里,我们再进一步探索有关基础选择器的相关内容,理解选择器的作用。选择…...

ChatGPT入门案例|商务智能对话客服(三)

本篇介绍智能客服的基本功能架构和基本概念,并利用对话流技术构建商务智能应用。 01、商务智能客服功能结构 互联网的发展已经深入到社会的各个方面,智能化发展已经成为社会发展的大趋势。在大数据和互联网时代,企业和组织愈加重视客户沟通…...

Matlab 最小二乘法拟合平面(SVD)

文章目录 一、简介1.1最小二乘法拟合平面1.2 SVD角度二、实现代码三、实现效果参考资料一、简介 1.1最小二乘法拟合平面 之前我们使用过最为经典的方式对平面进行了最小二乘拟合(点云最小二乘法拟合平面),其推导过程如下所示: 仔细观察一下可以发现...

AtCoder Regular Contest 126 D题题解

思路 首先我们看看假设选中 mmm 个数后的答案。 我们首先现将 mmm 个数移动到一起,在将他们重新排序。 我们知道,mmm 个数移在一起时,当位于中间的那个数不动时交换次数最少,于是可以列出式子(cic_ici​ 是点 iii 的…...

Android R WiFi热点流程浅析

Android R WiFi热点流程浅析 Android上的WiFi SoftAp功能是用户常用的功能之一,它能让我们分享手机的网络给其他设备使用。 那Android系统是如何实现SoftAp的呢,这里在FWK层面做一个简要的流程分析,供自己记录和大家参考。 以Android R版本为…...

【C++进阶】二、多态详解(总)

目录 一、多态的概念 二、多态的定义及实现 2.1 多态的构成条件 2.2 虚函数 2.3 虚函数的重写 2.4 虚函数重写的两个例外 2.4.1 协变 2.4.2 析构函数的重写 2.5 C11 override 和 final 2.5.1 final 2.5.2 override 2.6 重载、覆盖(重写)、隐藏(重定义)的对比 三、…...

node-sass@4.14.1 包含风险, 如何升级依赖至 dart-sass

文章目录需求我上网都查到了哪些信息在 github 看到了 node-sass 依赖的最新版本的列表:关于方案2的失败不同版本的 nodejs 和 node-sass依赖的**适配关系**从何得知替代方案——dart-sass如何安装 dart sass?需求 在做一个基于Node、React的前端项目&a…...

DataWhale 大数据处理技术组队学习task2

三、Hadoop分布式文件系统 1. 产生背景 数据量越来越大,一台独立的计算机已经无法存储所有的数据---->将大规模的数据存储到成百上千的计算机中------为了解决数据管理以及维护极其繁琐与低效------>分布式文件系统 分布式文件系统是管理网络中跨多台计算机…...

一文读懂select、poll、epoll的用法

select,poll,epoll都是IO多路复用的机制。I/O多路复用就通过一种机制,可以监视多个描述符,一旦某个描述符就绪(一般是读就绪或者写就绪),能够通知程序进行相应的读写操作。但select,…...

《C陷阱与缺陷》----词法“陷阱”

导言: 由于一个程序错误可以从不同层面采用不同方式进行考察,而根据程序错误与考察程序的方式之间的相关性,可以将程序错误进行划分为各种陷阱与缺陷: ①.词法“陷阱” ②.语法“陷阱” ③.语义“陷阱” ④.连接问题 ⑤.库函数问…...

千锋教育+计算机四级网络-计算机网络学习-04

UDP概述 UDP协议 面向无连接的用户数据报协议,在传输数据前不需要先建立连接;目地主机的运输层收到UDP报文后,不需要给出任何确认 UDP特点 相比TCP速度稍快些简单的请求/应答应用程序可以使用UDP对于海量数据传输不应该使用UDP广播和多播应用…...

蓝桥杯算法训练合集十四 1.P08052.P07053.同余方程4.P08015.ascii应用

目录 1.P0805 2.P0705 3.同余方程 4.P0801 5.ascii应用 1.P0805 问题描述 当两个比较大的整数相乘时,可能会出现数据溢出的情形。为避免溢出,可以采用字符串的方法来实现两个大数之间的乘法。具体来说,首先以字符串的形式输入两个整数&…...

判断字符串中的字符的类型isdecimal();isalpha();isdigit();isalnum()

【小白从小学Python、C、Java】 【计算机等级考试500强双证书】 【Python-数据分析】 判断字符串中的字符的类型 isdecimal();isalpha();isdigit();isalnum() [太阳]选择题 对于代码中isdecimal()和isalnum()输出的结果是? s "ABc123&…...

VSCode远程调试Linux代码,python解释器配置

安装插件并配置 安装后找到插件图标,点击 点击SSH上的 号 在弹出框中输入命令:ssh usernameip -p port username: 远程服务器的用户名 ip: 远程ip port:端口号,没有可以不用 输入完毕后点击enter 选择ssh配置文件保存…...

03:入门篇 - CTK Plugin Framework 基本原理

作者: 一去、二三里 个人微信号: iwaleon 微信公众号: 高效程序员 CTK Plugin Framework 技术是面向 C++ 的动态模型系统。该系统允许插件之间的松散耦合,并且提供了设计良好的方式来进行功能和数据的交互。此外,它没有预先对插件施加限制,这样就可以很容易地将插件的相关…...

面试攻略,Java 基础面试 100 问(九)

数组有没有 length()方法?String 有没有 length()方法? 数组没有 length()方法,有 length 的属性。String 有 length()方法。JavaScript 中,获得字符串的长度是通过 length 属性得到的,这一点容易和 Java混淆。 在 Java 中&…...

JavaScript 代码不嵌套主义

文章目录前言一、何为嵌套代码二、避免嵌套1.提炼抽取2.反转排列总结前言 看过不少过度嵌套的代码, 我真正意识到问题的严重性是刚入职那会, 我在一个老项目里看到了40个连续的else if, 套了6层的if, for和forEach, 因为我们并没有做什么限制代码嵌套的提前约定. 呃, 那之后认…...

使用默认参数的4大要点

概述 默认参数是C中新增的特性。在C中,可以为函数的参数指定默认值。调用函数时,如果没有指定实参,则自动使用默认参数。默认参数的基本语法这里就不作介绍了,下面重点介绍使用默认参数的一些知识要点。 基本规则 1、当函数中某个…...

Linux文件系统中的硬链接及常见面试题

如果能对inode的概念有所了解,对理解本文会有所帮助。如果对inode的概念不太清楚也没有关系,我们会捎带介绍一下。在文件系统的实现层面,我们可以认为包含两个组件:一个是包含数据块的池子,池子中的数据块是等大小的&a…...

opencv-StereoBM算法

原理解释目前立体匹配算法是计算机视觉中的一个难点和热点,算法很多,但是一般的步骤是:A、匹配代价计算匹配代价计算是整个立体匹配算法的基础,实际是对不同视差下进行灰度相似性测量。常见的方法有灰度差的平方SD(squ…...

图像分类竞赛进阶技能:OpenAI-CLIP使用范例

OpenAI-CLIP 官方介绍 尽管深度学习已经彻底改变了计算机视觉,但目前的方法存在几个主要问题:典型的视觉数据集是劳动密集型的,创建成本高,同时只教授一组狭窄的视觉概念;标准视觉模型擅长于一项任务且仅擅长于一项任务,并且需要大…...

Metasploit框架基础(一)

文章目录前言一、基础认知二、批量POC/EXP的构想三、poc检测框架的简单实现四、xray五、Meatsploit框架参考前言 Metasploit 一款渗透测试框架漏洞利用的集合与构建和定制满足你的需求的基础漏洞利用和验证的工具 这几个说法都是百度或者官方文档中出现的手法,说…...

pytorch零基础实现语义分割项目(二)——标签转换与数据加载

数据转换与加载项目列表前言标签转换RGB标签到类别标签映射RGB标签转换成类别标签数据数据加载随机裁剪数据加载项目列表 语义分割项目(一)——数据概况及预处理 语义分割项目(二)——标签转换与数据加载 语义分割项目&#x…...

python(8.5)--列表习题

目录 一、求输出结果题 二、计算列表元素个数 三、查找是否存在某元素 四、删除某元素 五、如何在列表中插入元素 六、如何从列表中删除重复的元素 七、 如何将列表中的元素按照从小到大的顺序排序 八、从列表中删除重复的元素 九、大到小的顺序排序 一、求输出结…...

rt-thread pwm 多通道

一通道pwm参考 https://blog.csdn.net/yangshengwei230612/article/details/128738351?spm1001.2014.3001.5501 以下主要是多通道与一通道的区别 芯片 stm32f407rgt6 1、配置PWM设备驱动相关宏定义 添加PWM宏定义 #define BSP_USING_PWM8 #define BSP_USING_PWM8_CH1 #d…...

C语言练习 | 初学者经典练习汇总

目录 1、下面代码输出多少,为什么? 2、你要好好学习么? 3、一直写代码, 4、两个数求最大值 5、输入1-5输出工作日,输入6-7输出休息日,其他输入错误 6、写一个输入密码的代码 7、怎么样当输入数字时候…...

华为OD机试 - 自动曝光(Python) | 机试题算法思路 【2023】

最近更新的博客 华为OD机试 - 卡片组成的最大数字(Python) | 机试题算法思路 华为OD机试 - 网上商城优惠活动(一)(Python) | 机试题算法思路 华为OD机试 - 统计匹配的二元组个数(Python) | 机试题算法思路 华为OD机试 - 找到它(Python) | 机试题算法思路 华为OD机试…...

「6」线性代数(期末复习)

🚀🚀🚀大家觉不错的话,就恳求大家点点关注,点点小爱心,指点指点🚀🚀🚀 目录 第五章 相似矩阵及二次型 &2)方阵的特征值与特征向量 &3&#xff…...

1.1 硬件与micropython固件烧录及自编译固件

1.ESP32硬件和固件 淘宝搜ESP32模块,20-50元都有,自带usb口,即插即用. 固件下载地址:MicroPython - Python for microcontrollers 2.烧录方法 为简化入门难度,建议此处先使用带GUI的开发工具THonny,记得不是给你理发的tony老师. 烧录的入口是: 后期通过脚本一次型生成和烧…...

电商app制作开发/京东关键词优化技巧

历经300多个日夜,2021年8月,基于国产企业级分布式数据库腾讯云TDSQL打造的昆山农商银行新一代核心系统成功投产上线。它采用“微服务应用国产分布式数据库”架构,该架构在同类银行中尚属首次。 新核心系统整体处理能力可以达到6300TPS&#x…...

有用织梦做的大网站吗/专门用来查找网址的网站

作者:Natasha The Robot,原文链接,原文日期:2016-07-25译者:Lanford3_3;校对:千叶知风;定稿:CMB上周我出席了 iOSDevCampDC,并有幸参加了 ayanonagon 关于测试…...

学电子商务有用吗/seo网络优化软件

因为经常要涉及到版本号的判断,经常记不住特来记录下供下次查阅 版本号判断代码: if(Build.VERSION.SDK_INT > Build.VERSION_CODES.Q){//>安卓10的逻辑部分 }API 版本号版本名称英文名称194.4KITKAT204.4KITKAT_WATCH215.0LOLLIPOP225.1LOLL…...

外包公司企业网站/管理方面的培训课程

Flume NG 1.x 是Flume 0.9.x的重构版本,基本面目全非了,Master和zookeeper没有了,collector没有了,Web console没有了,只有 source (avro:很简单使用;exec:使用shell命令…...

网站怎样做wap端/国内新闻最新消息十条

第1关:通信簿.csv文件进行查询 本关任务:编写函数cztxb,实现根据姓名对通信录.csv文件查询。输入查询的姓名,若文件中存在该人,显示其信息;当查询的姓名不存在时,显示“查无此人”。 #当前位置的sc文件夹下csv格式文件“通信簿”, #该文件每行记录一个姓名、住址、电话…...

鸡西市建设局网站/营销的方法和技巧

1.基本的读取配置文件-read(filename) 直接读取ini文件内容-sections() 得到所有的section,并以列表的形式返回-options(section) 得到该section的所有option-items(section) 得到该section的所有键值对-get(section,option) 得到section中option的值,返…...