前言:在微服务架构中,高性能的 RPC 通信是核心。gRPC 凭借其基于 HTTP/2 的传输协议和 Protobuf 的高效序列化,成为了跨语言服务的首选。这边文章是基于 Hyperf 协程框架,演示如何快速接入 gRPC 客户端,并实现与服务端的无缝通信。这边文章做了一个最简单的demo, 就是PHP作为客户端调用gRPC服务端, 传什么数据过去就返回什么数据
一、 定义服务契约 (Proto)
gRPC 是“契约先行”的。我们先创建一个 proto 文件来定义通信协议。
在对应的项目目录新建 proto/echo.proto文件:
syntax = "proto3";package echo;// 请求参数message EchoRequest { string message = 1; map<string, string> metadata = 2;}// 响应参数message EchoResponse { string message = 1; map<string, string> metadata = 2; int64 timestamp = 3;}// 定义服务service Echo { rpc Echo(EchoRequest) returns (EchoResponse);}
服务端部分
一、Rust 实现的服务端
1.1 初始化项目
首先创建 Rust 项目并添加依赖。核心依赖如下:
# Cargo.toml[package]name = "grpc-echo-server"version = "0.1.0"edition = "2021"[dependencies]# gRPC 框架tonic = "0.9"prost = "0.11"# 异步运行时tokio = { version = "1", features = ["macros", "rt-multi-thread"] }# 时间处理chrono = "0.4"[build-dependencies]# Proto 编译器tonic-build = "0.9"
1.2 配置 Proto 编译
在项目目录创建 build.rs,告诉 Cargo 在编译前自动处理 Proto 文件:
// build.rsfnmain() -> Result<(), Box<dynstd::error::Error>> { // 编译 proto 文件,生成的代码会放在 OUT_DIR tonic_build::compile_protos("proto/echo.proto")?; Ok(())}
这样每次 cargo build 时,Proto 文件会自动编译成 Rust 代码,不需要手动操作。
1.3 实现 Echo 服务
现在来写核心逻辑。创建 src/services/echo.rs:
use tonic::{Request, Response, Status};use chrono::Utc;// 引入由 tonic-build 生成的代码pub mod echo { tonic::include_proto!("echo");}use echo::echo_server::{Echo, EchoServer};use echo::{EchoRequest, EchoResponse};/// Echo服务实现 - 传什么返回什么#[derive(Debug, Default)]pub struct EchoService;impl EchoService { pub fn new() -> Self { Self } /// 创建 gRPC 服务 pub fn into_service(self) -> EchoServer<Self> { EchoServer::new(self) }}#[tonic::async_trait]impl Echo for EchoService { async fn echo( &self, request: Request<EchoRequest>, ) -> Result<Response<EchoResponse>, Status> { let req = request.into_inner(); // 获取当前时间戳(毫秒) let timestamp = Utc::now().timestamp_millis(); // 原样返回请求内容 let response = EchoResponse { message: req.message, metadata: req.metadata, timestamp, }; Ok(Response::new(response)) }}
在创建 src/services/mod.rs:
pub mod echo;pub use echo::EchoService;
主要用于声明和管理模块结构
1.4 启动服务
最后,在 src/main.rs 中把服务跑起来:
mod services;use tonic::transport::Server;use std::net::SocketAddr;use services::EchoService;#[tokio::main]async fn main() -> Result<(), Box<dyn std::error::Error>> { // 监听所有网卡,方便 Docker 部署 let addr: SocketAddr = "0.0.0.0:50051".parse()?; println!("🚀 gRPC 服务启动: {}", addr); println!("📡 可用服务: echo.Echo"); // 创建 Echo 服务 let echo_service = EchoService::new(); // 启动 gRPC 服务 Server::builder() .add_service(echo_service.into_service()) .serve(addr) .await?; Ok(())}
运行 cargo run,服务就跑起来了!
$ cargo run🚀 gRPC 服务启动: 0.0.0.0:50051📡 可用服务: echo.Echo
二、 生成 PHP 代码与自动加载
1. 生成代码
使用 protoc 工具将 .proto 文件转换为 PHP 类文件。执行以下命令:
mkdir -p ./app/Grpcprotoc --proto_path=./proto --php_out=./app/Grpc ./proto/echo.proto
2. 配置自动加载
生成的代码通常带有命名空间(如 PBEcho),我们需要在 composer.json 中注册它:
"autoload": { "psr-4": { "App\\": "app/", "PBEcho\\": "app/Grpc/PBEcho/" }}
注意:路径和命名空间需根据你生成的实际目录调整。
执行命令使配置生效:
三、 依赖注入配置 (DI)
在 Hyperf 中,我们通过 dependencies.php 来管理客户端实例。这里有一个关键细节:不要在闭包内使用 make(),否则会引起无限递归。
修改 config/autoload/dependencies.php:
use App\Grpc\EchoClient;return [ EchoClient::class => function () { // gRPC 服务端地址 $host = 'x.x.x.x'; $port = 'xxxx'; // 直接实例化,避免使用 make() 造成递归调用 return new EchoClient("{$host}:{$port}"); },];
四、 业务逻辑实现
现在,我们可以在 Controller 中轻松调用 gRPC 服务了。Hyperf 的 gRPC 客户端返回的是一个数组:[$reply, $status, $rawResponse]。
/** * Echo 服务测试接口 */public function echo(): array{ $message = $this->request->input('message', 'Hello from Hyperf!'); try { // 1. 创建 gRPC 请求对象 $request = new \PBEcho\EchoRequest(); $request->setMessage($message); $request->setMetadata([ 'client' => 'hyperf', 'time' => date('Y-m-d H:i:s') ]); // 2. 调用服务 // _simpleRequest 是基类提供的方法,返回 [响应对象, 状态码, 原始响应] [$reply, $status, $rawResponse] = $this->echoClient->echo($request); // 3. 检查状态 (0 表示成功) if($status !== 0) { return [ 'success' => false, 'error' => is_string($reply) ? $reply : 'gRPC 调用失败', 'status' => $status, ]; } /** @var \PBEcho\EchoResponse $reply */ $metadata = $reply->getMetadata(); return [ 'success' => true, 'data' => [ 'message' => $reply->getMessage(), 'metadata' => $metadata ? iterator_to_array($metadata) : [], 'timestamp' => $reply->getTimestamp(), ], ]; } catch(\Throwable $e) { return [ 'success' => false, 'error' => $e->getMessage(), 'status' => -1, ]; }}

结语:通过以上步骤,我们成功在 Hyperf 中打通了 gRPC 的通信链路。gRPC 不仅带来了更强的类型约束,更在性能上为 PHP 赋能。有兴趣的话快去你的项目中试试吧!