gRPC TCP Connect Handshaker 源码解析:连接建立的第一步是如何完成的 gRPC TCP Connect Handshaker 源码解析连接建立的第一步是如何完成的【免费下载链接】grpcC based gRPC (C, Python, Ruby, Objective-C, PHP, C#)项目地址: https://gitcode.com/GitHub_Trending/gr/grpc导读gRPC 客户端在真正发送 HTTP/2 请求之前需要经过一个可插拔的握手Handshake流程来建立并准备底层连接。其中TCP Connect Handshaker是整条握手链上最基础也最关键的一环——它负责把一个解析好的远端地址真正转换为一个可用的 TCP 连接。本文以 gRPC 仓库中 src/core/handshaker/tcp_connect/AGENTS.md 为骨架结合其实现源码 tcp_connect_handshaker.cc 与头文件 tcp_connect_handshaker.h深入讲解该 handshaker 的设计目的、核心参数、执行流程与生命周期以及它如何与安全握手security handshaker协作构成完整的 gRPC 连接建立链路。读完本文你将能说清楚 gRPC 客户端连接建立时谁在什么时候发起了 TCP 连接以及这个连接最终交给了谁。一、定位连接建立握手链上的第一环1.1 Handshaker 框架概述gRPC 的握手框架位于 src/core/handshaker 目录其总览文档 src/core/handshaker/AGENTS.md 明确指出该框架提供了一种可插拔pluggable的机制用于在客户端与服务器建立连接时执行各种握手操作例如 TLS、ALTS 和 HTTP CONNECT并且被设计为易于扩展可以轻松加入新的 handshaker 实现。框架中的核心抽象包括handshaker.h定义单个握手操作的核心Handshaker类handshaker_factory.h定义负责创建Handshaker实例的HandshakerFactory类handshaker_registry.cc / handshaker_registry.h定义HandshakerFactory的注册表按类型与优先级组织握手器proxy_mapper.h 与 proxy_mapper_registry.cc用于决定目标地址的代理设置。在 handshaker_factory.h 中定义了握手器的优先级枚举HandshakerPriority从低到高依次为kPreTCPConnectHandshakersTCP 连接建立之前执行主要用于客户端kTCPConnectHandshakers负责真正的 TCP 连接建立主要用于客户端kHTTPConnectHandshakers负责 HTTP CONNECT 连接的建立主要用于客户端kReadAheadSecurityHandshakers在连接建立之后、安全握手之前执行目前主要用于服务端kSecurityHandshakers负责连接建立之后的安全握手TLS/ALTS 等客户端与服务端均适用。TCPConnectHandshakerFactory::Priority()返回HandshakerPriority::kTCPConnectHandshakers见 tcp_connect_handshaker.cc也就是说TCP 连接建立是整个客户端握手链上的第一道实质性工序HTTP CONNECT 与安全握手都排在其后。1.2 TCP Connect Handshaker 的定位tcp_connect/AGENTS.md 对它的职责做了三句精确概括该 handshaker 负责与远端主机建立一条 TCP 连接它是最简单的 handshaker并且被用作更复杂 handshaker如安全 handshaker的基础它不提供任何安全特性仅仅建立一条明文 TCP 连接通常作为握手流程的第一步后续再由安全 handshaker 建立安全通道。从源码上看TCPConnectHandshaker类在 tcp_connect_handshaker.cc 中定义它继承自Handshaker基类并实现了三个纯虚接口name()返回tcp_connectDoHandshake()执行握手Shutdown()负责中断。二、入口与注册它何时进入握手链2.1 工厂与优先级注册TCPConnectHandshakerFactorytcp_connect_handshaker.cc实现了HandshakerFactory接口AddHandshakers()把一个新的TCPConnectHandshaker实例加入HandshakeManagerPriority()返回kTCPConnectHandshakers即 TCP 连接建立优先级。而RegisterTCPConnectHandshaker()tcp_connect_handshaker.cc负责把该工厂注册进CoreConfiguration::Builder的客户端握手器注册表HANDSHAKER_CLIENT。在 src/core/plugin_registry/grpc_plugin_registry.cc 的BuildCoreConfiguration()中注册顺序有明确的注释说明注册顺序至关重要TCP connect handshaker 必须最后注册以便它被添加到握手列表的最前面RegisterEndpointInfoHandshaker(builder); RegisterHttpConnectClientHandshaker(builder); RegisterTCPConnectHandshaker(builder);结合 handshaker_registry.cc 的实现可以理解RegisterHandshakerFactory()会按工厂的Priority()对同类型握手器排序插入优先级低的排在前AddHandshakers()则按顺序把每个工厂产出的握手器加入HandshakeManager由HandshakeManager按加入顺序依次执行。因此TCP connect 握手器总是最先被调用。2.2 谁来调用Chttp2Connector以默认的 HTTP/2 传输为例客户端连接器 src/core/ext/transport/chttp2/client/chttp2_connector.cc 的Chttp2Connector::Connect()展示了完整的调用链从ChannelArgs中尝试取出预置的 endpoint例如通过EndpointChannelArgWrapper传入的已建连接如果没有预置 endpoint就把解析得到的地址通过grpc_sockaddr_to_uri()转为 URI 字符串并设置两个内部 channel arg详见第三节创建HandshakeManager调用handshaker_registry().AddHandshakers(HANDSHAKER_CLIENT, ...)装配所有客户端握手器调用handshake_mgr_-DoHandshake(...)在回调OnHandshakeDone()中把握手成功得到的 endpoint 交给grpc_create_chttp2_transport()创建 HTTP/2 传输层。这一调用关系印证了 AGENTS.md 中的描述TCP connect 握手器是整个握手流程的开路先锋其产出一个已连接的grpc_endpoint最终被 HTTP/2 传输层消费。三、两个内部 Channel Arg地址与绑定开关tcp_connect_handshaker.h 定义了握手器消费的两个内部参数均以grpc.internal.前缀标记属于内部实现细节用户应用层不应直接设置参数宏实际 channel arg 字符串含义GRPC_ARG_TCP_HANDSHAKER_RESOLVED_ADDRESSgrpc.internal.tcp_handshaker_resolved_address指示 TCP connect handshaker 应当连接的目标地址URI 字符串形式GRPC_ARG_TCP_HANDSHAKER_BIND_ENDPOINT_TO_POLLSETgrpc.internal.tcp_handshaker_bind_endpoint_to_pollset是否把连接成功的 endpoint 绑定到 pollset在 tcp_connect_handshaker.cc 的DoHandshake()中握手器从args-args读取并消费这两个参数然后主动把它们从 ChannelArgs 中移除args-args.Remove(...)避免它们泄漏给后续的握手器或传输层absl::string_view resolved_address_text args-args.GetString(GRPC_ARG_TCP_HANDSHAKER_RESOLVED_ADDRESS).value(); absl::StatusOrURI uri URI::Parse(resolved_address_text); if (!uri.ok() || !grpc_parse_uri(*uri, addr_)) { // 解析失败以 Resolved address in invalid format 错误结束握手 ... } bind_endpoint_to_pollset_ args-args.GetBool(GRPC_ARG_TCP_HANDSHAKER_BIND_ENDPOINT_TO_POLLSET) .value_or(false); // 消费后移除避免影响后续握手器 args-args args-args.Remove(GRPC_ARG_TCP_HANDSHAKER_RESOLVED_ADDRESS) .Remove(GRPC_ARG_TCP_HANDSHAKER_BIND_ENDPOINT_TO_POLLSET);从 chttp2_connector.cc 可以看到这两个参数的典型设置方式channel_args channel_args .Set(GRPC_ARG_TCP_HANDSHAKER_RESOLVED_ADDRESS, address.value()) .Set(GRPC_ARG_TCP_HANDSHAKER_BIND_ENDPOINT_TO_POLLSET, 1);此外src/core/util/http_client/httpcli.cc 在发起 HTTP 客户端连接时也只设置了GRPC_ARG_TCP_HANDSHAKER_RESOLVED_ADDRESS说明bind_endpoint_to_pollset并非必须默认值为falsevalue_or(false)。四、核心执行流程DoHandshake 一探究竟DoHandshake()的整体流程tcp_connect_handshaker.cc可分为四条分支路径。4.1 分支一endpoint 已存在no-op如果args-endpoint非空例如上层通过EndpointChannelArgWrapper传入了一个已经建立的连接握手器直接以absl::OkStatus()完成握手不做任何动作if (args-endpoint ! nullptr) { InvokeOnHandshakeDone(args, std::move(on_handshake_done), absl::OkStatus()); return; }这正是 handshaker.h 中HandshakerArgs设计意图的体现endpoint是输入/输出参数握手器可能直接消费它也可能替换为包装后的新 endpoint。4.2 分支二地址解析失败读取GRPC_ARG_TCP_HANDSHAKER_RESOLVED_ADDRESS后先用URI::Parse()解析再用grpc_parse_uri()转换为grpc_resolved_addresstcp_connect_handshaker.cc。任一环节失败都会以Resolved address in invalid format: 地址的错误消息调用FinishLocked()结束握手。4.3 分支三发起异步 TCP 连接这是主要路径。握手器做三件事先Ref().release()自增引用计数——注释明确指出某些实现中grpc_tcp_client_connect()返回前闭包就可能被 flush回调需要访问mu_不加引用会导致 use-after-free参见代码中对 grpc/grpc#16427 死锁问题的说明把回调闭包connected_、输出指针endpoint_to_destroy_、pollset 集合、ChannelArgsEndpointConfig包装的 channel args、目标地址addr_与 deadline 一起传给grpc_tcp_client_connect()因为 shutdown 时可能伪造连接失败所以不直接传递args-endpoint而是先写入endpoint_to_destroy_成功后再移交swap给args-endpoint。grpc_tcp_client_connect()的语义定义在 src/core/lib/iomgr/tcp_client.h异步连接指定地址完成后回调on_connect闭包interested_parties是一组对该连接建立感兴趣的 pollset返回值是可用于取消连接尝试的句柄对应grpc_tcp_client_cancel_connect()。4.4 分支四回调 Connected连接完成或失败后Connected()静态回调被触发tcp_connect_handshaker.cc它拿到此前release()的引用恢复为RefCountedPtr出错或已 shutdown若error非 OK或shutdown_为 true则销毁endpoint_to_destroy_并以相应错误结束若错误为 OK 但处于 shutdown则构造tcp handshaker shutdown错误成功校验endpoint_to_destroy_非空将其转交给args_-endpoint若bind_endpoint_to_pollset_为 true则调用grpc_endpoint_add_to_pollset_set()把 endpoint 加入 pollset 集合最后以absl::OkStatus()调用FinishLocked()。FinishLocked()tcp_connect_handshaker.cc在持锁状态下把自身从 pollset 集合中移除然后调用基类辅助函数InvokeOnHandshakeDone()把结果交还给HandshakeManager由它决定是否调用下一个握手器如 HTTP CONNECT 或安全握手器。五、生命周期与并发安全5.1 构造与析构构造函数tcp_connect_handshaker.cc创建独立的 pollset 集合interested_parties_并通过grpc_polling_entity_create_from_pollset_set()建立轮询实体特别地在 Apple 等平台上interested_parties可能为 null因此代码显式判空后再grpc_polling_entity_add_to_pollset_set()。析构函数则负责销毁残留的endpoint_to_destroy_与 pollset 集合。5.2 Shutdown 语义Shutdown()tcp_connect_handshaker.cc在持锁下置位shutdown_如果此时on_handshake_done_尚在即连接进行中会以tcp handshaker shutdown错误立即调用FinishLocked()结束握手——后续grpc_tcp_client_connect的回调到达时由于shutdown_已为 true将只做清理而不再重复回调。代码中的 TODO 也标注了后续迁移到 EventEngine 后应支持取消进行中的连接尝试。5.3 线程安全设计整个类围绕Mutex mu_进行并发保护shutdown_、endpoint_to_destroy_、on_handshake_done_均以ABSL_GUARDED_BY(mu_)标注Connected()回调全程持锁DoHandshake()在保存回调与读取参数时分段持锁。同时handshaker.h 对InvokeOnHandshakeDone()有明确警示回调可能在另一线程立刻完成并销毁握手器对象因此调用者必须持有自己的引用——这正是Ref().release()设计的原因。六、与安全握手器的协作明文在前加密在后tcp_connect/AGENTS.md 反复强调的协作模式是TCP connect 只建立明文连接安全握手在其后完成加密。从 src/core/handshaker/security/AGENTS.md 提到的security/目录可以印证security_handshaker.cc负责 TLS/ALTS 等安全握手。结合 handshaker_factory.h 的优先级顺序一条典型的 gRPC 客户端握手链为EndpointInfo → TCP Connect建立明文 TCP 连接 → HTTP CONNECT如配置代理 → SecurityTLS/ALTS 握手将 endpoint 包装为加密 endpoint → chttp2 transport消费最终 endpoint 建立 HTTP/2 会话HandshakerArgshandshaker.h为此提供了机制支撑endpoint是输入输出参数安全握手器可以把明文 endpoint 替换为SecureEndpoint包装的加密 endpointread_buffer用于把本握手器未消费的字节透传给下一握手器exit_early允许握手器提前结束后续握手。从源码结构可以推断HTTP CONNECT 握手器src/core/handshaker/http_connect位于kHTTPConnectHandshakers优先级执行顺序在 TCP connect 之后而安全握手器位于kSecurityHandshakers执行顺序更靠后。三者的组合正好覆盖了建立连接 → 穿越代理 → 加密传输的完整链路TCP Connect 正是这条链路的基石。七、关键源码路径速查关注点文件总览文档src/core/handshaker/AGENTS.mdTCP Connect 文档src/core/handshaker/tcp_connect/AGENTS.md核心实现src/core/handshaker/tcp_connect/tcp_connect_handshaker.cc参数定义与注册声明src/core/handshaker/tcp_connect/tcp_connect_handshaker.h握手器基类与参数结构src/core/handshaker/handshaker.h工厂与优先级枚举src/core/handshaker/handshaker_factory.h工厂注册表src/core/handshaker/handshaker_registry.cc全局注册入口src/core/plugin_registry/grpc_plugin_registry.cc客户端调用方HTTP/2src/core/ext/transport/chttp2/client/chttp2_connector.cc底层异步连接 APIsrc/core/lib/iomgr/tcp_client.h安全握手器目录src/core/handshaker/securityHTTP CONNECT 握手器目录src/core/handshaker/http_connect结语TCP Connect Handshaker虽然只做一件事——建立一条明文 TCP 连接——但它集中体现了 gRPC 握手框架的全部设计精华可插拔的工厂注册机制、按优先级排序的握手链、输入输出双向的HandshakerArgs、严格的并发与生命周期管理以及把基础工作做简单、把扩展点留给复杂握手器的架构哲学。理解它就理解了 gRPC 从解析地址到发出第一个 HTTP/2 请求之间最关键的一段旅程。【免费下载链接】grpcC based gRPC (C, Python, Ruby, Objective-C, PHP, C#)项目地址: https://gitcode.com/GitHub_Trending/gr/grpc创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考