BRpc 搭建简单 rpc 服务器

📚 系列:C++ 工程实践 第 09 篇 ⏱ 阅读约 12 分钟
📌 一句话总结

本文从一份 .proto 写起,跑通 brpc 的完整闭环:① 定义 EchoService 并起服务端;② 同步客户端一个 stub.Echo 拿结果;③ 异步客户端用 done 回调收结果;④ 同一个端口再用 HTTP 访问一次。

文章目录

    • [1. BRpc 是什么:一个端口、三种调用](#1. BRpc 是什么:一个端口、三种调用)
    • [2. 先定 proto:两个 message 一个 service,生成 Stub 和基类](#2. 先定 proto:两个 message 一个 service,生成 Stub 和基类)
    • [3. 实现 Service:done 是唯一需要你负责的参数](#3. 实现 Service:done 是唯一需要你负责的参数)
    • [4. 起服务端:AddService、Start、RunUntilAskedToQuit](#4. 起服务端:AddService、Start、RunUntilAskedToQuit)
    • [5. 同步客户端:第 4 个参数传 NULL](#5. 同步客户端:第 4 个参数传 NULL)
    • [6. 异步客户端:done 一响,回调里收尾](#6. 异步客户端:done 一响,回调里收尾)
    • [7. 一个端口两种协议:同一份 Echo,HTTP 也能调](#7. 一个端口两种协议:同一份 Echo,HTTP 也能调)
    • [8. 构建与踩坑:Makefile 五条链接参数 + 八个高频坑](#8. 构建与踩坑:Makefile 五条链接参数 + 八个高频坑)

1. BRpc 是什么:一个端口、三种调用

BRpc 是 C++ 的工业级 RPC 框架,本篇只用它四个角色:Server、Service、Channel、Controller。 RPC 的目标很朴素------让调用远程服务像调用本地函数,序列化、网络、超时重试都由框架吞掉。

先认识这六个名词,后面代码里出现什么都不会慌:

角色 是什么 谁创建
.proto 里的 message 请求/响应的数据结构 你写,protoc 生成 C++ 类
service 一组 RPC 方法的声明 你写,protoc 生成基类和 Stub
brpc::Server 服务端容器 你在 main 里创建
Service 子类 你实现的业务逻辑 你写类,AddService 注册
brpc::Channel 客户端到服务端的一条连接 客户端 Init
brpc::Controller 一次调用的上下文(超时、错误) 每次调用创建

它最实用的三个特性:一个端口跑 RPC + HTTP 、同步/异步/半同步都支持 、自带 /status 调试页。本文只讲原生用法,二次封装放到下一篇。

先把环境装好(源码编译,首次大约 5~10 分钟):

bash 复制代码
sudo apt-get install -y git g++ make libssl-dev libprotobuf-dev \
    libprotoc-dev protobuf-compiler libleveldb-dev
git clone https://github.com/apache/brpc.git && cd brpc && mkdir build && cd build
cmake -DCMAKE_INSTALL_PREFIX=/usr .. && make -j6 && sudo make install

装完 protoc、libbrpc、libprotobuf 都可用,下面四个文件放在同一目录即可编译。

上手成本要有心理准备:brpc 不是 header-only,它依赖 protobuf、gflags、leveldb、openssl 这一串库,第一次源码编译要等几分钟;但装好之后,同一份代码既能当 RPC 服务,又能当 HTTP 服务,后面加服务只需要新增 .proto 和实现类,不用再动网络层。

2. 先定 proto:两个 message 一个 service,生成 Stub 和基类

option cc_generic_services = true; 是 brpc 能生成 Service/Stub 的开关,漏了它后面全是编译错误。 新建 main.proto:

proto 复制代码
syntax = "proto3";

package example;

// 生成 C++ 服务基类,必须打开
option cc_generic_services = true;

message EchoRequest {
  string message = 1;
}

message EchoResponse {
  string message = 1;
}

// 定义一个 RPC 方法:Echo
service EchoService {
  rpc Echo(EchoRequest) returns (EchoResponse);
}

目录里一共四个文件,make 会先生成 main.pb.* 再编译:

text 复制代码
brpc-demo/
├── main.proto
├── brpc_server.cc
├── brpc_client.cc
├── brpc_client_async.cc
└── makefile

执行 protoc --cpp_out=. main.proto 后得到两类东西:

生成物 内容 谁用
main.pb.h / main.pb.cc 消息类 + EchoService 基类 + EchoService_Stub 服务端、客户端都要
EchoService(虚基类) 纯虚方法 Echo(...),服务端继承它 服务端
EchoService_Stub 带 Channel 的调用代理,客户端直接 .Echo(...) 客户端

⚠️ 生成物不要提交、不要手改

main.pb.cc / main.pb.h 由 .proto 每次生成,入库存一份只会制造冲突;改接口永远改 .proto,让 Makefile 重新生成。

3. 实现 Service:done 是唯一需要你负责的参数

四个参数里 request 只读、response 你填、done 必须 Run 一次------ClosureGuard 让它自动 Run。

cpp 复制代码
#include <brpc/server.h>

#include "main.pb.h"

namespace example {

class EchoServiceImpl : public EchoService {
public:
    void Echo(google::protobuf::RpcController* cntl_base,
              const EchoRequest* request,
              EchoResponse* response,
              google::protobuf::Closure* done) override {
        // 离开作用域自动 done->Run(),成功失败都不会漏
        brpc::ClosureGuard done_guard(done);

        // response 由框架创建,这里只负责填充
        response->set_message("echo: " + request->message());
    }
};

}  // namespace example
参数 谁负责 说明
cntl_base 框架 需要读超时/错误时 static_cast<brpc::Controller*>
request 框架(只读) 不要改它,也不要保存指针到回调外
response 你 框架已创建,填字段即可;required 没填会失败
done 你(Run 一次) ClosureGuard 兜底,异常路径也不会漏

还有一个容易被忽略的协议细节:proto3 的标量字段默认没有「是否设置」的语义,int32=0 和「没赋值」在序列化后无法区分。如果你的接口需要区分这两种情况,用 optional(protobuf 3.15+)或 wrapper 类型,别用 -1 这类魔法值兜底。

4. 起服务端:AddService、Start、RunUntilAskedToQuit

三行起服务:AddService 注册、Start 监听、RunUntilAskedToQuit 阻塞;SERVER_DOESNT_OWN_SERVICE 表示 service 对象生命周期你自己管。

cpp 复制代码
#include <gflags/gflags.h>
#include <brpc/server.h>

#include <iostream>

#include "main.pb.h"

DEFINE_int32(listen_port, 8000, "监听端口");
DEFINE_int32(thread_count, 3, "工作线程数");

int main(int argc, char* argv[]) {
    google::ParseCommandLineFlags(&argc, &argv, true);

    brpc::Server server;
    example::EchoServiceImpl svc;
    if (server.AddService(&svc, brpc::SERVER_DOESNT_OWN_SERVICE) != 0) {
        std::cerr << "AddService failed" << std::endl;
        return -1;
    }

    brpc::ServerOptions options;
    options.num_threads = FLAGS_thread_count;
    if (server.Start(FLAGS_listen_port, &options) != 0) {
        std::cerr << "Start failed" << std::endl;
        return -1;
    }

    server.RunUntilAskedToQuit();   // 阻塞到 Ctrl+C
    return 0;
}
ServerOptions / 调用 说明
num_threads 默认等于 CPU 核数,压测时按业务调
idle_timeout_sec 空闲连接多久断开,-1 表示不断
SERVER_DOESNT_OWN_SERVICE 服务对象在栈上,服务器不要帮忙 delete;反之会二次释放

启动成功的日志大致长这样(前两行是 brpc 自己的日志):

text 复制代码
I... brpc/server.cpp] Server[EchoServiceImpl] is serving on port=8000.
I... brpc/server.cpp] Check out http://127.0.0.1:8000 in web browser.

5. 同步客户端:第 4 个参数传 NULL

stub.Echo(&cntl, &req, &rsp, nullptr) 的 done 为 nullptr 就是同步:阻塞到响应或错误。

cpp 复制代码
#include <brpc/channel.h>

#include <iostream>

#include "main.pb.h"

int main() {
    brpc::Channel channel;
    brpc::ChannelOptions options;
    options.protocol = "baidu_std";   // 默认协议
    options.timeout_ms = 500;
    options.max_retry = 3;
    if (channel.Init("127.0.0.1:8000", &options) != 0) {
        std::cerr << "channel init failed" << std::endl;
        return -1;
    }

    example::EchoService_Stub stub(&channel);

    example::EchoRequest req;
    example::EchoResponse rsp;
    brpc::Controller cntl;
    req.set_message("hello brpc");

    // done = nullptr:同步阻塞
    stub.Echo(&cntl, &req, &rsp, nullptr);

    if (cntl.Failed()) {
        std::cerr << "rpc failed: " << cntl.ErrorText() << std::endl;
        return -1;
    }
    std::cout << rsp.message() << std::endl;
    return 0;
}

运行后输出:

text 复制代码
echo: hello brpc

⚠️ 超时别用默认值兜底

timeout_ms 默认 500ms,压测或跨机房很容易误判失败;按接口的 P99 再加余量,别一个值走天下。

6. 异步客户端:done 一响,回调里收尾

把 done 换成 NewCallback,stub.Echo 立刻返回;response/controller 必须 new 在堆上,在回调里释放。

cpp 复制代码
#include <brpc/channel.h>

#include <atomic>
#include <chrono>
#include <iostream>
#include <memory>
#include <thread>

#include "main.pb.h"

static std::atomic<bool> g_done{false};

static void OnEchoDone(example::EchoResponse* rsp, brpc::Controller* cntl) {
    // 回调结束时自动释放,避免泄漏
    std::unique_ptr<example::EchoResponse> rsp_guard(rsp);
    std::unique_ptr<brpc::Controller> cntl_guard(cntl);

    if (cntl->Failed()) {
        std::cerr << "rpc failed: " << cntl->ErrorText() << std::endl;
        return;
    }
    std::cout << rsp->message() << std::endl;
    g_done = true;          // 通知主线程:回调结束
}

int main() {
    brpc::Channel channel;
    brpc::ChannelOptions options;
    options.timeout_ms = 500;
    if (channel.Init("127.0.0.1:8000", &options) != 0) {
        std::cerr << "channel init failed" << std::endl;
        return -1;
    }

    example::EchoService_Stub stub(&channel);

    example::EchoRequest req;          // 请求可以栈上
    req.set_message("hello async");

    auto* rsp = new example::EchoResponse;
    auto* cntl = new brpc::Controller;

    // 回调由框架在响应到达时执行一次
    stub.Echo(cntl, &req, rsp, brpc::NewCallback(OnEchoDone, rsp, cntl));

    // 等回调真正结束再退出,避免固定 sleep 在慢网络下提前退出
    while (!g_done) {
        std::this_thread::sleep_for(std::chrono::milliseconds(10));
    }
    return 0;
}

服务端 客户端 服务端 客户端 #mermaid-svg-vEFPl6QehjKh0ogW{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-vEFPl6QehjKh0ogW .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-vEFPl6QehjKh0ogW .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-vEFPl6QehjKh0ogW .error-icon{fill:#552222;}#mermaid-svg-vEFPl6QehjKh0ogW .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-vEFPl6QehjKh0ogW .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-vEFPl6QehjKh0ogW .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-vEFPl6QehjKh0ogW .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-vEFPl6QehjKh0ogW .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-vEFPl6QehjKh0ogW .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-vEFPl6QehjKh0ogW .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-vEFPl6QehjKh0ogW .marker{fill:#333333;stroke:#333333;}#mermaid-svg-vEFPl6QehjKh0ogW .marker.cross{stroke:#333333;}#mermaid-svg-vEFPl6QehjKh0ogW svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-vEFPl6QehjKh0ogW p{margin:0;}#mermaid-svg-vEFPl6QehjKh0ogW .actor{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-vEFPl6QehjKh0ogW text.actor>tspan{fill:black;stroke:none;}#mermaid-svg-vEFPl6QehjKh0ogW .actor-line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);}#mermaid-svg-vEFPl6QehjKh0ogW .innerArc{stroke-width:1.5;stroke-dasharray:none;}#mermaid-svg-vEFPl6QehjKh0ogW .messageLine0{stroke-width:1.5;stroke-dasharray:none;stroke:#333;}#mermaid-svg-vEFPl6QehjKh0ogW .messageLine1{stroke-width:1.5;stroke-dasharray:2,2;stroke:#333;}#mermaid-svg-vEFPl6QehjKh0ogW #arrowhead path{fill:#333;stroke:#333;}#mermaid-svg-vEFPl6QehjKh0ogW .sequenceNumber{fill:white;}#mermaid-svg-vEFPl6QehjKh0ogW #sequencenumber{fill:#333;}#mermaid-svg-vEFPl6QehjKh0ogW #crosshead path{fill:#333;stroke:#333;}#mermaid-svg-vEFPl6QehjKh0ogW .messageText{fill:#333;stroke:none;}#mermaid-svg-vEFPl6QehjKh0ogW .labelBox{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-vEFPl6QehjKh0ogW .labelText,#mermaid-svg-vEFPl6QehjKh0ogW .labelText>tspan{fill:black;stroke:none;}#mermaid-svg-vEFPl6QehjKh0ogW .loopText,#mermaid-svg-vEFPl6QehjKh0ogW .loopText>tspan{fill:black;stroke:none;}#mermaid-svg-vEFPl6QehjKh0ogW .loopLine{stroke-width:2px;stroke-dasharray:2,2;stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);}#mermaid-svg-vEFPl6QehjKh0ogW .note{stroke:#aaaa33;fill:#fff5ad;}#mermaid-svg-vEFPl6QehjKh0ogW .noteText,#mermaid-svg-vEFPl6QehjKh0ogW .noteText>tspan{fill:black;stroke:none;}#mermaid-svg-vEFPl6QehjKh0ogW .activation0{fill:#f4f4f4;stroke:#666;}#mermaid-svg-vEFPl6QehjKh0ogW .activation1{fill:#f4f4f4;stroke:#666;}#mermaid-svg-vEFPl6QehjKh0ogW .activation2{fill:#f4f4f4;stroke:#666;}#mermaid-svg-vEFPl6QehjKh0ogW .actorPopupMenu{position:absolute;}#mermaid-svg-vEFPl6QehjKh0ogW .actorPopupMenuPanel{position:absolute;fill:#ECECFF;box-shadow:0px 8px 16px 0px rgba(0,0,0,0.2);filter:drop-shadow(3px 5px 2px rgb(0 0 0 / 0.4));}#mermaid-svg-vEFPl6QehjKh0ogW .actor-man line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-vEFPl6QehjKh0ogW .actor-man circle,#mermaid-svg-vEFPl6QehjKh0ogW line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;stroke-width:2px;}#mermaid-svg-vEFPl6QehjKh0ogW :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 阻塞等待 直到拿到结果 stub.Echo 同步请求 响应
服务端 客户端 服务端 客户端 #mermaid-svg-jSHn9oUomscA2FAM{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-jSHn9oUomscA2FAM .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-jSHn9oUomscA2FAM .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-jSHn9oUomscA2FAM .error-icon{fill:#552222;}#mermaid-svg-jSHn9oUomscA2FAM .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-jSHn9oUomscA2FAM .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-jSHn9oUomscA2FAM .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-jSHn9oUomscA2FAM .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-jSHn9oUomscA2FAM .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-jSHn9oUomscA2FAM .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-jSHn9oUomscA2FAM .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-jSHn9oUomscA2FAM .marker{fill:#333333;stroke:#333333;}#mermaid-svg-jSHn9oUomscA2FAM .marker.cross{stroke:#333333;}#mermaid-svg-jSHn9oUomscA2FAM svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-jSHn9oUomscA2FAM p{margin:0;}#mermaid-svg-jSHn9oUomscA2FAM .actor{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-jSHn9oUomscA2FAM text.actor>tspan{fill:black;stroke:none;}#mermaid-svg-jSHn9oUomscA2FAM .actor-line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);}#mermaid-svg-jSHn9oUomscA2FAM .innerArc{stroke-width:1.5;stroke-dasharray:none;}#mermaid-svg-jSHn9oUomscA2FAM .messageLine0{stroke-width:1.5;stroke-dasharray:none;stroke:#333;}#mermaid-svg-jSHn9oUomscA2FAM .messageLine1{stroke-width:1.5;stroke-dasharray:2,2;stroke:#333;}#mermaid-svg-jSHn9oUomscA2FAM #arrowhead path{fill:#333;stroke:#333;}#mermaid-svg-jSHn9oUomscA2FAM .sequenceNumber{fill:white;}#mermaid-svg-jSHn9oUomscA2FAM #sequencenumber{fill:#333;}#mermaid-svg-jSHn9oUomscA2FAM #crosshead path{fill:#333;stroke:#333;}#mermaid-svg-jSHn9oUomscA2FAM .messageText{fill:#333;stroke:none;}#mermaid-svg-jSHn9oUomscA2FAM .labelBox{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-jSHn9oUomscA2FAM .labelText,#mermaid-svg-jSHn9oUomscA2FAM .labelText>tspan{fill:black;stroke:none;}#mermaid-svg-jSHn9oUomscA2FAM .loopText,#mermaid-svg-jSHn9oUomscA2FAM .loopText>tspan{fill:black;stroke:none;}#mermaid-svg-jSHn9oUomscA2FAM .loopLine{stroke-width:2px;stroke-dasharray:2,2;stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);}#mermaid-svg-jSHn9oUomscA2FAM .note{stroke:#aaaa33;fill:#fff5ad;}#mermaid-svg-jSHn9oUomscA2FAM .noteText,#mermaid-svg-jSHn9oUomscA2FAM .noteText>tspan{fill:black;stroke:none;}#mermaid-svg-jSHn9oUomscA2FAM .activation0{fill:#f4f4f4;stroke:#666;}#mermaid-svg-jSHn9oUomscA2FAM .activation1{fill:#f4f4f4;stroke:#666;}#mermaid-svg-jSHn9oUomscA2FAM .activation2{fill:#f4f4f4;stroke:#666;}#mermaid-svg-jSHn9oUomscA2FAM .actorPopupMenu{position:absolute;}#mermaid-svg-jSHn9oUomscA2FAM .actorPopupMenuPanel{position:absolute;fill:#ECECFF;box-shadow:0px 8px 16px 0px rgba(0,0,0,0.2);filter:drop-shadow(3px 5px 2px rgb(0 0 0 / 0.4));}#mermaid-svg-jSHn9oUomscA2FAM .actor-man line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-jSHn9oUomscA2FAM .actor-man circle,#mermaid-svg-jSHn9oUomscA2FAM line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;stroke-width:2px;}#mermaid-svg-jSHn9oUomscA2FAM :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 立即返回 主线程继续 触发 done 回调 收尾 stub.Echo 带 done 回调 响应到达

对象 放哪 谁释放
EchoRequest 栈或堆都行 调用返回后框架不再用它
EchoResponse 堆 回调里 unique_ptr 释放
brpc::Controller 堆 回调里 unique_ptr 释放
done(Closure) 框架创建 Run() 后自己 delete,不用管

为什么请求可以放栈上、响应必须放堆上?因为请求在 stub.Echo 返回时就已经被框架序列化发走,之后不再访问;而响应要等网络回来才被写入,回调执行完才安全释放。把这条记成一句话:调用返回后仍会被框架访问的对象,必须活到回调结束。

7. 一个端口两种协议:同一份 Echo,HTTP 也能调

brpc 的 Server 天然双协议:RPC 走默认协议,HTTP 走 POST /服务全名/方法名,调的是同一个方法。 服务起来后,另开一个终端:

bash 复制代码
curl -X POST http://127.0.0.1:8000/example.EchoService/Echo \
     -H 'Content-Type: application/json' \
     -d '{"message":"from http"}'
text 复制代码
{"message":"echo: from http"}
调用方式 走什么 适合谁
stub.Echo(...) brpc 默认协议 内部 C++ 服务之间
curl / 浏览器 HTTP + JSON 脚本、调试、其他语言
/status 页面 HTTP 内置页 排查连接数、QPS、延迟分位

#mermaid-svg-cJAtwRdEQFXgIwHM{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-cJAtwRdEQFXgIwHM .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-cJAtwRdEQFXgIwHM .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-cJAtwRdEQFXgIwHM .error-icon{fill:#552222;}#mermaid-svg-cJAtwRdEQFXgIwHM .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-cJAtwRdEQFXgIwHM .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-cJAtwRdEQFXgIwHM .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-cJAtwRdEQFXgIwHM .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-cJAtwRdEQFXgIwHM .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-cJAtwRdEQFXgIwHM .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-cJAtwRdEQFXgIwHM .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-cJAtwRdEQFXgIwHM .marker{fill:#333333;stroke:#333333;}#mermaid-svg-cJAtwRdEQFXgIwHM .marker.cross{stroke:#333333;}#mermaid-svg-cJAtwRdEQFXgIwHM svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-cJAtwRdEQFXgIwHM p{margin:0;}#mermaid-svg-cJAtwRdEQFXgIwHM .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-cJAtwRdEQFXgIwHM .cluster-label text{fill:#333;}#mermaid-svg-cJAtwRdEQFXgIwHM .cluster-label span{color:#333;}#mermaid-svg-cJAtwRdEQFXgIwHM .cluster-label span p{background-color:transparent;}#mermaid-svg-cJAtwRdEQFXgIwHM .label text,#mermaid-svg-cJAtwRdEQFXgIwHM span{fill:#333;color:#333;}#mermaid-svg-cJAtwRdEQFXgIwHM .node rect,#mermaid-svg-cJAtwRdEQFXgIwHM .node circle,#mermaid-svg-cJAtwRdEQFXgIwHM .node ellipse,#mermaid-svg-cJAtwRdEQFXgIwHM .node polygon,#mermaid-svg-cJAtwRdEQFXgIwHM .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-cJAtwRdEQFXgIwHM .rough-node .label text,#mermaid-svg-cJAtwRdEQFXgIwHM .node .label text,#mermaid-svg-cJAtwRdEQFXgIwHM .image-shape .label,#mermaid-svg-cJAtwRdEQFXgIwHM .icon-shape .label{text-anchor:middle;}#mermaid-svg-cJAtwRdEQFXgIwHM .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-cJAtwRdEQFXgIwHM .rough-node .label,#mermaid-svg-cJAtwRdEQFXgIwHM .node .label,#mermaid-svg-cJAtwRdEQFXgIwHM .image-shape .label,#mermaid-svg-cJAtwRdEQFXgIwHM .icon-shape .label{text-align:center;}#mermaid-svg-cJAtwRdEQFXgIwHM .node.clickable{cursor:pointer;}#mermaid-svg-cJAtwRdEQFXgIwHM .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-cJAtwRdEQFXgIwHM .arrowheadPath{fill:#333333;}#mermaid-svg-cJAtwRdEQFXgIwHM .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-cJAtwRdEQFXgIwHM .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-cJAtwRdEQFXgIwHM .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-cJAtwRdEQFXgIwHM .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-cJAtwRdEQFXgIwHM .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-cJAtwRdEQFXgIwHM .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-cJAtwRdEQFXgIwHM .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-cJAtwRdEQFXgIwHM .cluster text{fill:#333;}#mermaid-svg-cJAtwRdEQFXgIwHM .cluster span{color:#333;}#mermaid-svg-cJAtwRdEQFXgIwHM div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-cJAtwRdEQFXgIwHM .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-cJAtwRdEQFXgIwHM rect.text{fill:none;stroke-width:0;}#mermaid-svg-cJAtwRdEQFXgIwHM .icon-shape,#mermaid-svg-cJAtwRdEQFXgIwHM .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-cJAtwRdEQFXgIwHM .icon-shape p,#mermaid-svg-cJAtwRdEQFXgIwHM .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-cJAtwRdEQFXgIwHM .icon-shape .label rect,#mermaid-svg-cJAtwRdEQFXgIwHM .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-cJAtwRdEQFXgIwHM .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-cJAtwRdEQFXgIwHM .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-cJAtwRdEQFXgIwHM :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} RPC 客户端 stub
8000 端口
curl 或浏览器
EchoService.Echo

路径写成 /example.EchoService/Echo(全名)或 /EchoService/Echo(短名)都能命中------brpc 按首段是否带 . 来判断用哪种查找方式。

这就是「一个端口多协议」最直观的收益:同一份业务逻辑,不用为 HTTP 再写一遍 Controller。

对调试也友好:联调时不用先写一个客户端,curl 一条命令就能验证参数和返回;线上排查时打开 /status,能看到每个方法的 QPS、延迟分位和错误码分布,比在业务代码里到处埋点更省事。

8. 构建与踩坑:Makefile 五条链接参数 + 八个高频坑

链接参数固定 -lbrpc -lleveldb -lprotobuf -lgflags -lssl -lcrypto;坑集中在 proto 开关、端口、done 生命周期、超时。

makefile 复制代码
CXX      := g++
CXXFLAGS := -std=c++17 -O2
LDLIBS   := -lbrpc -lleveldb -lprotobuf -lgflags -lssl -lcrypto -lpthread

all: brpc_server brpc_client brpc_client_async

main.pb.cc main.pb.h: main.proto
	protoc --cpp_out=. $<

brpc_server: brpc_server.cc main.pb.cc
	$(CXX) $(CXXFLAGS) -o $@ $^ $(LDLIBS)

brpc_client: brpc_client.cc main.pb.cc
	$(CXX) $(CXXFLAGS) -o $@ $^ $(LDLIBS)

brpc_client_async: brpc_client_async.cc main.pb.cc
	$(CXX) $(CXXFLAGS) -o $@ $^ $(LDLIBS)

clean:
	rm -f brpc_server brpc_client brpc_client_async main.pb.cc main.pb.h

.PHONY: all clean
坑 现象 修法
忘了 cc_generic_services 没有 EchoService 类 打开该选项后重新 protoc
少链接 brpc 依赖 undefined reference to brpc::... 补 -lbrpc -lleveldb -lprotobuf -lgflags -lssl -lcrypto
端口被占 Start failed 换端口,或 ss -lntp 查占用
异步用栈上 response 偶发崩溃 response/controller 必须堆上且活到回调
回调忘了释放 内存缓慢上涨 unique_ptr 守卫 / ClosureGuard
同步调用卡住 服务端没起、地址/端口不对 先跑 ./brpc_server,核对 channel.Init 的地址与端口
brpc 与 protobuf 版本不匹配 编译/链接诡异报错 用配套版本,或按 brpc README 安装
生成物提交进仓库 每次编译冲突 .gitignore 掉 *.pb.cc / *.pb.h

如果只记一句话:proto 是契约,框架是管道,业务只负责填 response。契约稳定了,后面换协议、加节点、做灰度,都是管道层的事。
📝 小结

① 接口先行:`.proto` 定契约,`cc_generic_services` 生成 Stub。

② 同步/异步只差最后一个参数:`nullptr` 阻塞,`NewCallback` 回调。

③ 一个端口两种协议:RPC 给服务调,HTTP 给脚本和浏览器调。
📦 项目源码

本文示例所在的 C++ 微服务脚手架(点播系统):https://github.com/syagain-zsx/cpp-microservice-scaffold-vod-system
🔜 下一篇

《BRpc 二次封装:Channel 管理、服务管理与回调工厂》------业务代码里不该到处 new Channel。关注专栏不迷路。
🔗 参考

1 brpc 官方文档:https://brpc.apache.org/

2 brpc GitHub:https://github.com/apache/brpc

3 Protocol Buffers:https://protobuf.dev/

相关推荐
云飞云共享云桌面1 小时前
一台SolidWorks设计服务器多人共享落地方案和配置
运维·服务器·3d·电脑·制造
Funlang1 小时前
HTTP 服务端,能写业务了
网络·网络协议·http
Pointer Pursuit1 小时前
进程优先级
linux·运维·服务器
旖旎夜光1 小时前
LeetCode 215: 数组中的第K个最大元素 (快速排序) —— 题解
c++·学习·算法·leetcode·力控
情 九11 小时前
Dify HTTP 请求节点实战:发送 JSON、解析响应与处理异常
网络协议·http·json·dify
Cry丶11 小时前
ubuntu安装vsftpd
服务器·网络·ubuntu
木子欢儿13 小时前
openconnect Linux客户端安装 以及开机自启和登录
linux·运维·服务器
..Dauntless..13 小时前
【Linux】文件系统(一)
linux·运维·服务器
·present·14 小时前
CSAPP(lecture 4_01)
c语言·c++