"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 -> address
people.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 8085
int 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);
}
// 生成唯一标识UID
static 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; // 联系人ID
string 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; // 联系人ID
string 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; // 联系人ID
string 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); // 构造resp resp->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 { // 读取原contact contacts::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 构建resp buildFindOneContactResponse(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); } // 反序列化Response add_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); // 序列化 request std::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对用户进行删除
本代码功能演示也就这样。
本篇到此结束,感谢你的阅读。
祝你好运,向阳而生~