为什么不应该把TCP思维套在UDP上

  • 为什么不应该把TCP思维套在UDP上

为什么不应该把 TCP 思维套在 UDP 上?

结构差异

TCP表头
UDP表头

TCP 上的概念很多: 建立通路, 资源使用, 数据传输, 可靠传输, 基于重复累计确认的重传, 超时重传, 校验和, 流量控制, 拥塞控制, 最大分段大小, 选择确认, TCP 窗口缩放选项, TCP 时间戳, 强制数据递交, 终结通路.

以上这些能力, UDP 基本上都没有, 它仅比链路层多一点区分应用层目的的能力. UDP 足够简单意味着足够灵活.

如果可能发生,则一定会发生

墨菲定律:

如果有多过一种方式去做某事,而其中一种方式将导致灾难,则必定有人会这样选择。

通常介绍 UDP 适合应用在游戏/语音/视频等场景, 少量的错包不影响业务. 为什么 UDP 适合这些场景? 它能用在这些场景, 不代表它是这些场景的最优方案, 必然是存在 TCP 无法解决的问题, 才让这些服务选择了功能简陋的 UDP 协议. 错包不影响业务扩展开来讲是指 TCP 协议在乎错包, UDP 不在乎错包, 更在乎实时性/连续性. UDP 的特点就是它不在乎 TCP 在乎的因素, 这些因素影响了实时性.

在代码实现上, UDP 只需要创建一个 socket, 绑定到一个端口上, 即可以开始收发. 通常 socket 用完时, 端口也用完了.

因此我可以这样使用 UDP:

  1. 往任意 IP 的任意端口发送随机报文, 看看哪个端口有响应
  2. 甲通过 A 端口, 将请求报文发送到乙的 B 端口; 乙将响应报文用 C 端口, 发给甲的 D 端口
  3. 甲通过 A 端口, 将请求报文发送到乙的 B 端口; 乙委托丙将响应报文用 C 端口, 发给甲的 D 端口
  4. 甲通过 A 端口, 将请求报文发送到乙的 B 端口, 但将发送报文的源 IP 修改为了丙的 IP, 乙将会将响应报文发往丙
  5. 双方协商各用 10 个 UDP 端口, 同时进行接受和发送

这些方法在 TCP 里自然是行不通的, 但在 UDP 协议中, 只要可以这样做, 就一定会有人这样做. 所以当把 TCP 的一些思维套在 UDP 上是一种理想主义, 真实情况常常不是我们能枚举完的.

UDP 的报文非常简单, 使用也非常灵活, 原本没有连接的概念, 需要自己定义 UDP 连接. 尝试了一些定义方法, 都不能完全准确达到连接方向判断意图, 这时需要接纳一些容错, 毕竟原本就没有 UDP 连接的定义, 当各方对 UDP 连接的定义不一致时, 必然会导致行为与预期不一样.

客户端视角的 UDP

语音/视频等业务常会产生丢包, 但是丢包方式的不同对业务有着不同的影响. 比如 30%的丢包是均匀发生的, 还是全丢在某个时间段, 对体验的影响有明显的区分. 显然, 我们期待的是更均匀的丢包. 可是 UDP 没有流量控制防止方法, 如何丢包则有一些方法. 尽管 UDP 通信常被描述为"尽力而为", 但是不同方式的"尽力"会达到不同的效果.

服务商视角的 UDP

如果是 TCP 攻击, 客户端需要一定的开销, 创建连接, 维护连接, 也就是攻击者需要付出一定的代价. 而在 UDP 攻击中, 攻击者付出的代价小很多, 如果攻击者想消耗的就是服务方的带宽流量, UDP 是一个很好的方式. 比如说服务购买了 100GB 的不限速流量, 处理能力仅 10MB 每秒, 但接受速度 1GB 每秒, 那么 90%的请求流量无效, 但这些流量不是免费的. 服务方应该避免产生这种情况.

运营商的视角的 UDP

完成一次通信需包含多个终端以及通信通道, 受关注的总是服务端和客户端, 其实运营商的视角同样重要. DDoS 攻击中, 我们常关心服务端的资源消耗情况, 实际上运营商的资源也是有限的, 服务端简单不响应请求, 但接收流量却已经消耗了带宽, 只是这个资源一般属于运营商. 我们在压力测试中常用到"丢包率"指标, 这个指标表达的完整通信链条中的丢包, 而不仅仅是服务端的丢包. 运营商也会丢包. 在运营商看, 服务方仅购买了 1MB/s 的带宽, 但客户端以 1GB/s 的速度发送, 双方都不必为浪费的流量付费, 是运营商承担了这部分带宽的代价. 因此, 运营商必然想办法屏蔽这种流量, 也就是 UDP 的 QoS. 在 TCP 中有拥塞控制, 但在 UDP 中, 运营商可以通过丢包来控制流量. 实际情况中, 运营商更加简单粗暴, 直接屏蔽长时间使用的端口的流量, 也就是 UDP 的端口屏蔽. 在微信通话的实际测试中发现, 每一通电话客户端会使用多个端口, 其中有一个 UDP 端口会和同一服务器的 6 个 UDP 端口进行通信, 推测就是为了应对运营商的端口屏蔽.

总结

UDP 的灵活表示在实现一个目标时, 它有着多种实现方式, 并且都是合法的, 只要能最终实现稳定的通信, 不管它实现的如何和 TCP 大相径庭, 都是"存在即合理"的. 因而, 我们不能完全将 TCP 的概念套用在 UDP 上, 即便为了产品设计, 创造了新的 UDP 连接定义, 也应该能预期并允许出错, 毕竟"允许出错"就是 UDP 的核心功能, 这是 UDP 的优势, 不是它的缺点, 是服务主动选择的协议核心能力, 而不是不得不接受的缺点.

更多阅读