tips-net-tcp

首部格式

tips-net-tcp-2019311171945

各个段位说明:

  • 源端口和目的端口:  各占 2 字节.端口是传输层与应用层的服务接口.传输层的复用和分用功能都要通过端口才能实现
  • 序号:  占 4 字节.TCP 连接中传送的数据流中的每一个字节都编上一个序号.序号字段的值则指的是本报文段所发送的数据的第一个字节的序号
  • 确认号:  占 4 字节,是期望收到对方的下一个报文段的数据的第一个字节的序号
  • 数据偏移/首部长度:  占 4 位,它指出 TCP 报文段的数据起始处距离 TCP 报文段的起始处有多远.“数据偏移”的单位是 32 位字(以 4 字节为计算单位)
  • 保留:  占 6 位,保留为今后使用,但目前应置为 0
  • 紧急URG:  当 URG=1 时,表明紧急指针字段有效.它告诉系统此报文段中有紧急数据,应尽快传送(相当于高优先级的数据)
  • 确认ACK:  只有当 ACK=1 时确认号字段才有效.当 ACK=0 时,确认号无效
  • PSH(PuSH):  接收 TCP 收到 PSH = 1 的报文段,就尽快地交付接收应用进程,而不再等到整个缓存都填满了后再向上交付
  • RST (ReSeT):  当 RST=1 时,表明 TCP 连接中出现严重差错(如由于主机崩溃或其他原因),必须释放连接,然后再重新建立运输连接
  • 同步 SYN:  同步 SYN = 1 表示这是一个连接请求或连接接受报文
  • 终止 FIN:  用来释放一个连接.FIN=1 表明此报文段的发送端的数据已发送完毕,并要求释放运输连接
  • 检验和:  占 2 字节.检验和字段检验的范围包括首部和数据这两部分.在计算检验和时,要在 TCP 报文段的前面加上 12 字节的伪首部
  • 紧急指针:  占 16 位,指出在本报文段中紧急数据共有多少个字节(紧急数据放在本报文段数据的最前面)
  • 选项:  长度可变.TCP 最初只规定了一种选项,即最大报文段长度 MSS.MSS 告诉对方 TCP:“我的缓存所能接收的报文段的数据字段的最大长度是 MSS 个字节.” [MSS(Maximum Segment Size)是 TCP 报文段中的数据字段的最大长度.数据字段加上 TCP 首部才等于整个的 TCP 报文段]
  • 填充:  这是为了使整个首部长度是 4 字节的整数倍
  • 其他选项:
    • 窗口扩大:  占 3 字节,其中有一个字节表示移位值 S.新的窗口值等于TCP 首部中的窗口位数增大到(16 + S),相当于把窗口值向左移动 S 位后获得实际的窗口大小
    • 时间戳:  占10 字节,其中最主要的字段时间戳值字段(4字节)和时间戳回送回答字段(4字节)
    • 选择确认:  接收方收到了和前面的字节流不连续的两2字节.如果这些字节的序号都在接收窗口之内,那么接收方就先收下这些数据,但要把这些信息准确地告诉发送方,使发送方不要再重复发送这些已收到的数据

数据单位

TCP 传送的数据单位协议是 TCP 报文段(segment)

特点

TCP 是面向连接的传输层协议 每一条 TCP 连接只能有两个端点(endpoint),每一条 TCP 连接只能是点对点的(一对一) TCP 提供可靠交付的服务 TCP 提供全双工通信 面向字节流

注意

TCP 对应用进程一次把多长的报文发送到TCP 的缓存中是不关心的 TCP 根据对方给出的窗口值和当前网络拥塞的程度来决定一个报文段应包含多少个字节(UDP 发送的报文长度是应用进程给出的) TCP 可把太长的数据块划分短一些再传送.TCP 也可等待积累有足够多的字节后再构成报文段发送出去 每一条 TCP 连接有两个端点 TCP 连接的端点不是主机,不是主机的IP 地址,不是应用进程,也不是传输层的协议端口.TCP 连接的端点叫做套接字(socket)或插口

自动重传请求ARQ

定义:

可靠传输协议常称为自动重传请求ARQ (Automatic Repeat reQuest)

累积确认:

  • 定义:  接收方一般采用累积确认的方式.即不必对收到的分组逐个发送确认,而是对按序到达的最后一个分组发送确认,这样就表示:到这个分组为止的所有分组都已正确收到了
  • 优点:  容易实现,即使确认丢失也不必重传
  • 缺点:  不能向发送方反映出接收方已经正确收到的所有分组的信息

    Go-back-N(回退N):

    如果发送方发送了前 5 个分组,而中间的第 3 个分组丢失了.这时接收方只能对前两个分组发出确认.发送方无法知道后面三个分组的下落,而只好把后面的三个分组都再重传一次

具体实现

说明:

  • TCP 连接的每一端都必须设有两个窗口 一个发送窗口和一个接收窗口
  • TCP 可靠传输机制用字节的序号进行控制.TCP 所有的确认都是基于序号而不是基于报文段
  • TCP 两端的四个窗口经常处于动态变化之中
  • TCP连接的往返时间 RTT 也不是固定不变的.需要使用特定的算法估算较为合理的重传时间

图释

tips-net-tcp-201931117259

发送缓存

发送缓存用来暂时存放:

  • 发送应用程序传送给发送方 TCP 准备发送的数据
  • TCP 已发送出但尚未收到确认的数据

    图释:

    tips-net-tcp-2019311172637

接收缓存

接收缓存用来暂时存放:

  • 按序到达的、但尚未被接收应用程序读取的数据;
  • 不按序到达的数据

    图释:

    tips-net-tcp-2019311172725

滑动窗口

滑动窗口(rwnd)是用于流控的动态缩放可靠滑动的接收与发送窗口,防止发送端发送过快接收端被淹没 对应的还有拥塞窗口(rwnd),是在一个RTT内可以最多一次可发送的报文段数 — 发送方的流量控制

TCP是以报文段(若干字节)为单位,每一个报文段需要一次ACK确认收到,但是其带来的问题很明显,频繁的发送确认等待导致用于确认与等待的时间太长。引入窗口后,发送端只要在窗口内,便不用每次都等待ACK才发送下一个报文段,可以在发送窗口内一次连续发送几个报文段而无需等待ACK

图释:

tips-net-tcp-2019311172750

特点:

  • 以字节为单位的滑动窗口
  • A 的发送窗口并不总是和 B 的接收窗口一样大(因为有一定的时间滞后)

    要求:

  • TCP 标准没有规定对不按序到达的数据应如何处理.通常是先临时存放在接收窗口中,等到字节流中所缺少的字节收到后,再按序交付上层的应用进程
  • TCP 要求接收方必须有累积确认的功能,这样可以减小传输开销

具体实现:

tips-net-tcp-201931117296 tips-net-tcp-2019311172934 tips-net-tcp-2019311172949 tips-net-tcp-201931117305

发送窗口与接收窗口的关系

TCP是双工协议,会话双方都可以同时接收与发送数据,因此双方都同时维护一个发送窗口与接收窗口。

  • 接收窗口大小取决于应用、系统、硬件等限制;
  • 发送窗口大小取决于对方接收窗口的大小

窗口滑动协定

  • 发送窗口只有在收到窗口内字节的ACK确认,才会滑动其左边界
  • 接收窗口只有在窗口中所有的段都正确收到的情况下,才会滑动其左边界;当有字节未接收,但收到后面的字节的情况下,也会滑动,也不对后续字节确认,确保对方重传未接收字节

    哪些允许变化

  • 最大报文段大小在握手中,就确定了
  • 窗口缩放因子在握手中,就确定了
  • 接收窗口大小在根据本地的处理能力与缓存剩余空间动态调整,通过ACK带给对方当前剩余的接收窗口大小

确认丢失和确认迟到

tips-net-tcp-2019311173223 RTT = 传播时间+接收端处理时间+路由器的排队时间(变化较大反应当前网络拥塞情况)

超时重传时间选择

具体实现:

TCP 每发送一个报文段,就对这个报文段设置一次计时器.只要计时器设置的重传时间到但还没有收到确认,就要重传这一报文段

加权平均往返时间:

做法:

TCP 保留了 RTT 的一个加权平均往返时间 RTTS(这又称为平滑的往返时间),第一次测量到 RTT 样本时,RTTS 值就取为所测量到的 RTT 样本值.以后每测量到一个新的 RTT 样本,就按下式重新计算一次 RTTS: R

公式:

新的 RTTS = ( 1 - α)×(旧的 RTTS)+α(新的 RTT 样本)

说明:

式中,0 ≤ α< 1.若α很接近于零,表示 RTT 值更新较慢若选择 α 接近于1,则表示 RTT 值更新较快 RFC 2988 推荐的 α 值为 1/8,即 0.125

超时重传时间RTO:

RTO 应略大于上面得出的加权平均往返时间 RTTS. RFC 2988 建议使用下式计算 RTO:

1
RTO=RTTS + 4×RTTD

RTTD 是 RTT 的偏差的加权平均值 RFC 2988 建议这样计算 RTTD.第一次测量时,RTTD 值取为测量到的 RTT 样本值的一半.在以后的测量中,则使用下式计算加权平均的 RTTD:

新的 RTTD = (1-β)×(旧的RTTD)+β×|RTTS﹣新的 RTT 样本| β是个小于 1 的系数,其推荐值是 1/4,即 0.25 在计算平均往返时间 RTT 时,只要报文段重传了,就不采用其往返时间样本

修正的Karn算法:

报文段每重传一次,就把 RTO 增大一些:

1
新的 RTO= γ×(旧的 RTO)

系数γ 的典型值是 2 当不再发生报文段的重传时,才根据报文段的往返时延更新平均往返时延 RTT 和超时重传时间 RTO 的数值

持续计时器

  • TCP 为每一个连接设有一个持续计时器
  • 只要 TCP 连接的一方收到对方的零窗口通知,就启动持续计时器
  • 若持续计时器设置的时间到期,就发送一个零窗口探测报文段(仅携带 1 字节的数据),而对方就在确认这个探测报文段时给出了现在的窗口值
  • 若窗口仍然是零,则收到这个报文段的一方就重新设置持续计时器
  • 若窗口不是零,则死锁的僵局就可以打破了

报文段的发送时机

TCP 维持一个变量,它等于最大报文段长度 MSS.只要缓存中存放的数据达到 MSS 字节时,就组装成一个 TCP 报文段发送出去 由发送方的应用进程指明要求发送报文段,即 TCP 支持的推送(push)操作 发送方的一个计时器期限到了,这时就把当前已有的缓存数据装入报文段(但长度不能超过 MSS)发送出去

运输连接

三个阶段:

连接建立:

图释:

tips-net-tcp-2019311174443

步骤:
  • A 的 TCP 向 B 发出连接请求报文段,其首部中的同步位 SYN = 1,并选择序号 seq = x,表明传送数据时的第一个数据字节的序号是 x
  • B 的 TCP 收到连接请求报文段后,如同意,则发回确认(B 在确认报文段中应使 SYN = 1,使 ACK = 1,其确认号ack = x﹢1,自己选择的序号 seq = y)
  • A 收到此报文段后向 B 给出确认,其 ACK = 1,确认号 ack = y﹢1(A 的 TCP 通知上层应用进程,连接已经建立,B 的 TCP 收到主机 A 的确认后,也通知其上层应用进程:TCP 连接已经建立)

数据传送

连接释放:

图释

tips-net-tcp-2019311174722

步骤:
  • 数据传输结束后,通信的双方都可释放连接.现在 A 的应用进程先向其 TCP 发出连接释放报文段,并停止再发送数据,主动关闭 TCP 连接(A 把连接释放报文段首部的 FIN = 1,其序号seq = u,等待 B 的确认)
  • B 发出确认,确认号 ack = u+1,而这个报文段自己的序号 seq = v(TCP 服务器进程通知高层应用进程.从 A 到 B 这个方向的连接就释放了,TCP 连接处于半关闭状态.B 若发送数据,A 仍要接收)
  • 若 B 已经没有要向 A 发送的数据,其应用进程就通知 TCP 释放连接
  • A 收到连接释放报文段后,必须发出确认,在确认报文段中 ACK = 1,确认号 ack=w﹢1,自己的序号 seq = u + 1
注意:

TCP 连接必须经过时间 2MSL 后才真正释放掉(2MSL 的时间的用意 — 为了保证 A 发送的最后一个 ACK 报文段能够到达 B.防止 “已失效的连接请求报文段”出现在本连接中.A 在发送完最后一个 ACK 报文段后,再经过时间 2MSL,就可以使本连接持续的时间内所产生的所有报文段,都从网络中消失.这样就可以使下一个新的连接中不会出现这种旧的连接请求报文段)

发现丢失确认时候的处理

tips-net-tcp-2019311174940

三个问题:

  • 要使每一方能够确知对方的存在
  • 要允许双方协商一些参数(如最大报文段长度,最大窗口大小,服务质量等)
  • 能够对运输实体资源(如缓存大小,连接表中的项目等)进行分配

发送TCP请求客户端

tips-net-tcp-2019311175058

拥塞处理相关概念

拥塞窗口:

  • 含义:拥塞窗口的大小取决于网络的拥塞程度,并且动态地在变化.发送方让自己的发送窗口等于拥塞窗口.如再考虑到接收方的接收能力,则发送窗口还可能小于拥塞窗口
  • 发送方控制拥塞窗口的原则:只要网络没有出现拥塞,拥塞窗口就再增大一些,以便把更多的分组发送出去.但只要网络出现拥塞,拥塞窗口就减小一些,以减少注入到网络中的分组数

乘法减小:

是指不论在慢开始阶段还是拥塞避免阶段,只要出现一次超时(即出现一次网络拥塞),就把慢开始门限值 ssthresh 设置为当前的拥塞窗口值乘以 0.5

加法增大:

是指执行拥塞避免算法后,在收到对所有报文段的确认后(即经过一个往返时间),就把拥塞窗口 cwnd增加一个 MSS 大小,使拥塞窗口缓慢增大,以防止网络过早出现拥塞

快重传:

快重传算法首先要求接收方每收到一个失序的报文段后就立即发出重复确认.这样做可以让发送方及早知道有报文段没有到达接收方,发送方只要一连收到三个重复确认就应当立即重传对方尚未收到的报文段 tips-net-tcp-2019311175358

快恢复:

当发送端收到连续三个重复的确认时,就执行“乘法减小”算法,把慢开始门限 ssthresh 减半.但接下去不执行慢开始算法

发送窗口的上限值:

发送方的发送窗口的上限值应当取为接收方窗口 rwnd 和拥塞窗口 cwnd 这两个变量中较小的一个,即应按以下公式确定: 发送窗口的上限值 Min [rwnd, cwnd]

当 rwnd < cwnd 时,是接收方的接收能力限制发送窗口的最大值 当 cwnd < rwnd 时,则是网络的拥塞限制发送窗口的最大值

避免拥塞具体实现

有滑动窗口了,为什么还要拥塞窗口

发送方与接收方之间存在多个路由器和速率较慢的链路时,一些中间路由器就必须缓存分组,并可能耗尽缓存,此时便会出现拥塞,这将严重降低了TCP连接的吞吐量,拥塞窗口就是为了防止过多的数据注入到网络中,中间路由无法消化的问题。

TCP的做法是引入拥塞窗口(cwnd)并策略性的调整其大小,如上文提到的发送窗口大小是取滑动窗口大小与拥塞窗口大小的最小值,这个正是用来缓解该问题,下面是拥塞窗口大小变化的策略:

1. 慢开始、拥塞控制

tips-net-tcp-2019312133854 其目的是: 拥塞发生时循序减少主机发送到网络的报文数,使得这时路由器有足够的时间消化积压的报文。

  • 当主机开发发送数据时,拥塞窗口(cwnd)被初始化为1个报文段,试探性的发送1个字节的报文
  • 每收到一个ACK,拥塞窗口大小就指数的增加报文段数目(1,2,4,16…)
  • 最终到达提前预设的慢开始阀值(ssthresh),停止使用慢开始算法,改用拥塞避免算法
  • 拥塞避免算法是每经过一个RTT,拥塞窗口就增加一个报文段,即改为线性的增加报文段
  • 最终会出现网络拥塞,比如丢包等情况,停止拥塞避免算法,将慢开始阀值设置为目前拥塞时拥塞窗口大小的一半(但不能小于2),并重置拥塞窗口大小为1个报文段,开始新的一轮慢开始

慢开始门限 ssthresh 的用法:

  • 当 cwnd < ssthresh 时,使用慢开始算法
  • 当 cwnd > ssthresh 时,停止使用慢开始算法而改用拥塞避免算法
  • 当 cwnd = ssthresh 时,既可使用慢开始算法,也可使用拥塞避免算法

2. 快重传,快恢复

tips-net-tcp-2019312134328 其目的是: 减少因为拥塞导致的数据包丢失的重传时间,避免无用的数据到网络

接收方: 如果一个包丢失,后续的包继续发送针对该包的重传请求

发送方: 一旦收到三个一样的确认,判定为拥塞:

  • 立即重传该包
  • 开始执行快恢复算法
  • 快恢复是慢开始阀值设置为目前拥塞时拥塞窗口大小的一半;拥塞窗口大小设置为目前设置后的慢开始阀值的大小;执行拥塞避免算法

TCP窗口特殊情况

1. Persistence timer

tips-net-tcp-2019312134856 防止丢包导致发送端停留在上次收到的接收窗口大小为0的情况:

  • 接收端B: 我的缓存已满,接收窗口为0
  • 发送端A: 停止发送数据, 并启动持续计时器(Persistence timer)
  • 接收端B: 消化完缓存,发送报文给发送端A,我的接收窗口大小为400,但是 这个报文丢了
  • 发送端A: 计时器时间到,发送一个1字节的探测报文
  • 接收端B: 重新发送,接收窗口大小为400
  • 发送端A: 继续发送数据

    2. 应用层每次单字节发送

    单个发送字节,然后等待一个确认,再发送一个字节,这样为一个字节添加40个字节头的做法,无疑增加了网络中许多不必要的报文,该问题TCP层的解决方案:

发送方采用Nagle算法:

  • 若应用层是逐个字节把数据送到TCP,那么TCP不会逐个的发送,而是先发送第一个数据字节,然后缓存剩余的
  • 在收到第一个字节的ACK获知网络情况与对方的接收窗口大小后,把缓存的剩余字节组成合适的报文发送出去
  • 到达的数据达到发送窗口大小的一半或者报文段的最大长度时,立即发送

接收方的做法:

  • 等待本地有足够的缓存空间容纳一个报文段,或者等到本地的缓存空间有一半空闲的时候,再通知发送端发送数据。

TCP 的有限状态机

说明:

  • TCP 有限状态机的图中每一个方框都是 TCP 可能具有的状态
  • 每个方框中的大写英文字符串是 TCP 标准所使用的 TCP 连接状态名.状态之间的箭头表示可能发生的状态变迁
  • 箭头旁边的字,表明引起这种变迁的原因,或表明发生状态变迁后又出现什么动作
  • 图中有三种不同的箭头
    • 粗实线箭头表示对客户进程的正常变迁
    • 粗虚线箭头表示对服务器进程的正常变迁
    • 另一种细线箭头表示异常变迁

tips-net-tcp-2019311175949

其他概念

MTU

什么是MTU(Maximum Transmit Unit)

由于以太网传输的限制,每个以太网网数据帧的大小都是落在在区间[64Bytes,1518Bytes]中的,不在区间内的一般会被视为错误的数据帧,以太网转发设备直接丢弃。而根据以太网每帧的数据构成,除去固定的部分,留给上层协议的只有Data域的1500Bytes,我们将它称为MTU。

以太网(Ethernet II)每帧的数据构成: 目的Mac地址(DMAC)+源Mac地址(SMAC)+类型(Type)+数据(Data)+校验(CRC) = 6Bytes(48bit)DMAC + 6Bytes(48bit)SMAC + 2Bytes(16bit)Type + 1500BytesData + 4Bytes(24bit)CRC

MTU造成什么影响

由于一个帧放不下,如IP协议,就会对数据包进行分片处理,这就导致了原本一次可以搞定的,被分为多次,降低传输性能,不过我们可以通过在数据包包头加上DF(DonotFragment)标签来强制不被分片处理。

UDP协议不用关心数据的到达的有序以及正确,因此对分片无特殊要求 TCP协议相反,因此TCP协议本身的最大报文段大小MSS也受MTU影响,通常MSS是: MTU - 20Bytes(IP Header) - 20Bytes(TCP Header) 不过好在绝大多数的网络链路都是1500Bytes的MTU或者更大

什么是MSS(Maximum Segment Size)

TCP的最大报文段大小,只包含TCP Payload(不包含TCP Header与TCP Option)的TCP每次能够传输的最大数据分段的大小,可以用来限制每次发送的字节数。通常大小为1460Bytes(1500BytesMTU - 20Bytes(IP Header) - 20Bytes(TCP Header))

MSS是在TCP建连时确定的,通讯双方会根据双方提供的MSS值,取最小的MSS作为该次连接数据传输的MSS

什么是WS(Window Scaling)

TCP首部中表示Window Size的字段只有16位,因此按照协议,能表示的最大窗口大小是2^16-1=65535Bytes(64Kb),因此TCP的选项字段中包含了窗口扩大因子(WS)分别用option-kind、option-length、option-data来表示,这个参数可带可不带,只有在双方都支持的情况下,才会生效。如双方的WS都是256,而后我们ACK Window size value是5,那么此时就可以表示我们的接收窗口是1280Bytes(5*256=1280)。

参考

TCP的滑动窗口与拥塞窗口 计算机网络【七】:可靠传输的实现 TCP窗口控制、流控制、拥塞控制 也谈一下TCP segment of a reassembled PDU TCP流量控制中的滑动窗口大小 TCP 滑动窗口(发送窗口和接收窗口) TCP协议的滑动窗口具体是怎样控制流量的?

坚持原创技术分享,您的支持将鼓励我继续创作!