demo地址
brpc
brpc是百度开源的一个rpc框架,源码地址
bazel
bazel是谷歌开源的一个代码构建工具,可以编译c++\java\go等主流语言,使用文档地址
搭建过程
虽然brpc的使用文档的指引是使用cmake或者make来进行编译,然后跑example的,但是brpc的源码也是可以通过bazel进行编译的,因为brpc的源码里是有Build文件的,因此我们使用bazel来继承brpc进行开发。
本机环境
本文使用vscode remote ssh 在ubuntu20进行开发
root@5669353fc2c1:~# cat /etc/issue
Ubuntu 20.04.3 LTS \n \l
复制代码
安装bazel
经过作者踩坑,最好安装Bazel 1.0.0,不然构建的时候可能会因为protobuf的版本导致bazel编译失败,其他版本不确定兼容性。 不使用本文安装bazel的办法可以参考这个使用文档的其他方法
首先打开bazel的realese的页面,找到1.0.0的版本,然后copy一波链接,然后在bash上操作一下
apt-get update -y
apt-get install wget -y
apt-get install g++ unzip zip openssl
#这个是copy的链接,如果访问很慢,建议去gitee找一下clone的仓库
wget https://github.com/bazelbuild/bazel/releases/download/1.0.0/bazel-1.0.0-installer-linux-x86_64.sh
sudo chmod +x bazel-1.0.0-installer-linux-x86_64.sh
./bazel-1.0.0-installer-linux-x86_64.sh
复制代码
安装完以后看一下bazel的版本,确定为1.0.0
root@5669353fc2c1:~# bazel --version
bazel 1.0.0
复制代码
在ubuntu上创建一个新的目录进行环境构建
root@5669353fc2c1:~# mkdir juejin-example
#然后vscode打开这个新的目录,如果不用vscode可忽略code命令
root@5669353fc2c1:~# code juejin-example/
复制代码
使用vscode的话可以先安装一下c++插件和bazel的官方插件,比较方便去书写bazel的BUILD和WORKSPACE文件 WORKSPACE文件: 每个项目都必须有一个WORKSPACE来控制整个项目的依赖 BUILD文件: 指定需要编译的lib库和二进制文件
我们先来创建WORKSPACE文件
root@5669353fc2c1:~/juejin-example# code WORKSPACE
复制代码
然后在WORKSPACE文件中使用git_repository规则拉到brpc的源码,便于后续编译,因为国内网速确实不太行,所以换了gitee的brpc源码地址来下载,同时也需要把brpc依赖的gflags、protobuf、glog、leveldb、googletest、openssl、zlib的依赖拉下来
load("@bazel_tools//tools/build_defs/repo:git.bzl", "git_repository")
load("@bazel_tools//tools/build_defs/repo:http.bzl", "http_archive")
http_archive(
name = "com_google_protobuf",
strip_prefix = "protobuf-3.6.1.3",
sha256 = "9510dd2afc29e7245e9e884336f848c8a6600a14ae726adb6befdb4f786f0be2",
type = "zip",
url = "https://github.com/protocolbuffers/protobuf/archive/v3.6.1.3.zip",
)
http_archive(
name = "com_github_gflags_gflags",
strip_prefix = "gflags-46f73f88b18aee341538c0dfc22b1710a6abedef",
url = "https://github.com/gflags/gflags/archive/46f73f88b18aee341538c0dfc22b1710a6abedef.tar.gz",
)
bind(
name = "gflags",
actual = "@com_github_gflags_gflags//:gflags",
)
http_archive(
name = "com_github_google_leveldb",
build_file = "//:depBUILD/leveldb.BUILD",
strip_prefix = "leveldb-a53934a3ae1244679f812d998a4f16f2c7f309a6",
url = "https://github.com/google/leveldb/archive/a53934a3ae1244679f812d998a4f16f2c7f309a6.tar.gz"
)
http_archive(
name = "com_github_google_glog",
build_file = "depBUILD/glog.BUILD",
strip_prefix = "glog-a6a166db069520dbbd653c97c2e5b12e08a8bb26",
url = "https://github.com/google/glog/archive/a6a166db069520dbbd653c97c2e5b12e08a8bb26.tar.gz"
)
http_archive(
name = "com_google_googletest",
strip_prefix = "googletest-0fe96607d85cf3a25ac40da369db62bbee2939a5",
url = "https://github.com/google/googletest/archive/0fe96607d85cf3a25ac40da369db62bbee2939a5.tar.gz",
)
new_local_repository(
name = "openssl",
path = "/usr",
build_file = "depBUILD/openssl.BUILD",
)
new_local_repository(
name = "openssl_macos",
build_file = "depBUILD/openssl.BUILD",
path = "/usr/local/opt/openssl",
)
bind(
name = "ssl",
actual = "@openssl//:ssl"
)
bind(
name = "ssl_macos",
actual = "@openssl_macos//:ssl"
)
new_local_repository(
name = "zlib",
build_file = "depBUILD/zlib.BUILD",
path = "/usr",
)
git_repository(
name = "brpc",
remote = "https://gitee.com/baidu/BRPC.git",
branch = "master"
)
复制代码
注意到有些依赖的build_file的参数是depBUILD/XXXX.BUILD,是因为有些拉下来的仓库是没有BUILD文件的,需要自己提供一个BUILD文件给到bazel去编译,当然这个不需要我们去编写,Brpc的源码就有这几个依赖的BUILD文件,所以大伙自己拷贝一下几个没有的BUILD文件到depBUILD目录下就可以。还有一点是openssl这个也需要安装在本地,如果大家是按照我的命令操作的话是已经安装了。
配置完WORKSPACE文件就已经把Brpc的依赖和Brpc配置好了
接下来配置BUILD文件
# Licensed to the Apache Software Foundation (ASF) under one or more
# contributor license agreements. See the NOTICE file distributed with
# this work for additional information regarding copyright ownership.
# The ASF licenses this file to You under the Apache License, Version 2.0
# (the "License"); you may not use this file except in compliance with
# the License. You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
# 运行指定的命令
COPTS = [
"-D__STDC_FORMAT_MACROS",
"-DBTHREAD_USE_FAST_PTHREAD_MUTEX",
"-D__const__=",
"-D_GNU_SOURCE",
"-DUSE_SYMBOLIZE",
"-DNO_TCMALLOC",
"-D__STDC_LIMIT_MACROS",
"-D__STDC_CONSTANT_MACROS",
"-fPIC",
"-Wno-unused-parameter",
"-fno-omit-frame-pointer",
"-DGFLAGS_NS=google",
] + select({
"//:with_glog": ["-DBRPC_WITH_GLOG=1"],
"//conditions:default": ["-DBRPC_WITH_GLOG=0"],
})
# pb的编译
proto_library(
name = "c++_http_proto",
srcs = [
"proto/http.proto",
],
)
# 将pb编译声明为一个依赖dep
cc_proto_library(
name = "cc_c++_http_proto",
deps = [
":c++_http_proto",
],
)
# 二进制文件的依赖依赖于上面声明的dep
cc_binary(
name = "http_c++_server",
srcs = [
"http_c++_server.cpp",
],
deps = [
":cc_c++_http_proto",
"//:brpc",
],
copts = COPTS,
)
复制代码
BUILD文件十分简单,就是一个c++的http的服务(http_c++_server),这个服务依赖于一个proto文件,我们把这个依赖关系书写出来,bazel就会帮我们处理这个链接的过程,不需要我们去操心。 BUILD文件里声明了一个http.proto文件 我们创建http.proto文件
root@5669353fc2c1:~/juejin-example# mkdir proto
root@5669353fc2c1:~/juejin-example# code proto/http.proto
复制代码
在http.proto里拷贝一下brpc的example下的http_c++的http.proto文件
// http.proto
syntax="proto2";
package example;
option cc_generic_services = true;
message HttpRequest {};
message HttpResponse {};
service HttpService {
rpc Echo(HttpRequest) returns (HttpResponse);
rpc EchoProtobuf(HttpRequest) returns (HttpResponse);
};
service FileService {
rpc default_method(HttpRequest) returns (HttpResponse);
};
service QueueService {
rpc start(HttpRequest) returns (HttpResponse);
rpc stop(HttpRequest) returns (HttpResponse);
rpc getstats(HttpRequest) returns (HttpResponse);
};
复制代码
这个proto比较简单,因为rest的请求,Message不需要任何的数据,query和body都是从brpcController里面拿到的,不需要从proto中解析出来。
根据BUILD文件,还需要一个http_c++_server.cpp文件 在项目根目录下创建http_c++_server.cpp (与WORKSPACE同级) 然后在brpc的exmaple/http_c++下复制文件http_server.cpp的内容到http_c++_server.cpp
// Licensed to the Apache Software Foundation (ASF) under one
// or more contributor license agreements. See the NOTICE file
// distributed with this work for additional information
// regarding copyright ownership. The ASF licenses this file
// to you under the Apache License, Version 2.0 (the
// "License"); you may not use this file except in compliance
// with the License. You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing,
// software distributed under the License is distributed on an
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
// KIND, either express or implied. See the License for the
// specific language governing permissions and limitations
// under the License.
// A server to receive HttpRequest and send back HttpResponse.
#include <gflags/gflags.h>
#include <butil/logging.h>
#include <brpc/server.h>
#include <brpc/restful.h>
#include "http.pb.h"
DEFINE_int32(port, 8010, "TCP Port of this server");
DEFINE_int32(idle_timeout_s, -1, "Connection will be closed if there is no "
"read/write operations during the last `idle_timeout_s'");
DEFINE_int32(logoff_ms, 2000, "Maximum duration of server's LOGOFF state "
"(waiting for client to close connection before server stops)");
DEFINE_string(certificate, "cert.pem", "Certificate file path to enable SSL");
DEFINE_string(private_key, "key.pem", "Private key file path to enable SSL");
DEFINE_string(ciphers, "", "Cipher suite used for SSL connections");
namespace example {
// Service with static path.
class HttpServiceImpl : public HttpService {
public:
HttpServiceImpl() {};
virtual ~HttpServiceImpl() {};
void Echo(google::protobuf::RpcController* cntl_base,
const HttpRequest*,
HttpResponse*,
google::protobuf::Closure* done) {
// This object helps you to call done->Run() in RAII style. If you need
// to process the request asynchronously, pass done_guard.release().
brpc::ClosureGuard done_guard(done);
brpc::Controller* cntl =
static_cast<brpc::Controller*>(cntl_base);
// Fill response.
cntl->http_response().set_content_type("text/plain");
butil::IOBufBuilder os;
os << "queries:";
for (brpc::URI::QueryIterator it = cntl->http_request().uri().QueryBegin();
it != cntl->http_request().uri().QueryEnd(); ++it) {
os << ' ' << it->first << '=' << it->second;
}
os << "\nbody: " << cntl->request_attachment() << '\n';
os.move_to(cntl->response_attachment());
}
};
// Service with dynamic path.
class FileServiceImpl : public FileService {
public:
FileServiceImpl() {};
virtual ~FileServiceImpl() {};
struct Args {
butil::intrusive_ptr<brpc::ProgressiveAttachment> pa;
};
static void* SendLargeFile(void* raw_args) {
std::unique_ptr<Args> args(static_cast<Args*>(raw_args));
if (args->pa == NULL) {
LOG(ERROR) << "ProgressiveAttachment is NULL";
return NULL;
}
for (int i = 0; i < 100; ++i) {
char buf[16];
int len = snprintf(buf, sizeof(buf), "part_%d ", i);
args->pa->Write(buf, len);
// sleep a while to send another part.
bthread_usleep(10000);
}
return NULL;
}
void default_method(google::protobuf::RpcController* cntl_base,
const HttpRequest*,
HttpResponse*,
google::protobuf::Closure* done) {
brpc::ClosureGuard done_guard(done);
brpc::Controller* cntl =
static_cast<brpc::Controller*>(cntl_base);
const std::string& filename = cntl->http_request().unresolved_path();
if (filename == "largefile") {
// Send the "largefile" with ProgressiveAttachment.
std::unique_ptr<Args> args(new Args);
args->pa = cntl->CreateProgressiveAttachment();
bthread_t th;
bthread_start_background(&th, NULL, SendLargeFile, args.release());
} else {
cntl->response_attachment().append("Getting file: ");
cntl->response_attachment().append(filename);
}
}
};
// Restful service. (The service implementation is exactly same with regular
// services, the difference is that you need to pass a `restful_mappings'
// when adding the service into server).
class QueueServiceImpl : public example::QueueService {
public:
QueueServiceImpl() {};
virtual ~QueueServiceImpl() {};
void start(google::protobuf::RpcController* cntl_base,
const HttpRequest*,
HttpResponse*,
google::protobuf::Closure* done) {
brpc::ClosureGuard done_guard(done);
brpc::Controller* cntl =
static_cast<brpc::Controller*>(cntl_base);
cntl->response_attachment().append("queue started");
}
void stop(google::protobuf::RpcController* cntl_base,
const HttpRequest*,
HttpResponse*,
google::protobuf::Closure* done) {
brpc::ClosureGuard done_guard(done);
brpc::Controller* cntl =
static_cast<brpc::Controller*>(cntl_base);
cntl->response_attachment().append("queue stopped");
}
void getstats(google::protobuf::RpcController* cntl_base,
const HttpRequest*,
HttpResponse*,
google::protobuf::Closure* done) {
brpc::ClosureGuard done_guard(done);
brpc::Controller* cntl =
static_cast<brpc::Controller*>(cntl_base);
const std::string& unresolved_path = cntl->http_request().unresolved_path();
if (unresolved_path.empty()) {
cntl->response_attachment().append("Require a name after /stats");
} else {
cntl->response_attachment().append("Get stats: ");
cntl->response_attachment().append(unresolved_path);
}
}
};
} // namespace example
int main(int argc, char* argv[]) {
// Parse gflags. We recommend you to use gflags as well.
GFLAGS_NS::ParseCommandLineFlags(&argc, &argv, true);
// Generally you only need one Server.
brpc::Server server;
example::HttpServiceImpl http_svc;
example::FileServiceImpl file_svc;
example::QueueServiceImpl queue_svc;
// Add services into server. Notice the second parameter, because the
// service is put on stack, we don't want server to delete it, otherwise
// use brpc::SERVER_OWNS_SERVICE.
if (server.AddService(&http_svc,
brpc::SERVER_DOESNT_OWN_SERVICE) != 0) {
LOG(ERROR) << "Fail to add http_svc";
return -1;
}
if (server.AddService(&file_svc,
brpc::SERVER_DOESNT_OWN_SERVICE) != 0) {
LOG(ERROR) << "Fail to add file_svc";
return -1;
}
if (server.AddService(&queue_svc,
brpc::SERVER_DOESNT_OWN_SERVICE,
"/v1/queue/start => start,"
"/v1/queue/stop => stop,"
"/v1/queue/stats/* => getstats") != 0) {
LOG(ERROR) << "Fail to add queue_svc";
return -1;
}
// Start the server.
brpc::ServerOptions options;
options.idle_timeout_sec = FLAGS_idle_timeout_s;
options.mutable_ssl_options()->default_cert.certificate = FLAGS_certificate;
options.mutable_ssl_options()->default_cert.private_key = FLAGS_private_key;
options.mutable_ssl_options()->ciphers = FLAGS_ciphers;
if (server.Start(FLAGS_port, &options) != 0) {
LOG(ERROR) << "Fail to start HttpServer";
return -1;
}
// Wait until Ctrl-C is pressed, then Stop() and Join() the server.
server.RunUntilAskedToQuit();
return 0;
}
复制代码
然后需要把cpp文件中依赖的key.pem和cert.pem也要复制到cpp的同级目录
目前整个project的结构如下
root@5669353fc2c1:~/juejin-example# tree
.
|-- BUILD
|-- WORKSPACE
|-- cert.pem
|-- depBUILD
| |-- glog.BUILD
| |-- leveldb.BUILD
| |-- openssl.BUILD
| `-- zlib.BUILD
|-- http_c++_server.cpp
|-- key.pem
`-- proto
`-- http.proto
复制代码
接下来就可以开始跑了
root@5669353fc2c1:~/juejin-example# bazel run //:http_c++_server
Starting local Bazel server and connecting to it...
INFO: Writing tracer profile to '/root/.cache/bazel/_bazel_root/c2b1f1c39c843e8d13f18c6e59f038b8/command.profile.gz'
ERROR: /root/juejin-example/BUILD:51:1: //:with_glog is not a valid configuration key for //:http_c++_server
ERROR: Analysis of target '//:http_c++_server' failed; build aborted: //:with_glog is not a valid configuration key for //:http_c++_server
INFO: Elapsed time: 1.783s
INFO: 0 processes.
FAILED: Build did NOT complete successfully (1 packages loaded, 0 targets configured)
FAILED: Build did NOT complete successfully (1 packages loaded, 0 targets configured)
复制代码
根据报错信息把BUILD文件里的COPT做一下修改,把选中的这一块去掉
然后再跑
root@5669353fc2c1:~/juejin-example# bazel run //:http_c++_server
INFO: Writing tracer profile to '/root/.cache/bazel/_bazel_root/c2b1f1c39c843e8d13f18c6e59f038b8/command.profile.gz'
ERROR: /root/.cache/bazel/_bazel_root/c2b1f1c39c843e8d13f18c6e59f038b8/external/com_google_protobuf/protobuf.bzl:130:19: Traceback (most recent call last):
File "/root/.cache/bazel/_bazel_root/c2b1f1c39c843e8d13f18c6e59f038b8/external/com_google_protobuf/protobuf.bzl", line 125
rule(attrs = {"srcs": attr.label_list...()}, <2 more arguments>)
File "/root/.cache/bazel/_bazel_root/c2b1f1c39c843e8d13f18c6e59f038b8/external/com_google_protobuf/protobuf.bzl", line 130, in rule
attr.label(cfg = "host", executable = True, sin..., ...)
'single_file' is no longer supported. use allow_single_file instead. You can use --incompatible_disable_deprecated_attr_params=false to temporarily disable this check.
ERROR: /root/juejin-example/BUILD:33:1: every rule of type proto_library implicitly depends upon the target '@com_google_protobuf//:protoc', but this target could not be found because of: error loading package '@com_google_protobuf//': Extension file 'protobuf.bzl' has errors
ERROR: Analysis of target '//:http_c++_server' failed; build aborted: error loading package '@com_google_protobuf//': Extension file 'protobuf.bzl' has errors
INFO: Elapsed time: 1.856s
INFO: 0 processes.
FAILED: Build did NOT complete successfully (14 packages loaded, 49 targets configured)
FAILED: Build did NOT complete successfully (14 packages loaded, 49 targets configured)
复制代码
看提示,还是bazel版本的问题,需要创建一个.bazelrc文件去控制bazel run和bazel build的指令选项
root@5669353fc2c1:~/juejin-example# code .bazelrc
复制代码
.bazelrc的内容
build --incompatible_disable_deprecated_attr_params=false
复制代码
再跑
root@5669353fc2c1:~/juejin-example# bazel run //:http_c++_server
INFO: Writing tracer profile to '/root/.cache/bazel/_bazel_root/c2b1f1c39c843e8d13f18c6e59f038b8/command.profile.gz'
ERROR: /root/juejin-example/BUILD:53:12: in deps attribute of cc_binary rule //:http_c++_server: target '//:brpc' does not exist
ERROR: Analysis of target '//:http_c++_server' failed; build aborted: Analysis of target '//:http_c++_server' failed; build aborted
INFO: Elapsed time: 0.903s
INFO: 0 processes.
FAILED: Build did NOT complete successfully (15 packages loaded, 483 targets configured)
FAILED: Build did NOT complete successfully (15 packages loaded, 483 targets configured)
复制代码
发现是一个依赖项的问题,:brpc是一般指BUILD上声明的依赖,例如cc_library,而brpc是我们在WORKSPACE拉下来的,需要修改一下BUILD文件的这个依赖的名字,修改为@brpc//:brpc
# 二进制文件的依赖依赖于上面声明的dep
cc_binary(
name = "http_c++_server",
srcs = [
"http_c++_server.cpp",
],
deps = [
":cc_c++_http_proto",
"@brpc//:brpc",
],
copts = COPTS,
)
复制代码
然后因为bazel是基于沙箱跑的,所以cert.pem和key.pem需要指定绝对路径,不然二进制文件跑的时候会读取当前目录下的pem文件会报错,所以需要修改(如果有老哥可以知道怎么可以将这两个文件放到bazel-bin里的话,可以跟我说一下)
DEFINE_string(certificate, "/root/juejin-example/cert.pem", "Certificate file path to enable SSL");
DEFINE_string(private_key, "/root/juejin-example/key.pem", "Private key file path to enable SSL");
复制代码
修改为绝对路径后再跑
Use --incompatible_new_actions_api=false to temporarily disable this check.
复制代码
再次报错,需要在.bazelrc再修改一下(有一个是查资料查出来的配置项)
build --copt -DHAVE_ZLIB=1
build --incompatible_disable_deprecated_attr_params=false
build --incompatible_new_actions_api=false
复制代码
再跑
http_c++_server.cpp:24:10: fatal error: http.pb.h: No such file or directory
24 | #include "http.pb.h"
| ^~~~~~~~~~~
compilation terminated.
复制代码
需要再修改一下http.pb.h的文件引用,因为这个是在proto文件夹里的所以需要修改为
#include "proto/http.pb.h"
复制代码
再跑然后编译成功,开始运行二进制
INFO: Elapsed time: 155.248s, Critical Path: 7.01s
INFO: 504 processes: 504 processwrapper-sandbox.
INFO: Build completed successfully, 505 total actions
INFO: Build completed successfully, 505 total actions
I0227 21:24:48.670600 5176 external/brpc/src/brpc/server.cpp:1046] Server[example::QueueServiceImpl+example::HttpServiceImpl+example::FileServiceImpl] is serving on port=8010.
I0227 21:24:48.671348 5176 external/brpc/src/brpc/server.cpp:1049] Check out http://5669353fc2c1:8010 in web browser.
复制代码
打开 http://ip:8010 查看暴露出来的rest接口,然后环境就搭建成功!
参考文章