"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对用户进行删除
本代码功能演示也就这样。
本篇到此结束,感谢你的阅读。
祝你好运,向阳而生~