惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

量子位
Vercel News
Vercel News
Google DeepMind News
Google DeepMind News
罗磊的独立博客
WordPress大学
WordPress大学
The Cloudflare Blog
GbyAI
GbyAI
The Register - Security
The Register - Security
L
LangChain Blog
酷 壳 – CoolShell
酷 壳 – CoolShell
Microsoft Security Blog
Microsoft Security Blog
MyScale Blog
MyScale Blog
A
About on SuperTechFans
U
Unit 42
T
The Blog of Author Tim Ferriss
Cyber Security Advisories - MS-ISAC
Cyber Security Advisories - MS-ISAC
V
Visual Studio Blog
云风的 BLOG
云风的 BLOG
Stack Overflow Blog
Stack Overflow Blog
博客园 - 三生石上(FineUI控件)
博客园 - 司徒正美
Blog — PlanetScale
Blog — PlanetScale
CTFtime.org: upcoming CTF events
CTFtime.org: upcoming CTF events
奇客Solidot–传递最新科技情报
奇客Solidot–传递最新科技情报
M
MIT News - Artificial intelligence
C
CERT Recently Published Vulnerability Notes
T
The Exploit Database - CXSecurity.com
T
Tor Project blog
A
Arctic Wolf
H
Hacker News: Front Page
NISL@THU
NISL@THU
F
Full Disclosure
雷峰网
雷峰网
L
LINUX DO - 热门话题
Recent Announcements
Recent Announcements
cs.AI updates on arXiv.org
cs.AI updates on arXiv.org
Apple Machine Learning Research
Apple Machine Learning Research
Google Online Security Blog
Google Online Security Blog
I
InfoQ
Webroot Blog
Webroot Blog
S
Security Affairs
freeCodeCamp Programming Tutorials: Python, JavaScript, Git & More
爱范儿
爱范儿
cs.CV updates on arXiv.org
cs.CV updates on arXiv.org
N
News | PayPal Newsroom
Forbes - Security
Forbes - Security
博客园 - Franky
V
Vulnerabilities – Threatpost
博客园 - 【当耐特】
Latest news
Latest news

C++

小孩马上高一,但是想学信奥赛 c++,有什么学 c++的书推荐? - V2EX 真是意想不到的操作:有好几个人一起协作向 C++库 fmtlib 加上了 C11 包装接口,确实能用 - V2EX 似乎在 C 的领域,让一个新程序“为未来准备好”是一件很麻烦的事 - V2EX 请教各位 centos 7.9 通过 devtoolset 启用 c++14/17 时遇到的链接问题 - V2EX 求大佬指点:Windows 上 c++部署最新 Paddleocr,无法通过内存识字 - V2EX 为 c++ 提供模式匹配 - V2EX 分享一下我个人开源的 C++23 协程网络框架 - V2EX 为什么写 C++的人年龄偏大? - V2EX 大型 c++项目,在 ai 帮助下完成 Linux 平台移植,可行性多大? - V2EX 少用 auto - V2EX 再一次感觉到 C++的恶心 - V2EX 分布式存储 - V2EX [求助] Linux 有什么好的引入 c++ 第三方库的方案 - V2EX [求助]请教一个 C++多线程的性能问题 - V2EX 2026 年找 C++的开发工作,应该学习 C++的哪个版本? - V2EX 分布式系统 - V2EX 使用匿名结构体指针作为常量来杜绝魔数,是否合理/值得? - V2EX 有没有什么工具可以统计 C++项目里标识符的使用情况? - V2EX 看到一些 C++ 或者 C#项目 驼峰和下划线一块用,为啥泥? - V2EX [求助] Linux 系统下动态库卸载后全局变量未重置的问题 - V2EX 交叉编译 asop android adb 最新版的问题 - V2EX [有偿] 小白, Windows UI Automation TextPattern 检测问题求助 - V2EX 小白问个 vcpkg 相关的问题 - V2EX 记录一次踩坑过程(clion + cmake + vcpkg) - V2EX 用智能指针管理 ffmpeg 中的数据结构是有必要的吗? - V2EX 定位重载的插件或者 IDE - V2EX 想系统的学习 Modern C++,麻烦大佬们推荐一些书籍 - V2EX 困扰几天的问题,这是被 gcc 优化了吗? - V2EX 好的 c++代码是什么样的 - V2EX 为什么 C/C++ 语言的标准库不做成 Java 那样可安装的运行时? - V2EX CLion 提供非商业免费使用了 - V2EX MFC c++对话框项目使用 httplib 创建静态资源服务, Debug 时每次访问都会被断下是什么原因 - V2EX c++调试遇到奇怪问题 - V2EX 求推荐 C++ 高性能的 web 框架, curd 用 - V2EX 一个项目下有多个 cpp 程序的源文件以及多个程序共用的源文件与头文件, vscode 的 clangd 插件与 compile_commands.json 该如何配置 - V2EX C++库脚手架项目及思考 - V2EX 有人遇到过 vs2022 c++代码高亮不生效的问题吗? - V2EX 使用 C++20 协程与 ASIO 库写了个 Socks5 Server 的跨平台 Demo 程序,几乎全功能,单文件源码少于一千行 - V2EX 小众论坛问个好奇的问题关于 c++ - V2EX 市面上大部分 c++程序员都在 c++98 水平吧? - V2EX cpp 一个隐式转换奇怪的现象 - V2EX C++入门书籍,我是不是被坑了? - V2EX mac clion 调试怎么才能看到 stl 容器的值 - V2EX 有没有合适开源的 C++项目可以快速实现一些功能 - V2EX 用 c++实现 bluez 对于 31 个字节蓝牙广播限制的问题 - V2EX 寻找一个在线运行 c++的方案 - V2EX 这种情况如何消除几百个 if/else - V2EX cmake + googletest 的目录结构最佳实践是? - V2EX 为什么打印模板元编程计算阶乘结果,比打印 for 循环计算阶乘结果更耗时 - V2EX Asio 异步模型 - V2EX 求教, mac 下 cmake c++工程编译后 elf 里面还有很多路径字符串 - V2EX C 怎么进阶学 C++呀
基于 C++20 协程编写 gRPC 客户端与服务端
Hackerl · 2026-04-23 · via C++

基于 C++20 协程编写 gRPC 客户端与服务端

完整代码

gRPCC++ 异步接口十分不友好,尤其是对于需要支持并发的服务端来说,官方的例子是实现一个小型状态机进行请求处理。是否可以将 gRPC 与 C++20 的协程结合,编写出简单易懂的代码呢?

实现细节请阅读博客,在此仅展示最终代码。

协议:

syntax = "proto3";

package sample;

service SampleService {
  // Unary RPC:最简单的请求-响应模式
  rpc Echo(EchoRequest) returns (EchoResponse);

  // 服务端流式:服务端持续向客户端推送数据
  rpc GetNumbers(GetNumbersRequest) returns (stream Number);

  // 客户端流式:客户端持续向服务端发送数据,服务端返回一个结果
  rpc Sum(stream Number) returns (SumResponse);

  // 双向流式:双方都可以持续发送和接收数据
  rpc Chat(stream ChatMessage) returns (stream ChatMessage);
}

message EchoRequest  { string message = 1; }
message EchoResponse { string message = 1; int64 timestamp = 2; }
message GetNumbersRequest { int32 value = 1; int32 count = 2; }
message Number       { int32 value = 1; }
message SumResponse  { int32 total = 1; int32 count = 2; }
message ChatMessage  { string user = 1; string content = 2; int64 timestamp = 3; }

客户端:

class Client final : public GenericClient<sample::SampleService> {
public:
    using GenericClient::GenericClient;

    static Client make(const std::string &address) {
        return Client{sample::SampleService::NewStub(grpc::CreateChannel(address, grpc::InsecureChannelCredentials()))};
    }

    asyncio::task::Task<sample::EchoResponse>
    echo(
        sample::EchoRequest request,
        std::unique_ptr<grpc::ClientContext> context = std::make_unique<grpc::ClientContext>()
    ) {
        co_return co_await call(&sample::SampleService::Stub::async::Echo, std::move(context), std::move(request));
    }

    asyncio::task::Task<void>
    getNumbers(
        sample::GetNumbersRequest request,
        asyncio::Sender<sample::Number> sender,
        std::unique_ptr<grpc::ClientContext> context = std::make_unique<grpc::ClientContext>()
    ) {
        co_await call(
            &sample::SampleService::Stub::async::GetNumbers,
            std::move(context),
            std::move(request),
            std::move(sender)
        );
    }

    asyncio::task::Task<sample::SumResponse> sum(
        asyncio::Receiver<sample::Number> receiver,
        std::unique_ptr<grpc::ClientContext> context = std::make_unique<grpc::ClientContext>()
    ) {
        co_return co_await call(&sample::SampleService::Stub::async::Sum, std::move(context), std::move(receiver));
    }

    asyncio::task::Task<void>
    chat(
        asyncio::Receiver<sample::ChatMessage> receiver,
        asyncio::Sender<sample::ChatMessage> sender,
        std::unique_ptr<grpc::ClientContext> context = std::make_unique<grpc::ClientContext>()
    ) {
        co_return co_await call(
            &sample::SampleService::Stub::async::Chat,
            std::move(context),
            std::move(receiver),
            std::move(sender)
        );
    }
};

asyncio::task::Task<void> asyncMain(const int argc, char *argv[]) {
    auto client = Client::make("localhost:50051");

    co_await all(
        // Unary RPC
        asyncio::task::spawn([&]() -> asyncio::task::Task<void> {
            sample::EchoRequest req;
            req.set_message("Hello gRPC!");
            const auto resp = co_await client.echo(req);
            fmt::print("Echo: {}\n", resp.message());
        }),

        // 服务端流 + 客户端流,用 channel 串联
        asyncio::task::spawn([&]() -> asyncio::task::Task<void> {
            sample::GetNumbersRequest req;
            req.set_value(1);
            req.set_count(5);

            auto [sender, receiver] = asyncio::channel<sample::Number>();

            const auto result = co_await all(
                client.getNumbers(req, std::move(sender)),
                client.sum(std::move(receiver))
            );

            const auto &resp = std::get<sample::SumResponse>(result);
            fmt::print("Sum: {}, count: {}\n", resp.total(), resp.count());
        }),

        // 双向流
        asyncio::task::spawn([&]() -> asyncio::task::Task<void> {
            auto [inSender, inReceiver] = asyncio::channel<sample::ChatMessage>();
            auto [outSender, outReceiver] = asyncio::channel<sample::ChatMessage>();

            co_await all(
                client.chat(std::move(outReceiver), std::move(inSender)),
                asyncio::task::spawn([&]() -> asyncio::task::Task<void> {
                    sample::ChatMessage msg;
                    msg.set_content("Hello server!");
                    co_await asyncio::error::guard(outSender.send(std::move(msg)));
                    outSender.close();
                }),
                asyncio::task::spawn([&]() -> asyncio::task::Task<void> {
                    const auto msg = co_await asyncio::error::guard(inReceiver.receive());
                    fmt::print("Chat reply: {}\n", msg.content());
                })
            );
        })
    );
}

服务端:

class Server final : public GenericServer<sample::SampleService> {
public:
    using GenericServer::GenericServer;

    static Server make(const std::string &address) {
        auto service = std::make_unique<sample::SampleService::AsyncService>();

        grpc::ServerBuilder builder;

        builder.AddListeningPort(address, grpc::InsecureServerCredentials());
        builder.RegisterService(service.get());

        auto completionQueue = builder.AddCompletionQueue();
        auto server = builder.BuildAndStart();

        return {std::move(server), std::move(service), std::move(completionQueue)};
    }

private:
    // Unary:直接返回 Response ,错误自动转换为 gRPC 错误状态
    static asyncio::task::Task<sample::EchoResponse> echo(sample::EchoRequest request) {
        sample::EchoResponse response;
        response.set_message(request.message());
        response.set_timestamp(std::time(nullptr));
        co_return response;
    }

    // 服务端流:接受 Writer ,逐个写入
    static asyncio::task::Task<void>
    getNumbers(sample::GetNumbersRequest request, Writer<sample::Number> writer) {
        for (int i = 0; i < request.count(); ++i) {
            sample::Number number;
            number.set_value(request.value() + i);
            co_await writer.write(number);
        }
    }

    // 客户端流:接受 Reader ,读取并聚合
    static asyncio::task::Task<sample::SumResponse> sum(Reader<sample::Number> reader) {
        int total{0}, count{0};
        while (const auto number = co_await reader.read()) {
            total += number->value();
            ++count;
        }
        sample::SumResponse response;
        response.set_total(total);
        response.set_count(count);
        co_return response;
    }

    // 双向流:读一条,回一条
    static asyncio::task::Task<void> chat(Stream<sample::ChatMessage, sample::ChatMessage> stream) {
        while (const auto message = co_await stream.read()) {
            sample::ChatMessage response;
            response.set_user("Server");
            response.set_timestamp(std::time(nullptr));
            response.set_content(fmt::format("Echo: {}", message->content()));
            co_await stream.write(response);
        }
    }

    // 将方法指针和 handler 绑定,启动各 RPC 的监听循环
    asyncio::task::Task<void> dispatch() override {
        co_await all(
            handle(&sample::SampleService::AsyncService::RequestEcho, echo),
            handle(&sample::SampleService::AsyncService::RequestGetNumbers, getNumbers),
            handle(&sample::SampleService::AsyncService::RequestSum, sum),
            handle(&sample::SampleService::AsyncService::RequestChat, chat)
        );
    }
};

asyncio::task::Task<void> asyncMain(const int argc, char *argv[]) {
    auto server = Server::make("0.0.0.0:50051");
    auto signal = asyncio::Signal::make();

    co_await race(
        asyncio::task::spawn([&]() -> asyncio::task::Task<void> {
            asyncio::sync::Event event;

            co_await asyncio::task::Cancellable{
                all(
                    server.run(),
                    asyncio::task::spawn([&]() -> asyncio::task::Task<void> {
                        co_await asyncio::error::guard(event.wait());
                        co_await server.shutdown(); // 通知 gRPC 服务器关闭
                    })
                ),
                [&]() -> std::expected<void, std::error_code> {
                    event.set(); // 触发 shutdown 流程
                    return {};
                }
            };
        }),
        asyncio::task::spawn([&]() -> asyncio::task::Task<void> {
            co_await asyncio::error::guard(signal.on(SIGINT));
        })
    );
}

正常运行:

正常运行截图

连接失败:

连接失败截图

错误信息友好,包含原始的 gRPC 错误信息,以及协程调用栈。