首页
/ gRPC Python 通过 Unix Domain Socket (UDS) 实现进程间通信:命名规范、同步与异步示例全解析

gRPC Python 通过 Unix Domain Socket (UDS) 实现进程间通信:命名规范、同步与异步示例全解析

2026-09-08 13:24:22作者:郜逊炳

导读

在微服务与本地代理(如 Envoy sidecar、本地 gRPC 网关)等场景中,Unix Domain Socket(UDS)可以避免 TCP 协议栈开销并借助文件系统权限提供进程间访问控制。本指南基于本仓库的 examples/python/uds/README.md 展开,讲解 gRPC Python 如何利用官方统一的"名称解析(Name Resolution)"机制,让服务端绑定到 unix:unix:// 两种 UDS 地址、客户端直连该地址完成 RPC。读完本文,你将掌握 UDS 地址的 URI 语法细节、同步(grpc)与异步(grpc.aio)两种服务端/客户端的最小可运行写法,并能结合 C-core 底层源码理解其地址解析与校验原理。

核心概念:gRPC 通过名称解析机制统一处理 UDS

gRPC 在构建 Channel(客户端连接)与 Server 监听地址时,并不强制要求使用 host:port 这种 TCP 形态。gRPC 定义了通用名称解析机制:目标地址使用符合 URI 语法的字符串,URI 的 scheme(协议前缀) 决定使用哪个解析器插件,URI 的 path(路径部分) 则给出待解析的目标名称。

这一设计规范被完整记录在本仓库的 doc/naming.md 中。其中与本文直接相关的 UDS 语法为:

Scheme 语法 含义 说明
unix:path Unix 域套接字(仅 Unix 系系统) path 可以是相对路径,也可以是绝对路径
unix:///absolute_path Unix 域套接字(仅 Unix 系系统) 路径必须为绝对路径unix:// 中前两个 / 是 URI 的 authority 分隔符,第三个 / 属于路径本身,因此 URI path 实际为 /absolute_path
unix-abstract:abstract_path 抽象命名空间中的 Unix 域套接字 名称与文件系统路径无关,不适用文件权限,任何进程/用户都可访问;实现时以 \0 为名字首字节并自动补前缀,用户不应在 abstract_path 中手写该空字节
vsock:cid:port Linux VSOCK 用于虚拟机与宿主机通信,cidport 均为 32 位整数

unix 系列外,dns 为默认 scheme(未写前缀或前缀未知时使用);若未显式指定端口,默认 443(部分实现对非安全 Channel 回退 80)。理解这一点很重要:在 gRPC Python 中创建连接 UDS 的 Channel 时,只需把 unix:... 字符串直接作为 target 传入,无需像 TCP 那样手动拆分 host/port,这正是 UDS 用法简洁的根源。

注:unix-abstractvsock 仅在支持它们的平台上生效,本文示例聚焦 unix:unix:// 两种最常用的文件系统路径形式。

示例概览:一个 Greeter 服务的两种 UDS 地址

本示例位于 examples/python/uds 目录,服务端会同时将监听地址绑定到以下两个 UDS 地址,客户端则依次连接并调用:

  • unix:helloworld.sock:相对路径形式,套接字文件将出现在当前工作目录下(即运行服务端命令的目录)。
  • unix:///tmp/helloworld.sock:绝对路径形式,等效于 /tmp/helloworld.sock

服务端对每个请求返回的响应体中包含 context.peer()——即对端地址字符串。因此当客户端连接 unix:helloworld.sock 时收到 Hello to unix:helloworld.sock!,连接 /tmp/helloworld.sock 时收到 Hello to unix:///tmp/helloworld.sock!,可以直观地在日志里验证当前 RPC 到底走的是哪条 UDS。

目录结构与预生成代码

该示例与仓库内其他 Python 示例(如 helloworld、route_guide)结构一致:

文件 作用
greeter_server.py 同步(threading)服务端实现
greeter_client.py 同步客户端实现
async_greeter_server.py AsyncIO(grpc.aio)服务端实现
async_greeter_client.py AsyncIO 客户端实现
helloworld_pb2.pyhelloworld_pb2_grpc.py、helloworld_pb2.pyi 由 proto 预生成的 message / stub 代码及其类型提示文件

接口定义来自仓库共享的 examples/protos/helloworld.protoGreeter.SayHello(HelloRequest) returns (HelloReply)HelloRequest 携带 string nameHelloReply 返回 string message。示例运行目录中已包含编译好的 *_pb2*.py,因此无需在本例中再次执行 protoc。

环境准备

运行本示例前,Python 环境需要已安装 grpcioprotobuf,例如:

pip install grpcio protobuf

同时需要注意:UDS 仅存在于 Unix 系操作系统(Linux、macOS 等),在 Windows 上该语法不可用;此外,绑定 UDS 的服务端与客户端必须处于同一台主机(或共享同一文件系统挂载点)的文件系统命名空间中,UDS 无法跨机器通信。

运行示例

所有命令均应在 examples/python/uds 目录内执行,以便 Python 找到同目录下的 helloworld_pb2.py

方式一:启动同步服务端并用同步客户端连接

终端一启动服务端:

python3 greeter_server.py

预期输出:

INFO:root:Server listening on: unix:helloworld.sock
INFO:root:Server listening on: unix:///tmp/helloworld.sock
...

终端二运行客户端:

python3 greeter_client.py

预期输出:

INFO:root:Received: Hello to unix:helloworld.sock!
INFO:root:Received: Hello to unix:///tmp/helloworld.sock!

方式二:启动异步服务端并用异步客户端连接

终端一:

python3 async_greeter_server.py

预期输出:

INFO:root:Server listening on: unix:helloworld.sock
INFO:root:Server listening on: unix:///tmp/helloworld.sock
...

终端二:

python3 async_greeter_client.py

预期输出:

INFO:root:Received: Hello to unix:helloworld.sock!
INFO:root:Received: Hello to unix:///tmp/helloworld.sock!

观察重点:同一种 UDS 地址写法在同步与异步 API 中完全通用,因此已掌握同步 gRPC Python 的开发者可以零成本迁移。

运行结束后,工作目录下会留下服务端创建的套接字文件 helloworld.sock/tmp/helloworld.sock 位于 /tmp)。删除旧 socket 文件后重新绑定或重启服务时,需确保文件路径可写、无权限冲突。

服务端实现解析

同步服务端(threading 模型)

greeter_server.py 的核心逻辑(约 L30-L38):

def serve():
    uds_addresses = ["unix:helloworld.sock", "unix:///tmp/helloworld.sock"]
    server = grpc.server(futures.ThreadPoolExecutor(max_workers=10))
    helloworld_pb2_grpc.add_GreeterServicer_to_server(Greeter(), server)
    for uds_address in uds_addresses:
        server.add_insecure_port(uds_address)
        logging.info("Server listening on: %s", uds_address)
    server.start()
    server.wait_for_termination()

要点:

  1. grpc.server(futures.ThreadPoolExecutor(max_workers=10)) 创建同步服务器,线程池决定可并行处理请求的线程数。
  2. add_GreeterServicer_to_server(Greeter(), server) 注册业务实现。
  3. 关键一行 server.add_insecure_port(uds_address):传入 "unix:helloworld.sock""unix:///tmp/helloworld.sock" 后,同一个 Server 即可同时监听两个不同的 UDS 路径。add_insecure_port 是 gRPC Python 统一暴露的端口注册 API(见 src/python/grpcio/grpc/init.py 的签名与 src/python/grpcio/grpc/_server.py 的实现),传入 unix: 目标时与传入 [::]:50051 这类 TCP 目标走的是同一套接口——这也是名称解析机制统一性的直接体现。
  4. server.start() 为非阻塞调用,随后 server.wait_for_termination() 阻塞主线程直到进程被终止(如 Ctrl+C)。
  5. SayHello 通过 context.peer() 拿到对端地址并拼进响应消息(L25-L27),这是示例用于"自证"数据确实穿越了对应 UDS 的手段。

异步服务端(AsyncIO 模型)

async_greeter_server.py 与同步版的差异点:

  • 使用 grpc.aio.server() 创建 AsyncIO 服务器(无需显式指定线程池,由事件循环驱动)。
  • Servicer 方法声明为协程:async def SayHello(...),方法签名中的 context: grpc.aio.ServicerContext 类型注解体现了异步上下文类型。
  • 生命周期 API 变为协程:await server.start()await server.wait_for_termination()
  • 通过 asyncio.run(serve()) 启动整个事件循环。

除此之外,UDS 地址列表与 add_insecure_port 的用法与同步版完全一致:

uds_addresses = ["unix:helloworld.sock", "unix:///tmp/helloworld.sock"]
server = grpc.aio.server()
helloworld_pb2_grpc.add_GreeterServicer_to_server(Greeter(), server)
for uds_address in uds_addresses:
    server.add_insecure_port(uds_address)

异步服务器同样支持同一进程内多 UDS 监听,适合高并发、IO 密集的本地服务场景。AsyncIO 版 add_insecure_port 的底层实现在 src/python/grpcio/grpc/_cython/_cygrpc/aio/server.pyx.pxi

客户端实现解析

同步客户端

greeter_client.py 的核心(L23-L29):

def run():
    uds_addresses = ["unix:helloworld.sock", "unix:///tmp/helloworld.sock"]
    for uds_address in uds_addresses:
        with grpc.insecure_channel(uds_address) as channel:
            stub = helloworld_pb2_grpc.GreeterStub(channel)
            response = stub.SayHello(helloworld_pb2.HelloRequest(name="you"))
            logging.info("Received: %s", response.message)

grpc.insecure_channel(uds_address) 直接将 unix: 目标字符串作为 target 创建 Channel(全局入口定义于 src/python/grpcio/grpc/init.py,同时可传入 optionscompression 等参数)。随后实例化生成的 GreeterStub 并发起一元 RPC SayHellowith 语句保证 Channel 在退出作用域时被关闭。

异步客户端

async_greeter_client.py 对应差异点:

async with grpc.aio.insecure_channel(uds_address) as channel:
    stub = helloworld_pb2_grpc.GreeterStub(channel)
    response = await stub.SayHello(helloworld_pb2.HelloRequest(name="you"))
    logging.info("Received: %s", response.message)

仅两处变化:Channel 创建函数改为 grpc.aio.insecure_channel,RPC 调用前加 await。同一套 unix: 地址字符串无需任何改写,即可在同步与异步两套客户端 API 间复用。

底层原理:UDS 地址如何被 C-core 解析

gRPC 的 Python 层只是薄封装,真正的地址解析发生在 C-core。在 src/core/lib/address_utils/parse_address.cc 中可以看到 UDS 的解析实现(仅在编译宏 GRPC_HAVE_UNIX_SOCKET 定义的平台上编译,即再次印证"UDS 依赖平台"):

  • grpc_parse_unix(...)(约 L60-L73)要求 URI 的 scheme 必须为 unix,随后把 URI 的 path 交给 UnixSockaddrPopulate 填充套接字地址结构。
  • grpc_parse_unix_abstract(...)(约 L75-L89)对应 unix-abstract scheme。
  • UnixSockaddrPopulate(约 L93-L108)内部完成 sockaddr_un 的组装:sun_family = AF_UNIX,将路径字符串拷贝进 sun_path 并补 \0 结尾。其中有一处路径长度上限校验:若 path 长度超过 sizeof(un->sun_path) - 1,会直接报错拒绝(Linux 上该上限通常为 107/108 字节左右)。这意味着极端长的 UDS 路径在 gRPC 中不可用,命名 socket 路径时应保持简洁。
  • 抽象命名空间的填充(约 L110-L124)则是在 sun_path[0] 处写入 \0,再将名字拷到其后——对应 doc/naming.md 中"实现会自动补前缀空字节"的说明。

从源码结构可以推断,unix:path(路径部分直接取相对或绝对路径)与 unix:///absolute_path(URI 解析后 path 为绝对路径)在进入上述填充函数后殊途同归,最终都落到同一个 AF_UNIX 地址创建流程。这也解释了为什么两种写法可以并列用于 add_insecure_port

UDS 的使用边界与注意事项

综合 doc/naming.md、示例代码与仓库测试,实践中值得注意的边界条件包括:

  1. 平台限制unix: / unix:// 仅限 Unix 系系统;抽象命名空间(unix-abstract)与 vsock 有额外平台约束。
  2. 相对 vs 绝对unix:path 中相对路径相对于服务端进程的当前工作目录,目录迁移或 cwd 变化会导致 socket 地址改变;unix:///absolute_path 消除了这种歧义,推荐在脚本/容器中固定使用绝对路径形式。
  3. 文件系统权限即安全边界unix: 形式的 UDS 受文件系统权限管控,创建 socket 的目录决定了谁能访问;而抽象命名空间套接字不适用任何权限。如果需要基于凭据的强认证,可参考仓库测试 src/python/grpcio_tests/tests/unit/_local_credentials_test.pyserver_addr = "unix:/tmp/grpc_fullstack_test" 后再配合 add_secure_port/本地凭据使用的方式;gRPC 也支持在 UDS 之上叠加 TLS 或本地凭证完成加密与鉴权。
  4. 路径长度:底层 sockaddr_un.sun_path 有长度限制,过长路径会解析失败。
  5. 单服务器多 UDS:一个 Server 可通过多次 add_insecure_port 同时监听多个 UDS(以及混用 TCP 端口),本示例即演示了这一能力。
  6. Windows:需依赖 AF_UNIX 支持,标准 Windows 环境无法使用上述两种 UDS 地址。

仓库单元测试与集成测试(例如 src/python/grpcio_tests/tests/unit/_contextvars_propagation_test.py 中通过 f"unix:{_UDS_PATH}" 拼接地址并在服务端 add_secure_port 后经 UDS 发起调用的用例)进一步确认:UDS 目标字符串被完整地贯穿于 gRPC Python 的 Channel/Server 两层接口,是官方支持的一等公民地址形式,而非示例特有的 hack。

延伸学习

  • 想在同步 API 之外做对比,可对照本仓库 examples/python/helloworld 的 TCP 版 Greeter,体会 host:portunix: 地址在代码上的对称性。
  • 若需要了解 UDS 之外更多名称解析 scheme(dnsipv4ipv6vsock、抽象命名空间等)的完整规范,请阅读 doc/naming.md
  • 若要在生产代码中使用 UDS 支撑服务发现与多地址容错,可参考 examples/python/multiplexexamples/python/interceptors 中关于单 Channel 多服务、拦截器等组合模式。
登录后查看全文
热门项目推荐
相关项目推荐

项目优选

收起
kernelkernel
deepin linux kernel
C
33
18
docsdocs
暂无描述
Markdown
899
5.83 K
ops-transformerops-transformer
本项目是CANN提供的transformer类大模型算子库,实现网络在NPU上加速计算。
C++
1.14 K
2.76 K
pytorchpytorch
作为 Ascend for PyTorch 社区的核心组件,TorchNPU 是昇腾专为 PyTorch 打造的深度学习适配插件,使 PyTorch 框架能够直接调用昇腾 NPU,为开发者提供昇腾 AI 处理器的超强算力。
Python
860
1.35 K
ops-nnops-nn
本项目是CANN提供的神经网络类计算算子库,实现网络在NPU上加速计算。
C++
925
1.85 K
jiuwenswarmjiuwenswarm
JiuwenSwarm 是一款基于openJiuwen开发的智能AI Agent,它能够将大语言模型的强大能力,通过你日常使用的各类通讯应用,直接延伸至你的指尖。
Python
3.84 K
1.02 K
kernelkernel
openEuler内核是openEuler操作系统的核心,既是系统性能与稳定性的基石,也是连接处理器、设备与服务的桥梁。
C
533
601
ops-mathops-math
本项目是CANN提供的数学类基础计算算子库,实现网络在NPU上加速计算。
C++
1.37 K
1.46 K
AscendNPU-IRAscendNPU-IR
AscendNPU-IR是基于MLIR(Multi-Level Intermediate Representation)构建的,面向昇腾亲和算子编译时使用的中间表示,提供昇腾完备表达能力,通过编译优化提升昇腾AI处理器计算效率,支持通过生态框架使能昇腾AI处理器与深度调优
C++
548
395
cann-learning-hubcann-learning-hub
CANN 学习中心仓,支持在线互动运行、边学边练,提供教程、示例与优化方案,一站式助力昇腾开发者快速上手。
Jupyter Notebook
1.04 K
525