- 概述
UDP 是一种无连接、不可靠的传输协议,但在某些场景下(如实时音视频、DNS 查询等)具有重要的应用价值。Envoy 作为高性能的云原生代理,也提供了对 UDP 协议的支持。本文将深入分析 Envoy 中 UDP 通信的实现细节。
- 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(通用分段卸载)优化
-
批量发送多个数据包
- 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_;};
- 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 核心优势
-
无连接设计:UDP 监听器和数据包处理不需要维护连接状态,降低了资源消耗
-
会话管理:使用会话关联数据包到客户端,提供类似连接的体验,同时保持 UDP 的高效性
-
性能优化:支持 GSO/GRO 优化、批量发送/接收、零拷贝技术
-
可靠性增强:提供空闲超时和会话管理机制,避免资源泄漏
-
代理支持: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 应用提供了强大的基础设施支持。