公司项目需要brpc来构建,提前学习一点能够更加顺利的融入团队
首先在gitee,github上clone,并且按照getting_start中的构建方式make后,确认自带的example能够成功运行后,我们开始自己构建自己的服务器。
cmake用了一天入门,今天来学习下BRPC的写法。
echo的例子在brpc官方例子里有,但是本着学习的精神,我还是想自己总结一份经验。
1.书写protoc2文件:
protoc文件是谷歌出品的网络通讯规范,他被brpc用作底层消息结构模块。
按照自带的example——echo中的protoc文件定义,我们可以先复制到一个自己的目录:
proto文件定义了客户端和服务端的请求、相应的消息格式。
syntax="proto2"; //指定proto版本
option cc_generic_services = true; //设置为生成C++类
message MsgClient{ //客户端发送的消息
required string msg=1; //必须要有的字段:required
};
message MsgServer{ //服务器回复的消息
required string msg=1; //必须要有的字段:required
}
/*
定义服务(Service)
如果想要将消息类型用在RPC(远程方法调用)系统中,
可以在.proto文件中定义一个RPC服务接口,protocol buffer编译器将会根据
所选择的不同语言生成服务接口代码及存根。如,想要定义一个RPC服务并具有一个方法
,该方法能够接收 SearchRequest并返回一个SearchResponse,此时可以在.proto
文件中进行如下定义:
*/
service FirstService{ //自己的第一个brpc服务
rpc Answer(MsgClient) returns (MsgServer);
};
//定义一个rpc服务,有一个名为Answer的函数
//接受一个来自客户端的消息MsgClient,返回一个服务器相应消息MsgServer
抛开啰嗦的注释也就4句话,十分简洁
syntax="proto2";
option cc_generic_services = true;
message MsgClient{
required string msg=1;
};
message MsgServer{
required string msg=1;
}
service FirstService{
rpc Answer(MsgClient) returns (MsgServer);
};
定义好protoc文件后,即可生成相应的C++类文件:
protoc --cpp_out=./ ./msg.proto
我们打开msg.pb.h,里面可以找到一个名为:FirstService的类,其中有一个virtual方法,Answer,这就是我们刚刚在service段中定义的rpc函数。我们要做的,就是在服务器server.cpp实现这个Answer函数,在其中处理相关的信息
现在开始书写server.cpp文件,先把基础的文件引入
#include<iostream>
#include<brpc/server.h>
#include"msg.pb.h"
using namespace std;
int main(){
return 0;
}
现在开始实现Answer这个RPC,我们需要public继承msg.pb.h中的FirstService类
#include<iostream>
#include<brpc/server.h>
#include"msg.pb.h"
using namespace std;
class AnswerImpl : public FirstService{
public:
AnswerImpl(){}//构造函数
virtual ~AnswerImpl(){}//析构函数
virtual void Answer(::google::protobuf::RpcController* controller,
const ::MsgClient* request,
::MsgServer* response,
::google::protobuf::Closure* done){
}
};
int main(){
return 0;
}
因为我们做的是echo服务器,所以我们就只是简单的把客户端发送的消息原封不动发送回去就可以了。
初始化的部分是brpc的标准流程,具体请查看brpc文档查阅https://www.bookstack.cn/books/incubator-brpc
不过目前我学的就只用这两句closureguard & static_cast
#include<iostream>
#include<brpc/server.h>
#include"msg.pb.h"
using namespace std;
class AnswerImpl : public FirstService{
public:
AnswerImpl(){}//构造函数
virtual ~AnswerImpl(){}//析构函数
virtual void Answer(::google::protobuf::RpcController* controller,
const ::MsgClient* request,
::MsgServer* response,
::google::protobuf::Closure* done){
//brpc标准流程
// 这个对象确保在return时自动调用done->Run()
brpc::ClosureGuard done_guard(done);
brpc::Controller* cntl = static_cast<brpc::Controller*>(controller);
//LOG是glog的功能,也可以自己选择其他的日志库or输出流
LOG(INFO) << "收到消息[log_id=" << cntl->log_id()
<< "] 从 " << cntl->remote_side()
<< " 到 " << cntl->local_side()
<< ": " << request->msg()
<< " (附加消息=" << cntl->request_attachment() << ")";
// 填写response
response->set_msg(request->msg()); //把客户端发送的消息原封不动发送回去
//这个msg正是msg.proto中定义的msg结构
}
};
int main(){
return 0;
}
现在已经实现了具体的rpc函数了,开始填充main函数,让他运行起来
#include<iostream>
#include<brpc/server.h>
#include"msg.pb.h"
using namespace std;
class AnswerImpl : public FirstService{
public:
AnswerImpl(){}//构造函数
virtual ~AnswerImpl(){}//析构函数
virtual void Answer(::google::protobuf::RpcController* controller,
const ::MsgClient* request,
::MsgServer* response,
::google::protobuf::Closure* done){
// 这个对象确保在return时自动调用done->Run()
brpc::ClosureGuard done_guard(done);
brpc::Controller* cntl = static_cast<brpc::Controller*>(controller);
//LOG是glog的功能,也可以自己选择其他的日志库or输出流
LOG(INFO) << "收到消息[log_id=" << cntl->log_id()
<< "] 从 " << cntl->remote_side()
<< " 到 " << cntl->local_side()
<< ": " << request->msg()
<< " (附加消息=" << cntl->request_attachment() << ")";
// 填写response
response->set_msg(request->msg()); //把客户端发送的消息原封不动发送回去
//这个msg正是msg.proto中定义的msg结构
}
};
int main(){
brpc::Server server;
AnswerImpl aImpl; //实例化你的类
//默认构造后的Server不包含任何服务,也不会对外提供服务,仅仅是一个对象。
//通过如下方法插入你的Service实例。
//若ownership参数为SERVER_OWNS_SERVICE,
//Server在析构时会一并删除Service,否则应设为SERVER_DOESNT_OWN_SERVICE。
if (server.AddService(&aImpl,
brpc::SERVER_DOESNT_OWN_SERVICE) != 0) {
LOG(ERROR) << "service添加失败!";
return -1;
}
//开始启动服务器
brpc::ServerOptions options;
options.idle_timeout_sec = 200; //客户端200s没动作则断开链接
if (server.Start(8000, &options) != 0) { //启动服务器在8000端口
LOG(ERROR) << "无法启动服务器";
return -1;
}
//等待到ctrl+c按下,否则就一直启动服务
server.RunUntilAskedToQuit();
return 0;
}
总的server.cpp端代码已经完成,现在需要书写camke文件
参照昨天学习的cmake我们可以先搭建一个大致的框架出来
这个cmake是参照brpc官方example中的cmake化简后得到的
cmake_minimum_required(VERSION 2.6) #制定最低版本
project(brpcstudy) #设置工程名
include(FindThreads) #brpc需要用到thread库,引入
include(FindProtobuf) #cmake自带搜索protobuf,把protobuf相关库文件包含进来
protobuf_generate_cpp(PROTO_SRC PROTO_HEADER msg.proto)
#开始搜索依赖,为了简介,我把判断“是否搜索到依赖“的判断删掉了
find_path(BRPC_INCLUDE_PATH NAMES brpc/server.h) #搜索brpc相关依赖
find_library(BRPC_LIB NAMES libbrpc.a brpc)
include_directories(${BRPC_INCLUDE_PATH})
find_path(GFLAGS_INCLUDE_PATH gflags/gflags.h) #搜索gflags相关依赖,这个主要是解析命令行参数的,用不到可以不搜索
find_library(GFLAGS_LIBRARY NAMES gflags libgflags)
include_directories(${GFLAGS_INCLUDE_PATH})
find_path(LEVELDB_INCLUDE_PATH NAMES leveldb/db.h) #搜索leveldb相关依赖
find_library(LEVELDB_LIB NAMES leveldb)
include_directories(${LEVELDB_INCLUDE_PATH})
find_package(OpenSSL) #brpc必须要openssl的lib
add_executable(msg_server server.cpp ${PROTO_SRC} ${PROTO_HEADER}) #把server.cpp和protoc所生成的接口文件一起编译
#添加BRPC所需要的lib
set(DYNAMIC_LIB
${CMAKE_THREAD_LIBS_INIT}
${GFLAGS_LIBRARY}
${PROTOBUF_LIBRARIES}
${LEVELDB_LIB}
${OPENSSL_CRYPTO_LIBRARY}
${OPENSSL_SSL_LIBRARY}
dl
)
target_link_libraries(msg_server ${BRPC_LIB} ${DYNAMIC_LIB}) #连接动态库和bprc库
mkdir out
cd out
cmake ..
make
如果在这一步出现下面类似的报protoc版本号的错误:
那么请注意 ,当你已经安装了一个确定的protoc版本,不管是apt还是从git上拉去自己编译,然后编译bprc以后,brpc内部的所有protoc文件都会使用你当前安装的protoc来编译,一旦重装系统or更换环境,就需要使用新版本的protoc来生成新的msg.pb.h。。。等接口文件,也就是说要清空原来brpc目录下的bld文件【如果是用cmake的话】,然后整个bprc重新执行cmake .. &make&make install否则他就会提示版本不符合。
而且,重新编译brpc前,一定要把原来的bld文件夹全部rm -rf掉,否则编译以后版本还是错误的版本。
【这几天换ubuntu发行版给我折腾死了】
如果上面的步骤都符合规范,则可以成功编译
现在server已经书写完毕,我们来写一个和他通信的客户端。
1:同目录新建一个client.cpp,输入标准模板
#include<iostream>
#include<brpc/channel.h> //客户端引用的是channel.h,和服务器的server.h不一样
#include"msg.pb.h"
using namespace std;
int main(){
return 0;
}
2:根据brpc文档可以得到这样的代码,可以慢慢消化
#include<iostream>
#include<brpc/channel.h> //客户端引用的是channel.h,和服务器的server.h不一样
#include"msg.pb.h"
using namespace std;
int main(){
brpc::Channel channel;//新建一个channel和服务器通信
//填充这个channel的相关参数
brpc::ChannelOptions options;
options.protocol="baidu_std";//rpc协议,默认是百度std
options.max_retry=3;//当与服务器丢失连接时重试的最大次数
if (channel.Init("0.0.0.0:8000", "", &options) != 0) {
LOG(ERROR) << "初始化channel失败";
return -1;
}
//通常,您不应直接调用Channel,而应构造
//包装它的存根服务。 存根也可以由所有线程共享。
//每一个你生成的类都有一个"名字_Stub"的包装对象,可以在xxx.pb.h里面搜索
FirstService_Stub stub(&channel);
int log_id=0;
//brpc的每条消息都有一个log_id用来指示这个客户端自连接以来发送消息的条数,可以手动设置
while(1){
::MsgClient request; //客户端的“对象”
::MsgServer response; //服务器的“对象”
brpc::Controller cntl; //控制句柄
cntl.set_log_id(log_id++); //设置消息的id
string msg;
LOG(INFO)<<"请输入要发送到服务器的消息:";
cin>>msg;
request.set_msg(msg.c_str());//设置消息
//因为`done'(最后一个参数)为NULL,所以此函数一直等到
//响应返回或发生错误(包括超时)。
stub.Answer(&cntl,&request,&response,nullptr);
if (!cntl.Failed()) {
LOG(INFO) << "收到服务器 " << cntl.remote_side()
<< " 发送到 " << cntl.local_side()
<< ": " << response.msg()
<< " 延时=" << cntl.latency_us() << "us";
} else {
LOG(WARNING) << cntl.ErrorText();
}
}
return 0;
}
写完client后,只需在原来
add_executable(msg_server server.cpp ${PROTO_SRC} ${PROTO_HEADER})
的下面加入一句
add_executable(msg_server server.cpp ${PROTO_SRC} ${PROTO_HEADER})
把server.cpp和protoc所生成的接口文件一起编译
以及下面的动态连接处增加:
就可以了。
现在我们重新生成执行
cmake ..
make
编译成功,我们运行看看效果
成功运行