如何使用grpc

一、使用C++举例

二、场景是让一个客户端发送消息到客户端,整个消息传递通过grpc来承载.要达到这样的目的,需要写代码时候要做哪些事情?

三、要实现这个例子的目的,其实真正只需要涉及3个文件.

client.cpp---模拟客户端发送消息的文件

server.cpp---模拟服务端接收消息的文件

example.proto----定义消息(消息格式),和服务端处理消息的函数.目的是让protoc根据这个设定自动生成一部分xxx.grpc.pb.cc(帮着生成一部分符合grpc逻辑的代码)代码和xxx.pb.cc(帮忙生成一部分序列化和反序列化的)代码.

四、我用AI生成了这个实例的代码.

注意这里CMakeLists.txt和README.md文件是方便编译和代码提示的文件,本身不涉及这个例子的功能代码.

example.proto文件

复制代码
syntax = "proto3";

package example;

service Greeter {
  rpc SayHello (HelloRequest) returns (HelloReply) {}
}

message HelloRequest {
  string name = 1;
}

message HelloReply {
  string message = 1;
}

client.cpp文件

cpp 复制代码
// ============================================================
// gRPC 客户端程序
// 功能: 连接到服务端,调用远程的 SayHello 和 SayHelloAgain 方法
// ============================================================

// ========== C++ 标准库头文件 ==========
#include <iostream>   // 标准输入输出,用于打印结果
#include <memory>     // 智能指针库,用于 std::unique_ptr 和 std::shared_ptr
#include <string>     // C++ 字符串类
#include <iomanip>    // 用于格式化输出
#include <vector>     // 用于存储字节数据
#include <chrono>     // 用于获取时间戳
#include <ctime>       // 用于时间格式化
#include <sstream>     // 用于字符串流

// ========== gRPC 头文件 ==========
#include <grpcpp/grpcpp.h>      // gRPC 核心库
#include "example.grpc.pb.h"    // 由 example.proto 生成的客户端存根

// ============================================================
// 获取当前时间戳的辅助函数
// ============================================================
std::string GetCurrentTimestamp() {
  // 获取当前时间点
  auto now = std::chrono::system_clock::now();

  // 转换为 time_t
  std::time_t now_time = std::chrono::system_clock::to_time_t(now);

  // 转换为本地时间
  std::tm local_tm = *std::localtime(&now_time);

  // 格式化为字符串 HH:MM:SS.mmm
  std::ostringstream oss;
  oss << std::setfill('0') << std::setw(2) << local_tm.tm_hour << ":"
      << std::setfill('0') << std::setw(2) << local_tm.tm_min << ":"
      << std::setfill('0') << std::setw(2) << local_tm.tm_sec << "."
      << std::setfill('0') << std::setw(3)
      << std::chrono::duration_cast<std::chrono::milliseconds>(
          now.time_since_epoch()).count() % 1000;

  return oss.str();
}

// ============================================================
// 客户端类
// 封装了调用 gRPC 服务的操作
// ============================================================
class GreeterClient {
 public:
  // ========== 构造函数 ==========
  // 参数: channel - 到服务端的通信通道
  //
  // 构造函数使用初始化列表创建 RPC 存根(Stub)
  // Stub 是客户端的代理对象,负责处理网络通信和序列化
  GreeterClient(std::shared_ptr<grpc::Channel> channel)
      : stub_(example::Greeter::NewStub(channel)) {}

  // ========== 调用 SayHello RPC 方法 ==========
  // 参数: user - 要发送给服务端的名字
  // 返回: 服务端的响应消息
  std::string SayHello(const std::string& user) {
    // ========== 1. 准备请求 ==========
    // 创建请求对象
    example::HelloRequest request;

    // 设置请求参数
    // set_name() 是 protoc 自动生成的 setter 方法
    request.set_name(user);

    // ========== 【打印客户端发送的消息】==========
    std::cout << "========== 客户端发送 SayHello 请求 ==========" << std::endl;
    std::cout << "【时间】" << GetCurrentTimestamp() << std::endl;
    std::cout << "【原始消息内容】" << std::endl;
    std::cout << "  name: " << request.name() << std::endl;

    // 打印 protobuf 的调试字符串(显示所有字段)
    std::cout << "【Protobuf DebugString】" << std::endl;
    std::cout << "  " << request.DebugString() << std::endl;

    // 手动序列化获取原始字节
    std::string serialized_data;
    request.SerializeToString(&serialized_data);
    std::cout << "【序列化后的二进制数据】" << std::endl;
    std::cout << "  大小: " << serialized_data.size() << " 字节" << std::endl;
    std::cout << "  十六进制: ";
    for (size_t i = 0; i < serialized_data.size(); i++) {
      printf("%02X ", (unsigned char)serialized_data[i]);
    }
    std::cout << std::endl;
    std::cout << "=============================================" << std::endl << std::endl;

    // ========== 2. 准备接收响应 ==========
    // 创建响应对象(用于接收服务端的回复)
    example::HelloReply reply;

    // 创建客户端上下文
    // ClientContext 可以设置超时、添加元数据等
    grpc::ClientContext context;

    // 记录开始时间
    auto start_time = std::chrono::high_resolution_clock::now();

    // ========== 3. 发起 RPC 调用 ==========
    // stub_->SayHello() 会自动完成以下操作:
    //   1. 将 request 序列化为二进制
    //   2. 通过 HTTP/2 发送到服务端
    //   3. 等待服务端响应
    //   4. 将响应反序列化到 reply
    //   5. 返回调用状态
    grpc::Status status = stub_->SayHello(&context, request, &reply);

    // 计算耗时
    auto end_time = std::chrono::high_resolution_clock::now();
    auto duration = std::chrono::duration_cast<std::chrono::milliseconds>(end_time - start_time);

    // ========== 4. 处理调用结果 ==========
    if (status.ok()) {
      // RPC 调用成功,返回服务端的响应消息
      // message() 是 protoc 自动生成的 getter 方法

      // ========== 【打印服务端返回的消息】==========
      std::cout << "========== 客户端收到 SayHello 响应 ==========" << std::endl;
      std::cout << "【时间】" << GetCurrentTimestamp() << std::endl;
      std::cout << "【耗时】" << duration.count() << " ms" << std::endl;
      std::cout << "【原始消息内容】" << std::endl;
      std::cout << "  message: " << reply.message() << std::endl;
      std::cout << "【Protobuf DebugString】" << std::endl;
      std::cout << "  " << reply.DebugString() << std::endl;
      std::cout << "=============================================" << std::endl << std::endl;

      return reply.message();
    } else {
      // RPC 调用失败,返回错误信息
      std::cout << "【时间】" << GetCurrentTimestamp() << std::endl;
      std::cout << "【错误】RPC failed: " << status.error_message() << std::endl;
      return "RPC failed";
    }
  }

 private:
  // ========== 成员变量 ==========
  // stub_: RPC 存根,用于调用远程服务
  // unique_ptr 表示独占所有权,自动管理内存
  std::unique_ptr<example::Greeter::Stub> stub_;
};

// ============================================================
// 程序入口
// ============================================================
int main(int argc, char** argv) {
  // ========== 【程序启动】==========
  std::cout << "╔══════════════════════════════════════════════════════════════╗" << std::endl;
  std::cout << "║              gRPC 客户端程序启动                          ║" << std::endl;
  std::cout << "║              启动时间: " << GetCurrentTimestamp() << "              ║" << std::endl;
  std::cout << "╚══════════════════════════════════════════════════════════════╝" << std::endl;
  std::cout << std::endl;

  // ========== 1. 定义服务端地址 ==========
  std::string target_str = "localhost:50052";

  // ========== 2. 创建客户端对象 ==========
  // grpc::CreateChannel() 创建到服务端的通信通道
  // grpc::InsecureChannelCredentials() 表示不加密的连接(仅用于开发)
  GreeterClient greeter(grpc::CreateChannel(
      target_str, grpc::InsecureChannelCredentials()));

  std::cout << "【连接信息】目标地址: " << target_str << std::endl;
  std::cout << std::endl;

  // ========== 3. RPC 调用 ==========
  std::string user("World");                    // 准备参数
  std::cout << "【操作】开始 RPC 调用: SayHello(\"" << user << "\")" << std::endl;
  std::cout << std::endl;

  std::string reply = greeter.SayHello(user);   // 调用远程方法

  std::cout << "【最终输出】Greeter received: " << reply << std::endl;
  std::cout << std::endl;

  // ========== 【程序结束】==========
  std::cout << "╔══════════════════════════════════════════════════════════════╗" << std::endl;
  std::cout << "║              gRPC 客户端程序结束                          ║" << std::endl;
  std::cout << "║              结束时间: " << GetCurrentTimestamp() << "              ║" << std::endl;
  std::cout << "╚══════════════════════════════════════════════════════════════╝" << std::endl;

  // ========== 5. 程序结束 ==========
  return 0;
}

server.cpp

cpp 复制代码
// ============================================================
// gRPC 服务端程序
// 功能: 监听 50052 端口,响应客户端的 SayHello 和 SayHelloAgain 请求
// ============================================================

// ========== C++ 标准库头文件 ==========
#include <iostream>   // 标准输入输出,用于打印日志
#include <memory>     // 智能指针库,用于 std::unique_ptr 和 std::shared_ptr
#include <string>     // C++ 字符串类
#include <cstdlib>    // 用于 exit()
#include <chrono>     // 用于获取时间戳
#include <ctime>       // 用于时间格式化
#include <sstream>     // 用于字符串流
#include <iomanip>    // 用于格式化输出

// ========== gRPC 头文件 ==========
#include <grpcpp/grpcpp.h>      // gRPC 核心库
#include "example.grpc.pb.h"    // 由 example.proto 生成的服务基类

// ============================================================
// 获取当前时间戳的辅助函数
// ============================================================
std::string GetCurrentTimestamp() {
  // 获取当前时间点
  auto now = std::chrono::system_clock::now();

  // 转换为 time_t
  std::time_t now_time = std::chrono::system_clock::to_time_t(now);

  // 转换为本地时间
  std::tm local_tm = *std::localtime(&now_time);

  // 格式化为字符串 HH:MM:SS.mmm
  std::ostringstream oss;
  oss << std::setfill('0') << std::setw(2) << local_tm.tm_hour << ":"
      << std::setfill('0') << std::setw(2) << local_tm.tm_min << ":"
      << std::setfill('0') << std::setw(2) << local_tm.tm_sec << "."
      << std::setfill('0') << std::setw(3)
      << std::chrono::duration_cast<std::chrono::milliseconds>(
          now.time_since_epoch()).count() % 1000;

  return oss.str();
}

// ============================================================
// 服务实现类
// 继承自 example::Greeter::Service (由 protoc 自动生成的基类)
// ============================================================
class GreeterServiceImpl final : public example::Greeter::Service {
 public:
  // ========== 实现 SayHello RPC 方法 ==========
  // 当客户端调用 SayHello 时,gRPC 框架会自动调用这个方法
  //
  // 参数说明:
  //   context  - RPC 调用的上下文信息(如超时时间、元数据等)
  //   request  - 客户端发送的请求(只读指针)
  //   reply    - 要返回给客户端的响应(需要填充数据)
  //
  // 返回值:
  //   grpc::Status - 表示调用成功或失败的状态
  grpc::Status SayHello(grpc::ServerContext* context,
                        const example::HelloRequest* request,
                        example::HelloReply* reply) override {

    // ========== 【服务端接收到的消息】==========
    std::cout << "========== 服务端收到 SayHello 请求 ==========" << std::endl;
    std::cout << "【时间】" << GetCurrentTimestamp() << std::endl;
    std::cout << "【gRPC 框架已自动反序列化】" << std::endl;
    std::cout << "【原始消息内容】" << std::endl;
    std::cout << "  name: " << request->name() << std::endl;

    // 打印 protobuf 的调试字符串(显示所有字段)
    std::cout << "【Protobuf DebugString】" << std::endl;
    std::cout << "  " << request->DebugString() << std::endl;

    // 手动序列化显示接收到的原始字节
    std::string serialized_data;
    request->SerializeToString(&serialized_data);
    std::cout << "【序列化后的二进制数据】" << std::endl;
    std::cout << "  大小: " << serialized_data.size() << " 字节" << std::endl;
    std::cout << "  十六进制: ";
    for (size_t i = 0; i < serialized_data.size(); i++) {
      printf("%02X ", (unsigned char)serialized_data[i]);
    }
    std::cout << std::endl;
    std::cout << "=============================================" << std::endl;

    // ========== 业务逻辑 ==========
    // 拼接 "Hello " 和客户端发来的名字
    // request->name() 获取客户端发送的名字
    std::string response_msg = "Hello " + request->name();

    // ========== 【准备返回响应】==========
    reply->set_message(response_msg);

    std::cout << "【服务端准备返回响应】" << std::endl;
    std::cout << "  时间: " << GetCurrentTimestamp() << std::endl;
    std::cout << "  message: " << response_msg << std::endl;
    std::cout << "=============================================" << std::endl << std::endl;

    // 返回 OK 状态,告诉 gRPC 这次调用成功
    return grpc::Status::OK;
  }
};

// ============================================================
// 启动服务器的函数
// ============================================================
void RunServer() {
  // ========== 【服务器启动】==========
  std::cout << "╔══════════════════════════════════════════════════════════════╗" << std::endl;
  std::cout << "║              gRPC 服务端程序启动                          ║" << std::endl;
  std::cout << "║              启动时间: " << GetCurrentTimestamp() << "              ║" << std::endl;
  std::cout << "╚══════════════════════════════════════════════════════════════╝" << std::endl;
  std::cout << std::endl;

  // ========== 1. 定义服务器地址 ==========
  // "0.0.0.0" 表示监听所有网卡(可以被任何 IP 访问)
  // "50052" 是端口号
  std::string server_address("0.0.0.0:50052");

  // ========== 2. 创建服务实现对象 ==========
  // 这个对象包含了我们实现的 RPC 方法
  GreeterServiceImpl service;

  // ========== 3. 创建 gRPC 服务器构建器 ==========
  // ServerBuilder 是建造者模式,用于配置和启动服务器
  grpc::ServerBuilder builder;

  // ========== 4. 配置服务器 ==========
  // 添加监听端口和认证方式
  // InsecureServerCredentials() 表示不加密(仅用于开发环境)
  builder.AddListeningPort(server_address, grpc::InsecureServerCredentials());

  // 注册服务实现
  // 告诉服务器:收到 Greeter 服务的请求时,调用 service 对象的方法
  builder.RegisterService(&service);

  // ========== 5. 构建并启动服务器 ==========
  // BuildAndStart() 同时完成:
  //   1. 创建服务器对象
  //   2. 绑定端口
  //   3. 启动监听线程
  std::unique_ptr<grpc::Server> server(builder.BuildAndStart());

  // ========== 错误检查 ==========
  // 如果服务器启动失败(比如端口被占用),server 会是 nullptr
  if (!server) {
    std::cerr << "Failed to start server on " << server_address << std::endl;
    std::cerr << "The port may already be in use. Try:" << std::endl;
    std::cerr << "  1. Kill the process using port 50052:" << std::endl;
    std::cerr << "     lsof -i :50052  # find the process" << std::endl;
    std::cerr << "     kill -9 <PID>   # kill it" << std::endl;
    std::cerr << "  2. Or change the port in server.cpp" << std::endl;
    exit(1);  // 退出程序
  }

  // 输出启动成功的日志
  std::cout << "【服务端已启动】监听地址: " << server_address << std::endl;
  std::cout << "等待客户端连接...\n" << std::endl;

  // ========== 6. 等待请求 ==========
  // Wait() 会阻塞主线程,持续处理客户端请求
  // 只有在服务器被关闭时才会返回
  server->Wait();
}

// ============================================================
// 程序入口
// ============================================================
int main(int argc, char** argv) {
  // 启动服务器(程序会阻塞在这里,直到服务器被关闭)
  RunServer();

  // 理论上不会执行到这里,因为 Wait() 是无限阻塞的
  return 0;
}

CMakeLists.txt

cpp 复制代码
# ============================================================
# CMake 配置文件: gRPC C++ 示例项目
# ============================================================

# ========== 基本项目配置 ==========
# 指定需要的最低 CMake 版本
cmake_minimum_required(VERSION 3.14)

# 定义项目名称和使用的编程语言 (CXX = C++)
project(gRPC_example CXX)

# 设置 C++ 标准为 C++17
set(CMAKE_CXX_STANDARD 17)
# 强制要求使用 C++17,不允许降级
set(CMAKE_CXX_STANDARD_REQUIRED ON)

# ========== 查找依赖包 ==========
# 查找 PkgConfig 工具(用于查找系统库)
# REQUIRED 表示必须找到,找不到就报错
find_package(PkgConfig REQUIRED)

# 使用 pkg-config 查找 gRPC 库
# grpc++ 是 gRPC 的 C++ 库包名
# 查找成功后,会设置 GRPC_PKG_INCLUDE_DIRS 和 GRPC_PKG_LDFLAGS 变量
pkg_check_modules(GRPC_PKG REQUIRED grpc++)

# 使用 pkg-config 查找 protobuf 库
# protobuf 是 Protocol Buffers 的包名
# 查找成功后,会设置 PROTOBUF_PKG_INCLUDE_DIRS 和 PROTOBUF_PKG_LDFLAGS 变量
pkg_check_modules(PROTOBUF_PKG REQUIRED protobuf)

# ========== 查找 grpc_cpp_plugin 工具 ==========
# protoc 的 gRPC 插件,用于生成 gRPC 代码
find_program(GRPC_CPP_PLUGIN grpc_cpp_plugin)
# 输出找到的插件路径,方便调试
message(STATUS "Found grpc_cpp_plugin: ${GRPC_CPP_PLUGIN}")

# ========== 设置头文件搜索路径 ==========
# 当前源代码目录(用于找到我们自己写的头文件)
include_directories(${CMAKE_CURRENT_SOURCE_DIR})
# 构建目录(用于找到 protoc 生成的头文件)
include_directories(${CMAKE_CURRENT_BINARY_DIR})
# gRPC 的头文件路径
include_directories(${GRPC_PKG_INCLUDE_DIRS})
# protobuf 的头文件路径
include_directories(${PROTOBUF_PKG_INCLUDE_DIRS})

# ========== 定义 .proto 文件 ==========
# 指定要编译的 protobuf 文件
set(PROTO_FILES "${CMAKE_CURRENT_SOURCE_DIR}/example.proto")

# ========== 遍历每个 .proto 文件并生成代码 ==========
foreach(PROTO_FILE ${PROTO_FILES})
  # 获取文件名(不带路径和扩展名)
  # 例如: /path/to/example.proto → example
  get_filename_component(PROTO_NAME ${PROTO_FILE} NAME_WE)

  # ========== 定义生成的文件路径 ==========
  # gRPC 生成的 C++ 源文件和头文件
  set(PROTO_SRCS "${CMAKE_CURRENT_BINARY_DIR}/${PROTO_NAME}.grpc.pb.cc")
  set(PROTO_HDRS "${CMAKE_CURRENT_BINARY_DIR}/${PROTO_NAME}.grpc.pb.h")
  # protobuf 生成的 C++ 源文件和头文件
  set(PROTO_PB_SRCS "${CMAKE_CURRENT_BINARY_DIR}/${PROTO_NAME}.pb.cc")
  set(PROTO_PB_HDRS "${CMAKE_CURRENT_BINARY_DIR}/${PROTO_NAME}.pb.h")

  # ========== 自定义命令: 调用 protoc 生成代码 ==========
  # add_custom_command: 在构建过程中执行自定义命令
  # OUTPUT: 指定这个命令会生成哪些文件
  add_custom_command(
    OUTPUT ${PROTO_SRCS} ${PROTO_HDRS} ${PROTO_PB_SRCS} ${PROTO_PB_HDRS}
    # COMMAND: 要执行的命令(protoc 编译器)
    COMMAND protoc
    # ARGS: 传递给 protoc 的参数
    ARGS --grpc_out "${CMAKE_CURRENT_BINARY_DIR}"        # gRPC 代码输出目录
         --cpp_out "${CMAKE_CURRENT_BINARY_DIR}"         # C++ 代码输出目录
         -I "${CMAKE_CURRENT_SOURCE_DIR}"                # .proto 文件搜索路径
         --plugin=protoc-gen-grpc="${GRPC_CPP_PLUGIN}"   # 指定 gRPC 插件
         "${PROTO_FILE}"                                 # 要编译的 .proto 文件
    # DEPENDS: 指定依赖关系,当 .proto 文件改变时重新生成
    DEPENDS ${PROTO_FILE}
    # COMMENT: 执行时显示的消息
    COMMENT "Generating gRPC files from ${PROTO_FILE}"
  )

  # ========== 标记这些文件是自动生成的 ==========
  # 告诉 CMake 这些文件是由构建系统生成的,不是用户手动写的
  set_source_files_properties(${PROTO_SRCS} ${PROTO_PB_SRCS} PROPERTIES GENERATED TRUE)
endforeach()

# ========== 构建服务端可执行文件 ==========
# add_executable: 创建一个可执行文件目标
add_executable(server
  server.cpp          # 服务端源代码
  ${PROTO_SRCS}       # gRPC 生成的代码
  ${PROTO_PB_SRCS}    # protobuf 生成的代码
)

# 为服务端设置头文件搜索路径
target_include_directories(server PRIVATE ${GRPC_PKG_INCLUDE_DIRS} ${PROTOBUF_PKG_INCLUDE_DIRS})
# 为服务端链接需要的库
# ${GRPC_PKG_LDFLAGS}: 包含所有 gRPC 相关的库及其依赖
# ${PROTOBUF_PKG_LDFLAGS}: 包含 protobuf 库
target_link_libraries(server ${GRPC_PKG_LDFLAGS} ${PROTOBUF_PKG_LDFLAGS})

# ========== 构建客户端可执行文件 ==========
add_executable(client
  client.cpp          # 客户端源代码
  ${PROTO_SRCS}       # gRPC 生成的代码(和服务端共用)
  ${PROTO_PB_SRCS}    # protobuf 生成的代码(和服务端共用)
)

# 为客户端设置头文件搜索路径
target_include_directories(client PRIVATE ${GRPC_PKG_INCLUDE_DIRS} ${PROTOBUF_PKG_INCLUDE_DIRS})
# 为客户端链接需要的库
target_link_libraries(client ${GRPC_PKG_LDFLAGS} ${PROTOBUF_PKG_LDFLAGS})

README.md

cpp 复制代码
# gRPC C++ 示例

## 项目结构

```
.
├── example.proto      # Protocol Buffers 服务定义
├── server.cpp         # gRPC 服务端实现
├── client.cpp         # gRPC 客户端实现
├── CMakeLists.txt     # CMake 构建配置
└── README.md          # 本文件
```

## 前置要求

### macOS (使用 Homebrew)

```bash
# 安装 gRPC 和 protobuf
brew install grpc protobuf cmake pkg-config

# 如果有旧版 protobuf 在 /usr/local/include,需要移除
sudo mv /usr/local/include/google/protobuf /usr/local/include/google/protobuf.bak
```

## 构建项目

```bash
mkdir build && cd build
cmake ..
make
```

## 运行

### 启动服务端(终端1)

```bash
./server
# 输出: Server listening on 0.0.0.0:50051
```

### 运行客户端(终端2)

```bash
./client
# 输出:
# Greeter received: Hello World
# Greeter received: Hello again World
```

运行效果

相关推荐
rleS IONS2 小时前
SQL2000在win10上安装的方法
运维·服务器
zly35002 小时前
centos7 sshd无法启动
linux·运维·服务器
山峰哥3 小时前
告别“点点点”:AI 如何重构我们的测试体系与质量防线
服务器·汇编·数据库·人工智能·性能优化·重构
编程大师哥3 小时前
Linux 命名管道(FIFO)通信 超清晰讲解
linux·运维·服务器
Smile_2542204183 小时前
linux服务器清理磁盘
linux·运维·服务器
KivenMitnick4 小时前
Claude Code--Ubuntu Linux超详细配置教程(附每步的可能报错及解决方法)
linux·运维·ubuntu
JoyCong19984 小时前
OpenClaw实践玩法,简单三步搭建自动化工作流(附真香平替方案)
运维·人工智能·自动化
panamera124 小时前
linux下SPI、IIC、UART、CAN的编码
linux·运维·服务器