摘要:gRPC 区别于传统 HTTP 接口最大的亮点就是支持四种通信模型,也是企业开发、面试的核心考点。本文基于 C++ 从零实战 gRPC 全部主流业务场景:简单单向调用、服务端流式推送、客户端流式上传、双向流式交互,覆盖日志上报、实时推送、文件传输、实时交互等真实业务,所有代码可直接编译运行。

关键词:C++ gRPC、gRPC流式通信、服务端流、客户端流、双向流、gRPC实战场景

通信模型

对应场景

业务用途

简单RPC(Unary)

一问一答

接口查询、数据请求、普通业务接口

服务端流式(Server Stream)

一次请求,多次响应

实时消息推送、日志批量下发、大数据分页返回、设备状态订阅

客户端流式(Client Stream)

多次请求,一次响应

批量数据上报、日志采集、文件分片上传、设备心跳上报

双向流式(Bidirectional Stream)

多次请求、多次响应、互不阻塞

实时聊天、音视频传输、实时游戏交互、实时设备双向通信

下面我们通过一套完整 Proto 协议 + C++ 服务端/客户端代码,一次性实现四种场景,覆盖 99% gRPC 业务开发需求。

二、统一 Proto 协议定义(四种场景全覆盖)

新建 scene.proto,同时定义四种通信模型接口,为后续 C++ 代码生成提供基础:

syntax = "proto3";

package scene;
option cc_generic_services = true;

// 通用请求
message CommonReq {
  string msg = 1;
}

// 通用响应
message CommonRsp {
  int32 code = 1;
  string data = 2;
}

// 流式数据单元
message StreamData {
  string content = 1;
  int32 seq = 2;
}

// gRPC 四大场景服务定义
service SceneService {
  // 1. 基础单向RPC:一问一答
  rpc NormalCall(CommonReq) returns (CommonRsp);

  // 2. 服务端流式RPC:客户端一次请求,服务端持续返回数据流
  rpc ServerStreamCall(CommonReq) returns (stream StreamData);

  // 3. 客户端流式RPC:客户端持续上传数据,服务端最后统一返回结果
  rpc ClientStreamCall(stream StreamData) returns (CommonRsp);

  // 4. 双向流式RPC:双方持续实时交互
  rpc BidirectionalStreamCall(stream StreamData) returns (stream StreamData);
}

三、C++ 服务端完整实现(四种场景统一实现)

基于 gRPC 原生异步/流式接口,实现全部业务逻辑,代码规范可直接上线。

#include <iostream>
#include <memory>
#include <string>
#include <vector>
#include <grpcpp/grpcpp.h>
#include "scene.grpc.pb.h"

using grpc::Server;
using grpc::ServerBuilder;
using grpc::ServerContext;
using grpc::ServerReader;
using grpc::ServerReaderWriter;
using grpc::ServerWriter;
using scene::SceneService;
using scene::CommonReq;
using scene::CommonRsp;
using scene::StreamData;

class SceneServiceImpl final : public SceneService::Service
{
    // 1. 普通单向RPC调用
    grpc::Status NormalCall(ServerContext* context, const CommonReq* req, CommonRsp* rsp) override
    {
        std::cout << "[普通RPC] 收到客户端请求:" << req->msg() << std::endl;
        rsp->set_code(200);
        rsp->set_data("普通接口响应成功:" + req->msg());
        return grpc::Status::OK;
    }

    // 2. 服务端流式:一次请求,服务端多次返回数据
    grpc::Status ServerStreamCall(ServerContext* context, const CommonReq* req, ServerWriter<StreamData>* writer) override
    {
        std::cout << "[服务端流] 开始推送数据,客户端请求:" << req->msg() << std::endl;
        // 模拟分5次流式推送数据
        for (int i = 1; i <= 5; i++)
        {
            StreamData data;
            data.set_seq(i);
            data.set_content("服务端流式推送数据_" + std::to_string(i));
            writer->Write(data);
        }
        return grpc::Status::OK;
    }

    // 3. 客户端流式:客户端多次上传,服务端最终统一返回
    grpc::Status ClientStreamCall(ServerContext* context, ServerReader<StreamData>* reader, CommonRsp* rsp) override
    {
        std::cout << "[客户端流] 接收客户端批量上传数据..." << std::endl;
        StreamData data;
        std::vector<std::string> recv_list;

        // 循环读取客户端流式数据
        while (reader->Read(&data))
        {
            std::cout << "收到分片数据:seq=" << data.seq() << " content=" << data.content() << std::endl;
            recv_list.push_back(data.content());
        }

        // 全部接收完成后统一响应
        rsp->set_code(200);
        rsp->set_data("客户端上传完成,总分片数:" + std::to_string(recv_list.size()));
        return grpc::Status::OK;
    }

    // 4. 双向流式:双方实时互发数据
    grpc::Status BidirectionalStreamCall(ServerContext* context, ServerReaderWriter<StreamData, StreamData>* stream) override
    {
        std::cout << "[双向流] 双向通信通道建立成功" << std::endl;
        StreamData recv_data;

        // 循环读取客户端实时消息
        while (stream->Read(&recv_data))
        {
            std::cout << "双向流-接收客户端:" << recv_data.content() << std::endl;
            
            // 实时回包响应
            StreamData send_data;
            send_data.set_seq(recv_data.seq());
            send_data.set_content("服务端已收到:" + recv_data.content());
            stream->Write(send_data);
        }
        return grpc::Status::OK;
    }
};

void RunServer()
{
    std::string addr = "0.0.0.0:9000";
    SceneServiceImpl service;

    ServerBuilder builder;
    builder.AddListeningPort(addr, grpc::InsecureServerCredentials());
    builder.RegisterService(&service);

    std::unique_ptr<Server> server(builder.BuildAndStart());
    std::cout << "gRPC场景服务启动成功:" << addr << std::endl;
    server->Wait();
}

int main()
{
    RunServer();
    return 0;
}

四、C++ 客户端完整实现(四种场景逐个调用)

#include <iostream>
#include <memory>
#include <string>
#include <grpcpp/grpcpp.h>
#include "scene.grpc.pb.h"

using grpc::Channel;
using grpc::ClientContext;
using grpc::ClientReader;
using grpc::ClientReaderWriter;
using grpc::ClientWriter;
using grpc::Status;
using scene::SceneService;
using scene::CommonReq;
using scene::CommonRsp;
using scene::StreamData;

class SceneClient
{
public:
    SceneClient(std::shared_ptr<Channel> channel)
        : stub_(SceneService::NewStub(channel)) {}

    // 1. 普通单向调用
    void TestNormal()
    {
        CommonReq req;
        CommonRsp rsp;
        ClientContext ctx;
        req.set_msg("Hello Normal RPC");

        Status status = stub_->NormalCall(&ctx, req, &rsp);
        if (status.ok())
            std::cout << "[普通调用结果] " << rsp.data() << std::endl;
    }

    // 2. 测试服务端流
    void TestServerStream()
    {
        CommonReq req;
        req.set_msg("请求服务端流式数据");
        ClientContext ctx;

        std::unique_ptr<ClientReader<StreamData>> reader = stub_->ServerStreamCall(&ctx, req);
        StreamData data;

        std::cout << "[服务端流接收数据]:" << std::endl;
        while (reader->Read(&data))
        {
            std::cout << "seq:" << data.seq() << " content:" << data.content() << std::endl;
        }
        Status status = reader->Finish();
    }

    // 3. 测试客户端流
    void TestClientStream()
    {
        CommonRsp rsp;
        ClientContext ctx;

        std::unique_ptr<ClientWriter<StreamData>> writer = stub_->ClientStreamCall(&ctx, &rsp);
        
        // 模拟上传5个分片数据
        for (int i = 1; i <= 5; i++)
        {
            StreamData data;
            data.set_seq(i);
            data.set_content("客户端分片数据_" + std::to_string(i));
            writer->Write(data);
        }
        writer->WritesDone();
        Status status = writer->Finish();

        if (status.ok())
            std::cout << "[客户端流结果] " << rsp.data() << std::endl;
    }

    // 4. 测试双向流
    void TestBidirectionalStream()
    {
        ClientContext ctx;
        std::unique_ptr<ClientReaderWriter<StreamData, StreamData>> stream = stub_->BidirectionalStreamCall(&ctx);

        // 客户端连续发送3条消息
        for (int i = 1; i <= 3; i++)
        {
            StreamData data;
            data.set_seq(i);
            data.set_content("客户端双向消息_" + std::to_string(i));
            stream->Write(data);

            // 同步读取服务端返回
            StreamData recv;
            if (stream->Read(&recv))
            {
                std::cout << "[双向流接收] " << recv.content() << std::endl;
            }
        }
        stream->WritesDone();
        Status status = stream->Finish();
    }

private:
    std::unique_ptr<SceneService::Stub> stub_;
};

int main()
{
    SceneClient client(grpc::CreateChannel("127.0.0.1:9000", grpc::InsecureChannelCredentials()));
    
    std::cout << "===== 1.测试普通单向RPC =====" << std::endl;
    client.TestNormal();

    std::cout << "\n===== 2.测试服务端流式RPC =====" << std::endl;
    client.TestServerStream();

    std::cout << "\n===== 3.测试客户端流式RPC =====" << std::endl;
    client.TestClientStream();

    std::cout << "\n===== 4.测试双向流式RPC =====" << std::endl;
    client.TestBidirectionalStream();

    return 0;
}

五、配套 CMakeLists.txt 编译文件

cmake_minimum_required(VERSION 3.13)
project(grpc_scene_demo)

set(CMAKE_CXX_STANDARD 11)
set(CMAKE_CXX_STANDARD_REQUIRED ON)

find_package(Protobuf REQUIRED)
find_package(GRPC REQUIRED)

include_directories(${PROTOBUF_INCLUDE_DIRS})

set(PROTO_FILE scene.proto)
set(GENERATED_PROTO
    ${CMAKE_CURRENT_BINARY_DIR}/scene.pb.cc
    ${CMAKE_CURRENT_BINARY_DIR}/scene.pb.h
    ${CMAKE_CURRENT_BINARY_DIR}/scene.grpc.pb.cc
    ${CMAKE_CURRENT_BINARY_DIR}/scene.grpc.pb.h
)

add_custom_command(
    OUTPUT ${GENERATED_PROTO}
    COMMAND protoc
    ARGS --grpc_out=${CMAKE_CURRENT_BINARY_DIR}
         --plugin=protoc-gen-grpc=`which grpc_cpp_plugin`
         --cpp_out=${CMAKE_CURRENT_BINARY_DIR}
         ${PROTO_FILE}
    DEPENDS ${PROTO_FILE}
)

add_executable(server server.cpp ${GENERATED_PROTO})
target_link_libraries(server grpc grpc++ ${PROTOBUF_LIBRARIES} pthread)

add_executable(client client.cpp ${GENERATED_PROTO})
target_link_libraries(client grpc grpc++ ${PROTOBUF_LIBRARIES} pthread)

六、编译运行步骤

mkdir build && cd build
cmake ..
make -j4

# 终端1:启动服务端
./server

# 终端2:启动客户端,自动执行全部4种场景
./client

七、场景落地总结

1. 普通Unary RPC

适用于所有短平快的单次查询接口,如用户信息查询、配置获取、接口校验,是业务最常用模式。

2. 服务端流式 ServerStream

客户端只需一次请求,服务端持续推送,适合 消息订阅、实时日志推送、设备状态监控、大批量数据分页拉取

3. 客户端流式 ClientStream

客户端持续上传分片数据,服务端汇总结果返回,适合日志批量上报、文件分片上传、传感器高频数据采集,极大减少网络握手开销。

4. 双向流式 BidirectionalStream

全双工实时通信,双方互不阻塞,是 IM聊天、实时游戏、音视频传输、远程设备控制 的核心方案。

八、文末总结

gRPC 相比传统 HTTP 接口最大的优势就是多形态流式通信能力,也是 C++ 后端高性能服务开发、面试的核心重难点。本文一套代码覆盖 gRPC 全部四大通信模型,所有案例贴合真实企业业务场景,可直接复用至微服务、物联网、实时通信、日志系统等项目。

Logo

汇聚全球AI编程工具,助力开发者即刻编程。

更多推荐