Protobuf数据交互实战

"no one gonna make me down" 

        在之前呢,我们介绍了什么protobuf以及它的语法、数据类型。 一句老话说得好,"多说不练,假把式!"。因此,本篇会选择以protobuf的语法,完成一个简易的通讯录,一个是文件版的,一个是网络版的。这样才能让我亲切地感受到,protobuf以及和它拥有类似功能的Json、xml这些数据交换格式,它们是如何运作的。

---前言


一、通讯录1

(1) 通讯录格式设计

(2) 通讯录功能实现

        那么要编写文件版的demo代码,首先就需要两个.cc文件,其中一个文件是用来写入(out),一个是读取(in),而Protobuf的数据格式最终是以二进制码转换存储的。

write.cc:

#include <iostream>
#include <fstream>
#include <string>#include "contact.pb.h"using namespace std;const std::string file_name = "./contact.txt";// 添加联系人操作 之后实现
void AddContact() {}int main()
{contacts::Contact con;// 可能需要先加载本地文件fstream input(file_name, ios::in | ios::binary);// 从打开的文件流里进行序列化到con里if (!input){cout << "contacts.bin not find, create new file!" << endl;}if (con.ParseFromIstream(&input) == false){cerr << "Parse error!" << endl;input.close();return -1;}// 可以进行 增添联系人AddContact();// 联系人添加完毕后 写回文件fstream output(file_name, ios::out | ios::binary);if (!con.SerializeToOstream(&output)){cerr << "write error!" << endl;input.close();output.close();return -1;}cout << "write success!" << endl;input.close();output.close();return 0;
}

        

read.cc:

#include <iostream>
#include <fstream>
#include <string>#include "contact.pb.h"
using namespace std;const std::string file_name = "./contact.txt";// 打印通讯录
void PrintContact() {}int main()
{contacts::Contact con;fstream input(file_name, ios::in | ios::binary);if (!con.ParseFromIstream(&input)){cerr << "parse error!" << endl;input.close();return -1;}// 打印通讯录列表PrintContact();input.close();return 0;
}

增加一个联系人:

姓名、年龄、电话设置:

设置地址字段:

设置其他联系方式和备注:

write.cc代码:

#include <iostream>
#include <fstream>
#include <string>#include "contact.pb.h"using namespace std;const std::string file_name = "./contact.txt";// 添加联系人操作
void AddContact(contacts::PeopleInfo *people)
{cout << "-------------新增联系⼈-------------" << endl;string name;cout << "请输入联系人姓名: ";getline(cin, name);people->set_name(name);int age;cout << "请输入联系人年龄: ";cin >> age;people->set_age(age);cin.ignore(256, '\n'); // 设置清空for (int i = 0;; i++){contacts::PeopleInfo_Phone *phone = people->add_phone();cout << "请输入联系人电话" << i + 1 << "(只输⼊回⻋完成电话新增):";string number;getline(cin, number);if (number.empty() == true){cout << "电话信息填充完毕" << endl;break;}phone->set_number(number);cout << "请输入该电话类型(1、移动电话   2、固定电话): ";int type;cin >> type;switch (type){case 1:phone->set_type(contacts::PeopleInfo_Phone_PhoneType::PeopleInfo_Phone_PhoneType_MP);break;case 2:phone->set_type(contacts::PeopleInfo_Phone_PhoneType::PeopleInfo_Phone_PhoneType_TEL);default:cout << "选择有误!" << endl;break;}cin.ignore(256, '\n');}// 录入地址// 先将数据存入 消息字段address中contacts::Address address;cout << "请输入联系人家庭地址:";string home_address;getline(cin, home_address);address.set_home_address(home_address);cout << "请输入联系人单位地址:";string unit_address;getline(cin, unit_address);address.set_unit_address(unit_address);// 转为Any字段// mutable_data: 这里会为Any开辟内存空间 并返回这个内存空间的地址people->mutable_data()->PackFrom(address);cout << "请选择要添加的其他联系方式(1、qq     2、微信号):";int other_contact;cin >> other_contact;cin.ignore(256, '\n');if (1 == other_contact){cout << "请输入联系人qq号: ";string qq;getline(cin, qq);people->set_qq(qq);}else if (2 == other_contact){cout << "请输入联系人微信号: ";string wechat;getline(cin, wechat);people->set_wechat(wechat);}else{cout << "选择有误,未成功设置其他联系方式!" << endl;}for (int i = 0;; i++){cout << "请输入备注" << i + 1 << "标题(只输入回车完成备注新增):";string remark_key;getline(cin, remark_key);if (remark_key.empty()){break;}cout << "请输入备注" << i + 1 << "内容: ";string remark_value;getline(cin, remark_value);people->mutable_remark()->insert({remark_key, remark_value});}cout << "-----------添加联系⼈成功-----------" << endl;
}int main()
{contacts::Contact con;// 可能需要先加载本地文件fstream input(file_name, ios::in | ios::binary);// 从打开的文件流里进行序列化到con里if (!input){cout << "contacts.bin not find, create new file!" << endl;}if (con.ParseFromIstream(&input) == false){cerr << "Parse error!" << endl;input.close();return -1;}// 可以进行 增添联系人AddContact(con.add_con());// 联系人添加完毕后 写回文件fstream output(file_name, ios::out | ios::binary);if (!con.SerializeToOstream(&output)){cerr << "write error!" << endl;input.close();output.close();return -1;}cout << "write success!" << endl;input.close();output.close();return 0;
}

打印联系人信息:

 打印姓名、年龄、电话:

地址信息:

其他联系方式与备注:

read.cc完整代码:

#include <iostream>
#include <fstream>
#include <string>#include "contact.pb.h"
using namespace std;const std::string file_name = "./contact.txt";// 打印通讯录
void PrintContact(contacts::Contact &con)
{for (int i = 0; i < con.con_size(); ++i){cout << "---------------联系人" << i + 1 << "---------------" << endl;const contacts::PeopleInfo &people = con.con(i);cout << "联系人姓名: " << people.name() << std::endl;cout << "联系人年龄: " << people.age() << std::endl;for (int j = 0; j < people.phone_size(); j++){const contacts::PeopleInfo_Phone &phone = people.phone(j);// 联系人电话1:1311111  (MP)cout << "联系人电话" << j + 1 << ":" << phone.number();cout << "   (" << phone.PhoneType_Name(phone.type()) << ")"<< " ";}cout << endl;// 对Any字段 可以通过查看是否设置来确认打印情况if (people.has_data() && people.data().Is<contacts::Address>()){contacts::Address address;// adress -> any// people.data().PackFrom()// any -> addresspeople.data().UnpackTo(&address);if (!address.home_address().empty()){cout << "联系人家庭地址:" << address.home_address() << endl;}else if (!address.unit_address().empty()){cout << "联系人家庭地址:" << address.home_address() << endl;}}switch (people.other_contact_case()){case contacts::PeopleInfo::OtherContactCase::kQq:cout << "联系人qq: " << people.qq() << endl;break;case contacts::PeopleInfo::OtherContactCase::kWechat:cout << "联系人微信号: " << people.wechat() << endl;break;default:cout << " " << endl;break;}if (people.remark_size()){cout << "备注信息:" << endl;}for (auto it = people.remark().cbegin(); it != people.remark().cend(); it++){cout << "   " << it->first << ": " << it->second << endl;}}
}int main()
{contacts::Contact con;fstream input(file_name, ios::in | ios::binary);if (!con.ParseFromIstream(&input)){cerr << "parse error!" << endl;input.close();return -1;}// 打印通讯录列表PrintContact(con);input.close();return 0;
}

(3) 测试 

        代码编写的部分已经完成了,那么来看看简易版的通讯录是怎样的?

         我们先对文件进行写入,因为存在二进制,很多信息是可读性不高。

         我们此时调用read进程,将文件里的内容读出来,

         那么此时原安静躺在磁盘上的二进制文件的内容,是能被可读性强地展示出来。


二、基于Protbuf的网络实战版通讯录

(1) 环境搭建

        Httplib库:cpp-httplib是个开源的库,是⼀个c++封装的http库,使⽤这个库可以在linux、
windows平台下完成http客⼾端、http服务端的搭建。使⽤起来⾮常⽅便,只需要包含头⽂件
httplib.h即可。编译程序时,需要带上-lpthread选项。
源码库地址:https://github.com/yhirose/cpp-httplib

镜像仓库: https://gitcode.net/mirrors/yhirose/cpp-httplib?utm_source=csdn_github_accelerator

        值得注意的是,在大多数Centos的环境下,yum自带的gcc/g++编译器的最新版本是4.8.5发布于2015年,年代久远。译该项⽬会出现异常。将gcc/g++升级为更⾼版本可解决问题。当然,同时安装这两款编译器是不会冲突的。

# 安装gcc 8版本
yum install -y devtoolset-8-gcc devtoolset-8-gcc-c++# 启⽤版本 否则还是会默认使用原g++4.8.5的版本
source /opt/rh/devtoolset-8/enable

        

如何使用httplib?

        这里写了一份小的demo代码来搭建一个简易的http服务器。

  server:

#include <iostream>
// 只需要将该httplib.h文件引入即可
#include "../../cpp-httplib/httplib.h"using namespace std;
using namespace httplib;int main()
{cout << "---服务器启动---" << endl;Server server;// 服务端接收到Post请求  这里传了个Lambda表达式,自动填写响应状态码server.Post("/test-Post", [](const Request &req, Response &resp){cout << "接收到Post请求" << endl;resp.status = 200; });// 服务端接收到Get请求  这里传了个Lambda表达式,自动填写响应状态码server.Get("/test-Get", [](const Request &req, Response &resp){cout << "接收到Post请求" << endl;resp.status = 200; });// bind端口server.listen("0.0.0.0",8085);return 0;
}

     

client:

#include <iostream>
#include "../../cpp-httplib/httplib.h"
using namespace httplib;
using namespace std;
#define CONTACTS_HOST "127.0.0.1"
#define CONTACTS_PORT 8085int main()
{Client cli(CONTACTS_HOST, CONTACTS_PORT);Result res1 = cli.Post("/test-Post");if (res1->status == 200){cout << "调用post成功!" << endl;}Result res2 = cli.Get("/test-Get");if (res2->status == 200){cout << "调用Get成功!" << endl;}return 0;
}

        我们分别启动服务器,进行基于http协议通信: 

(2) 业务逻辑 

        下面就开始代码实战!

(3) Server服务端

实用的小程序功能块:        

        随机数生成 与 备注信息插入

#include <sstream>
#include <random>
#include <google/protobuf/map.h>class Util
{
public:static unsigned int random_char(){// 获取随机数种子std::random_device rd;// mt19937C++11的新特性,类似于rand(),但是速度跟快周期长std::mt19937 gen(rd());// 限制范围生成i [0,255]std::uniform_int_distribution<> dis(0, 255);return dis(gen);}// 生成唯一标识UIDstatic std::string generate_uid(const unsigned int len = 4){std::stringstream ss;for (int i = 0; i < len; ++i){const auto rc = random_char();std::stringstream hexstream;hexstream << std::hex << rc;auto hex = hexstream.str();ss << (hex.length() < 2 ? '0' + hex : hex);}return ss.str();}static void map_copy(google::protobuf::Map<std::string, std::string> *target, const google::protobuf::Map<std::string, std::string> &source){if (nullptr == target){std::cout << "map_copy warning, target is nullptr!" << std::endl;return;}for (auto it = source.cbegin(); it != source.cend(); ++it){target->insert({it->first, it->second});}}
};

        异常类,简单的打印信息:

#pragma once
// 自定义异常类
#include <iostream>
#include <string>
class ContactException
{
private:std::string msg;
public:ContactException(std::string str ):msg(str){}std::string& what(){msg += "A problem happend";return msg;}
};

功能实现的proto:

        对于客户端而言,它会有四个选项来发送不同的req,并且得到不同的resp。对于服务端而言,它也应该根据网络拿到客户端的req,并且将处理结果resp给客户端。如何进行这些数据的交互呢?当然是我们正在讲的.proto文件!

 response:

// base 默认携带 退出码和错误描述
syntax = "proto3";
package base_response;message BaseResponse {bool success = 1;                 // 返回结果string error_desc = 2;            // 错误描述
}// add_contact_resp
syntax = "proto3";
package add_contact_resp;import "base_response.proto";message AddContactResponse{base_response.BaseResponse base_resp = 1;string uid = 2;
}   // del_contact_resp
syntax = "proto3";
package del_contact_resp;import "base_response.proto";   // 引入base_response// 删除一个联系人 resp
message DelContactResponse {base_response.BaseResponse base_resp = 1;string uid = 2;
}// find_all_contacts_resp
syntax = "proto3";
package find_all_contacts_resp;import "base_response.proto";  // 引入base_response// 联系人摘要信息
message PeopleInfo {string uid = 1;                   // 联系人IDstring name = 2;                  // 姓名
}// 查询所有联系人 resp
message FindAllContactsResponse {base_response.BaseResponse base_resp = 1;repeated PeopleInfo contacts = 2;
}// find_one_contact_resp
syntax = "proto3";
package find_one_contact_resp;import "base_response.proto";  // 引入base_response
// 查询一个联系人 resp
message FindOneContactResponse {base_response.BaseResponse base_resp = 1;string uid = 2;                   // 联系人IDstring name = 3;                  // 姓名int32 age = 4;                    // 年龄message Phone {string number = 1;     // 电话号码enum PhoneType {MP = 0;    // 移动电话TEL = 1;   // 固定电话}PhoneType type = 2;    // 类型}repeated Phone phone = 5;         // 电话map<string, string> remark = 6;   // 备注
}

        

request:

// add_contact_req
syntax = "proto3";
package add_contact_req;
// 新增联系人 req
message AddContactRequest {string name = 1;                  // 姓名int32 age = 2;                    // 年龄message Phone {string number = 1;     // 电话号码enum PhoneType {MP = 0;    // 移动电话TEL = 1;   // 固定电话}PhoneType type = 2;    // 类型}repeated Phone phone = 3;         // 电话map<string, string> remark = 4;   // 备注
}// del_contact_req
syntax = "proto3";
package del_contact_req;// 删除一个联系人 req
message DelContactRequest {string uid = 1;                    // 联系人ID
}// find_one_contact_req
syntax = "proto3";
package find_one_contact_req;// 查询一个联系人 req
message FindOneContactRequest {string uid = 1;                    // 联系人ID
}

        对于request就不需要为find_all_contact定制一个req请求,因为一旦收到这个指示,直接返回

find_all_contact的resp就行了。

          那么以上是"Client端"(之后就不再细讲了)和Server端都需要具备的数据交换格式信息。

入口函数main.cc:

Server_Storage:

         如何将数据进行持久化存储,其实也是需要考究的。在这里呢,我们不打算复杂化,介入Mysql数据库来管理这些通讯录信息,而是继续采用本地持续化存储的策略。

Contact.proto: 存储格式

syntax = "proto3";
package contacts;// 联系人
message PeopleInfo {string uid = 1;                    // 联系人IDstring name = 2;                  // 姓名int32 age = 3;                    // 年龄message Phone {string number = 1;     // 电话号码enum PhoneType {MP = 0;    // 移动电话TEL = 1;   // 固定电话}PhoneType type = 2;    // 类型}repeated Phone phone = 4;         // 电话map<string, string> remark = 5;   // 备注
}// 通讯录
// <uid,信息>
message Contacts {map<string, PeopleInfo> contacts = 1;
}

 ContactMapper: 写入、读取文件的模块

#include "contact.pb.cc"
#include "../common/ContactException.h"
#include <fstream>
#define CONTACT_TXT "contacts.txt"
class ContactsMapper
{
public:void selectContacts(contacts::Contacts *contacts) const;void insertContacts(contacts::Contacts &contacts) const;
};#include "ContactsMapper.h"
// 文件操作
void ContactsMapper::selectContacts(contacts::Contacts *con) const
{std::fstream input(CONTACT_TXT, std::ios::in | std::ios::binary);if(!con->ParseFromIstream(&input)){input.close();throw ContactException("ContactsMapper::selectContacts) Failed to parse contacts.");}input.close();
}void ContactsMapper::insertContacts(contacts::Contacts &con) const
{// 写入std::fstream output(CONTACT_TXT,std::ios::out | std::ios::binary);if(!con.SerializeToOstream(&output)){output.close();throw ContactException("ContactsMapper::insertContacts) Failed to write contacts.");}output.close();
}

        

Server_Control:真正后台操控数据存储、读取的功能模块。

        我们使用的Client端、Server端互相定义的协议格式(req.proto\resp.proto),在这里进行处理。

add\buildPeopleInfo\printAddContactRequest:

// 仅仅用作 Server端查看
void ContactServer::printAddContactRequest(add_contact_req::AddContactRequest &request) const
{cout << "---> (ContactsServer::add) AddContactRequest:" << endl;cout << "姓名:" << request.name() << endl;cout << "年龄:" << request.age() << endl;for (auto &phone : request.phone()){int j = 1;cout << "电话" << j++ << ": " << phone.number();cout << "  (" << phone.PhoneType_Name(phone.type()) << ")" << endl;}if (request.remark_size()){cout << "备注信息: " << endl;}for (auto it = request.remark().cbegin(); it != request.remark().cend(); ++it){cout << "    " << it->first << ": " << it->second << endl;}
}void ContactServer::buildPeopleInfo(contacts::PeopleInfo *people, add_contact_req::AddContactRequest &req) const
{// 生成随机数 种子std::string uid = Util::generate_uid(10);// 简单的数据更新people->set_uid(uid);people->set_name(req.name());people->set_age(req.age());for (auto &phone : req.phone()){contacts::PeopleInfo_Phone *peo_phone = people->add_phone();peo_phone->set_number(phone.number());switch (phone.type()){case add_contact_req::AddContactRequest_Phone_PhoneType::AddContactRequest_Phone_PhoneType_MP:peo_phone->set_type(contacts::PeopleInfo_Phone_PhoneType::PeopleInfo_Phone_PhoneType_MP);break;case add_contact_req::AddContactRequest_Phone_PhoneType::AddContactRequest_Phone_PhoneType_TEL:peo_phone->set_type(contacts::PeopleInfo_Phone_PhoneType::PeopleInfo_Phone_PhoneType_TEL);break;default:break;}}// 拷贝备注信息Util::map_copy(people->mutable_remark(), req.remark());
}void ContactServer::add(add_contact_req::AddContactRequest &req, add_contact_resp::AddContactResponse *resp) const
{// 0.先打印提交上来的信息(可以忽略)printAddContactRequest(req);// 1.先读取已经存在的contacts::Contacts con;contactsMapper.selectContacts(&con);// 2.转换为存入文件的消息对象google::protobuf::Map<std::string, contacts::PeopleInfo> *map_contact = con.mutable_contacts(); // 通过con 开辟新的空间contacts::PeopleInfo people;// 通过req 构建新的people字段buildPeopleInfo(&people, req);// {uid,message}map_contact->insert({people.uid(), people});// 3.向磁盘里写入contactsMapper.insertContacts(con);resp->set_uid(people.uid());resp->mutable_base_resp()->set_success(true);// 打印日志cout << "---> (ContactsServer::add) Success to write contacts." << endl;
}

del:

void ContactServer::del(del_contact_req::DelContactRequest &req, del_contact_resp::DelContactResponse *resp) const
{// 删除哪条记录cout << "---> (ContactsServer::del) DelContactRequest: uid: " << req.uid() << endl;// 1.先读取contacts 方便比对删除contacts::Contacts con;contactsMapper.selectContacts(&con);// 不含uid 直接返回if (con.contacts().find(req.uid()) == con.contacts().end()){cout << "---> (ContactsServer::del) not find uid: " << req.uid() << endl;resp->set_uid(-1);// 设置返回报头resp->mutable_base_resp()->set_success(false);resp->mutable_base_resp()->set_error_desc("not be found");return;}// 含uid 删除用户con.mutable_contacts()->erase(req.uid());// 写回磁盘contactsMapper.insertContacts(con);// 构造respresp->mutable_base_resp()->set_success(true);// 日志打印cout << "---> (ContactsServer::del) Success to del contact, uid: " << req.uid() << endl;
}

find_one\buildFindOneContactResponse:

void ContactServer::buildFindOneContactResponse(const contacts::PeopleInfo &people,find_one_contact_resp::FindOneContactResponse *resp) const
{if (nullptr == resp){return;}// 设置信息resp->mutable_base_resp()->set_success(true);resp->set_uid(people.uid());resp->set_name(people.name());resp->set_age(people.age());// 将people里的phone 设置进resp中for (auto &phone : people.phone()){find_one_contact_resp::FindOneContactResponse_Phone* resp_phone = resp->add_phone();    resp_phone->set_number(phone.number());switch(phone.type()){case find_one_contact_resp::FindOneContactResponse_Phone_PhoneType::FindOneContactResponse_Phone_PhoneType_MP:resp_phone->set_type(find_one_contact_resp::FindOneContactResponse_Phone_PhoneType::FindOneContactResponse_Phone_PhoneType_MP);break;case find_one_contact_resp::FindOneContactResponse_Phone_PhoneType::FindOneContactResponse_Phone_PhoneType_TEL:resp_phone->set_type(find_one_contact_resp::FindOneContactResponse_Phone_PhoneType::FindOneContactResponse_Phone_PhoneType_TEL);}}
}void ContactServer::find_one(find_one_contact_req::FindOneContactRequest &req, find_one_contact_resp::FindOneContactResponse *resp) const
{// 读取原contactcontacts::Contacts con;contactsMapper.selectContacts(&con);// 获取map地图const google::protobuf::Map<std::string, contacts::PeopleInfo> &map_contacts = con.contacts();auto it = map_contacts.find(req.uid());// 不含uid 返回if (it == map_contacts.end()){cout << "---> (ContactServer::find_one) not found uid" << req.uid() << endl;resp->set_uid(req.uid());resp->mutable_base_resp()->set_success(false);resp->mutable_base_resp()->set_error_desc("查无此人");return;}// 含uid 构建respbuildFindOneContactResponse(it->second, resp);return;
}

find_all\buildFindAllContactsResponse:

void ContactServer::buildFindAllContactsResponse(contacts::Contacts &con,find_all_contacts_resp::FindAllContactsResponse *resp) const
{if (resp == nullptr){return;}resp->mutable_base_resp()->set_success(true);// 遍历 con内部for (auto it = con.contacts().cbegin(); it != con.contacts().cend(); ++it){find_all_contacts_resp::PeopleInfo *people = resp->add_contacts();people->set_uid(it->first);people->set_name(it->second.name());}
}void ContactServer::find_all(find_all_contacts_resp::FindAllContactsResponse *resp) const
{// 打印日志cout << "---> (ContactsServer::findAll) " << endl;// 获取通讯录contacts::Contacts con;contactsMapper.selectContacts(&con);// 转换为resp对象buildFindAllContactsResponse(con, resp);
}


        我们将Server功能实现模块的代码工作完成后,就可以填补Server_Entry的缺漏代码了。完成一次完整的客户端代码函数调用、响应的流程可以认为会走这几步流程:

● 创建req、resp对象(输出型参数)

● 将Request的内容进行反序列化到req当中

● 调用功能函数,传入(req,resp)

● resp的内容序列化到一个容器里

● 最后填入返回resp剩余信息 

#include <iostream>
#include "../../../../../../cpp-httplib/httplib.h"
#include "../server/ContactsServer.h"
#include "../common/ContactException.h"#include "Request/add_contact_request.pb.h"
#include "Request/del_contact_request.pb.h"
#include "Request/find_one_contact_request.pb.h"#include "Response/add_contact_response.pb.h"
#include "Response/find_one_contact_response.pb.h"
#include "Response/find_all_contacts_response.pb.h"
#include "Response/del_contact_response.pb.h"using std::cerr;
using std::cout;
using std::endl;
using namespace httplib;int main()
{cout << "---> 服务启动..." << endl;Server srv; // 创建服务器对象ContactServer contact_server; // 创建功能函数控制srv.Post("/contacts/add", [&contact_server](const Request &req, Response &resp) {// 1.创建 输出型对象add_contact_req::AddContactRequest request;add_contact_resp::AddContactResponse response;// 功能函数可能出现异常情况try{// 2.反序列化if(!request.ParseFromString(req.body)){throw ContactException("Parse AddContactRequest error!");}// 3. 调用功能函数contact_server.add(request,&response);// 4.resp的内容序列化到一个容器里 std::string resp_str;if(!response.SerializeToString(&resp_str)){throw ContactException("Serialize AddContactResponse error");}resp.body = resp_str;resp.set_header("Content-Type","application/protobuf");resp.status = 200;}catch(ContactException& e){cerr << "---> /contacts/add 发现异常!!!" << endl;std::cout << e.what() << std::endl; // 设置报头resp.status = 500;base_response::BaseResponse* baseResponse = response.mutable_base_resp();baseResponse->set_success(false);baseResponse->set_error_desc(e.what());std::string response_str;if(response.SerializeToString(&response_str)){// 写进正文resp.body = response_str;resp.set_header("Content-Type","application/protobuf");}}});srv.Post("/contacts/del", [&contact_server](const Request &req, Response &resp) {del_contact_req::DelContactRequest request;del_contact_resp::DelContactResponse response;try{// 2.反序列化if(!request.ParseFromString(req.body)){throw ContactException("Parse DelContactRequest  error!");}// 3. 调用功能函数contact_server.del(request,&response);// 4.resp的内容序列化到一个容器里 std::string resp_str;if(!response.SerializeToString(&resp_str)){throw ContactException("Serialize DelContactRequest error");}resp.body = resp_str;resp.set_header("Content-Type","application/protobuf");resp.status = 200;}catch(ContactException& e){cerr << "---> /contacts/del 发现异常!!!" << endl;std::cout << e.what() << std::endl; // 设置报头resp.status = 500;base_response::BaseResponse* baseResponse = response.mutable_base_resp();baseResponse->set_success(false);baseResponse->set_error_desc(e.what());std::string response_str;if(response.SerializeToString(&response_str)){// 写进正文resp.body = response_str;resp.set_header("Content-Type","application/protobuf");}}});srv.Get("/contacts/fine-one", [&contact_server](const Request &req, Response &resp) {find_one_contact_req::FindOneContactRequest request;find_one_contact_resp::FindOneContactResponse response;try{// 2.反序列化if(!request.ParseFromString(req.body)){throw ContactException("Parse FindOneContactRequest  error!");}// 3. 调用功能函数contact_server.find_one(request,&response);// 4.resp的内容序列化到一个容器里 std::string resp_str;if(!response.SerializeToString(&resp_str)){throw ContactException("Serialize FindOneContactRequest  error");}resp.body = resp_str;resp.set_header("Content-Type","application/protobuf");resp.status = 200;}catch(ContactException& e){cerr << "---> /contacts/find_one 发现异常!!!" << endl;std::cout << e.what() << std::endl; // 设置报头resp.status = 500;base_response::BaseResponse* baseResponse = response.mutable_base_resp();baseResponse->set_success(false);baseResponse->set_error_desc(e.what());std::string response_str;if(response.SerializeToString(&response_str)){// 写进正文resp.body = response_str;resp.set_header("Content-Type","application/protobuf");}}});srv.Get("/contacts/find-all", [&contact_server](const Request &req, Response &resp) {find_all_contacts_resp::FindAllContactsResponse response;try{// 3. 调用功能函数contact_server.find_all(&response);// 4.resp的内容序列化到一个容器里 std::string resp_str;if(!response.SerializeToString(&resp_str)){throw ContactException("Serialize FindAllContactsResponse  error");}resp.body = resp_str;resp.set_header("Content-Type","application/protobuf");resp.status = 200;}catch(ContactException& e){cerr << "---> /contacts/find_all 发现异常!!!" << endl;std::cout << e.what() << std::endl; // 设置报头resp.status = 500;base_response::BaseResponse* baseResponse = response.mutable_base_resp();baseResponse->set_success(false);baseResponse->set_error_desc(e.what());std::string response_str;if(response.SerializeToString(&response_str)){// 写进正文resp.body = response_str;resp.set_header("Content-Type","application/protobuf");}}});srv.listen("0.0.0.0", 8085);return 0;
}


(4) Client端

        Client端入口,很明显写了一个很挫的交互页面。 

Client_Control:

addContact/buildAddContactRequest:

void ContactServer::buildAddContactRequest(add_contact_req::AddContactRequest *req)
{std::cout << "请输入联系人姓名: ";std::string name;getline(std::cin, name);req->set_name(name);std::cout << "请输入联系人年龄: ";int age;std::cin >> age;req->set_age(age);std::cin.ignore(256, '\n');for (int i = 1;; i++){std::cout << "请输入联系人电话" << i << "(只输入回车完成电话新增): ";std::string number;getline(std::cin, number);if (number.empty()){break;}add_contact_req::AddContactRequest_Phone *phone = req->add_phone();phone->set_number(number);std::cout << "选择此电话类型 (1、移动电话   2、固定电话) : ";int type;std::cin >> type;std::cin.ignore(256, '\n');switch (type){case 1:phone->set_type(add_contact_req::AddContactRequest_Phone_PhoneType::AddContactRequest_Phone_PhoneType_MP);break;case 2:phone->set_type(add_contact_req::AddContactRequest_Phone_PhoneType::AddContactRequest_Phone_PhoneType_TEL);break;default:std::cout << "----非法选择,使用默认值!" << std::endl;break;}}for (int i = 1;; i++){std::cout << "请输入备注" << i << "标题 (只输入回车完成备注新增): ";std::string remark_key;getline(std::cin, remark_key);if (remark_key.empty()){break;}std::cout << "请输入备注" << i << "内容: ";std::string remark_value;getline(std::cin, remark_value);req->mutable_remark()->insert({remark_key, remark_value});}
}void ContactServer::addContact()
{// 进行http的连接建立Client cli(port);// 构建 request 请求add_contact_req::AddContactRequest req;buildAddContactRequest(&req);// 序列化std::string req_str;if (!req.SerializeToString(&req_str)){throw ContactException("AddContactRequest序列化失败!");}// 发起Post请求auto res = cli.Post("contact/add",req_str,"applicationi/protbuf");if (!res){std::string err_desc;err_desc.append("/contacts/add 链接错误!错误信息:").append(httplib::to_string(res.error()));throw ContactException(err_desc);}// 反序列化Responseadd_contact_resp::AddContactResponse resp;bool parse = resp.ParseFromString(res->body);// 处理异常if (res->status != 200 && !parse){std::string err_desc;err_desc.append("post '/contacts/add/' 失败:").append(std::to_string(res->status)).append("(").append(res->reason).append(")");throw ContactException(err_desc);}else if (res->status != 200){// 处理服务异常std::string err_desc;err_desc.append("post '/contacts/add/' 失败 ").append(std::to_string(res->status)).append("(").append(res->reason).append(")  错误原因:").append(resp.base_resp().error_desc());throw ContactException(err_desc);}else if (!resp.base_resp().success()){// 处理结果异常std::string err_desc;err_desc.append("post '/contacts/add/' 结果异常:").append("异常原因:").append(resp.base_resp().error_desc());throw ContactException(err_desc);}// 正常返回,打印结果std::cout << "---> 新增联系人成功,联系人ID: " << resp.uid() << std::endl;
}

del:

void ContactServer::delContact()
{httplib::Client cli(port);del_contact_req::DelContactRequest req;std::cout << "请输入要删除的联系人id: ";std::string uid;getline(std::cin, uid);req.set_uid(uid);// 反序列化std::string req_str;if (!req.SerializeToString(&req_str)){throw ContactException("DelContactRequest序列化失败!");}// 发起Post请求auto res = cli.Post("contact/del", req_str, "application/protobuf");if (!res){std::string err_desc;err_desc.append("/contacts/del 链接错误!错误信息:").append(httplib::to_string(res.error()));throw ContactException(err_desc);}// 反序列化del_contact_resp::DelContactResponse resp;bool parse = resp.ParseFromString(res->body);// 处理异常if (res->status != 200 && !parse){std::string err_desc;err_desc.append("post '/contacts/del' 失败:").append(std::to_string(res->status)).append("(").append(res->reason).append(")");throw ContactException(err_desc);}else if (res->status != 200){std::string err_desc;err_desc.append("post '/contacts/del' 失败 ").append(std::to_string(res->status)).append("(").append(res->reason).append(")  错误原因:").append(resp.base_resp().error_desc());throw ContactException(err_desc);}else if (!resp.base_resp().success()){// 结果异常std::string err_desc;err_desc.append("post '/contacts/del' 结果异常:").append("异常原因:").append(resp.base_resp().error_desc());throw ContactException(err_desc);}// 正常返回,打印结果std::cout << "---> 成功删除联系人,被删除的联系人ID为:" << resp.uid() << std::endl;
}

find_all_Contact\printFindAllContactsResponse:

void ContactServer::printFindAllContactsResponse(find_all_contacts_resp::FindAllContactsResponse &resp)
{if (0 == resp.contacts_size()){std::cout << "还未添加任何联系人" << std::endl;return;}for (auto contact : resp.contacts()){std::cout << "联系人姓名: " << contact.name() << " 联系人ID: " << contact.uid() << std::endl;}
}void ContactServer::find_all_Contact()
{httplib::Client cli(port);// 查询所有联系人没有需要填写的 req报头auto res = cli.Get("/contacts/find-all");if (!res){std::string err_desc;err_desc.append("/contacts/find-all 链接错误!错误信息:").append(httplib::to_string(res.error()));throw ContactException(err_desc);}find_all_contacts_resp::FindAllContactsResponse resp;bool parse = resp.ParseFromString(res->body);// 处理异常if (res->status != 200 && !parse){std::string err_desc;err_desc.append("get '/contacts/find-all' 失败:").append(std::to_string(res->status)).append("(").append(res->reason).append(")");throw ContactException(err_desc);}else if (res->status != 200){// 服务端异常std::string err_desc;err_desc.append("post '/contacts/find-all' 失败 ").append(std::to_string(res->status)).append("(").append(res->reason).append(")  错误原因:").append(resp.base_resp().error_desc());throw ContactException(err_desc);}else if (!resp.base_resp().success()){// 结果异常std::string err_desc;err_desc.append("post '/contacts/find-all' 结果异常:").append("异常原因:").append(resp.base_resp().error_desc());throw ContactException(err_desc);}// 正常返回,打印结果printFindAllContactsResponse(resp);
}

find_one_Contact\printFindOneContactResponse:

void ContactServer::printFindOneContactResponse(find_one_contact_resp::FindOneContactResponse &resp)
{std::cout << "姓名:" << resp.name() << std::endl;std::cout << "年龄:" << resp.age() << std::endl;for (auto &phone : resp.phone()){int j = 1;std::cout << "电话" << j++ << ": " << phone.number();std::cout << "  (" << phone.PhoneType_Name(phone.type()) << ")" << std::endl;}if (resp.remark_size()){std::cout << "备注信息: " << std::endl;}for (auto it = resp.remark().cbegin(); it != resp.remark().cend(); ++it){std::cout << "    " << it->first << ": " << it->second << std::endl;}
}void ContactServer::find_one_Contact()
{httplib::Client cli(port);// 构建 request 请求find_one_contact_req::FindOneContactRequest req;std::cout << "请输入要查询的联系人id: ";std::string uid;getline(std::cin, uid);req.set_uid(uid);// 序列化 requeststd::string req_str;if (!req.SerializeToString(&req_str)){throw ContactException("FindOneContactRequest序列化失败!");}auto res = cli.Post("/contacts/find-one", req_str, "application/protobuf");if (!res){std::string err_desc;err_desc.append("/contacts/find-one 链接错误!错误信息:").append(httplib::to_string(res.error()));throw ContactException(err_desc);}find_one_contact_resp::FindOneContactResponse resp;bool parse = resp.ParseFromString(res->body);// 处理异常if (res->status != 200 && !parse){std::string err_desc;err_desc.append("post '/contacts/find-one' 失败:").append(std::to_string(res->status)).append("(").append(res->reason).append(")");throw ContactException(err_desc);}else if (res->status != 200){std::string err_desc;err_desc.append("post '/contacts/find-one' 失败 ").append(std::to_string(res->status)).append("(").append(res->reason).append(")  错误原因:").append(resp.base_resp().error_desc());throw ContactException(err_desc);}else if (!resp.base_resp().success()){// 结果异常std::string err_desc;err_desc.append("post '/contacts/find-one' 结果异常:").append("异常原因:").append(resp.base_resp().error_desc());throw ContactException(err_desc);}// 正常返回,打印结果std::cout << "---> 查询到联系人ID为: " << resp.uid() << " 的信息:" << std::endl;printFindOneContactResponse(resp);
}

(5) 测试

        我们分别编译完client与server文件。

        我们举例插入一名通讯录人员小王,

server端:

 

client端:

        我们可以通过其id查找到该用户信息。

        再次新增或用户后,查看详细联系人列表。

         

        同样,也可以按照id对用户进行删除      

         本代码功能演示也就这样。


        

本篇到此结束,感谢你的阅读。

祝你好运,向阳而生~

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/8901.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Godot 4 源码分析 - 获取脚本

获取属性列表 今天搂草打兔&#xff0c;取得了脚本内容 因为已能取得属性值&#xff0c;那就再进一步&#xff0c;取得属性名列表 if (SameText(drGet.propertyName, "propertyNames", DRGRAPH_FLAG_CASESENSITIVE)) {List<PropertyInfo> *p_list new List…

工信部、国家标准委联合印发《国家车联网产业指南(2023 版)》

国家工信部和标委发布了最新的《国家车联网产业标准体系建设指南&#xff08;智能网联汽车&#xff09;&#xff08;2023 版&#xff09;》&#xff0c;了解这篇文章&#xff0c;不论您是智能网联汽车的追随者&#xff0c;还是对智能网联汽车产业前景感兴趣的人&#xff0c;都非…

性能测试请求重试实现思路

文章目录 一、背景二、尝试的解决方案三、解决方案1&#xff1a;jmeter retrier插件&#xff01;有点用但是不是特别有用-_-||四&#xff0c;最终解决方案&#xff1a;lucust! 一、背景 最近系统需要压测一些活动&#xff0c;场景是新建抽奖活动之后&#xff0c;每隔2s查询1次…

Spring6——入门

文章目录 入门环境要求构建模块程序开发引入依赖创建java类创建配置文件创建测试类运行测试程序 程序分析启用Log4j2日志框架Log4j2日志概述引入Log4j2依赖加入日志配置文件测试使用日志 入门 环境要求 JDK&#xff1a;Java17&#xff08;Spring6要求JDK最低版本是Java17&…

1-Linux的目录结构

Linux的目录结构是规定好的&#xff0c;不可以随意进行更改&#xff01; Linux的文件系统是采用级层式的树状目录结构&#xff0c;最上层是根目录–/&#xff0c;然后再在根目录下创建其它的目录。 各个目录中主要负责的功能和作用如下&#xff1a;&#xff08;主体的结构一定…

引入第三方字体库 第三方字体库Google Fonts

googlefonts官方网站 googlefonts中国网站 本人是在微信小程序中引入 在static中建一个文件夹font-family 例如字体链接&#xff1a;https://fonts.font.im/css?familyKirangHaerang 将该链接的返回的资源的复制到css文件中 font-family.css /* [0] */ font-face {font-fam…

使用JMeter进行接口测试教程

安装 使用JMeter的前提需要安装JDK&#xff0c;需要JDK1.7以上版本目前在用的是JMeter5.2版本&#xff0c;大家可自行下载解压使用 运行 进入解压路径如E: \apache-jmeter-5.2\bin&#xff0c;双击jmeter.bat启动运行 启动后默认为英文版本&#xff0c;可通过Options – Ch…

使用node内置test runner,和 Jest say 拜拜

参考 https://nodejs.org/dist/latest-v20.x/docs/api/test.html#test-runner 在之前&#xff0c;我们写单元测试&#xff0c;必须安装第三方依赖包&#xff0c;而从node 20.0.0 版本之后&#xff0c;可以告别繁琐的第三方依赖包啦&#xff0c;可直接使用node的内置test runner…

centos中修改防火墙端口开放配置

1、直接进入文件修改 vim /etc/sysconfig/iptables 2、添加需要开放的端口 &#xff08;1&#xff09;添加需要开放的单个端口 4001 -A INPUT -m state --state NEW -m tcp -p tcp --dport 4001 -j ACCEPT &#xff08;2&#xff09;添加需要开放的某个网段端口 4001:4020 …

需求管理中最易忽视的6大重点

需求管理是产品经理的重点工作&#xff0c;如果无法有效进行需求管理&#xff0c;往往会引起需求变更、项目延期以及成本增加等问题。那么如何对需求进行高效管理&#xff0c;我们在需求管理中&#xff0c;往往最容易忽视的重点都有哪些&#xff1f; 1、重视项目整体管理计划 首…

VMWare虚拟机常用操作命令

今日一语&#xff1a;做到所有的细节都不放过&#xff0c;则可以避免99%已知的风险&#xff0c;但大多数都因懒惰而甘愿承受风险&#xff0c;至此悔不当初 查看虚拟机在本机网络的IP ip addr 本地向虚拟机传送文件 scp 文件 rootpath 虚拟机路径 enter后输入密码即可传输&am…

账号列表的删除编辑提交

<template><div><plan title"账号列表"><!-- selection-change"handleSelectionChange"添加这个属性就是点击可以得到你想要的value值 --><el-tablestyle"width: 100%":data"list"selection-change"h…

视频基础知识

1.视频比特率 视频的比特率是指传输过程中单位时间传输的数据量。可以理解为视频的编码采样率。单位是kbps&#xff0c;即每秒千比特。视频比特率是决定视频清晰度的一个重要指标。比特率越高&#xff0c;视频越清晰&#xff0c;但数据量也会越大。比如一部100分钟的电影&#…

K8S初级入门系列之五-Pod的高级特性

一、前言 前一篇我们了解了Pod的基本概念和操作&#xff0c;本篇我们继续研究Pod的一些高级特性&#xff0c;包括Pod的生命周期&#xff0c;pod探针&#xff0c;pod的调度等。 二、生命周期 1、Pod的生命周期 Pod的生命周期示意图如下&#xff1a; 挂起(Pending)&#xff0c…

【C进阶】指针进阶(1)_二次复习版

目录 1. 字符指针 1.1常量字符串的修改 加上const解决问题 打印常量字符串 1.2数组存放的字符串 1.3例题:数组创建与常量池的区别 2. 指针数组 2.1字符指针数组 2.2整型指针数组 2.3使用3个一维数组,模拟实现一个二维数组 2.4例题: 3.数组指针 3.1 数组指针的定义…

老年公寓人员定位管理系统:提升安全与关怀的智能解决方案

老年公寓作为提供安全居住环境和关怀服务的重要场所&#xff0c;面临着人员管理和安全控制的挑战。为了解决这些问题&#xff0c;老年公寓人员定位管理系统应运而生。基于为提供全面的安全管理和个性化关怀服务&#xff0c;华安联大便通过老年公寓人员定位管理系统的技术原理、…

数字孪生和 GIS 系统融合将为水利领域带来哪些变化?

随着科技的不断进步&#xff0c;数字孪生和 GIS 系统的融合应用逐渐成为了水利领域的新趋势。数字孪生是指通过数字化技术模拟物理实体和过程&#xff0c;将现实世界与虚拟世界相结合的技术&#xff0c;而 GIS 系统则是地理信息系统&#xff0c;用于收集、存储、管理和分析地理…

网工内推 | 售前、售后工程师,IE认证优先

01 广州佳杰科技有限公司 招聘岗位&#xff1a;IT售前工程师 职责描述&#xff1a; 1、负责所在区域 IT 产品的售前技术支持工作,包括客户交流、方案编写、配置报价、投标应标、测试、赋能等; 2、与厂商相关人员建立和保持良好的关系,相互配合,提高项目成功率和厂商满意度; 3、…

Python:使用openpyxl读取Excel文件转为json数据

文档 https://openpyxl.readthedocs.io/en/stable/https://pypi.org/project/openpyxl/ 安装 pip install openpyxl环境 $ python --version Python 3.7.0读取文件示例&#xff1a;将Excel文件读取为json数据 有如下一个文件 data.xlsx 实现代码 # -*- coding: utf-8 -…

如何恢复损坏/删除的 Word 文件

有关如何修复不可读的 Microsoft Word 文件或 Rich Text 文件中的文本的分步说明。这些说明有助于从损坏的*.doc、*.docx、*.dot、*.dotx、*.rtf文件&#xff08;任何版本和大小&#xff09;中提取文本&#xff0c;只需单击几下&#xff1a; 从此处下载奇客数据恢复 &#xff…