Linux C/C++ 学习日记(70):grpc(三):基于grpc编写同步的server、client

Linux C/C++ 学习日记(70):grpc(三):基于grpc编写同步的server、client

注:该文用于个人学习记录和知识交流,如有不足,欢迎指点。

一、proto文件

syntax = "proto3"; package example; // 定义四种 RPC 模式的服务 service ExampleService { // 1. 一元 RPC (Unary):客户端发一个请求,服务端回一个响应 rpc UnaryCall (Request) returns (Response); // 2. 服务端流式 RPC (Server Streaming):客户端发一个请求,服务端回多个响应 rpc ServerStream (Request) returns (stream Response); // 3. 客户端流式 RPC (Client Streaming):客户端发多个请求,服务端回一个响应 rpc ClientStream (stream Request) returns (Response); // 4. 双向流式 RPC (Bidirectional Streaming):双方互相发多个消息 rpc BidiStream (stream Request) returns (stream Response); } // 请求消息 message Request { string data = 1; } // 响应消息 message Response { string data = 1; }

二、server同步

/* * 同步服务端 */ #include <grpcpp/grpcpp.h> #include "./build/example.grpc.pb.h" using grpc::Server; using grpc::ServerBuilder; using grpc::ServerContext; using grpc::ServerReader; using grpc::ServerWriter; using grpc::ServerReaderWriter; using grpc::Status; using example::ExampleService; using example::Request; using example::Response; // 服务实现类 class ExampleServiceImpl final : public ExampleService::Service { // 1. 一元 RPC Status UnaryCall(ServerContext* context, const Request* req, Response* res) override { res->set_data("Server: " + req->data()); return Status::OK; } // 2. 服务端流式 RPC Status ServerStream(ServerContext* context, const Request* req, ServerWriter<Response>* writer) override { for (int i = 0; i < 3; ++i) { Response res; res.set_data("Server Stream " + std::to_string(i) + ": " + req->data()); writer->Write(res); // 每次循环立即发送 1 条数据 } return Status::OK; // 发送结束标记,告诉客户端“发完了” } // 3. 客户端流式 RPC Status ClientStream(ServerContext* context, ServerReader<Request>* reader, Response* res) override { std::string combined; Request req; while (reader->Read(&req)) { // Read()阻塞等待响应,收到数据时返回 true,收到结束标记时返回 false combined += req.data() + " "; } res->set_data("Server Combined: " + combined); return Status::OK; } // 4. 双向流式 RPC Status BidiStream(ServerContext* context, ServerReaderWriter<Response, Request>* stream) override { Request req; while (stream->Read(&req)) { Response res; res.set_data("Server Echo: " + req.data()); stream->Write(res); } return Status::OK; } }; void RunServer() { std::string addr("0.0.0.0:50051"); ExampleServiceImpl service; ServerBuilder builder; builder.AddListeningPort(addr, grpc::InsecureServerCredentials()); builder.RegisterService(&service); std::unique_ptr<Server> server(builder.BuildAndStart()); server->Wait(); } int main() { RunServer(); return 0; }

 三、client同步

/* * 同步客户端 */ #include <grpcpp/grpcpp.h> #include "./build/example.grpc.pb.h" #include <vector> using grpc::Channel; using grpc::ClientContext; using grpc::ClientReader; using grpc::ClientWriter; using grpc::ClientReaderWriter; using grpc::Status; using example::ExampleService; using example::Request; using example::Response; class ExampleClient { public: ExampleClient(std::shared_ptr<Channel> channel) : stub_(ExampleService::NewStub(channel)) {} // 1. 一元 RPC void CallUnary(const std::string& msg) { Request req; req.set_data(msg); Response res; ClientContext ctx; Status status = stub_->UnaryCall(&ctx, req, &res); if (status.ok()) std::cout << "Unary: " << res.data() << std::endl; } // 2. 服务端流式 RPC void CallServerStream(const std::string& msg) { Request req; req.set_data(msg); ClientContext ctx; std::unique_ptr<ClientReader<Response>> reader = stub_->ServerStream(&ctx, req); Response res; while (reader->Read(&res)) { // Read()阻塞等待响应,收到数据时返回 true,收到结束标记时返回 false std::cout << "Server Stream: " << res.data() << std::endl; } } // 3. 客户端流式 RPC void CallClientStream(const std::vector<std::string>& msgs) { ClientContext ctx; Response res; std::unique_ptr<ClientWriter<Request>> writer = stub_->ClientStream(&ctx, &res); for (const auto& msg : msgs) { Request req; req.set_data(msg); writer->Write(req); // 发送数据给服务端 } writer->WritesDone(); // 发送结束标记,告诉服务端“发完了” Status status = writer->Finish(); // 阻塞等待服务端的响应 if (status.ok()) std::cout << "Client Stream: " << res.data() << std::endl; } // 4. 双向流式 RPC void CallBidiStream(const std::vector<std::string>& msgs) { ClientContext ctx; std::unique_ptr<ClientReaderWriter<Request, Response>> stream = stub_->BidiStream(&ctx); // 写请求 for (const auto& msg : msgs) { Request req; req.set_data(msg); stream->Write(req); } stream->WritesDone(); // 读响应 Response res; while (stream->Read(&res)) { std::cout << "Bidi Stream: " << res.data() << std::endl; } } private: std::unique_ptr<ExampleService::Stub> stub_; }; int main() { ExampleClient client(grpc::CreateChannel("localhost:50051", grpc::InsecureChannelCredentials())); client.CallUnary("Hello"); client.CallServerStream("Hi"); client.CallClientStream({"A", "B", "C"}); client.CallBidiStream({"X", "Y", "Z"}); return 0; }

四、消息的发送和接收的时机

同步 API 会阻塞当前线程,直到操作完成

1. 一元 RPC (Unary)

客户端发送时机

调用 stub_->UnaryCall(&ctx, req, &res) 时:

  • 函数内部会将 Request 序列化为二进制,立即发送给服务端
  • 然后阻塞,直到收到服务端的 Response 或出错。
服务端发送时机

UnaryCall 函数中执行 return Status::OK; 时:

  • 之前设置的 res->set_data(...) 会被序列化,Status::OK 一起发送给客户端

2. 服务端流式 RPC (Server Streaming)

客户端发送时机

调用 stub_->ServerStream(&ctx, req) 时:

  • Request立即发送给服务端
  • 函数返回 ClientReader,后续通过 reader->Read(&res) 阻塞接收服务端的流式响应。
服务端发送时机

每次调用 writer->Write(res) 时:

  • 当前的 Response立即发送给客户端(不会等循环结束)。
  • Write 会阻塞直到数据写入传输缓冲区。

3. 客户端流式 RPC (Client Streaming)

客户端发送时机
  • 每次调用 writer->Write(req) 时:当前的 Request立即发送给服务端
  • 调用 writer->WritesDone() 时:向服务端发送「客户端已写完所有请求」的标记(不发数据,只发控制帧)。
  • 调用 writer->Finish() 时:阻塞等待服务端的最终 Response
服务端发送时机

ClientStream 函数中执行 return Status::OK; 时:

  • 之前设置的 res->set_data(...)Status::OK 一起发送给客户端

4. 双向流式 RPC (Bidirectional Streaming)

客户端发送时机
  • 每次调用 stream->Write(req) 时:当前的 Request立即发送给服务端
  • 调用 stream->WritesDone() 时:发送「客户端已写完」的标记。
服务端发送时机

每次调用 stream->Write(res) 时:当前的 Response立即发送给客户端

总结

模式客户端发送数据时机服务端发送数据时机
一元调用 UnaryCallreturn Status::OK
服务端流调用 ServerStream 时(发起请求,无后续主动发送,仅通过 reader->Read() 接收)每次 writer->Write 时;return Status::OK 时发送结束标记
客户端流每次 writer->Write 时;writer->WritesDone() 发送结束标记return Status::OK
双向流每次 stream->Write 时;stream->WritesDone() 发送结束标记每次 stream->Write 时;return Status::OK 时发送结束标记
调用模式客户端接收数据时机服务端接收数据时机
一元 RPC(Unary)调用 stub->UnaryCall(&ctx, req, &res) 阻塞返回时,从 res 中获取服务端响应实现 ExampleService::UnaryCall 方法时,直接从入参 req 中获取客户端请求数据
服务端流(ServerStream)调用 stub->ServerStream(&ctx, req, &reader) 后,循环调用 reader->Read(&res) 接收服务端推送的每一条数据;Read() 返回 false 表示流结束实现 ExampleService::ServerStream 方法时,直接从入参 req 中获取客户端请求数据(仅 1 次)
客户端流(ClientStream)调用 writer->Finish(&res) 阻塞返回时,从 res 中获取服务端最终响应实现 ExampleService::ClientStream 方法时,循环调用 reader->Read(&req) 接收客户端推送的每一条数据;Read() 返回 false 表示客户端发送完毕
双向流(BidiStream)调用 stub->BidiStream(&ctx, &stream) 后,循环调用 stream->Read(&res) 接收服务端推送的每一条数据;Read() 返回 false 表示服务端流结束实现 ExampleService::BidiStream 方法时,循环调用 stream->Read(&req) 接收客户端推送的每一条数据;Read() 返回 false 表示客户端流结束

Read more

在 Mac 上完美配置 VSCode 的 C/C++ 开发环境(GCC/G++ 详细教程 )

本文手把手教你如何在 macOS 系统上配置 VSCode 的 C/C++ 开发环境,解决各种常见问题,让你轻松开启 C/C++ 编程之旅! 前言 作为程序员,一个顺手的开发环境至关重要。VSCode 作为轻量级但功能强大的代码编辑器,配合 GCC/G++ 编译器,能够在 Mac 上提供优秀的 C/C++ 开发体验。本文将详细介绍从零开始的完整配置过程。 一、环境准备:安装编译工具 1.1 安装 Xcode Command Line Tools(推荐首选) 打开终端,执行以下命令: xcode-select --install 执行后会弹出安装对话框,点击"安装"即可。

By Ne0inhk
C++:继承

C++:继承

Hello大家好! 很高兴与大家见面! 给生活添点快乐,开始今天的编程之路。 我的博客:<但愿. 我的专栏:C语言、题目精讲、算法与数据结构、C++ 欢迎点赞,关注 目录   一 继承的概念及定义        1.1继承的概念        1.2继承的定义               1.2.1定义格式               1.2.2类继承基类方式改变对应成员访问⽅式的变化               1.2.3  继承类模板【类继承类似】      二 基类和派⽣类间的转换          2.1不同的转换方式                 2.1.1会产生临时变量                 2.1.2不会产生临时变量(基类和派⽣类间的转换)                         2.1.2.1不会产生临时变量(

By Ne0inhk
【C++】红黑树详解(2w字详解)

【C++】红黑树详解(2w字详解)

手搓AVL树 * 手搓红黑树 * github地址 * 0. 前言 * 1. 什么是红黑树 * 概念与定义 * 红黑树示例 * 2. 红黑树的性质 * 红黑树的性质解读 * 树的路径再认识 * 3. 红黑树如何确保最长路径不超过最短路径的2倍? * 4. 红黑树的实现 * 整体架构设计 * 结点颜色的枚举类 * 红黑树的结点定义 * 红黑树设计 * 红黑树的插入实现 * 1. 空树的插入 * 2. 新插入节点的父亲为黑色 * 新结点的颜色 * 3. 新插入节点的父亲为红色 * (1)叔叔存在且为红色:变色 + 继续向上处理 * (2)叔叔不存在或叔叔为黑色:旋转 + 变色 * ①LL型:右单旋 + 变色 * ②RR型:左单旋 + 变色 * ③LR型:左右双旋 + 变色 * ①RL型:右左双旋 + 变色 * 4.

By Ne0inhk