性能比拼: TCP vs UDP(延迟和吞吐量)

本内容是对知名性能评测博主 Anton Putra TCP vs UDP Performance (Latency & Throughput) 内容的翻译与整理, 有适当删减, 相关指标和结论以原作为准

介绍

最近,我正在开发一个 高性能项目 ,所以我决定尝试使用 原始套接字(raw sockets) 来通过 TCP 和 UDP 发送和接收 JSON 消息。

我想要比较 两种协议的延迟(latency)和吞吐量(throughput),并且不依赖任何框架。

我使用的是 C++ 语言 ,并参考了我认为目前 互联网上最好的网络编程指南。


什么是性能?

在衡量 任何事物的性能 时,我们需要关注 延迟(latency) 和 吞吐量(throughput)。

  • 延迟(Latency)

    在这个测试中,延迟指的是 从一台计算机发送 JSON 消息到另一台计算机接收它所花费的时间 。

    我们通常使用 百分位(percentiles) 来测量延迟,例如 p50、p90 和 p99。

    • p90 表示 90% 的消息 在此时间内被成功发送并接收。
    • p99 表示 99% 的消息 在此时间范围内完成传输,依此类推。
  • 吞吐量(Throughput)

    吞吐量衡量的是 单位时间内可以同时发送和接收的消息数量 。

    我们通常以 每秒消息数(messages per second)、每秒请求数(requests per second)或每秒查询数(queries per second) 来测量它。

通常情况下,如果一个系统被优化为 低延迟 ,那么它的 吞吐量可能较低 ,反之亦然。

在本次测试中,你将看到这一点的具体表现。


TCP vs UDP

你可能已经知道,TCP 是一个 更可靠的协议 ,但它需要先 建立连接。

TCP 传输过程

  1. 服务器端

    • 服务器创建一个 套接字(socket) ,该套接字将返回一个 文件描述符(file descriptor)。
    • 然后,它会将该套接字 绑定到服务器上的某个端口 ,例如 8080 端口。
    • 之后,服务器开始 监听(listen) 传入的连接请求。
    • 当有客户端连接时,服务器调用 accept() 函数,该函数会 阻塞并等待客户端请求。
  2. 客户端

    • 客户端也需要创建一个 套接字(socket)。
    • 然后,它调用 connect() 函数 连接服务器。
    • 这时,服务器端的 accept() 函数会返回 客户端的文件描述符 ,服务器可以使用它来 读写数据。

例如,服务器可以向客户端发送 "Hello" 消息,客户端则使用 recv() 函数来接收所有消息。

如果客户端 没有收到完整的消息 (例如缺少一个字符),它会 请求服务器重新发送 。

这正是 TCP 被认为可靠的原因 ---客户端和服务器都会验证数据是否被完整接收 ,并且 保证数据的顺序不变。


UDP 传输过程

UDP 的工作方式与 TCP 不同:

  1. 服务器端

    • 服务器创建一个 套接字(socket) ,并将其 绑定到端口。
    • 但它 不需要与客户端建立连接 ,它可以 直接使用 recvfrom() 接收消息。
  2. 客户端

    • 客户端 不需要调用 connect() ,也 不需要建立连接 ,它可以直接向服务器 发送消息。
    • 但客户端 不知道服务器的状态 ,它只是不断发送消息,假设服务器正在监听。

由于 UDP 不需要建立连接 ,它通常具有 更低的延迟 。

然而,因为服务器不会 确认数据是否完整接收 ,UDP 消息可能会 缺失或顺序混乱。

尽管如此,UDP 在 视频和音频流(streaming)、金融交易系统(trading systems) 等对 低延迟要求极高 的场景中仍然非常有用。


TCP vs UDP 的方向性

在 TCP 中,每个 客户端与服务器之间 都是 一对一的连接 。

例如,如果有 多个客户端 连接到 同一台服务器 ,那么每个客户端都必须 单独建立 TCP 连接,才能开始发送和接收数据。

但在 UDP 中,客户端可以 同时向所有服务器发送消息 ,这被称为 广播(broadcasting)。

例如,在 AWS VPC(虚拟私有云) 内,UDP 消息可以被 所有服务器接收和处理 。

但如果 只有部分服务器需要接收消息 ,广播就 不够高效 ,这时可以使用 多播(multicasting) ,只向 特定的服务器组 发送数据。

在这种情况下,我们可以有一个 单个生产者(producer) 和 多个消费者(consumers)。


代码概述

我使用 C++ 编写了 TCP 和 UDP 的服务器与客户端 ,你可以在我的 GitHub 公开仓库 中找到完整的 源代码。

此外,我还在每个客户端上 集成了 Prometheus 监控,并使用了:

  • 直方图(histogram) 来测量 延迟,
  • 计数器(counter metric) 来测量 吞吐量(由于某些异常情况)。

在接下来的视频中,我会提供一个 从零开始的 C++ 或 C 语言套接字编程指南。


测试

我在 AWS 上运行了基准测试,并为 TCP 和 UDP 服务器及客户端 分配了 专用虚拟机(VM) 。

由于我的应用是 单线程(single-threaded) ,只能使用 一个 CPU 核心 ,所以我选择了 m7a.medium EC2 实例 (1 个 CPU,4GB 内存 )。

当然,我还使用 EKS(Elastic Kubernetes Service) 运行 Prometheus 及其他监控组件。

测试过程

整个测试 大约持续 1 小时 ,但我将其 压缩为几分钟。

该测试包含 两个阶段:

  1. 第一阶段 :
    • 发送 每秒 16,000 条消息 ,测量 延迟 和 CPU 使用率。
    • 例如,你可以看到 TCP 服务器的 CPU 使用率很高 ,因为 TCP 需要执行数据验证。
    • 结果表明,UDP 的消息发送延迟 显著低于 TCP,这正是预期的。

(此处用到的json和之前测试过的其他http框架时是一致的,可以比较一下使用http框架的延迟)

  1. 第二阶段 :
    • 发送 尽可能多的消息 ,测量 吞吐量。
    • 结果显示,TCP 由于内置拥塞控制(congestion control),吞吐量更高。
    • 你可以同时运行 1,000 个 TCP 连接 ,它们会 自动感知可用带宽,保持高效。
    • 而 UDP 在高负载下可能反而降低吞吐量 ,并且 TCP 需要更多 CPU 资源处理消息。

结论

UDP 不总是最快的协议 ,它在 小规模场景下表现优秀 。

但如果你关心 吞吐量 ,或者想要处理 大量消息或请求 ,TCP 可能是更好的选择。

当然,还有许多 基于 UDP 的协议 实现了 数据包级别的验证 ,提高了可靠性。

但无论选择哪种协议,你都应该 自己进行测试 ,并 测量所有关键指标。


最后

感谢观看!

如果你对更多基准测试感兴趣,比如 数据库对比(PostgreSQL vs. MySQL)、缓存系统对比(Redis vs. Memcached),请继续关注我的频道!

相关推荐
深蓝AI10 小时前
Python 3.13 的 Queue.shutdown:异步消费者终于能正常收工了
后端
武子康11 小时前
MultiNet6 空闲崩溃:`EXCVADDR=0x8` 后又出现 `CORRUPT HEAP`
人工智能·后端·agent
武子康12 小时前
MultiNet7 每轮都 clean,ESP32-P4 内存为什么还在掉?
人工智能·后端·agent
超级架构师12 小时前
PRAXO-CORE 2.5.0:用 conformance 和显式边界构建 Go Agent 框架
开发语言·人工智能·后端·golang·ai编程·智能体框架·praxo-core
陈随易13 小时前
AI 运维服务器,安全又可靠的方法
前端·后端·程序员
IT_陈寒14 小时前
JavaScript的this坑人实录,这次我真的记下来了
前端·人工智能·后端
starzy199015 小时前
Scala类和对象:从构造器到样例类的完整指南
开发语言·后端·scala
梦影_15 小时前
MySQL进阶教程(五)—— SQL调优(下)
android·大数据·数据库·后端·sql·mysql·database
考虑考虑15 小时前
mariadb数据库
数据库·后端·mariadb
明月_清风16 小时前
用自然语言操控真机:Google Artemis 移动端 AI Agent 上手与实践
人工智能·后端