Envoy 中 UDP 网络通信实现分析

  1. 概述

UDP 是一种无连接、不可靠的传输协议,但在某些场景下(如实时音视频、DNS 查询等)具有重要的应用价值。Envoy 作为高性能的云原生代理,也提供了对 UDP 协议的支持。本文将深入分析 Envoy 中 UDP 通信的实现细节。

  1. UDP 监听器实现 - UdpListenerImpl

2.1 类定义

cpp 复制代码
class UdpListenerImpl : public BaseListenerImpl,                        public virtual UdpListener,                        protected Logger::Loggable<Logger::Id::main> {public:  UdpListenerImpl(Event::Dispatcher& dispatcher, Random::RandomGenerator& random,                  Runtime::Loader& runtime, const Address::InstanceConstSharedPtr& address,                  UdpListenerCallbacks& cb, SocketSharedPtr socket, bool bind_to_port,                  bool hand_off_restored_destination_connections);
  // UdpListener 接口实现  void disable() override;  void enable() override;  Address::InstanceConstSharedPtr localAddress() const override { return address_; }  UdpListenerCallbacks& callbacks() { return callbacks_; }
private:  // 内部状态和回调方法  void onSocketEvent(uint32_t events);  void onData(const Buffer::InstancePtr& buffer, const Address::InstanceConstSharedPtr& remote_address);  void onError(ErrorCode error);  // ... 其他成员变量};

2.2 核心功能

监听 UDP 数据包:

• 绑定到指定的网络地址和端口

• 监听和接收 UDP 数据包

• 处理数据包到达事件

状态管理:

• enable() - 启用监听器

• disable() - 禁用监听器

• localAddress() - 获取本地地址信息

回调机制:

• onSocketEvent() - 处理 socket 事件

• onData() - 处理收到的数据包

• onError() - 处理错误

2.3 实现细节

cpp 复制代码
// 简化的数据包处理流程void UdpListenerImpl::onSocketEvent(uint32_t events) {  // 确保是读事件  if (events & Event::FileEvents::Read) {    while (true) {      // 准备缓冲区和地址信息      uint8_t buffer[65536]; // 最大 UDP 数据包大小      sockaddr_storage addr;      socklen_t addr_len = sizeof(addr);
      // 接收数据包      ssize_t bytes_received = socket_->recvFrom(buffer, sizeof(buffer), 0,                                                reinterpret_cast<sockaddr*>(&addr), &addr_len);
      if (bytes_received > 0) {        // 处理数据包        Buffer::InstancePtr data = std::make_unique<Buffer::OwnedImpl>(          buffer, static_cast<uint64_t>(bytes_received));
        Address::InstanceConstSharedPtr remote_address =           Address::factory().createAddress(reinterpret_cast<sockaddr*>(&addr), addr_len);
        callbacks_.onData(data, remote_address);      } else if (bytes_received == -1) {        // 处理错误        if (errno == EAGAIN || errno == EWOULDBLOCK) {          // 没有更多数据了          break;        } else {          // 其他错误          callbacks_.onError(errorCodeFromErrno(errno));          break;        }      } else {        // 连接关闭        break;      }    }  }}

3. UDP 数据包写入实现

3.1 UdpPacketWriterImpl

cpp 复制代码
class UdpPacketWriterImpl : public UdpPacketWriter {public:  UdpPacketWriterImpl(Socket& socket) : socket_(socket) {}
  // UdpPacketWriter 接口实现  WriteResult writePacket(const Buffer::Instance& buffer,                         const Address::InstanceConstSharedPtr& peer_address) override {    // 获取地址信息    const sockaddr* addr = peer_address->sockAddr();    socklen_t addr_len = peer_address->sockAddrLen();
    // 发送数据包    ssize_t bytes_sent = socket_.sendTo(buffer.linearize(65536), buffer.length(), 0, addr, addr_len);
    if (bytes_sent > 0) {      // 发送成功      return WriteResult{WriteStatus::Success, static_cast<uint32_t>(bytes_sent)};    } else {      // 发送失败      if (errno == EAGAIN || errno == EWOULDBLOCK) {        return WriteResult{WriteStatus::Blocked, 0};      } else {        return WriteResult{WriteStatus::Error, 0};      }    }  }
  void setFlushCallback(std::function<void()> callback) override {    // 由于 UDP 是无连接的,通常不需要 flush 回调  }
  bool flush() override {    // UDP 是无连接的,立即发送数据,不需要 flush    return true;  }
private:  Socket& socket_;};

3.2 UDP 发送实现特点

无连接发送

  • 每个数据包都包含完整的目的地址

  • 不需要建立连接

  • 发送操作简单快速

缓冲区管理

  • 直接使用缓冲区数据发送

  • 支持发送前检查缓冲区是否可写

  • 处理发送阻塞和错误

性能优化

  • 使用线性化缓冲区提高发送效率
  • 支持 GSO(通用分段卸载)优化

  • 批量发送多个数据包

  1. UDP 代理过滤器 - UdpProxy

4.1 配置示例

css 复制代码
static_resources:  listeners:  - name: udp_listener    address:      socket_address: { address: 0.0.0.0, port_value: 10001 }    udp_listener_config:      quic_options: {}      max_datagram_size: 65535    filter_chains:    - filters:      - name: envoy.filters.udp_listener.manager        typed_config:          "@type": type.googleapis.com/envoy.extensions.filters.udp.udp_listener_manager.v3.UdpListenerManager          listener_filters: []          udp_filters:          - name: envoy.filters.udp.udp_proxy            typed_config:              "@type": type.googleapis.com/envoy.extensions.filters.udp.udp_proxy.v3.UdpProxy              stat_prefix: udp_proxy              cluster: upstream_udp_cluster              idle_timeout: 300s

4.2 UdpProxy 配置字段

java 复制代码
message UdpProxy {  // 统计信息前缀  string stat_prefix = 1 [(validate.rules).string = {min_len: 1}];
  // 上游集群名称  string cluster = 2 [(validate.rules).string = {min_len: 1}];
  // 连接空闲超时  google.protobuf.Duration idle_timeout = 3 [(validate.rules).duration.gte.seconds = 0];
  // 会话管理配置  UdpSessionConfig session_config = 4;
  // 访问日志配置  repeated accesslog.AccessLog access_log = 5;}

4.3 UDP 代理工作原理

cpp 复制代码
// 简化的 UDP 代理实现class UdpProxyFilter : public Network::UdpListenerCallbacks {public:  UdpProxyFilter(Upstream::ClusterManager& cluster_manager, const UdpProxyConfig& config)    : cluster_manager_(cluster_manager), config_(config) {}
  void onData(const Buffer::InstancePtr& buffer,              const Network::Address::InstanceConstSharedPtr& remote_address) override {    // 根据源地址查找会话    auto session_iter = sessions_.find(remote_address->asString());
    if (session_iter == sessions_.end()) {      // 创建新会话      auto session = std::make_shared<UdpProxySession>(cluster_manager_, config_, remote_address);      sessions_.emplace(remote_address->asString(), session);      session->onData(buffer);    } else {      // 复用现有会话      session_iter->second->onData(buffer);    }  }
  void onError(Network::ErrorCode error) override {    // 处理错误  }
private:  Upstream::ClusterManager& cluster_manager_;  const UdpProxyConfig& config_;  std::map<std::string, std::shared_ptr<UdpProxySession>> sessions_;};
  1. UDP 会话管理 - UdpProxySession

5.1 会话状态管理

cpp 复制代码
class UdpProxySession {public:  UdpProxySession(Upstream::ClusterManager& cluster_manager, const UdpProxyConfig& config,                  const Network::Address::InstanceConstSharedPtr& remote_address)    : cluster_manager_(cluster_manager), config_(config), remote_address_(remote_address) {    // 创建空闲超时定时器    idle_timer_ = dispatcher_.createTimer([this]() { this->onIdleTimeout(); });    idle_timer_->enableTimer(config_.idle_timeout());  }
  void onData(const Buffer::InstancePtr& buffer) {    // 重置空闲超时定时器    idle_timer_->enableTimer(config_.idle_timeout());
    // 查找或创建上游连接    if (!upstream_connection_) {      upstream_connection_ = createUpstreamConnection();    }
    // 转发数据包到上游    upstream_connection_->write(buffer);  }
private:  void onIdleTimeout() {    // 会话超时,关闭连接    close();  }
  void createUpstreamConnection() {    // 从集群管理器获取上游主机    auto host = cluster_manager_.get(config_.cluster())->loadBalancer().chooseHost(nullptr);    if (host) {      // 创建上游连接      upstream_connection_ = host->createConnection();      upstream_connection_->addCallbacks(this);    }  }
  Upstream::ClusterManager& cluster_manager_;  const UdpProxyConfig& config_;  Network::Address::InstanceConstSharedPtr remote_address_;  Network::UdpPacketWriterPtr upstream_connection_;  Event::TimerPtr idle_timer_;};

5.2 会话管理策略

空闲超时

  • 当会话在指定时间内没有活动时自动关闭

  • 避免资源泄漏

  • 配置字段:idle_timeout

会话关联

  • 根据源地址和端口关联数据包到会话

  • 保持会话状态一致性

  • 支持会话复用

流量控制

  • 限制单个会话的数据包数量

  • 防止资源耗尽

  • 配置字段:max_packets_per_session

6. UDP 监听器管理器 - UdpListenerManager

6.1 功能

过滤器链管理

  • 管理 UDP 监听器过滤器链

  • 支持多个 UDP 过滤器

  • 过滤器链配置和执行

监听器配置

  • 处理 UDP 监听器配置

  • 支持 QUIC 选项

  • 配置字段验证和处理

连接处理

  • 管理 UDP 连接

  • 处理连接事件

  • 提供统一的接口

6.2 配置结构

java 复制代码
message UdpListenerManager {  repeated UdpListenerFilter listener_filters = 1;  repeated UdpFilter udp_filters = 2;}
message UdpListenerFilter {  string name = 1 [(validate.rules).string = {min_len: 1}];  google.protobuf.Struct config = 2;}
message UdpFilter {  string name = 1 [(validate.rules).string = {min_len: 1}];  google.protobuf.Struct config = 2;}

6.3 工作流程

go 复制代码
┌─────────────────────────────────────────────────────────────────────┐│  UDP 数据包流程                                                 │└─────────────────────────────────────────────────────────────────────┘
┌──────────────┐     ┌──────────────────┐     ┌──────────────────┐     ┌──────────────┐│   客户端      │     │   UdpListenerImpl │    │ UdpListenerManager │  │  UdpProxy   │└──────┬───────┘     └──────┬──────────┘     └──────┬──────────┘     └──────┬───────┘       │                   │                       │                        │       │ 1. 发送数据包      │                       │                        │       │───────────────────▶│                       │                        │       │                   │                       │                        │       │                   │ 2. 读取数据包          │                        │       │                   │──────────────────────▶│                        │       │                   │                       │                        │       │                   │                       │ 3. 应用过滤器链        │       │                   │                       │──────────────────────▶│       │                   │                       │                        │       │                   │                       │ 4. 代理到上游          │       │                   │                       │──────────────────────▶│       │                   │                       │                        │       │                   │                       │ 5. 接收响应            │       │                   │                       │◀──────────────────────┘       │                   │                       │                        │       │                   │ 6. 发送响应到客户端    │                        │       │                   │◀──────────────────────┘                        │       │                   │                        │                        │       │ 7. 响应到达        │                        │                        │       │◀──────────────────┴────────────────────────┴──────────────────────┘

7. UDP 监听器配置 - UdpListenerConfig

7.1 核心配置

java 复制代码
message UdpListenerConfig {  // QUIC 协议支持  QuicOptions quic_options = 1;
  // 最大数据包大小  google.protobuf.UInt32Value max_datagram_size = 2;
  // UDP 套接字配置  core.v3.UdpSocketConfig udp_socket_config = 3;
  // 接收缓冲区大小  google.protobuf.UInt64Value receive_buffer_size = 4;
  // 发送缓冲区大小  google.protobuf.UInt64Value send_buffer_size = 5;}
message QuicOptions {  // QUIC 连接超时  google.protobuf.Duration connection_timeout = 1;
  // QUIC 最大并发连接数  google.protobuf.UInt32Value max_concurrent_connections = 2;
  // QUIC 空闲超时  google.protobuf.Duration idle_timeout = 3;}

7.2 配置示例

css 复制代码
static_resources:  listeners:  - name: udp_listener    address:      socket_address: { address: 0.0.0.0, port_value: 10001 }    udp_listener_config:      quic_options:        connection_timeout: 5s        max_concurrent_connections: 1000        idle_timeout: 300s      max_datagram_size: 1452  # 考虑 IPv4 以太网 MTU      udp_socket_config:        prefer_gro: true        recv_buffer_size: 1048576        send_buffer_size: 1048576      receive_buffer_size: 1048576      send_buffer_size: 1048576

8. 性能优化

8.1 UDP 发送优化 - GSO (Generic Segmentation Offload)

cpp 复制代码
class UdpGsoBatchWriterImpl : public UdpPacketWriter {public:  // 发送实现  WriteResult writePacket(const Buffer::Instance& buffer,                         const Address::InstanceConstSharedPtr& peer_address) override {    // 检查是否支持 GSO    if (supportsGso()) {      // 使用 GSO 优化发送      return writeGsoBatch(buffer, peer_address);    }
    // 回退到普通发送    return fallbackWriter_->writePacket(buffer, peer_address);  }
private:  WriteResult writeGsoBatch(const Buffer::Instance& buffer,                            const Address::InstanceConstSharedPtr& peer_address) {    // 组合多个数据包到一个 GSO 批次    std::vector<iovec> iov;    size_t total_size = 0;
    // 将缓冲区数据分割成适合 GSO 的批次    // ...
    // 使用 sendmmsg 发送批次    struct mmsghdr msg_hdr;    // ...
    int ret = socket_->sendMmsg(&msg_hdr, 1);    // ...  }};

8.2 性能调优建议

UDP 接收优化

  • 增加接收缓冲区大小 (receive_buffer_size)

  • 启用 GRO (Generic Receive Offload)

  • 使用 SO_REUSEPORT 提高接收性能

UDP 发送优化

  • 增加发送缓冲区大小 (send_buffer_size)

  • 启用 GSO (Generic Segmentation Offload)

  • 使用批量发送 (sendmmsg)

连接管理优化

  • 合理设置空闲超时时间
  • 限制会话最大数据包数量

  • 使用连接池复用上游连接

9. UDP 与 TCP 实现的区别

9.1 连接管理

TCP

  • 面向连接的协议

  • 三次握手建立连接

  • 四次挥手关闭连接

  • 维护连接状态

UDP

  • 无连接的协议

  • 没有连接建立过程

  • 发送数据包前不需要准备

  • 没有连接状态维护

9.2 可靠性

TCP

  • 可靠的传输

  • 确认、重传和超时机制

  • 流量控制和拥塞控制

  • 有序数据传输

UDP

  • 不可靠的传输

  • 无确认机制

  • 无重传和超时机制

  • 无流量控制

  • 数据可能无序到达

9.3 性能特征

TCP

  • 较高的连接建立开销

  • 较低的传输延迟

  • 带宽效率高

  • 适合大数据量传输

UDP

  • 低连接开销

  • 较低的传输延迟

  • 高带宽效率

  • 适合实时应用

9.4 在 Envoy 中的实现差异

监听器类型

  • TCP 监听器使用 TcpListenerImpl

  • UDP 监听器使用 UdpListenerImpl

数据处理

  • TCP 处理字节流 (Buffer::Instance)

  • UDP 处理数据包 (Buffer::InstancePtr)

连接管理

  • TCP 使用连接池和重用机制

  • UDP 使用会话管理和空闲超时

代理实现

  • TCP 代理维护连接状态
  • UDP 代理维护会话状态和数据包关联

10. 常见使用场景

10.1 DNS 代理

css 复制代码
static_resources:  listeners:  - name: dns_listener    address:      socket_address: { address: 0.0.0.0, port_value: 53 }    udp_listener_config: {}    filter_chains:    - filters:      - name: envoy.filters.udp_listener.manager        typed_config:          "@type": type.googleapis.com/envoy.extensions.filters.udp.udp_listener_manager.v3.UdpListenerManager          udp_filters:          - name: envoy.filters.udp.udp_proxy            typed_config:              "@type": type.googleapis.com/envoy.extensions.filters.udp.udp_proxy.v3.UdpProxy              stat_prefix: dns_proxy              cluster: upstream_dns_cluster              idle_timeout: 5s  clusters:  - name: upstream_dns_cluster    connect_timeout: 0.5s    type: STRICT_DNS    lb_policy: ROUND_ROBIN    dns_lookup_family: V4_ONLY    load_assignment:      cluster_name: upstream_dns_cluster      endpoints:      - lb_endpoints:        - endpoint:            address:              socket_address: { address: 8.8.8.8, port_value: 53 }

10.2 实时媒体传输

css 复制代码
static_resources:  listeners:  - name: rtp_listener    address:      socket_address: { address: 0.0.0.0, port_value: 5004 }    udp_listener_config:      max_datagram_size: 1500    filter_chains:    - filters:      - name: envoy.filters.udp_listener.manager        typed_config:          "@type": type.googleapis.com/envoy.extensions.filters.udp.udp_listener_manager.v3.UdpListenerManager          udp_filters:          - name: envoy.filters.udp.udp_proxy            typed_config:              "@type": type.googleapis.com/envoy.extensions.filters.udp.udp_proxy.v3.UdpProxy              stat_prefix: rtp_proxy              cluster: rtp_upstream              idle_timeout: 30s  clusters:  - name: rtp_upstream    connect_timeout: 0.1s    type: STATIC    lb_policy: LEAST_REQUEST    load_assignment:      cluster_name: rtp_upstream      endpoints:      - lb_endpoints:        - endpoint:            address:              socket_address: { address: 192.168.1.100, port_value: 5004 }

11. 实现架构总览

11.1 核心组件关系图

css 复制代码
┌─────────────────────────────────────────────────────────────────────────────┐│                           Envoy UDP 通信架构                                  │├─────────────────────────────────────────────────────────────────────────────┤│                                                                             ││  ┌─────────────────┐     ┌──────────────────┐     ┌───────────────────┐   ││  │  配置加载器      │────▶│  UdpListenerConfig│────▶│  UdpListenerManager│   ││  └─────────────────┘     └──────────────────┘     └─────────┬─────────┘   ││                                                              │             ││                                                              ▼             ││  ┌─────────────────┐     ┌──────────────────┐     ┌───────────────────┐   ││  │   UdpProxy      │◀────│ UdpListenerImpl  │     │  FilterChain      │   ││  │   (Filter)      │     │                  │     │                   │   ││  └────────┬────────┘     └──────────────────┘     └───────────────────┘   ││           │                                                                 ││           ▼                                                                 ││  ┌─────────────────┐     ┌──────────────────┐                              ││  │UdpProxySession  │────▶│ UdpPacketWriter  │                              ││  │                 │     │                  │                              ││  └─────────────────┘     └──────────────────┘                              ││                                                                             │└─────────────────────────────────────────────────────────────────────────────┘

11.2 数据流处理路径

cs 复制代码
外部客户端    │    ▼UDP Socket (recvfrom)    │    ▼UdpListenerImpl::onSocketEvent()    │    ▼UdpListenerCallbacks::onData()    │    ▼UdpListenerManager::onData()    │    ▼UdpProxyFilter::onData()    │    ▼UdpProxySession::onData()    │    ▼UdpPacketWriter::writePacket()    │    ▼UDP Socket (sendto)    │    ▼上游服务器

12. 总结

Envoy 中的 UDP 通信实现提供了高性能、可扩展的 UDP 数据包处理能力。主要特点包括:

12.1 核心优势

  1. 无连接设计:UDP 监听器和数据包处理不需要维护连接状态,降低了资源消耗

  2. 会话管理:使用会话关联数据包到客户端,提供类似连接的体验,同时保持 UDP 的高效性

  3. 性能优化:支持 GSO/GRO 优化、批量发送/接收、零拷贝技术

  4. 可靠性增强:提供空闲超时和会话管理机制,避免资源泄漏

  5. 代理支持:UDP 代理过滤器提供与 TCP 代理类似的功能,包括负载均衡、健康检查等

12.2 技术亮点

  • 高效的事件驱动模型:基于 libevent 的异步 I/O 处理

  • 灵活的过滤器链:支持自定义 UDP 过滤器扩展

  • 精细的资源控制:缓冲区大小、超时时间、并发限制等可配置

  • 完善的统计和监控:提供丰富的 metrics 用于性能分析

  • QUIC 协议支持:在 UDP 基础上实现可靠的 QUIC 传输

12.3 适用场景

  • DNS 代理和缓存:利用 UDP 的低延迟特性

  • 实时音视频传输:RTP/RTCP 流媒体代理

  • 游戏服务器代理:对延迟敏感的 UDP 游戏流量

  • IoT 设备通信:轻量级的 UDP 数据采集

  • QUIC 协议代理:下一代 HTTP/3 传输

12.4 未来展望

尽管 UDP 是不可靠的,但在实时音视频、DNS 查询等场景下具有重要价值。Envoy 的 UDP 实现通过抽象和优化,使得 UDP 通信在 Envoy 中变得可管理和高性能。随着 HTTP/3 和 QUIC 的普及,Envoy 对 UDP 的支持将变得更加重要,未来可能会看到:

  • 更智能的会话管理策略

  • 增强的拥塞控制算法

  • 改进的数据包调度机制

  • 更紧密的 QUIC 协议集成

Envoy 的 UDP 实现展示了如何在保持 UDP 优势的同时,提供企业级的可靠性和可管理性,为云原生环境中的 UDP 应用提供了强大的基础设施支持。

相关推荐
wanhengidc2 小时前
双线服务器的优势有哪些?
运维·服务器·网络·网络协议·智能手机
lbb 小魔仙2 小时前
无公网 IP 环境下的 PostgreSQL 远程访问方案:基于内网穿透技术的全流程解析
网络协议·tcp/ip·postgresql
biter down2 小时前
UDP 服务端 + 客户端 全场景字节序 & 类型转换
网络协议·udp·php
三佛科技-187366133972 小时前
便携式一字美甲灯方案开发
单片机·嵌入式硬件
飞睿科技2 小时前
从 Mesh 到无线视频,ESP32-E22 的场景落地指南,飞睿科技乐鑫代理商
单片机·嵌入式硬件
某风吾起2 小时前
通过mmwave studio配置TI毫米波雷达IWR1843的StaticConfig
嵌入式硬件·学习
Blurpath住宅代理3 小时前
静态独享代理IP深度解析:技术本质、优势边界与适用场景
网络·动态代理·住宅ip·住宅代理·静态住宅代理
爱学习的小囧3 小时前
VM硬件版本20与17核心区别(ESXi 8.0适配+实操指南)
运维·服务器·网络·数据库·esxi·vmware·虚拟化
Proxy_ZZ03 小时前
VLAN的作用:为什么要把一个网络切成好几块?
网络·智能路由器