FA :Formulas and Algorithm, IPC :Inter-Process Communication, gRPC :google Remote Procedure Call
对 OSI 7层模型 对应gRPC,它属于第7层 的应用层。一般使用gRPC的七层协议状况如下:
| 层序号 | 层名称 | 协议名 |
|---|---|---|
| 7 | 应用层(Application Layer) | gRPC |
| 6 | 表示层(Presentation Layer) | Protobuf + TLS(SSL) |
| 5 | 会话层(Session Layer) | HTTP/2 会话层 |
| 4 | 传输层(Transport Layer) | TCP |
| 3 | 网路层(Network Layer) | IP |
| 2 | 链路层(Data Link Layer) | 以太网/WiFi |
| 1 | 物理层(Physical Layer) | 光纤、无线电 |
一、各层协议的选配
第4层:传输层 → 默认 TCP,可 选配 UDP
- 默认:TCP
- 可选:UDP(基于QUIC,即gRPC over HTTP/3)
- 结论:可以选配
第5层:会话层→ HTTP/2(或 HTTP/3)
- 负责:建立会话、流控、多路复用
- 可选:无其他选择
- 结论:不可选配 (HTTP/2或HTTP/3自带)
第6层:表示层→ Protobuf(默认),可 选配 JSON
-
负责:数据序列化(大小端、数据实际意义)、加密(TLS)
-
默认:Protobuf
-
可选:JSON
-
结论:可以选配
总的来说,gRPC位于OSI第7层应用层,依赖第6层Protobuf(可替换JSON)、第5层HTTP/2、第4层TCP(可替换为UDP+QUIC)。
二、gRPC实例(机器人控制gRPC)
一般gRPC的通讯都是需要先编辑proto的通讯工程文件。随后根据编辑好的文件根据指令生成对应的被引用文件。此处以机器人控制的关节控制为例,生成gRPC实例,其包含如下:
- 机器人整体状态;
- 多关节数据(当前角度、角速度、下限、上限);
- 指令控制(获取状态、设置关节目标);
- 一行命令自动生成 C、C++、Python 调用代码;
1、核心文件:robot.proto(标准gRPC接口定义)
bash
syntax = "proto3";
// 包名,防止命名冲突
package robot;
// 【关节数据】:角度、角速度、上下限
message JointData {
int32 joint_id = 1; // 关节编号 1~6/1~7
float current_angle = 2; // 当前角度 (deg)
float current_velocity = 3; // 当前角速度 (deg/s)
float angle_limit_min = 4; // 角度下限
float angle_limit_max = 5; // 角度上限
}
// 【机器人整体状态】
message RobotState {
bool is_connected = 1; // 是否连接
bool is_enabled = 2; // 是否使能
bool is_error = 3; // 是否故障
string error_msg = 4; // 故障信息
repeated JointData joints = 5; // 所有关节数据(数组)
}
// 【控制指令】:设置目标角度
message SetJointTargetRequest {
int32 joint_id = 1;
float target_angle = 2;
}
// 【通用响应】
message CommonResponse {
bool success = 1;
string msg = 2;
}
// 【空请求】:用于获取状态
message EmptyRequest {}
// ==================== gRPC 服务 ====================
service RobotControlService {
// 获取机器人完整状态
rpc GetRobotState(EmptyRequest) returns (RobotState);
// 设置单个关节目标角度
rpc SetJointTarget(SetJointTargetRequest) returns (CommonResponse);
}
2、一行命令生成 C / C++ / Python 代码
只需要安装好 protobuf + grpc 工具,执行下面命令就能生成所有语言文件。
生成Python代码
bash
python -m grpc_tools.protoc \
--python_out=. \
--grpc_python_out=. \
robot.proto
生成文件介绍:
- robot_pb2.py(数据结构)
- robot_pb2_grpc.py(服务接口)
生成C++代码
bash
protoc --cpp_out=. --grpc_out=. \
--plugin=protoc-gen-grpc=`which grpc_cpp_plugin` \
robot.proto
生成文件介绍:
- robot.pb.h / robot.pb.cc(数据结构)
- robot.grpc.pb.h / robot.grpc.pb.cc(服务接口)
生成C代码(gRPC原生C版本)
bash
protoc --c_out=. --grpc-c_out=. \
robot.proto
备注:生成的数据包含数据结构和服务接口两部分,这是gRPC通讯的通用实例。
3、接口包含的全部数据
| 字段 | 含义 |
|---|---|
| joint_id | 关节编号 |
| current_angle | 当前角度 |
| current_velocity | 当前角速度 |
| angle_limit_min | 角度下限 |
| angle_limit_max | 角度上限 |
| is_connected/enabled/error | 机器人状态 |
| repeated JointData | 多关节数组(6 轴 / 7 轴通用) |
4、python调用实例
bash
import grpc
import robot_pb2
import robot_pb2_grpc
def run():
# 连接机器人 gRPC 服务
channel = grpc.insecure_channel('localhost:50051')
stub = robot_pb2_grpc.RobotControlServiceStub(channel)
# 1. 获取机器人完整状态
state = stub.GetRobotState(robot_pb2.EmptyRequest())
print("机器人状态:", state.is_enabled)
for joint in state.joints:
print(f"关节{joint.joint_id} 角度:{joint.current_angle} 限幅[{joint.angle_limit_min},{joint.angle_limit_max}]")
# 2. 控制关节1转到 90 度
res = stub.SetJointTarget(robot_pb2.SetJointTargetRequest(joint_id=1, target_angle=90.0))
print("控制结果:", res.success)
if __name__ == '__main__':
run()
三、gRPC切换到UDP运输层协议
需要说明的是默认的HTTP/2+TCP,强制可靠,有序,RPC保证调用报文不丢不乱。但是切换至UDP的唯一官方合规的方式是:QUIC + UDP(裸原生UDP,无QUIC无法对接标准gRPC),更改之后L4传输层UDP,上层QUIC自研可重传、多路复用。改用QUIC=底层UDP传输,proto文件完全不用改,C/C++/Python生成代码不变,仅仅改动channel创建逻辑。
方案1、C++ gRPC启用QUIC/UDP
1、编译gRPC开启QUIC编译选项,编译源码时添加如下配置项(此方法需要安装依赖库boringssl、quiche):
bash
-DgRPC_BUILD_CSHARP=OFF -DgRPC_SSL_PROVIDER=package -DgRPC_ENABLE_QUIC=ON
2、C++服务端代码(UDP端口监听)
bash
#include <grpcpp/grpcpp.h>
#include "robot.grpc.pb.h"
// QUIC TLS证书(必须,HTTP3强制TLS1.3)
auto creds = grpc::experimental::QuicServerCredentials(
grpc::SslServerCredentialsOptions{/*加载证书、私钥*/});
grpc::ServerBuilder builder;
// 监听UDP:50051(原TCP端口变UDP)
builder.AddListeningPort("0.0.0.0:50051", creds);
// 注册之前的RobotControlService
builder.RegisterService(&svr_impl);
auto server = builder.BuildAndStart();
3、C++客户端(UDP连接)
bash
auto cli_creds = grpc::experimental::QuicChannelCredentials(
grpc::SslCredentialsOptions{/*客户端证书*/});
// 自动走UDP+QUIC,接口调用和TCP版完全一致
auto channel = grpc::CreateChannel("127.0.0.1:50051", cli_creds);
auto stub = RobotControlService::NewStub(channel);
// GetRobotState、SetJointTarget调用代码一字不改
方案2、 Python gRPC启用 QUIC/UDP配置
python库grpcio从1.52+支持experimental QUIC/UDP,具体为:
1、安装依赖
bash
pip install grpcio[quic] grpcio-tools
2、客户端UDP支持代码
bash
import grpc.experimental.quic as quic
import robot_pb2, robot_pb2_grpc
# QUIC TLS配置,insecure仅调试机器人内网
opts = quic.QuicChannelOptions()
opts.insecure = True
# 创建UDP通道(自动QUIC over UDP)
channel = quic.insecure_channel('127.0.0.1:50051', options=opts)
stub = robot_pb2_grpc.RobotControlServiceStub(channel)
# 原有读取关节角度、设置目标代码完全复用
state = stub.GetRobotState(robot_pb2.EmptyRequest())
3、服务端UDP代码
服务端 Python 同理:grpc.experimental.quic.server()绑定 UDP 端口
方案3、不改动gRPC代码,网关中转UDP
通过网关中专不用改动,原有的代码和配置,知识在依赖的底层通信中,更改网络拓扑结构,非常适合老机器,不允许长时间停机和测试的项目,这是采用间接实现的方法。
用Envoy反向代理:
- Envoy 监听UDP 50051(QUIC/HTTP3),后端转发 TCP 50051 到原有 TCP-gRPC 服务;
- 客户端直接 QUIC (UDP) 连 Envoy,原有 C/C++/Python gRPC 服务零修改、不用重新编译。
使用场景和对比优势
QUIC/UDP的方式适用于无人移动机器人/WiFi漫游、室外AGV。能明显改善TCP队头阻塞、切网断链、关节告诉角速度实时传输更稳定。对于默认的TCP网络适合豫稳定的内网(比如有线工业机械臂)。
四、gRPC从表示层的Protobuf(默认)到JSON
gRPC切换至JSON的方式有两种实际路径:1、透明网关转换 :gRPC 内部依然 Protobuf,网关做 JSON↔Protobuf 转码(表示层旁路转换)gRPC 服务底层不变、代码不动、C/C++/Python 生成代码照旧;外部 HTTP 客户端(新的端口)发 JSON,网关自动转 Protobuf 进 gRPC、返回 JSON,同一服务同时兼容二进制 gRPC 与 JSON-HTTP。这种方式本质上就是在原来的内容的基础上,配置一个JSON的交换接口 ,具体在方案1、2中推进的就是这种原理方法。2、底层彻底替换 Marshaller,gRPC 原生链路全用 JSON-表示层完全抛弃 Protobuf这种方法需要自定义gRPC消息序列化器,这个RPC报文直接JSON文本传输,不再走Protobuf二进制,生成代码废弃protobuf stub,适合纯JSON定制RPC。
方案 1:gRPC-Gateway(最常用、机器人上位机调试首选|网关转 JSON)
1. proto 改造(复用你原有 robot.proto,加 http 注解)
protobuf
syntax = "proto3";
package robot;
// 导入网关依赖
import "google/api/annotations.proto";
message JointData {
int32 joint_id = 1;
float current_angle = 2;
float current_velocity = 3;
float angle_limit_min = 4;
float angle_limit_max = 5;
}
message RobotState {
bool is_connected = 1;
bool is_enabled = 2;
bool is_error = 3;
string error_msg = 4;
repeated JointData joints = 5;
}
message SetJointTargetRequest {
int32 joint_id = 1;
float target_angle = 2;
}
message CommonResponse {bool success=1;string msg=2;}
message EmptyRequest{}
service RobotControlService {
// 绑定REST JSON接口路径
rpc GetRobotState(EmptyRequest) returns (RobotState){
option(google.api.http)={
get:"/robot/state" // GET JSON查询机器人全状态
};
}
rpc SetJointTarget(SetJointTargetRequest) returns (CommonResponse){
option(google.api.http)={
post:"/robot/joint/set"
body:"*" // POST JSON载荷映射请求体
};
}
}
2. 一键生成代码(同时生成 grpc、gateway 网关代码,支持 C++/Python 原生 gRPC + HTTP-JSON 网关)
bash运行
# 生成grpc原生代码(C++/Python照旧)
# 生成gateway网关代码(Go实现网关,跨语言通用)
protoc --grpc-gateway_out=. --go_out=. --cpp_out=. --python_out=. robot.proto
3. 运行逻辑
- 50051 端口:原生 gRPC(Protobuf 二进制,C/C++/Python 客户端正常调用)
- 8080 端口:HTTP JSON 接口,curl/postman 直接发 JSON 访问机器人:
bash运行
# 获取机器人关节数据(返回JSON:各关节角度、角速度、上下限)
curl http://127.0.0.1:8080/robot/state
# 设置关节目标角度(POST JSON)
curl -X POST -H "Content-Type:application/json" -d '{"joint_id":1,"target_angle":60.0}' http://127.0.0.1:8080/robot/joint/set
适用场景
- 机器人Web 上位机调试、前端控制面板、第三方设备对接(PLC / 上位机软件只支持 HTTP-JSON 不支持 gRPC)
- 一套后端同时给嵌入式 gRPC 客户端 + Web JSON 客户端,不改原有机器人控制逻辑
方案 2:Envoy 全局代理(无代码改 proto,整机统一 JSON 转码)
零修改 proto 与服务代码,配置 Envoy 开启 grpc_json_transcoder 过滤器,Envoy 监听 80 端口接收 HTTP-JSON,自动转 Protobuf 转发后端 50051 gRPC 服务。
yaml
# Envoy关键配置片段
http_filters:
- name: envoy.grpc_json_transcoder
typed_config:
proto_descriptor: robot.pb
services: ["robot.RobotControlService"]
适用场景
- 多机器人集群网关、老项目快速接入 JSON 接口、统一 API 出入口,不用改任何业务代码
方案 3:gRPC-Web(前端 JS 浏览器 JSON 调用)
浏览器不能直接 TCP-gRPC,gRPC-Web 代理把前端 JSON→Protobuf,走 HTTP1.1,前端 JS 直接 JSON 请求 gRPC 服务
- 编译加protoc-gen-grpc-web生成前端 JS 代码
- Nginx/Envoy 反向代理,前端 fetch 发 JSON,后端 gRPC 仍 Protobuf
适用场景
- 机器人 Web 可视化面板、浏览器实时查看关节角速度 / 角度数据、网页下发运动指令
方案 4:自定义 Marshaller(gRPC 表示层彻底全量 JSON,完全弃用 Protobuf)
gRPC 核心MethodDescriptor.Marshaller负责表示层编解码,替换成 JSON 序列化(Gson/JsonCpp/json 模块),整条 RPC 链路传输纯 JSON 文本,不再有 Protobuf 二进制。
Python 极简示例
python运行
import grpc, json
# 自定义JSON序列化器
def json_serialize(msg):
return json.dumps(msg.__dict__).encode()
def json_deserialize(data, cls):
return cls(**json.loads(data))
# 创建基于JSON的Method,替换默认Protobuf Marshaller
缺点:不能用 protoc 自动生成 C/C++/Python stub 代码,需手动封装请求结构体
适用场景
- 快速原型调试、无固定结构体的动态机器人数据上报、嵌入式资源极小且只兼容 JSON 的单片机对接
四大方案选型对比(机器人项目专用)
| 方案 | 表示层编码 | gRPC 原生调用 | JSON 调用方式 | 性能 | 适用场景 |
|---|---|---|---|---|---|
| gRPC-Gateway | 内部 Protobuf,网关 JSON 转码 | 正常 | HTTP-JSON | 中 | 机器人上位机、PLC 对接(首选) |
| Envoy 代理 | 内部 Protobuf,网关 JSON 转码 | 正常 | HTTP-JSON | 高 | 集群机器人统一接入 |
| gRPC-Web | 内部 Protobuf,代理 JSON 转码 | 正常 | 前端 JS JSON | 中 | Web 机器人控制面板 |
| 自定义 Marshaller | 全链路 JSON 文本 | ❌废弃 protobuf 生成代码 | 原生 RPC JSON 报文 | 低 | 动态数据、极简嵌入式调试 |
五、gRPC通讯的优点和不足
| 优点 | 详细表述 |
|---|---|
| 强 IDL 契约(proto) | 一键生成 C/C++/Python 全平台代码,正好适配你关节角度 / 角速度 / 限位 / 机器人状态结构化传输,字段校验、版本兼容友好 |
| 跨语言天花板 | C/C++/Python/Go/Java 全生态,对接上位机、云端、Web 最便捷,配套 gRPC-Gateway 一键转 JSON |
| 自带链路能力 | 多路复用、TLS 加密、超时 / 重试 / 负载均衡、双向流式 RPC,不用自研握手重传 |
| IPC 本地进程通信优化 | unix domain socket,本机进程间跳过网卡,速度接近共享内存 |
| 云边一体化 | 天然对接 K8S、微服务,机器人后台配置、参数下发、故障查询首选 |
| 缺点 | 详细表述 |
|---|---|
| 原生面向 RPC 一问一答,天生不适合高频流式 Pub/Sub | 关节 1kHz 状态广播只能靠双向流模拟,资源开销大 |
| 延迟偏高 | TCP 握手 + HTTP2 头部封装,普通环境 1~5ms,无法满足 **<1ms 伺服闭环实时需求 ** |
| 中心化依赖 | 客户端必须预先填服务端 IP,无自动节点发现,新增机器人要手动改地址 |
| 带宽开销大 | 头部压缩有限,百万级高频关节采样包吞吐量弱于 DDS/VRPN |
因为有上述的特点所以当前的gRPC在机器人领域的应用状态如下表所示:
| 层级 | 通信选型 | 作用 |
|---|---|---|
| 伺服硬件层(驱动器 - 控制器) | DDK/EtherCAT/私有网络(can总线1s允许5000帧左右,所以该层一般不使用can总线) | 微秒级闭环,原始角度 / 电流读取 |
| 本机高频实时总线(多节点 1kHz 数据) | DDS/FastDDS | 关节角速度、高频状态广播 |
| 上位机 / 远程调试 / 云端 / MES 对接 | gRPC | 参数配置、限位读写、故障查询、低频状态上报、JSON 网关对接 Web |
| 动捕外设接入 | VRPN | Vicon/Nokov 位姿输入机器人 |
市场上使用状况:
国内市场:
智元机器人 :灵越人形/机械臂A1上层上位机、远程调试、云端对接统一使用gRPC。
海康AGV :调度平台和单台ARM车载控制器使用gRPC长连接。
优必选和追觅人形机器人:整机主控和云端大脑,以及视觉遥操作都是通过gRPC交互。
国际市场:
Boston Dynamics现代汽车-Spot自足机器狗\Stretch仓储码垛机器人 :推出的SDK全链路基于gRPC。
Viam Robotics美国通用机器人平台AGV :自研机器人中间件以gRPC+WebRTC取代传统ROS跨网短板。
ANYbotics防爆四足巡检机器人:DDS做1kHz关节闭环,gRPC做配置查询/状态上报。
六、结束语
gRPC从本源上说,是第七层应用层的适配、优化性的协议。它的核心优势是各个硬件和语言平台的通信信息的一致性和完备性,不是数据交互中的快速性和稳定性。