
文章目录本篇摘要文件操作子服务FileServer模块设计及实现实现主要功能总体概括操作流程图实现过程详细整体流程图纯手绘proto文件server端实现多模块整合实现fileserver端向brpc服务器中注册对应服务client端实现使用gtest测试框架进行四大接口测试使用CMake进行项目构建测试效果展示源码汇总本篇小结本篇摘要本文围绕文件操作子服务FileServer展开设计支持单/多文件上传下载功能基于BRPC通信与ETCD服务注册通过Protobuf定义接口协议C实现服务端存储逻辑与客户端调用结合GTest验证四大接口可靠性CMake管理项目模块构建实现高效服务端可靠的文件管理子服务。文件操作子服务FileServer模块设计及实现实现主要功能总体概括文件上传FileUpLoad单个文件上传后台接收文件消息后转给文件子服务存储。多个文件上传后台接收文件消息后转给文件子服务存储循环读取进行存储。文件下载FileDownLoad单个文件下载后台获取用户头像等数据、客户端获取文件/语音/图片消息数据。多个文件下载后台大批量获取用户头像等数据、前端批量文件下载进行循环读取发送。操作流程图操作过程简述首先文件服务客户端与服务端约定好对应的Protobuf协议然后brpc服务端重写实现对应的服务四大功能的接口函数单传多传单下多下然后进行etcd服务注册服务名- - - ipport再进行对应服务的添加到brpc服务器中然后绑定对应ipport开始运行。然后就是客户端进行声明关心的服务之后调用etcd封装的客户端进行注册对应回调函数此时调用etcd的查询服务之后获取对应关心的服务fileserver的brpc客户端请求信道channel之后构建brpc请求对象进行相应函数调用相应服务功能接口即可。实现过程详细整体流程图纯手绘proto文件base.proto:syntax proto3; package proto_module; option cc_generic_services true; //上传时候需要保存用户上传的文件名字内容等 message FileMessageInfo { optional string file_id 1;//文件id,客户端发送的时候不用设置 optional int64 file_size 2;//文件大小 optional string file_name 3;//文件名称 //文件数据在ES中存储消息的时候只要id和元信息不要文件数据, 服务端转发的时候也不需要填充 optional bytes file_contents 4; } //加载需要 message FileDownloadData { string file_id 1; bytes file_content 2; } //上传需要 message FileUploadData { string file_name 1; //文件名称 int64 file_size 2; //文件大小 bytes file_content 3; //文件数据 }file.ptoto:syntax proto3; package proto_module; import base.proto; option cc_generic_services true; message GetSingleFileReq { string request_id 1; string file_id 2; optional string user_id 3; //用户ID optional string session_id 4;//登录会话ID -- 网关进行身份鉴权 } message GetSingleFileRsp { string request_id 1; bool success 2; string errmsg 3; optional FileDownloadData file_data 4; } message GetMultiFileReq { string request_id 1; optional string user_id 2; optional string session_id 3; repeated string file_id_list 4; } message GetMultiFileRsp { string request_id 1; bool success 2; string errmsg 3; mapstring, FileDownloadData file_data 4;//文件ID与文件数据的映射map } message PutSingleFileReq { string request_id 1; //请求ID作为处理流程唯一标识 optional string user_id 2; optional string session_id 3; FileUploadData file_data 4; } message PutSingleFileRsp { string request_id 1; bool success 2; string errmsg 3; FileMessageInfo file_info 4; //返回了文件组织的元信息 } message PutMultiFileReq { string request_id 1; optional string user_id 2; optional string session_id 3; repeated FileUploadData file_data 4; } message PutMultiFileRsp { string request_id 1; bool success 2; string errmsg 3; repeated FileMessageInfo file_info 4; } service FileService { rpc GetSingleFile(GetSingleFileReq) returns (GetSingleFileRsp); rpc GetMultiFile(GetMultiFileReq) returns (GetMultiFileRsp); rpc PutSingleFile(PutSingleFileReq) returns (PutSingleFileRsp); rpc PutMultiFile(PutMultiFileReq) returns (PutMultiFileRsp); }提供对应的四大操作上传 *2 下载 *2的请求及应答还有对应的rpc服务函数调用接口。server端实现首先进行上面连个proto文件编译生成对应C对应对象的接口。多模块整合实现fileserver端首先还是按照之前的speechrecognizeserver那里采取建造者模式进行构建对应的服务端对象只不过把对应的asr语音对象去掉了只包含对应的brpcetcd封装的对象即可此外就是把对应给brpc调用服务对象传递对应的服务端存储文件的路径参数。然后就是进行内部成员如etcd中服务注册以及向brpc中添加对应服务对象启动rpc服务端而已和上面的类似这里就不细讲了。向brpc服务器中注册对应服务首先还是继承之前的由proto文件编译生成的FileService类然后进行四大接口虚函数重写。///////////////////支持brpc注册的fileservice服务的基类重写对应四个功能的函数classRpcFileService:publicproto_module::FileService{public:RpcFileService(conststd::stringstorage_path):_storage_path(storage_path){umask(0);// 设置 umask 为 0不屏蔽任何权限,防止后续创建目录失败mkdir(storage_path.c_str(),0775);if(_storage_path.back()!/)_storage_path.push_back(/);}private:std::string _storage_path;};下面就以一个函数为例向服务端进行put多文件请求与应答///////////////////进行从服务端上传对应文件集合voidPutMultiFile(google::protobuf::RpcController*controller,const::proto_module::PutMultiFileReq*request,::proto_module::PutMultiFileRsp*response,::google::protobuf::Closure*done){LOG_DEBUG(收到推送指定文件集合请求);brpc::ClosureGuardrpc_guard(done);// 读取文件id数组进行一一获取进行读取内容构建答复for(inti0;irequest-file_data_size();i){proto_module::FileUploadData data;datarequest-file_data()[i];std::string bodydata.file_content();std::string fidUtils::uuid();std::string filename_storage_pathfid;boolretUtils::writeFile(filename,body);if(retfalse){response-set_success(false);response-set_errmsg(读取文件数据失败);LOG_ERROR({} 读取文件数据失败,request-request_id());return;}// 写入成功进行文件标志 名字大小等信息填充proto_module::FileMessageInfo*inforesponse-add_file_info();info-set_file_id(fid);info-set_file_size(request-file_data(i).file_size());info-set_file_name(request-file_data(i).file_name());}response-set_request_id(request-request_id());response-set_success(true);}这里也比较简单就是根据对应传递过来的req的文件数据数组然后根据长度进行遍历获取出来内容为它制作专属id然后进行写入指定路径来存储最后拿着这个id以及其他文件信息构建应答返回之后用户就可以根据这个专属id进行查询对应文件即可。其他的三个接口实现也是根据对应根据请求进行对应操作读取文件存储文件等然后构建应答进行返回。client端实现实现思路进行向信道模块设置关心的服务以及注册对应的回调函数向etcd发现客户端中当etcd服务端有内容或者内容有变化的话就会进行回调刚刚注册进信道模块中的函数完成对应拿取value进行构建对应rpc服务信道的操作最后拿着对应信道构建brpc客户端进行对应服务接口函数请求即可。代码逻辑如下// //通过gflags完成对应参数解析google::ParseCommandLineFlags(argc,argv,true);// 勿忘初始化日志对象init_logger(FLAGS_run_mode,FLAGS_log_file,FLAGS_log_level);// 初始化谷歌测试框架testing::InitGoogleTest(argc,argv);// 客户端调用rpc接口通过信道与rpc服务端进行通信ServiceManager::Ptr smstd::make_sharedServiceManager();// 告诉服务管理者关系哪些服务sm-Cared(/service/file_service);// 绑定对应etcd监控的服务变化的回调函数(一旦有服务变化的话对应的回调函数就会完成services的维护工作)autoput_cbbind(ServiceManager::OnlineService,sm.get(),std::placeholders::_1,std::placeholders::_2);autodel_cbbind(ServiceManager::UnonlineService,sm.get(),std::placeholders::_1,std::placeholders::_2);Discovery::Ptr disstd::make_sharedDiscovery(FLAGS_etcd_host,FLAGS_base_service,put_cb,del_cb);pchannelsm-ChooseService(/service/file_service);if(!pchannel){std::this_thread::sleep_for(std::chrono::seconds(1));return-1;}使用gtest测试框架进行四大接口测试下面使用google的gtest框架进行测试者四个功能是否完整测试思路比如把本地的Makefile文件进行上传然后再下载回来对base.proto file.proto文件进行多文件上传以及下载回来根据对应md5sum值来判断是文件内容是否一致以及查看服务端指定路径是否根据uuid生成的文件id进行存储对应文件。四大功能接口的gtest测试代码TEST(put_file,single_file){// 1. 读取当前目录下的指定文件数据std::string body;ASSERT_TRUE(Utils::readFile(./Makefile,body));// 2. 实例化rpc调用客户端对象发起rpc调用proto_module::FileService_Stubstub(pchannel.get());proto_module::PutSingleFileReq req;req.set_request_id(1);req.mutable_file_data()-set_file_name(Makefile);req.mutable_file_data()-set_file_size(body.size());req.mutable_file_data()-set_file_content(body);brpc::Controller*cntlnewbrpc::Controller();proto_module::PutSingleFileRsp*rspnewproto_module::PutSingleFileRsp();stub.PutSingleFile(cntl,req,rsp,nullptr);ASSERT_FALSE(cntl-Failed());// 3. 检测返回值中上传是否成功ASSERT_TRUE(rsp-success());ASSERT_EQ(rsp-file_info().file_size(),body.size());ASSERT_EQ(rsp-file_info().file_name(),Makefile);single_fidrsp-file_info().file_id();LOG_DEBUG(文件ID{},rsp-file_info().file_id());}TEST(get_file,single_file){// 先发起Rpc调用进行文件下载proto_module::FileService_Stubstub(pchannel.get());proto_module::GetSingleFileReq req;proto_module::GetSingleFileRsp*rsp;req.set_request_id(2);req.set_file_id(single_fid);brpc::Controller*cntlnewbrpc::Controller();rspnewproto_module::GetSingleFileRsp();stub.GetSingleFile(cntl,req,rsp,nullptr);ASSERT_FALSE(cntl-Failed());ASSERT_TRUE(rsp-success());// 将文件数据存储到文件中ASSERT_EQ(single_fid,rsp-file_data().file_id());Utils::writeFile(make_file_download,rsp-file_data().file_content());}std::vectorstd::stringmulti_file_id;// 把上传成功的文件对应id保存起来后续方便下载TEST(put_files,multi_files){// 1. 读取当前目录下的指定文件数据std::string body1;ASSERT_TRUE(Utils::readFile(./base.pb.h,body1));std::string body2;ASSERT_TRUE(Utils::readFile(./file.pb.h,body2));// 2. 实例化rpc调用客户端对象发起rpc调用proto_module::FileService_Stubstub(pchannel.get());proto_module::PutMultiFileReq req;req.set_request_id(3);autofile_datareq.add_file_data();file_data-set_file_name(base.pb.h);file_data-set_file_size(body1.size());file_data-set_file_content(body1);file_datareq.add_file_data();file_data-set_file_name(file.pb.h);file_data-set_file_size(body2.size());file_data-set_file_content(body2);brpc::Controller*cntlnewbrpc::Controller();proto_module::PutMultiFileRsp*rspnewproto_module::PutMultiFileRsp();stub.PutMultiFile(cntl,req,rsp,nullptr);ASSERT_FALSE(cntl-Failed());// 3. 检测返回值中上传是否成功(把对应文件id保存起来方便后续检查)ASSERT_TRUE(rsp-success());for(inti0;irsp-file_info_size();i){multi_file_id.push_back(rsp-file_info(i).file_id());LOG_DEBUG(文件ID{},multi_file_id[i]);}}TEST(get_files,multi_files){// 先发起Rpc调用进行文件下载proto_module::FileService_Stubstub(pchannel.get());proto_module::GetMultiFileReq req;proto_module::GetMultiFileRsp*rsp;req.set_request_id(4);req.add_file_id_list(multi_file_id[0]);req.add_file_id_list(multi_file_id[1]);brpc::Controller*cntlnewbrpc::Controller();rspnewproto_module::GetMultiFileRsp();stub.GetMultiFile(cntl,req,rsp,nullptr);ASSERT_FALSE(cntl-Failed());ASSERT_TRUE(rsp-success());// 将文件数据存储到文件中ASSERT_TRUE(rsp-file_data().find(multi_file_id[0])!rsp-file_data().end());ASSERT_TRUE(rsp-file_data().find(multi_file_id[1])!rsp-file_data().end());automaprsp-file_data();autofile_data1map[multi_file_id[0]];Utils::writeFile(base_download_file1,file_data1.file_content());autofile_data2map[multi_file_id[1]];Utils::writeFile(file_download_file2,file_data2.file_content());}使用CMake进行项目构建CMakeLists.txt实现思路收集指定路径下的所有proto文件进行向构建目录使用命令输出对应的.h / .cc文件设置对应检查如果当前目录存在就不用cmake重新构建浪费时间了然后收集各个目录的.cc 以及对应源文件sever.cc client.cc等指定头文件搜索路径最后对他俩进行一一链接库生成可执行文件。CMakeLists.txt文件源码点击速览测试效果展示当前build构建目录不存在对应的proto的.cc 或者.h 文件因此直接构建出来。进行make 生成对应文件。存在对应proto依赖文件再次cmake就不会在打印重新生成proto依赖文件提示了。启动对应file后台brpc服务端。可以看到对应服务被从etcd注册中心拿到然后进行rpc客户端信道模块进行构建管理最后发起的四个gtest请求也是都跑通。下面测试下文件是否匹配服务端指定路径保存对应上传的文件。就拿单文件上传和下载下来通过md5值来判断对应文件内容都是相同的剩下的多文件的也应该是相同这里就不验证了。源码汇总点击速览本篇小结本文设计实现文件操作子服务FileServer支持单/多文件上传下载基于BRPCETCD构建通过Protobuf定义接口C实现服务端与客户端利用GTest验证功能CMake管理构建确保文件存储与检索可靠。