《计算机网络(英文版·第5版)》 The Transport Layer
传输服务
提供给上层的服务
传输层的目标
- 传输层的目标是向用户(通常是应用层的进程)提供高效的、可靠的和成本有效的数据传输服务
- 在传输层内,完成这项工作的硬件/软件称为传输实体(transport entity)
- 传输实体可以实现在主机的不同位置
- 操作系统内核
- 以一个链接库的形式绑定到网络应用中
- 以一个独立的用户进程运行
- 实现在网络接口卡(NIC)上
网络层、传输层和应用层之间的(逻辑)关系

传输层与网络层
与网络层提供的面向连接和无连接两种服务一样,传输层的服务类型也分两种
- 面向连接的传输服务
- 三个过程:连接建立、数据传输、连接释放
- 寻址和流量控制
- 无连接的传输服务
- 不需要连接
传输层服务与网络层服务相似,为何需要传输层
- 网络服务
- 传输层的代码完全运行在用户的机器上,但网络层的代码主要运行在由运营商操作的路由器上
- 用户对网络层没有真正的控制权,不能用更好的路由器或者在数据链路层上用更好的错误处理机制来解决服务太差的问题
- 在网络层之上再加一层,由该层来提高网络的服务质量
网络层提供的是“尽力而为”的服务
- 网络层会尝试去传递一个段,但没有保证
- 为了保证传输,需要保持发送数据和接收确认
- 问题
- 数据包可能被延迟、丢失、乱序
- 发送方和接收方的流量可能不同
- 高速传输可能增加网络拥塞
传输层的本质
- 由于传输层的存在,使得传输服务有可能比网络服务更加可靠
- 数据包的丢失和损坏可以被传输实体检测到
传输服务原语的实现
- 可以通过调用库程序来实现,从而使得原语独立于网络服务原语
- 将网络服务隐藏在一组传输服务原语的背后,一旦改变网络服务,只需要替换一组库程序即可
面向对象
- 传输层承担了把上层与技术、设计和各种缺陷隔离的关键作用
- 程序员可以按照一组标准的原语来编写代码,简化了程序员的工作
- 构成了可靠数据传输服务的提供者和用户两者之间的主要边界
- 第 1 层 ~ 第 4 层
- 传输服务的提供者 (transport service provider)
- 第 4 层之上
- 传输服务的用户 (transpoet service user)
传输服务原语
为允许用户访问传输服务,传输层必须为应用程序提供一些操作,也就是提供一个传输服务接口
- 每个服务都有它自己的接口
- 传输服务实体是用户-传输层最小的接口集合,允许用户访问传输服务
传输服务与网络服务
可靠性
- 网络服务按照实际网络提供的服务来建立模型,实际网络可能会丢失数据包,所以网络服务一般是不可靠的
- 面向连接的传输服务是可靠的
- 传输层的目标:在不可靠的网络之上提供可靠的服务
- 无连接的传输服务(不可靠的)
- 客户机-服务器
- 流式多媒体应用
服务对象
- 网络服务仅被传输实体使用
- 通常用户不会编写自己的传输实体
- 很少有用户或程序能看到裸露的网络服务
- 许多程序(员)可以看到传输原语
- 传输服务的使用必须非常方便、容易
简单的传输服务原语

- LISTEN:阻塞调用
客户机-服务器
- 服务器执行 LISTEN 原语
- 当一个客户希望与该服务器进行通话时,执行 CONNECT 原语
- 服务器给客户送回一个 CONNECTION ACCEPTED 段,连接建立
- 现在可以通过 SEND 和 RECEIVE 原语交换数据
- 当不需要一个连接时必须将它释放,以便释放两个传输实体内部的表空间,通过 DISCONNECT 原语
- 当双方都执行了 DISCONNECT 原语,一个连接才算真正被释放
段(Segment, 传输协议数据单元TPDU(Transport Protocol Data Unit))

传输层数据交换的复杂性
在传输层上,即使一个非常简单的单向数据交换过程也比网络层的交换过程复杂得多
- 发送的每个数据包(最终)都要被确认,携带控制段的数据包也要被确认,无论是隐式方式还是显式方式
- 这些确认由使用网络层协议的传输实体来管理,并且它们对于传输用户是不可见的
- 传输实体需要关心计时器和重传
- 这些机制对于传输用户全部是透明的。对传输用户而言,连接就是一个可靠的比特管道:一个用户在管道一端将比特塞进去,这些比特就会出现在管道的另一端
状态图
假设
- 每个段单独确认
- 对称的连接释放

Berkey 套接字
TCP 所用的套接字(socket)原语

解释
- SOCKET:创建一个新的端点(end point),并且在传输实体中为它分配相应的表空间
- 新创建的套接字没有网络地址
- BIND:为套接字分配地址
- 一旦服务器将一个地址绑定到一个套接字,则远程客户就能够与它建立连接
- LISTEN:为入境呼叫分配队列空间,以便多个客户同时发起连接请求时,将这些入境的连接请求排入队列依次处理
- 不是阻塞调用
- ACCEPT:阻塞以等待入境连接(被动)
- CONNECT:阻塞调用方,主动发起建立连接过程。当 CONNECT 调用完成,客户进程被解除阻塞,于是连接就被建立起来
- SEND/RECV:在新建的全双工连接上发送/接收数据
- CLOSE:对称释放连接
传输协议的要素
传输服务由传输协议实现,两个传输实体之间的通信必须使用传输协议
传输协议与数据链路协议

差别
- 路由
- 在数据链路层,两台路由器通过一条有线的或无线物理信道直接进行通信;在传输层,该物理信道被整个网络替代
- 寻址
- 在点到点链路上,路由器的每条出境线路直接通向一台特定的路由器;在传输层,必须显式地指定接收方的地址
- 初始连接建立
- 在一条线路上建立连接的过程非常简单:另一端总是在那;在传输层中,初始的连接建立过程非常复杂
- 网络存在着潜在的存储容量
- 数据链路层上,帧可能到达或丢失;网络层上,有可能多次跳跃才到了目的地
- 缓冲和流量控制
- 程度上的差别,而非类别上的差别
- 数据链路层协议,有些为每条链路分配了固定数量的缓冲区;传输层上存在着大量并且数量可变的连接,而且由于连接之间的相互竞争造成连接的可用带宽上下波动,所以必须管理大量的连接并且每个连接获得的带宽又是可变的

寻址
指定连接到那个应用进程上
- 为那些能够监听连接请求的进程定义相应的传输地址,在 Internet 中,这些端点称为端口(port)
传输服务访问点(TSAP)
- 表示传输层的一个特殊端点
网络服务访问点(NSAP)
- IP 地址是 NSAP 的实例
在 Internet 上,这个就是一个(IP地址. 本地端口)对
NSAP、TSAP 和传输连接的关系
应用程序(客户/服务器)可以将自己关联到一个本地 TSAP 上,以便于一个远程 TSAP 建立连接
- 这些连接运行在每台主机的 NSAP 上
在有些网络中,每台计算机只有一个 NSAP,但是可能有多个传输端点共享此 NSAP,因此需要区分

场景描述
- 主机 2 上的邮件服务器进程将自己关联到 TSAP 1522 上,等待入境连接请求的到来
- 主机 1 上的应用进程希望发送一个邮件消息,所以它把自己关联到 TSAP 1208 上,并发出一个 CONNECT 请求
- 该请求消息指定主机 1 上的 TSAP 1208 作为源,主机 2 上的 TSAP 1522 作为目标
- 这个动作导致在应用进程和服务器之间建立了一个连接
- 应用进程发送邮件消息
- 作为响应,邮件服务器表示它将传递该消息
- 传输连接释放
服务器的 TSAP
- 知名端口(well-known port)
- 小于 1024 的端口保留
- 端口映射器(portmapper)
- 服务-端口
- 初始连接协议(initial connection protocol)
- 每台希望向远程用户提供服务的机器有一个特殊的进程服务器(process server)充当那些不那么频繁使用的服务器的代理
- 在 UNIX 系统中称为 inetd
- 在同一时间监听一组端口,等待连接请求的到来
- 每台希望向远程用户提供服务的机器有一个特殊的进程服务器(process server)充当那些不那么频繁使用的服务器的代理

一个服务的潜在用户发出一个连接请求,指定所需服务的 TSAP 地址
- 如果该 TSAP 地址上没有服务器正等着,则它们得到一条与进程服务器的连接
在获取入境请求后,进程服务器派生出被请求的服务器,允许该服务器继承与用户的现有连接 - 新服务器完成要求的工作,进程服务器继续监听新的连接请求
- 这种方法只适用于服务器可按需创建的场合
连接建立
数据包的重复和被延迟
- 使用一次性的传输地址
- 知名端口就会有问题
- 连接发起方为每个连接分配一个唯一标识符(序号Seq)
- 要求每个传输实体维护历史信息,同步问题
- 如果服务器崩溃,就不知道哪些标识符已经使用过了
设计一种机制来杀死那些已经超时但仍滞留在网络中的数据包
- 数据包的生存期可以用一下一种(或多种)技术先行在一个给定的最大值之内:
- 限制网络设计
- 包括任何一种避免数据包进入循环的方法,给定某种限定延迟的方法,这个延迟要包括(已知的)最长可能路径上拥塞延迟的上界
- 在每个数据包中放置一个跳计数器(hop counter)
- 将跳计数器初始化为某个适当的值,然后每次转发时减一
- 简单丢弃跳计数器为零的数据包
- 为每个数据包打上时间戳
- 要求每个数据包携带创建时间
- 路由器负责丢弃年限超过某个预设值的数据包
- 要求同步所有路由器的时钟
不仅要保证一个数据包死亡,而且要保证它的所有确认最终也死亡
- 周期 $T$,是数据包实际最大生存周期的某个不太大的倍数
- 最大数据包的生存期是一个网络的保守常数
- 对 Internet 来说,120 s
限定了数据包的生存期上界,设计一种方法拒绝重复的段
- Sunshine and Dalal
- 方法的核心是源端用序号作为段的标签,使得该段在 $T$ 秒内不被重用
- $T$ 的大小以及数据包速率(数据包/秒)确定了序号的大小
- 一个具有相同序号的重复数据包,接收方只接收老数据包
- Tomlison
- 每台主机配备一个日时钟(time-of-day clock)
- 不同主机上的时钟不需要同步
- 每个连接都从一个完全不同的初始序号开始对段编号
- 每个连接上的最大数据速率是每个时钟滴答一次发送一段
- 通常不会记住接收方在该连接上的序号
三次握手

- (a)主机 1 发起连接请求时的正常建立过程
- 主机 1 选择一个序号 x ,并且发送一个包含 x 的 CONNECTION REQUEST 段给主机 2
- 主机 2 回应一个 ACK 段作为对 x 的确认,并宣告它自己的初始序号 y
- 最后,主机 1 在它发送的第一个数据段中,对主机 2 选择的初始序号进行确认
- (b)出现老的重复 CONNECT REQUEST
- 主机 1 拒绝主机 2 的连接建立请求
- (c)重复 CONNECT REQUEST 和重复 ACK
- 当第二个延迟的段到达主机 2 时,意识到是老的重复数据包
防止序号回绕(PAWS)
TCP 使用三次握手来建立连接
- 时间戳被用来辅助扩展 32 位序号,以便它在最大数据包生存期间不会回绕
- 基于时钟(Tomlison)的问题
- 容易预测初始序号
- 欺骗三次握手
连接释放
非对称释放(Asymmetric)
- 电话系统的工作方式:当一方挂机后,连接就被中断了
- 可能导致数据的丢失
对称释放(Symmetric)
- 把连接看成两个独立的单向连接,要求单独释放每一个单向连接
- 两军对垒问题(two-army problem)
- 解决
- 将释放连接的需求交给传输层用户,使得连接两端独立决定是否是释放连接
四种场景

- (a)正常的三次握手
- 一个用户发送一个 DR 段来启动释放连接过程,并启动计时器
- 当段到达对方,接收端也返回一个 DR 段,并启动计时器
- 设置计时器的目的是为了防止 DR 丢失
- 当 DR 到达时,最初的发送端返回一个 ACK 段,并且释放连接
- 当 ACK 返回后,接收端也释放连接
- 释放一个连接意味着传输实体将有关该连接的信息从它的内部表删除,并通知该连接的所有者(传输用户)
- (b)最后一个 ACK 被丢失
- 当计时器超时,则释放连接
- (c)响应 DR 丢失
- 发起释放连接的用户收不到响应,计时器超时,再次尝试释放连接
- (d)响应 DR 和随后的 DISCONNECT REQUEST 都丢失
- N 次尝试后,发送端放弃,并释放连接
- 接收端超时,于是退出连接
半开连接(half-open)
- 一方释放连接,而另一方仍处于活跃状态
自动断开连接规则
- 在规定给的一段时间内没有段到来,该连接将被自动断开
- 如果一个空闲连接上多个连续的哑段全部丢失,则首先是一方然后是另一方都将自动断开连接
TCP 中的释放连接
通常采用对称释放方式来断开连接
- 每一方在发送完自己的数据之后用一个 FIN 数据包独立关闭其一半的连接
许多 Web 服务器给客户端发送一个 RST 包,导致突然关闭连接
- 更像非对称释放方式
- Web 服务器接收来自客户端的请求,然后给客户端发送一个响应
- 当 Web 服务器完成响应工作后,意味着两个方向上的所有数据都已经发送完毕
- 服务器可以给客户端发送一个警告,然后突然关闭连接
- 如果客户端得到警告,它会释放它的连接状态;如果没有得到警告,最终会意识到服务器不再与自己交谈,并释放连接
- 两种情况下数据都已经得到成功传送
差错控制和流量控制
差错控制
- 确保数据传输具备所需的可靠性,通常指所有的数据均被无差错地传送到目的地
流量控制
- 防止快速发送端淹没慢速接收端
数据链路层的差错控制和流量控制
- 帧中携带一个检错码(如 CRC,校验和)用于检测信息是否被正确接收
- 帧中携带的序号用于标识本帧,发送方在收到接收方成功接收后返回的确认之前,必须重发帧。(自动重复请求(ARQ))
- 任何时候允许发送方发送一定数量的帧,如果接收方没有及时返回确认,则发送方必须暂停
- 如果只允许发送一帧,则协议称为停等式(stop-and-wait)协议
- 较大的窗口可使得发送管道化,因而提高距离长且速度快的链路性能
- 滑动窗口(sliding window)协议结合了这些功能,还能被用于支持数据的双向传送
链路层和传输层的不同
功能上的差异,考虑错误检测
- 链路层的校验和保护一个穿过单条链路的帧
- 没有考虑数据包在路由器内损坏的情形
- 提高了性能
- 传输层的校验和保护跨越整个网络路径的段(端到端的校验机制)
程度上的差异,考虑滑动窗口协议
- 传输协议通常使用较大的滑动窗口
- 缓存数据也是一个问题
缓冲区
发送端和接收端都需要缓冲区
- 接收端需要缓冲区用作滑动窗口
- 发送端需要缓冲区保存已发但未确认的段
源缓冲区和目标缓冲区之间的最佳权衡取决于由连接承载的流量类型(TCP 策略)
- 对于低带宽的突发流量(如交互式终端),在发送方缓冲
- 不设置任何缓冲区是合理的
- 如果段肯定偶尔会被丢弃,那么在连接两端动态获取缓冲区,具体的做法依赖于发送端的缓冲
- 高带宽平滑流量,在接收方缓冲
- 文件传输和其他高带宽流量,如果接收端由专用的满窗口的缓冲区,那么就允许数据流以最大速率发送
组织缓冲区
- (a)链式固定大小的缓冲区
- 将缓冲区组织成一个由大小统一的缓冲区构成的池,每个缓冲区容纳一段
- 大小的决定是个问题
- (b)链式可变大小的缓冲区
- 优点:能获得更好的利用率
- 代价:缓冲区的管理更加复杂
- (c)每个连接使用一个大循环缓冲区
- 不依赖于段的大小,只有当连接重载时,内存的使用情况才很好
管理动态缓冲区的分配
- 缓冲与确认机制分离
- 初始时,发送端根据需求,请求一定数量的缓冲区
- 然后,接收端尽可能分配缓冲区
- 每次发送端传输一段,接收端就必须减小分配给它的缓冲区数
- 当缓冲区为 0 时,完全停止发送
- 然后,接收端在逆向流量中捎带上单独的确认和缓冲区数
- 缓冲区的分配捎带在头的 Window size 字段中
动态管理窗口的例子

- 序号使用 4 位标识
- 段的数据流从主机 A 发往主机 B,段的确认和缓冲区分配流逆向传输
- 避免死锁
- 每台主机应该定期地在每个连接上发送控制段,这些控制段给出确认和缓冲区状态
当缓冲区空间不再限制最大数据流时,网络的承载能力成为瓶颈
- 需要一种基于网络承载容量而不是接收端缓冲容量的机制
- 滑动窗口流量控制方案
- 发送端动态调整窗口大小,以便与网络的承载容量相匹配
- 意味着动态滑动窗口可以同时实现流量控制和拥塞控制
多路复用

- (a)多路复用
- 如果主机只有一个网络地址可用,则该机器上的所有传输连接都必须使用这个地址。当到达了一个段,必须有某种方式告知把它交给哪个进程处理
- 4 个独立的传输连接都是用了相同的网络连接(IP 地址)到达远程主机
- (b)逆向多路复用
- 假设主机有多条网络路径,如果用户需要的带宽和可靠性比其中任何一条路径所能提供的还要多,就以轮询的方式把一个连接上的流量分摊到多条网络路径
- 若打开了 k 条网络连接,则有效带宽可能会增长 k 个因子
- 流控制传输协议(SCTP)
- 可以把一条连接运行在多个网络接口上
- 相反,TCP 使用了单个网络端点
- 也可以应用在链路层,把几条并发运行的低速率链路当作一条高速率链路使用
崩溃恢复
如果传输实体完全位于主机内,则从网络和路由器的崩溃中恢复比较直接
- 数据报服务
- 传输实体总在期待丢失的段,而且知道通过重传来处理这些段的丢失
- 面向连接的服务
- 虚拟电路的丢失意味着建立一条新的连接,并询问远程实体接收的状况
从主机的崩溃中恢复比较困难
- 服务器崩溃重启
- 内部表被重新初始化
- 可以给所有其他主机发送一个广播段,宣告崩溃,并要求客户告知关于所有打开连接的状态信息。根据一状态信息,客户必须确定是否重传。
- 客户的状态:发出一个段但尚未确认/没有未完成的段
客户和服务器策略的不同组合

- 发送一个确认(A)
- 将数据写到输出进程(W)
- 崩溃(C)
从第 $N$ 层崩溃中的恢复工作只能由 $N+1$ 层完成,并且仅当高层保留了问题发生前的足够状态信息时才有可能恢复
- 只要连接两端记录了它当前的状态信息,传输层才能够从网络层的故障中恢复过来
用户数据报协议 UDP
无连接的协议是 UDP,除了给应用程序提供发送数据包功能并允许它们在所需的层次之上架构自己的协议之外,没有做什么特别的事情。
概述
Internet 协议集支持一个无连接的传输协议,该协议称为用户数据报协议(UDP)
UDP 为应用程序提供了一种无需建立连接就可发送封装的 IP 数据报的方法(RFC768)
- UDP 提供了不可靠的数据报服务
- UDP 是在 IP 之上的一个“单薄”的层,它使用 IP 的原始数据报服务并且没有确认和重传机制
- 这使得 UDP 的开销很小,UDP 的一个价值在于将数据递交给进程,而 IP 只是将数据交给机器
UDP 传输的段(segment)由 8 字节的头和有效载荷字段构成
UDP 段的头

- 16 bit 两个端口(port)分别用来标识源机器和目标机器内部的端点
- 当一个 UDP 数据包到来时,它的有效载荷被递交给与目标端口相关联的那个进程
- 再调用了 BIND 原语(或类似的原语)后,这种关联关系就建立了起来
- 采用 UDP 而不是原始 IP 的主要价值在于增加了源端口和目标端口
- 如果没有端口字段,传输层将无从知道如何处理每个入境数据包;而有了端口字段后,它就能把内嵌的段递交给正确的应用程序处理
- 当接收端必须将一个应答返回给源端时,源端口(Source Port)字段是必须的
- 只要将入境段中的源端口字段复制到出境段中的目标端口(Source Port)字段,接收端就指定了由发送端机器上的哪个进程来接收应答
- 16 bit UDP 长度(UDP Length)字段包含8字节的头和数据两部分的总长度
- 最小长度是 8 个字节,刚好覆盖 UDP 头
- 最大长度是 65515 个字节,恰好填满低于 16 bit的最大字节数,由 IP 数据包限制的
- 16 bit 可选的校验和(UDP Checksum)字段提供了额外的可靠性
- 它校验头、数据和 IP 伪头
- 关闭校验和计算,除非数据传输的质量并不重要(如数字语音)
IP 伪头
IPv4 pseudoheader

- 32 bit 源机器和目标机器的 IP 地址
- 9 bit 0 填充
- 9 bit UDP 协议号(17)
- 14 bit UDP 段(包括头)的字节计数
应用
UDP 只提供了一个与IP协议的接口,并在此接口上增加通过端口号复用多个进程的功能,以及可选的端到端错误检测功能
客户机-服务器(Client-Server)
- 客户端向服务器发送一个简短的请求报文,并期待来自服务器的简短回复报文
- 如果请求或回复报文丢失,客户端就会超时,然后再试一次
- 使用 UDP 不仅代码简单,而且要交换的报文也少
域名系统(DNS)
- 如果一个程序需要查询某个主机名的 IP 地址,它可以给 DNS 服务器发送一个包含该主机名的 UDP 数据包
- 服务器用一个包含了该主机 IP 地址的 UDP 数据包作为应答
实时多媒体应用(real-time multitmedia applications)
- Internet 广播、Internet 电话、在线音乐、视频会议、视频点播
实时流协议(RTSP)
流媒体技术(Streaming Technology)
一种传输数据的技术,使得数据可以作为稳定和连续的流进行处理,客户端无需下载整个文件即可查看

好处
- 减少了下载时间
- 提供了稳定多个服务
- 慢的系统可以利用流媒体技术
- 提供按需服务
- 应用
- 流媒体视频、IP 电话、互联网广播、远程学习、视频会议、互动游戏
坏处
- 如果带宽较低的话,很难保持稳定的服务
- 流媒体服务器的维护成本较高
- 传输过程中可能会发生丢包
流媒体的三种传送方式
- 流媒体存储音频和视频
- 流媒体直播音频和视频
- 实时交互音频和视频
媒体播放器
主流媒体产品
- RealPlayer (RealNetworks)
- Media Player (Microsoft)
- QuickTime (Apple)
- FlashPlayer (Adobe)
基本任务
- 解压缩(decompression)
- 消除抖动(jitter removal)
- 纠错(error correction)
获取音频和视频
从浏览器获取

- 从元文件(meta file)获取
- 元文件包含要传输的音频/视频文件的 URL、编码类型等信息
协议
实时流协议(RTSP, Real Time Streaming Protocol)(RFC 2326)
- 实时传输协议(RTP, Real-time Transpor Protocol)
- 用于传输具有实时属性的数据
- 实时传输控制协议(RTCP, Real-time Control Protocol)
- 用于监控服务质量并在正在进行的会话中传递参与者的信息
RTSP
RTSP 流程图

- HTTP 不足以进行用户交互,流媒体服务器音频/视频文件可以通过 UDP 发送
RTSP
- RTSP 是行业标准 Internet 应用级协议
- RTSP 适用于传递和传输多播数据流的框架
- RTSP 是一种协议,它允许媒体播放器控制媒体流的传输以交换控制信息
RTSP 特点
- RTSP 与 RTP 协同工作,提供流式音频和视频
- RTSP 在传输过程中保持服务器状态,不像 HTTP
- 客户端-服务器架构
- RTSP 增强了 HTTP 的功能
- RTSP 提供事件同步
RTP
RTP 的位置

- 以数据包形式传输音频和视频数据
- 将几个实时数据流复用到一个 UDP 数据包流中
- 接收端的处理
- 涉及在正确的时间播放音频和视频
RTCP
能够处理反馈、同步和用户接口等信息,但不传输任何媒体样值
- 可以向源端提供有关延迟、抖动、带宽、拥塞和其他网络特性的反馈信息
- 处理流之间的同步
- 命名不同源
带有缓冲和抖动控制的播放
通过缓冲数据包来平滑输出流

根据抖动寻找播放点(playback point)
- 高抖动需要事先有更多的缓冲

分层结构概览

传输控制协议 TCP
概述
在不可靠的互联网络上提供可靠的端到端字节流
- 互联网络由许多网络组成,每个网络可能有不同的拓扑结构、带宽、延迟、数据包大小和其他参数
- TCP 的设计目标是能够动态适应互联网络的特性,而且具备面对各种故障时的健壮性(robust)
每台支持 TCP 的机器都有一个 TCP 传输实体
- TCP 传输实体可以是一个库过程、一个用户进程、内核的一部分
- 管理 TCP 流,以及与 IP 层之间的接口
工作流程
- TCP 传输实体接受本地进程的用户数据流,将它们分割成不超过 64 KB (去掉 IP 头和 TCP 头,通常不超过 1460 字节)的分段,每个分段以单独的 IP 数据报形式发送
- 当包含 TCP 数据的数据报到达一台机器时,它们被递交给 TCP 传输实体,TCP 传输实体重构出原始的字节流
IP 层并不保证数据报一定被正确地递交给接收方,也不指示数据报的发送速度有多快
- TCP 层负责既要足够快地发送数据报,以便使用网络容量,但又不能引起网络拥塞
- 而且,TCP 超时后,要重传没有递交的数据报
- 即使被正确交付的数据报,也可能存在错序的问题,TCP 负责把接收到的数据报重新装配成正确的顺序
简而言之,TCP 必须提供良好的性能,并提供大多数应用程序所需的可靠性,而 IP 不提供这种可靠性
服务模型
寻址
- TCP 服务由发送端和接收端创建一种称为套接字(socket)的端点来获得
- 每个套接字都有一个套接字编号(地址),该编号由主机的 IP 地址以及一个本地主机的 16 位端口数值组成,端口(port)是 TCP 的 TSAP 名字
- 为了获得 TCP 服务,必须显示地在一台机器的套接字和另一台机器的套接字之间建立一个连接
- 一个套接字有可能同时被用于多个连接
- 每个连接可以用两端的套接字标识符来标识,即
(socket1, socket2)
- 每个连接可以用两端的套接字标识符来标识,即
知名端口(well-known port)
1024 以下的端口号被保留,只能用作由特权用户启动的标准服务

服务守护进程(Service Daemon)
特殊的守护进程,在 UNIX 中称为 inetd(Internet daemon)
- 让一个守护进程同时关联到多个端口上,然后等待针对这些端口的第一个入境连接
- 当出现一个入境连接请求,inetd 就派生出一个新的进程,在这个进程中调用适当的守护程序,然后由这个守护程序来处理连接请求
- 除了 inetd ,其他守护程序都只在确实有工作需要它们做时才被真正激活
- inetd 通过一个配置文件知道哪个端口应该使用哪个守护程序
- 系统管理员可以配置系统使得比较忙的端口(比如 80 端口)使用永久守护程序,而其他端口则让 inetd 处理
TCP 连接
所有的 TCP 连接都是全双工的,并且是点到点的
- 全双工意味着同时可在两个方向上传输数据
- 点到点意味着每个连接恰好有两个端点,TCP 不支持组播或广播传输模式
字节流
一个 TCP 连接就是一个字节流,而不是消息流。端到端之间不保留消息的边界。接收端无法获知数据被写入字节流时的单元大小。一个字节就只是一个字节而已

- (a)4 个 512 字节段作为单独的 IP 数据报发送
- (b)一次 READ 调用中 2048 字节数据被递交给应用程序
缓冲
当一个应用将数据传递给 TCP 时,TCP 可能立即将数据发送出去,也可能将它缓冲起来(为了收集更多的数据一次发送出去)
- 强制数据发送出去,TCP 有个 PUSH 标志位,由数据包携带的 PUSH 标志是告诉 TCP 不要延迟传输
- 紧急数据(urgent data)的处理
- 当一个具有高优先级数据的应用立即被处理时,发送端应用程序把一些控制信息放在数据流中并且将它连同URGENT标志一起交给 TCP,这事件将导致 TCP 停止积累数据,将该连接上已有的所有数据立即传输出去
- 当接收方接收到紧急数据时,接收端应用程序被中断(按 UNIX 的术语是得到一个信号),它停止当前正在做的工作,并且读入数据流以便找到紧急数据。紧急数据的尾部应该被标记出来,因而应用程序能够知道紧急数据的结束位置
TCP 关键特征
序号
- TCP 连接上的每个字节都有它自己独有的 32 位序号。数据包携带的 32 位序号可用在一个方向上的滑动窗口以及另一个方向上的确认
TCP 段
- 发送端和接收端的 TCP 实体以段的形式交换数据
- TCP 段(TCP segment)由一个固定的 20 字节的头(加上可选的部分)以及随后 0 个或多个数据字节构成
TCP 软件决定了段的大小
- 将多次写操作中的数据累积起来,放到一个段中发送;也可以将以此写操作中的数据分割到多个段发送
两个因素限制了段的长度
- 包括 TCP 头在内的每个段,必须适合 IP 的 65515 个字节的有效载荷
- 每个网络都有一个最大传输单元(MTU)
- 发送端和接收端的每个段都必须适合 MTU,才能以单个不分段的数据包发送和接收
- 实际上,MTU 通常是 1500 字节(以太网的有效载荷大小),因此它规定了段长度的上界
- 路径 MTU 发现(path MTU discovery),该技术利用 ICMP 错误消息来发现某条路径上任意一条链路的最小 MTU
滑动窗口
- TCP 实体使用的基本协议是具有动态窗口大小的滑动窗口协议
- 当发送端传送一段时,启动一个计时器
- 当该段到达接收方时,接收端的 TCP 实体返回一个携带了确认号和剩余窗口大小的段(如果有数据要发送的话则包含数据),并且确认号的值等于接收端期望接收的下一个序号
- 如果发送端的计时器在确认段到达之前超时,则重传原来的段
可能的问题
- 段到达的顺序可能是错误的,可能 3072
4095 字节的数据已经到达了,但不能被确认,因为 20483071 字节的数据还没有到达 - 段在传输过程中可能会被延迟很长时间,因而发生超时并重传段
- 重传的段包含的字节范围有可能与原来传输的段的字节范围不同,所以要求管理以跟踪哪些字节已经被正确接收到了
- 由于数据流中的每个字节都有它自己唯一的偏移值,所以这项管理工作是可以完成的
TCP 段的头
TCP 段有一个 20 字节的头和可选的数据
- 每个段的起始部分是一个固定格式的 20 字节头
- 固定的头部之后可能有头的选项
- 如果该数据段有数据部分的话,那么在选项之后是最多可达
65535 -20(IP)- 20(TCP) = 65495
个字节的数据。没有任何数据的 TCP 段也是合法的,通常被用作确认和控制消息

16 bit 源端口(Source port)和目标端口(Destination port)标识了连接的本地端点
- TCP 端口加上所在主机的IP地址组成了 48 位的唯一端点
<IP address, port number>
- 源端点和目标端点一起标识了一条连接
<client IP address, port number>,<server IP address, port number>
- 这个连接标识符就称为 5 元组(5 tuple),由 5 个信息组成:协议(TCP)、源 IP 地址和源端口号、目标 IP 地址和目标端口号
32 bit 序号(Sequence number)
- 范围 $ 0 \leq {\rm Seq} \leq 2^{32} - 1 \approx 4.5 {\rm G bytes} $
- 段的数据部分中第一个字节的序号
- 建立连接时,设置初始序列号
32 bit 确认号(Acknowledgement number)
- 指定下一个期待的字节,而不是已经正确接收到的最后一个字节
- 累计确认,用一个数字概括了接收到的所有数据
- 确认序号为 0~1500 的数据,确认号为 1501
- 确认是被捎带的,如果发送确认段,那么 ACK 标志位将会被置为 1
- TCP 使用滑动窗口流量控制协议来调节从发送方到接收方的流量
4 bit TCP 头长度(TCP header length)
指明 TCP 头包含多少个 32 位的字
- 这个信息是必须的,因为选项(Options)是可变长的
- 实际上指明了数据部分在段内的起始位置(以 32 位字作为单位进行计量)
4 bit 未被使用
8 bit 标志位
CWR 和 ECE 用作拥塞控制的信号
- 采用显式拥塞通知(ECN, Explicit Congestion Notification)
- 当 TCP 接收端收到了来自网络的拥塞指示后,就设置 ECE 以便给 TCP 发送端发 ECN-Echo 信号,告诉发送端放慢发送速率
- TCP 发送端设置 CWR,给 TCP 接收端 CWR 信号,这样接收端就知道发送端已经放慢速率,不必再给发送端发送 ECN-Echo 信号
URG(urgent pointer)紧急指针 - 指向从当前序号开始找到紧急数据的字节偏移量
ACK 表示确认号字段是有效的 - 如果为 0 则该段不包含确认信息,确认号字段可以被忽略
PSH 指出这是被推送(PUSH)的数据 - 请求接收端一旦收到数据后立即将数据地交给应用程序,而不是将它缓冲起来直到整个缓冲区为满
RST 用于突然重置一个已经变得混乱的连接 - 主机崩溃或其他原因
SYN 用于连接建立过程,同步序列号 - CONNECTION REQUEST:SYN=1, ACK=0,意味着捎带的确认号没有被使用
- CONNECTION ACCEPTED:SYN=1, ACK=1
- 同时表示 request / accepted,用 ACK 进一步区分
FIN 用来释放一个连接 - 表示发送端已经没有数据要传输了
- 在关闭一个连接之后,关闭进程可能会在一段不确定的时间内继续接收数据
- SYN 和 FIN 段都有序号,从而保证了这两种段以正确的顺序被处理
- 两端都必须发送 FIN 段
16 bit 窗口大小(Window size)
- 流量控制
- 指定了从被确认的字节算起可以发送多少个字节
- 连接的两端都要宣告窗口大小 RcvWindow
- 窗口大小为 0 ,说明接收端希望拒绝继续接收数据。之后发送一个有同样确认号但是窗口大小非零的段来通知发送端继续发送
16 bit 校验和(Checksum)提供了额外的可靠性
- 校验头、数据、IP 伪头
- 伪头的协议号为 TCP(6),校验和必需强制执行

可变大小 选项(Options)提供了一种添加额外设施的途径
- 32 的倍数,不足部分用 0 填充,最大 40 个字节
- 某些选项用在连接建立期间,主要协商或通知另一端的能力里其它选项用在连接生存期间,通过数据包携带
- 每个选项具有
类型-长度-值
(Type-Length-Value
)编码

NOP 用于填充 TCP 头,使其为 4 字节的倍数
Window Scale 只能在建立连接时的 SYN 段中使用(第一个段)
最大段长(MSS)
- 默认接收 536 字节的有效载荷
- 所有 Internet 主机都要求能够接收 536+20=556 个字节的 TCP 段
- 两个方向上的最大段长可以不同
窗口尺度(Window scale)
- 取值 0-14
- 允许发送端和接收端在连接建立阶段协商窗口尺度因子
- 双方使用尺度因子将窗口大小字段向左移动至多14位,因此允许窗口最大可达 $2^{30}$ 个字节
时间戳(Timestamp)
- 携带由发送端发出的时间戳,并被接收端回应
- 一旦在连接建立阶段启用,则每个数据包都要包含这个选项,主要用来计算来回时间样值,该样值被用在估算多久时候数据包可以被认为丢失
- 还可以用作 32 位序号的逻辑扩展
- 防止序号回绕(PAWS)根据时间戳丢弃入境段,从而解决序号回绕问题
选择确认(Selective ACKnowledgement)
- 使得接收端可以告诉发送端已经收到段的序号范围
- 对确认号的补充,可用在一个数据包已丢失但后续(或重复)数据到达的特定情况下
- 新到达的数据无法反映出头的确认号字段,因为该字段给出的仅仅是下一个期待的有序字节
- 有了 SACK ,发送端可以明显地感知到接收端已经接收了什么数据,并据此确定应该重传什么数据
连接建立
TCP 使用了三次握手来建立连接
- 某一端,比如说服务器,必须限一次执行 LISTEN 和 ACCEPT 原语,然后被动地等待入境连接请求;并且可以指定只接受一个特定的请求源,也可以不指定
- 另一端,比如说客户,执行 CONNECT 原语,同时说明它希望连接的 IP 地址和端口、它愿意接受的最大 TCP 段长(MSS),以及一些可选的用户数据(比如口令)等参数。CONNECT 原语发送一个 SYN 标志位置为 on 和 ACK 标志位置为 off 的 TCP 段,然后等待服务器的响应
- 当这个段到达接收方时,那里的TCP实体检查是否有一个进程已经在目标端口字段指定的端口上执行了 LISTEN 。如果没有,则它发送一个设置了 RST 的应答报文,拒绝客户的连接请求
- 如果某个进程正在该端口上监听,那么 TCP 实体将入境的 TCP 段交给该进程处理
- 该进程可以接受或拒绝这个连接请求
- 如果接受,则发送回一个确认段
- SYN 段只消耗了 1 字节的序号空间,所以它可被毫无异议地确认
TCP 三次握手所完成的功能
- 确保双方都准备好发送数据,并且双方都知道在实际传输开始之前另一端已经准备好了
- 允许双方选择要使用的初始序号
- 采用基于时钟的方案,每 4 微秒有一个时钟滴答
- 当主机崩溃时,可能在最大数据包生命周期的一段时间内不会重新启动,以确保先前连接中的数据包不会滞留在 internet 上

- (a)正常情况
- TCP A 选择一个初始序号 $\rm A_{SEQ}$,并且给 B 发送一个段
- $\rm SYN_{FLAG}=1, ACK_{FLAG}=0, SEQ=A_{SEQ}$
- TCP B 接收到了 SYN,它选择自己的初始序号 $\rm B_{SEQ}$,并且给 A 发送一个段
- $\rm SYN_{FLAG}=1, ACK_{FLAG}=1, ACK=(A_{SEQ}+1), SEQ=B_{SEQ}$
- 当 A 收到了 B 的响应,它确认 B 选择的初始序号,再给 B 发送一个没有数据的段
- $\rm SYN_{FLAG}=0, ACK_{FLAG}=1, ACK=(B_{SEQ}+1), SEQ=A_{SEQ}+1$
- 接下来就可以传输数据了
- TCP A 选择一个初始序号 $\rm A_{SEQ}$,并且给 B 发送一个段
- (b)两台主机同时企图在一对套接字之间建立连接
- 结果是恰好只建立了一个连接,因为所有的连接都是由它们的端点来标识的。如果第一个请求产生了一个由
(x,y)
标识的连接,第二个请求也建立了这样一个连接,那么,实际上只有一个表项,即(x,y)
- 结果是恰好只建立了一个连接,因为所有的连接都是由它们的端点来标识的。如果第一个请求产生了一个由
SYN 段中的序号
实际上是序号的一部分
- 这就是为什么 A 发送的第三个段包含 $ SEQ = (A_{SEQ} + 1) $
- 这是必须的,这样才不会混淆新旧 SYN
- 为了确保忽略旧的段,TCP 忽略序号在接收窗口之外的任何段
- 包括设置了 SYN flag 的段
三次握手的漏洞
当监听进程接受一个连接请求,并立即以 SYN 段作为响应时,它必须记住该 SYN 段的序号。这意味着一个恶意的发送端很容易地占据一个主机资源
具体做法:SYN 泛洪(SYN flood)
- 恶意用户不断发送SYN段请求服务器的连接,但又故意不完成连接建立的后续过程,由此可消耗掉一台主机的资源
抵御:SYN 小甜饼(SYN Cookie)
- 主机不去记忆序号,而是选择一个加密生成的序号,将它放在出境段中,并且忘记它
- 如果三次握手完成后,该序号(+1)返回主机
- 主机运行相同的加密函数,只要该函数的输入是已知的(例如,其他主机的IP地址和端口,和一个本地密钥),它就能重新生成正确的序号
- 这个过程允许主机检查确认号是否正确,而不必记住单独的序号
- 无法处理 TCP 选项,只有当主机很容易受到 SYN 泛洪时,才可以用 SYN Cookie
连接释放

- 为了释放一个连接,任何一方都可以发送一个设置了 FIN 标志的 TCP 段,这表示它已经没有数据要发送了
- 当 FIN 段被另一方确认后,这个方向上的连接就被关闭,不再发送任何数据
- 当两个方向都关闭后,连接才算被彻底释放
- 通常情况下,释放一个连接需要 4 个 TCP 段,每个方向上一个 FIN 和一个 ACK
- 第一个 ACK 和第二个 FIN 组合在同一个段中,从而将所需段的总数降低到 3 个
避免两军对垒 - 使用计时器
- 如果在两倍于最大数据包生存期内,针对 FIN 的响应没有出现,那么 FIN 的发送端直接释放连接
- 另一方最终会注意到似乎没人在监听连接,因而也会超时
连接管理模型
建立连接和释放连接所需的步骤可以用一个有限状态机来表示
TCP 连接管理有限状态机使用的状态

有限状态机
- 客户用的是实线,服务器用的是虚线,细线表示不常用的事件
- 每一跳先都标记成一对事件 / 动作(event/action)
/-
表示什么都不做

服务器和用户的 TCP 周期

滑动窗口
窗口管理(window management)
TCP 的窗口管理将正确接收段的确认和接收端的接受缓冲区分配分离开来
- TCP 中的窗口管理并不直接与确认关联,因为它在许多数据链路协议中
- 在 TCP 中,确认和发送附加数据的权限是分离的
- 实际上,接收方可以这样说:我已经通过 k 接收了字节,但是现在我不再需要了
- 这种分离(实际上是可变大小的窗口)给予了额外的灵活性

当窗口变为 0
- 紧急数据,比如允许用户杀掉远程机器上运行的某一个进程
- 发送端发送 1 字节的段,强制接收端重新宣告下一个期望的字节和窗口大小
- 这种数据包称为窗口探测(window probe),防止窗口更新数据包丢失后发生死锁
发送端不一定接到应用程序传递来的数据就马上发送出去,接收端也不一定必须尽可能快地发送确认段
捎带(piggybacking)
TCP 的目标是努力获得良好的性能,因此它会延迟发送少量数据或 ACK 来收集更多的段
算法包括
- 延迟确认(delayed acknowledgement)和窗口更新,希望可以搭载某些数据,一般为 500 毫秒
Nagle 算法(Nagle’s Algorithm)-发送端
- 当数据每次以很少量的方式进入到发送端时,只发送第一次到达的数据字节,缓存所有剩余的内容,直到发送出去的那个数据包被确认
- 然后发送一个 TCP 段中的所有缓冲字符,并再次开始缓冲,直到它们全部被确认
避免发送端发送多个小的数据包
低能窗口综合症(silly window syndrome)
当数据以大块形式传递给发送端 TCP 实体时,但接收端的交互式应用程序一次仅读取一个字节数据的时候,这个问题就会发生

Clark 解决办法(Clark’s solution)-接收端
禁止接收端发送只有 1 个字节的窗口更新段。相反,它强制接收端必须等待一段时间,直到有了一定数量的可用空间之后再通告对方
特别是,只有当接收端能够处理它在连接建立时宣告的最大数据段(MSS),或者它的其缓冲区一半为空时,以较小者为准,才发送窗口更新段
此外,发送端不发送太小的段也会有所来帮助。相反,它应该等待一段时间,直到可以发送一个满的段,或者至少包含接收端缓冲区一半大小的段(根据过去收到的窗口更新模式来估计)
接收端的 TCP 实体除了向发送端宣告较大的窗口以外,还可以进一步提高性能。与发送端的 TCP 一样,它也可以缓冲数据,因此它可以阻塞来自应用程序的读取(READ)请求,直至它积累了大块的数据。这样做可以减少调用 TCP 的次数,从而减少开销(overhead)
- 接收端必须处理的另一个问题是乱序到达的数据段
- 累计确认(cumulative acknowledgement)
- 只有当确认字节之后的所有数据都到达之后才能发送确认
- 如果接收端已经获得 0、1、2、4、5、6、7 ,他可以确认直到 2(包括 2 )之前的数据
- 当发送端超时,重发 3
- 因为接收端已经缓冲了 4~7 ,一旦接收到 3 就可立即确认直到 7 的全部字节
Nagle 算法和 Clark 解决办法
Nagle 算法和 Clark 针对低能窗口综合症的解决方案相互补充
- Nagle 试图解决由于发送端应用每次向 TCP 传递一个字节而引起的问题
- Clark 试图解决由于接收端应用每次从 TCP 流中读取一个字节而引起的问题
计时器管理
重传计时器(RTO, Retransmission TimeOut)
问题是:超时间隔应该设置为多长?
- 如果超时间隔设置得太短,则会发生大量不必要的重传,将堵塞 Internet
- 如果超时间隔设置得太长,则一旦数据包丢失后,由于太长的重传延迟,所以性能会受到影响
- 理想情况下,希望计时器的时间间隔接近真实的往返时间(RTT)。由于实际往返延迟是动态变化(与数据链路层不同),因此使用固定计时器是不够的

- (a)在数据链路层,期望的延迟使可以测量的毫秒数,并且是高度可预测的(即方差很小),所以,计时器可以被设置成恰好比期望的确认到达时间稍长即可
- 由于在数据链路层中确认极少被延迟(因为不存在拥塞),所以,如果在预期的时间内确认没有到来,则往往意味着帧或确认已经被丢失了
- (b)TCP 要想确定到达接收方的往返时间非常困难,即使知道了这段时间,要确定超时间隔也非常困难
Jacobson 算法
对于每一个连接,TCP 维护一个变量 SRTT(Smoothed Round-Trip Time, 平滑往返时间),它代表到达接收方往返时间的当前最佳估计值
- 当一个段被发送出去时,TCP 启动一个计时器
- 该计时器有两个作用:一是看该段被确认需要多长时间,二是若确认时间太长,则触发重传
- 如果在计时器超时前确认返回,则 TCP 测量这次所花的时间 $R$ ,然后更新 SRTT ,$\rm SRTT = \alpha SRTT + (1 - \alpha)R$
由于 RTT 只是实际延迟的估计,因为每个数据包都有所不同,因此段的实际重新传输超时(RTO)设置取决于 SRTT 的标准偏差。通过了解 SRTT 的标准偏差,我们可以将 RTO 建立在真正的概率上
让超时值对往返时间的变化以及平滑地往返时间要变得敏感
- 跟踪另一个平滑变量,RTTVAR(Rount-Trip Time VARiation, 往返时间变化)的,其计算:$ \rm RTTVAR = \beta * RTTVAR + (1- \beta) * |SRTT - M| $
- 最后,在传输段时,将其重传计时器设置为 RTO:$\rm RTO = SRTT + 4 * RTTVAR$
重传计时器的最小值为 1 秒,无论估算值是多少
- 这是为了防止根据测量获得的欺骗性重传值而选择的保守值
Karn 算法
确认到达时,无法判断该确认是针对第一次传输,还是针对后来的重传
- 不更新任何重传段的估算值。此外,每次连续重传的超时间隔值加倍,直到段能一次通过为止
持续计时器(persistence timer)
为了避免死锁
- 接收端发送一个窗口大小为 0 的确认,让发送端等一等。稍后,接收端更新了窗口,但是携带更新消息的数据包丢失
- 当持续计时器超时后,发送端给接收端发送一个探询消息,接收端对探询消息的响应是将窗口大小告诉发送端
保活计时器(keepalive timer)
当一个连接空闲了较长一段时间以后,保活计时器可能超时,从而促使某一端查看另一端是否还在。如果另一端没有响应,则终止连接
每个 TCP 连接使用的最后一个计时器被应用于连接终止阶段,即在关闭过程中,当连接处于 TIMED WAIT 状态时所使用的计时器
- 它的超时值为两倍于最大数据包生存期,主要用来确保当前连接被关闭后,该连接上创建的所有数据包都已完全消失
拥塞控制
拥塞(congestion)
当提供给任何网络的敷在超过它的处理能力时,拥塞就会产生
- 和流量控制不同
表现
- 丢包(路由器中的缓冲区溢出)
- 长延迟(路由器中的缓冲区排队)
当路由器上的队列增长到很大时,网络层检测到拥塞,并试图通过丢弃数据包来管理拥塞。传输层接收到从网络层反馈来的拥塞信息,并减慢它发送到网络的流量速率
拥塞检测(congestion dectection)
在过去,丢包造成的超时可能是由以下两种情况之一引起的
- 传输线路上的噪声
- 拥塞路由器丢包
如今,所有的 TCP 算法都假设丢包是由拥塞和监控器超时所引起的
拥塞控制的两种方法
端到端的拥塞控制
- 没有来自网络的反馈
- 从系统观察到丢失、延迟,推断拥塞
- TCP 选择方法
网络辅助拥塞控制
- 路由器向终端系统提供反馈
- 单比特指示拥塞
- 发送方以显式速率发送
尽管网络层也尝试管理拥塞,但大部分工作都是由 TCP 完成的,因为真正的拥塞解决方案是降低数据速率
拥塞窗口(congestion window)
如果一个传输协议使用 AIMD(加法递增乘法递减)控制法则来响应从网络传来的二进制拥塞信号,那么该传输协议应该收敛到一个公平且有效的带宽分配
TCP 就是在这个方法的基础上实现的,把丢包当做二进制信号,窗口大小是任何时候发送端可以往网络发送的字节数
- 相应的速率则是窗口大小除以连接的往返时间
- TCP 根据 AIMD 规则来调整该窗口大小
流量控制窗口指出了接收端可以缓冲的字节数,就是连接建立中的宣告窗口
TCP 要并发跟踪流量控制窗口和拥塞窗口,可能的发送字节数是两个窗口中较小的那个
- Advertised Window
- 接收端宣告的
- Congestion Window
- 每个连接中,TCP 源保持的变量
MaxWindow::min(CongestionWindow, AdvertisedWindow)
EffectiveWindow= MaxWindow–(LastByteSent-LastByteAcked)
拥塞窗口(cwnd)是根据感知的拥塞程度设置的
- 主机接收内部拥塞的隐式(数据包丢弃)或显式(数据包标记)指示
AIMD(Additive Increase Multiplicative Decrease, 加法递增乘法递减)
加法递增
加法递增是感知可用容量的反应
线性增加基本思想
- 对于每个发送的数据包的 cwnd,将 cwnd 增加 1 个数据包(MSS)
- 实际上,对于每个到达的 ACK,cwnd 都是分数递增的
- 想法是在一个 TCP 连接所花费的大量时间中,它的拥塞窗口接近最佳值——不至于小到降低吞吐量,但也没大到发生拥塞
1 | increment = MSS x (MSS /cwnd) |

- 在每一个 RTT 结尾,发送端的拥塞窗口增长到足够它可以向网络注入额外一个数据包
乘法递减
关键假设是丢失的数据包和由此产生的重传是由于路由器(或交换机)的拥塞造成的
- TCP 对超时的响应是将 cwnd 减半
尽管 cwmd 是以字节为单位定义的,但文献中经常会以包的形式讨论拥塞控制(或者说 MSS)
cwnd 不允许低于单个数据包的大小
拥塞崩溃(congestion collapse)
由于网络拥塞,在很长一段时间内实际吞吐量急剧下降(即下降幅度超过了 100 倍)
- 避免拥塞而不是控制拥塞
防止拥塞
建立连接时,必须选择合适的窗口大小。接收方可以根据其缓冲区大小指定一个窗口。如果发送方使用这个窗口大小,接收端的缓冲区溢出问题不会发生,但它们仍可能由于网络内部的拥塞而发生
RFC 2001 TCP 慢启动(slow start),拥塞避免(congestion avoidance),快速重传(fast retransmit)
- 使用序号和确认号来跟踪拥塞窗口,并使用一个 AIMD 规则来调整拥塞窗口的大小
- 使用小的突发数据包
- 确认返回到发送端的速率恰好是数据包通过路径上最慢链路时的速率
- 确认时钟(ack clock)
- 通过使用一个确认时钟,TCP 平滑输出流量和避免不必要的路由器队列

- 如果网络拥塞窗口从一个很小的规模开始,那么在快速网络上应用 AIMD 规则将需要很长的时间才能达到一个良好的操作点
慢速启动(slow start)
线性递增需要很长的时间才能达到一个良好的操作点
从 TCP Tahoe (RFC 793,1981) 开始,增加了慢启动机制以提供 cwnd 大小的初始指数增长
慢启动机制可防止启动缓慢。而且,慢启动比一次发送整个流量控制窗口大小的数据要慢。
慢启动算法
- 建立连接时,发送端用连接中使用的最大段长初始化拥塞窗口。然后发送端发送该初始窗口大小的数据。
- 对于每个重传计时器超时前得到确认的段,发送端的拥塞窗口增加一个段的字节量,结果是每一个被确认的段允许发送两个段,每经过一个往返时间拥塞窗口增加一倍
- 拥塞窗口呈指数增长直到超时或达到接收端的窗口

强制数据包以它们之间的最小间距到达接收端。接收端在发送确认时要维持这种间隔,从而发送端也会以同样的间距接收确认
- 如果网络路径缓慢,确认将抵达得较晚(超过一个 RTT 延迟)
- 如果网络路径很快,确认会迅速到达(在一个 RTT 后)
发送端所要做的只是按照确认时钟的时间来注入新的数据包
慢速启动的缺陷
- 导致拥塞窗口按指数增长,很快它将太多的数据包以太快的速度发到网络。网络中将很快建立起队列,当队列满时,一个或多个包会被丢弃。此后,当确认未能如期返回发送端时,TCP 发送端将超时
- 拥塞和丢包
慢启动阈值(slow start threshold)
保持对慢速启动的控制,发送端为每个连接维持一个称为慢启动阈值的阈值。最初,这个值被设置得任意高,可以达到流量控制窗口的大小,因此不会限制连接速度。TCP 以慢速启动方式不断增加拥塞窗口,直到发生超时或者拥塞窗口超过阈值或者接收端的窗口为满
每当检测到丢包,比如超时,慢启动阈值就被设置为当前拥塞窗口的一半,整个过程再重新启动。基本想法是当前的窗口太大,因为是它在过去造成了阻塞,所以现在才检测到超时
一旦慢速启动超过了阈值,TCP 就从慢速启动切换到线性增加(加法递增)。在这种模式下,每个往返时间拥塞窗口只增加一段。像慢速启动一样,这通常也是为每一个被确认的段而不是为每一次 RTT 实施窗口的增加
慢启动的两种情况
- 在连接刚开始时(冷启动)
- 当连接失效时,等待发生超时(宣告窗口大小为 0 )
然而,在第二种情况下端口拥有更多的信息。cwnd 当前的值可以被保留作为重设阈值
阈值
- 最大段长为 1 KB
- 初始拥塞窗口为 64 KB
- 当发生超时后,阈值被设置为当前拥塞窗口的一半,拥塞窗口被重置为 MSS 的大小(1 KB),从 0 开始传输
- 使用慢启动,拥塞窗口呈指数增长,直到达到阈值(32 KB)
- 每次到达一个新的确认就增加窗口的大小,而不是连续增加,因而导致窗口的大小表现出离散的阶梯模式
- 当窗口大小达到阈值后,窗口大小就按线性增长,每个 RTT 只增加一段

- Initially: MSS=1024 bytes, CWnd=64 KB
- timeout occurred: Slow Start
- Threshold = 32 KB, CWnd = 1024 bytes, 1 segment
MSS 是 1024 字节,最初 cwnd 为 64 KB,发生超时,因此阈值设置为 32 KB,并且此处传输0的拥塞窗口为 1 KB 。 拥塞窗口呈指数增长,直到达到阈值(32 KB)。从此开始,它线性增长。
- 13 产生了超时,阈值设置为 cwnd 的一半(20 KB),慢启动重新开始
- 当 14 的确认到来,前四个都是 cwnd 的两倍,但之后它线性增长
- 如果不再发生超时,cwnd 将继续增长直到到接收端的窗口大小。此时,只要不再有超时并且接收端的窗口不发生改变,它就会停止增长并保持不变
快速重传(fast retransmission)
基本思想:重复确认
- 当丢失数据包的后续数据包到达接收端时,它们会给发送端返回确认。这些确认携带着相同的确认号
- 发送端每次收到重复确认时,可能有另一个包已经到达接收端,而丢失的包仍没有出现
- TCP 定三个重复的确认意味着一个数据包已经丢失
因为包可以选择网络中不同的路径,所以它们可能不按发送顺序到达接收端。这样就会触发重复确认,即使没有数据包被丢失。丢失包的序号可以从确认号推断出来,它是整个数据序列中紧接着的下一个数据包。因此,这个包可以被立即重传,在其计时器超时前就重新发送出去
重传后,慢启动阈值被设置为当前拥塞窗口的一半,就像发生了超时。重新开始慢启动过程,拥塞窗口被设置成一个包,有个这个窗口大小,如果在一个往返时间内确认了该重传的数据包以及丢包之前已发送的所有数据,则发出一个新的数据包
重传早早地在超时之前发生
- 通常,快速重传消除约一半的粗粒超时(粗略超时)
- 这样可以使吞吐量大约提高 20%
- 由于源的窗口很小,快速重传不会消除所有超时
- TCP Tahoe 增加了快速重传功能
快速恢复(fast recovery)
基本思想
- 当快速重传检测到三个重复的确认时,从拥塞避免区域开始恢复过程,并使用管道中的确认来调整发送数据包的速度
- 在快速重传后,将 cwnd 减半,并且从这一点开始线性加法递增,依据管道(pipe)中滞留的确认
- TCP Reno 增加了快速恢复功能
TCP 避免了慢速启动,只有第一次启动或者发生超时时才进入真正的慢速启动
- 当发生多个数据包丢失时,快速重传机制不足以恢复,仍然可能会发生超时
- 此时,当前连接的拥塞窗口遵循一种锯齿(saqtooth)模式,即 AIMD
- 在连接花费的大部分时间内其拥塞窗口接近于带宽延迟积的最佳值

TCP 拥塞控制

至今为止的重要变化
TCP 的许多复杂性来自于从一个重复确认流中推断出已经到达和已经丢失的数据包。累计确认号无法提供这种确切的信息
选择确认(SACK, Selective ACKnowledgement)
- 该确认列出了 3 个已接收的字节范围,有了这个信息,发送端在实现拥塞窗口时可以更直接地确定哪些数据包需要重传,并跟踪那些还在途中的数据包

SACK 是严格意义上的咨询信息。实际上使用重复确认来检测丢包和调整拥塞窗口仍像之前那样。然而,有了 SACK,TCP 可以更加容易地从同时丢失多个包的情况下恢复过来,因为TCP发送端知道哪些数据包尚未收到
除了用丢包作为拥塞信号外,也可以使用显式拥塞通知(ECN, Explicit Congestion Notification)
ECN 是 IP 层的机制,主要用来通知主机发生了拥塞,有了它,TCP 接收端可以接收来自 IP 的拥塞信号
当发送端和接收端在建立阶段过程中设置了 ECE 和 CWR 标志位,双方均表示它们能够使用这些标志位后,该 TCP 连接就可启用 ECN
如果使用 ECN,每个携带 TCP 段的数据包在 IP 头上打上标记,表明它可以携带 ECN 信号
支持 ECN 信号的路由器在接近拥塞时就会在携带 ECN 标志的数据包上设置拥塞信号,而不是在拥塞发生后丢弃这些数据包
如果到达的任何数据包携带了 ECN 拥塞信号,则会告知 TCP 接收端
然后接收端使用 ECE (ECN Echo) 标志位给 TCP 发送端发通知,告知它的数据包经历了拥塞
发送端通过拥塞窗口减少(CWR, Congestion Window Reduced)标志位告诉接收端它已经收到拥塞信号了
- TCP 发送端收到这些拥塞通知的反应和它根据重复确认检测到丢包的处理方式完全相同
- 严格来说这种情况更好,拥塞已经被检测出来,而且没有包收到任何损害