Fluent-Bit 内置 nghttp2:深入理解 nghttp2_session_get_local_settings 的确认语义与源码实现
【免费下载链接】fluent-bitFast and Lightweight Logs, Metrics and Traces processor for Linux, BSD, OSX and Windows项目地址: https://gitcode.com/GitHub_Trending/fl/fluent-bit
本文以 Fluent-Bit 仓库内嵌的 nghttp2-1.65.0 库中nghttp2_session_get_local_settings的 API 参考文档为主线,完整讲解该函数的原型与返回值语义,并结合 nghttp2 的nghttp2_settings_id枚举、会话数据结构、SETTINGS/SETTINGS ACK 握手流程与单元测试,说明"本地端点参数值何时被对端确认"这一核心概念,最后对照 Fluent-Bit 的 HTTP/2 客户端与 HTTP 服务器源码,展示 SETTINGS 帧提交的实际调用方式。读完本文,你将掌握如何在 HTTP/2 库中正确读取"已被对端确认的本地 SETTINGS 值",并理解它与默认值、nghttp2_session_get_remote_settings的区别。
一、函数原型与返回语义
nghttp2 官方 API 参考文档位于 nghttp2_session_get_local_settings.rst,其中给出的定义非常凝练:
#include <nghttp2/nghttp2.h> uint32_t nghttp2_session_get_local_settings( nghttp2_session *session, nghttp2_settings_id id);文档原文的语义说明是:"Returns the value of SETTINGSidof local endpoint acknowledged by the remote endpoint."即返回"本地端点的 SETTINGSid值,且该值已被远端端点确认(acknowledged)"。id必须是nghttp2_settings_id枚举中定义的某个值。
这句话看似简单,但包含了 HTTP/2 协议中一个容易混淆的关键点:
- "local settings"指的是本端通过 SETTINGS 帧发送给对端的参数(例如本端允许的最大并发流数、本端初始流控窗口大小);
- "acknowledged by the remote endpoint"指的是对端已经用SETTINGS ACK 帧确认收到这些参数。
因此在确认完成之前,读取到的本地参数仍然是库内部的默认值,而不是你通过nghttp2_submit_settings()提交的新值。这一点在 nghttp2 的单元测试中被明确验证:nghttp2_session_test.c 中的注释直接写明:
/* before receiving SETTINGS ACK, local settings have still default values */ assert_uint32(NGHTTP2_DEFAULT_MAX_CONCURRENT_STREAMS, ==, nghttp2_session_get_local_settings( session, NGHTTP2_SETTINGS_MAX_CONCURRENT_STREAMS));测试随后手动触发nghttp2_session_on_settings_received接收一个 SETTINGS ACK 帧,之后断言值即变为提交时的新值:
assert_uint32(50, ==, nghttp2_session_get_local_settings( session, NGHTTP2_SETTINGS_MAX_CONCURRENT_STREAMS)); assert_uint32(16 * 1024, ==, nghttp2_session_get_local_settings( session, NGHTTP2_SETTINGS_INITIAL_WINDOW_SIZE));这段测试完整演示了"提交 → 等待 ACK → 值切换为确认值"的全过程,是理解本函数语义的最佳实证。
二、id 的全部合法取值:nghttp2_settings_id 枚举
文档要求id必须是nghttp2_settings_id中的值。该枚举在 nghttp2.h 中定义了 8 个 SETTINGS 参数:
| 枚举值 | 数值 | 对应 HTTP/2 参数 |
|---|---|---|
NGHTTP2_SETTINGS_HEADER_TABLE_SIZE | 0x01 | HPACK 动态表大小 |
NGHTTP2_SETTINGS_ENABLE_PUSH | 0x02 | 是否启用服务器推送 |
NGHTTP2_SETTINGS_MAX_CONCURRENT_STREAMS | 0x03 | 最大并发流数 |
NGHTTP2_SETTINGS_INITIAL_WINDOW_SIZE | 0x04 | 连接级初始窗口大小 |
NGHTTP2_SETTINGS_MAX_FRAME_SIZE | 0x05 | 最大帧大小 |
NGHTTP2_SETTINGS_MAX_HEADER_LIST_SIZE | 0x06 | 最大头部块列表大小 |
NGHTTP2_SETTINGS_ENABLE_CONNECT_PROTOCOL | 0x08 | 启用 CONNECT 协议扩展(RFC 8441) |
NGHTTP2_SETTINGS_NO_RFC7540_PRIORITIES | 0x09 | 禁用 RFC 7540 优先级(RFC 9218) |
头文件源码注释还提示了一个维护要点:新增 SETTINGS 时需要同步更新NGHTTP2_INBOUND_NUM_IV容量,说明这 8 个 ID 与库内部 SETTINGS 载荷解析的存储容量是绑定设计的。
三、源码实现:从枚举分发到会话存储
函数声明位于 nghttp2.h#L4062-L4070,紧邻其上方的nghttp2_session_get_remote_settings文档说明为"返回远端端点通告的 SETTINGS 值"——两者成对出现,分别读取会话中的两份参数存储。
具体实现在 nghttp2_session.c#L7443-L7466:
uint32_t nghttp2_session_get_local_settings(nghttp2_session *session, nghttp2_settings_id id) { switch (id) { case NGHTTP2_SETTINGS_HEADER_TABLE_SIZE: return session->local_settings.header_table_size; case NGHTTP2_SETTINGS_ENABLE_PUSH: return session->local_settings.enable_push; case NGHTTP2_SETTINGS_MAX_CONCURRENT_STREAMS: return session->local_settings.max_concurrent_streams; case NGHTTP2_SETTINGS_INITIAL_WINDOW_SIZE: return session->local_settings.initial_window_size; case NGHTTP2_SETTINGS_MAX_FRAME_SIZE: return session->local_settings.max_frame_size; case NGHTTP2_SETTINGS_MAX_HEADER_LIST_SIZE: return session->local_settings.max_header_list_size; case NGHTTP2_SETTINGS_ENABLE_CONNECT_PROTOCOL: return session->local_settings.enable_connect_protocol; case NGHTTP2_SETTINGS_NO_RFC7540_PRIORITIES: return session->local_settings.no_rfc7540_priorities; } assert(0); abort(); /* if NDEBUG is set */ }从源码结构看,有三个值得注意的实现细节:
- 纯只读访问:函数直接返回
session->local_settings结构体中的对应字段,无锁、无计算、无副作用,因此调用成本极低,适合在帧回调中频繁读取。 local_settings的结构定义:该字段声明在 nghttp2_session.h#L326(nghttp2_settings_storage local_settings;),与会话中的remote_settings并列存放,对应"本端参数"与"对端参数"两份独立状态。- 非法 id 的行为:传入枚举之外的值会落入
assert(0)并在断言开启时abort()。这是库内部契约,调用方必须保证id合法——这也是 API 文档中"idmust be one of the values defined in nghttp2_settings_id"这一约束的底层依据。
local_settings 的生命周期:初始化、更新与确认
- 初始化:会话创建时(nghttp2_session.c#L482)调用
init_settings()填充协议默认值,例如最大并发流默认值定义为 nghttp2_session.h#L103 的NGHTTP2_DEFAULT_MAX_CONCURRENT_STREAMS(即0xffffffffu)。 - 更新:
nghttp2_session_update_local_settings()(nghttp2_session.c#L4146)根据 SETTINGS 帧携带的nghttp2_settings_entry数组逐项写入session->local_settings字段。 - 确认生效:测试用例进一步印证,收到 SETTINGS ACK 后,
session->local_settings.initial_window_size等字段才被最终认定为"已确认"状态;在 ACK 之前的窗口内,本函数读到的是默认值而非待提交值。
四、在 Fluent-Bit 中的实际使用场景
Fluent-Bit 在两个位置使用 nghttp2 的 SETTINGS 机制(通过nghttp2_submit_settings提交本端参数,这正是nghttp2_session_get_local_settings所读取的同一份状态):
1. HTTP/2 客户端(日志/数据发送侧)
flb_http_client_http2.c#L506-L531 在创建客户端会话(nghttp2_session_client_new)后,立即提交 3 项本地 SETTINGS:
session_settings[0].settings_id = NGHTTP2_SETTINGS_MAX_CONCURRENT_STREAMS; session_settings[0].value = 1; session_settings[1].settings_id = NGHTTP2_SETTINGS_MAX_FRAME_SIZE; session_settings[1].value = cfl_sds_alloc(session->parent->parent->temporary_buffer); session_settings[2].settings_id = NGHTTP2_SETTINGS_ENABLE_PUSH; session_settings[2].value = 0; result = nghttp2_submit_settings(session->inner_session, NGHTTP2_FLAG_NONE, session_settings, 3);可以看到 Fluent-Bit 的 HTTP/2 客户端把并发流限制为 1、关闭服务器推送,这符合其作为"单向数据投递客户端"的用途——不需要多路复用并发,也不需要服务器主动推送流。从源码结构看,当前客户端代码路径主要依赖nghttp2_submit_settings完成本端参数通告,尚未直接调用nghttp2_session_get_local_settings读取确认值;但该函数为上层在需要感知"对端已确认的本地窗口/并发参数"时提供了标准入口。
2. HTTP 服务器(内置 HTTP Server / 指标采集侧)
flb_http_server_http2.c#L397-L413 在nghttp2_session_server_new之后提交本端 SETTINGS:
session_settings[0].settings_id = NGHTTP2_SETTINGS_MAX_CONCURRENT_STREAMS; session_settings[0].value = 1; result = nghttp2_submit_settings(session->inner_session, NGHTTP2_FLAG_NONE, session_settings, 1);服务器端同样将MAX_CONCURRENT_STREAMS设为 1,随后通过nghttp2_session_send发出 SETTINGS 帧启动握手。对这类"参数固定、逻辑简单"的服务端场景,本地 SETTINGS 值在确认前是默认值、确认后可读回这一语义尤其值得注意:若上层逻辑需要判断握手是否完成,从确认值是否等于提交值入手是一种可行的推断方式(这一点属于从源码结构推断,nghttp2 文档本身未作此用途的明示)。
五、使用本函数的实践要点
结合 API 文档与源码,使用nghttp2_session_get_local_settings时应把握以下要点:
- 返回值类型是
uint32_t:所有 SETTINGS 参数值在 HTTP/2 帧中均为 32 位无符号整数,函数不通过返回值指示错误,因此不存在错误码分支。 - 区分"提交值"与"确认值":在收到对端 SETTINGS ACK 之前,读到的是库默认值(如最大并发流为
0xffffffffu);需要感知确认状态时,应结合 ACK 帧事件(如on_frame_recv回调)综合判断。 - 与
nghttp2_session_get_remote_settings配对使用:前者回答"对端确认了我的哪些参数",后者回答"对端要求我遵守哪些参数"(例如对端的NGHTTP2_SETTINGS_MAX_FRAME_SIZE决定了你能发出的最大帧长)。 - id 必须合法:非法枚举值会触发断言失败,属于未定义行为,不应在生产路径上依赖。
- 线程与调用时机:该函数是对会话结构体的直接字段读取,应与会话的读写处于同一线程上下文,避免与其他
nghttp2_session_*调用产生竞争。
小结
nghttp2_session_get_local_settings是 nghttp2 会话 API 中读取"已被对端确认的本地 SETTINGS 参数"的唯一标准入口:它以nghttp2_settings_id为索引,直接映射到会话内的local_settings存储(nghttp2_session.c#L7443-L7466)。其真正的技术含量不在函数本身,而在 HTTP/2 SETTINGS 握手的时序语义——提交新值后、ACK 之前读到默认值,ACK 之后才读到确认值,这一点由 nghttp2_session_test.c#L5990-L6016 的测试序列完整佐证。在 Fluent-Bit 中,HTTP/2 客户端(src/flb_http_client_http2.c)与内置 HTTP 服务器(src/http_server/flb_http_server_http2.c)均围绕同一套 SETTINGS 机制工作,本函数为理解这两处 SETTINGS 帧交换提供了完整的语义基准。
【免费下载链接】fluent-bitFast and Lightweight Logs, Metrics and Traces processor for Linux, BSD, OSX and Windows项目地址: https://gitcode.com/GitHub_Trending/fl/fluent-bit
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考