网络版通讯录
需求
Protobuf常⽤于通讯协议、服务端数据交换场景。接下来将实现⼀个⽹络版本的通讯录,模拟实现客⼾端与服务端的交互,通过Protobuf来实现各端之间的协议序列化。
需求如下:
- 客⼾端可以选择对通讯录进⾏以下操作:
- 新增⼀个联系⼈
- 删除⼀个联系人
- 查询通讯录列表
- 查询⼀个联系⼈的详细信息
- 服务端提供增删查能⼒,并需要持久化通讯录。
- 客⼾端、服务端间的交互数据使⽤Protobuf来完成。
约定C/S交互接口
新增联系人
[请求]
Post /contacts/add AddContactRequest
Content-Type: application/protobuf
[响应]
AddContactResponse
Content-Type: application/protobuf
删除一个联系人
[请求]
Post /contacts/del DelContactRequest
Content-Type: application/protobuf
[响应]
DelContactResponse
Content-Type: application/protobuf
查询通讯录列表
[请求]
GET /contacts/find-all
[响应]
FindAllContactsResponse
Content-Type: application/protobuf
查询一个联系人的详细信息
[请求]
Post /contacts/find-one FindOneContactRequest
Content-Type: application/protobuf
[响应]
FindOneContactResponse
Content-Type: application/protobuf
约定序列化并编写.proto文件
网络版通讯录需要两个.proto文件。一个是客户端的请求序列化文件:request.proto;一个是服务器的回应序列化文件:response.proto。
request.proto文件
syntax="proto3";
package request;
message Phone {
string number=1; //电话号码
enum PhoneType {
MP=0; //移动电话
TEL=1; //固定电话
}
PhoneType type=2; //电话类型
}
//添加一个联系人
message AddContactRequest {
string name=1; //姓名
int32 age=2; //年龄
repeated Phone phones=3 ; //电话
oneof gender {
string male=4;
string female=5;
}
map<string,string> remarks=6; //备注
}
//删除一个联系人
message DelContactRequest {
string uid=1;
}
// 查询⼀个联系⼈
message FindOneContactRequest {
string uid = 1; // 联系⼈ID
}
response.proto文件
syntax="proto3";
package request;
message Phone {
string number=1; //电话号码
enum PhoneType {
MP=0; //移动电话
TEL=1; //固定电话
}
PhoneType type=2; //电话类型
}
//添加一个联系人
message AddContactRequest {
string name=1; //姓名
int32 age=2; //年龄
repeated Phone phones=3 ; //电话
oneof gender {
string male=4;
string female=5;
}
map<string,string> remarks=6; //备注
}
//删除一个联系人
message DelContactRequest {
string uid=1;
}
// 查询⼀个联系⼈
message FindOneContactRequest {
string uid = 1; // 联系⼈ID
}
客户端编写
客户端主要实现四个功能:新增联系人、删除联系人、查找一个联系人、查找通讯录的所有联系人。
异常类定义
主要用于异常捕获,用于定位错误。
#include<iostream>
#include<string>
class ContactException
{
private:
std::string _message;
public:
ContactException(std::string message="A problem")
:_message(message)
{
}
~ ContactException(){
}
const std::string what(){
return _message;
}
};
客户端client服务定义
主要提供四个接口:
- 添加联系人服务:addContacts
- 删除联系人服务:delContacts
- 查询联系人服务:findContacts
- 查询整个通讯录:findContacts
以下是完整代码:ContactClient.hpp
class ContactClinet
{
private:
// 构建请求
void buildAddRequest(request::AddContactRequest *req_ptr)
{
std::cout << "请输入联系人姓名:";
std::string name;
getline(std::cin, name);
req_ptr->set_name(name);
std::cout << "请输入联系人年龄:";
int32_t age;
std::cin >> age;
std::cin.ignore(256, '\n');
req_ptr->set_age(age);
// 添加电话
for (int i = 1;; i++)
{
std::cout << "请输⼊联系⼈电话" << i << "(只输⼊回⻋完成电话新增): ";
std::string number;
getline(std::cin, number);
if (number.empty())
{
break;
}
request::Phone *phone = req_ptr->add_phones();
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(request::Phone_PhoneType::Phone_PhoneType_MP);
break;
case 2:
phone->set_type(request::Phone_PhoneType::Phone_PhoneType_TEL);
break;
default:
std::cout << "----⾮法选择,使⽤默认值!" << std::endl;
break;
}
}
// 添加性别
std::cout << " 请输入联系人性别:(1.male 2.female):";
int male = 0;
std::cin >> male;
std::cin.ignore(256, '\n');
if (male == 1)
{
std::string male = "male";
req_ptr->set_male(male);
}
else if (male == 2)
{
std::string female = "female";
req_ptr->set_female(female);
}
else
{
std::cout << " 非法选择,设置为默认值!" << std::endl;
}
// 添加备注
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_ptr->mutable_remarks()->insert({remark_key, remark_value});
}
}
void printFindAllContactsResponse(response::FindAllContactsResponse &rep)
{
if (rep.contacts().size() == 0)
{
std::cout << "-------还没有添加联系人-------" << std::endl;
return;
}
for (auto &contact : rep.contacts())
{
std::cout << "联系人姓名:" << contact.name() << " 联系人ID:" << contact.uid() << std::endl;
}
}
void printFindoneContactsResponse(response::FindOneContactResponse &rep)
{
std::cout << "联系人姓名:" << rep.name() << std::endl;
std::cout << "联系人ID:" << rep.uid() << std::endl;
std::cout << "联系人年龄:" << rep.age() << std::endl;
switch (rep.gender_case())
{
case response::FindOneContactResponse::kMale:
std::cout << "联系人性别:male" << std::endl;
break;
case response::FindOneContactResponse::kFemale:
std::cout << "联系人性别:female" << std::endl;
break;
case response::FindOneContactResponse::GENDER_NOT_SET:
std::cout << "联系人性别未设置" << std::endl;
break;
default:
std::cout << "----未知错误----" << std::endl;
break;
}
for (auto &phone : rep.phones())
{
int j = 1;
std::cout << "电话" << j++ << ": " << phone.number();
std::cout << " (" << phone.PhoneType_Name(phone.type()) << ")" << std::endl;
}
// 打印备注
if (rep.marks_size())
{
std::cout << "备注信息: " << std::endl;
}
for (auto it = rep.marks().cbegin(); it != rep.marks().cend(); ++it)
{
std::cout << " " << it->first << ": " << it->second << std::endl;
}
}
public:
ContactClinet() {}
~ContactClinet() {}
// 添加联系人
void addContact()
{
httplib::Client cli(CONTACTS_IP, CONTACTS_PORT);
// 构建请求
request::AddContactRequest req;
buildAddRequest(&req);
// 将请求序列化
std::string req_str;
if (!req.SerializePartialToString(&req_str))
{
throw ContactException("addContact: 反序列化失败!");
}
// 发送Post请求
auto res = cli.Post("/contacts/add", req_str, "application/protobuf");
if (!res)
{
std::string err_desc;
err_desc.append("addContact: Post err: ")
.append(std::to_string(res.error()));
throw ContactException(err_desc);
}
// 将结果反序列化
response::AddContactResponse rep;
bool parse = rep.ParseFromString(res->body);
if (res->status != 200 && !parse)
{
// 通过httplib的response获取错误信息
std::string err_desc;
err_desc.append("addContact: 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("addContacts: post '/contacts/add/' 失败 ")
.append(std::to_string(res->status))
.append("(")
.append(res->reason)
.append(") 错误原因:")
.append(rep.base().err_desc());
throw ContactException(err_desc);
}
else if (!rep.base().success())
{
// 结果异常
std::string err_desc;
err_desc.append("addContact: post '/contacts/add/' 结果异常:")
.append("异常原因:")
.append(rep.base().err_desc());
throw ContactException(err_desc);
}
std::cout << "-------新增联系人成功-------" << std::endl;
std::cout << "新增联系人ID:" << rep.uid() << std::endl;
}
// 删除联系人
void delContact()
{
httplib::Client cli(CONTACTS_IP, CONTACTS_PORT);
// 构建请求
request::DelContactRequest req;
std::cout << "请输入需要删除联系人的uid: ";
std::string uid;
getline(std::cin, uid);
req.set_uid(uid);
// 将请求序列化
std::string req_str;
if (!req.SerializePartialToString(&req_str))
{
throw ContactException("delContact: 反序列化失败!");
}
// 发送Post请求
auto res = cli.Post("/contacts/del", req_str, "application/protobuf");
if (!res)
{
std::string err_desc;
err_desc.append("delContact: Post err: ")
.append(std::to_string(res.error()));
throw ContactException(err_desc);
}
// 将结果反序列化
response::DelContactRequest rep;
bool parse = rep.ParseFromString(res->body);
if (res->status != 200 && !parse)
{
// 通过httplib的response获取错误信息
std::string err_desc;
err_desc.append("delContact: 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("delContacts: post '/contacts/del/' 失败 ")
.append(std::to_string(res->status))
.append("(")
.append(res->reason)
.append(") 错误原因:")
.append(rep.base().err_desc());
throw ContactException(err_desc);
}
else if (!rep.base().success())
{
// 结果异常
std::string err_desc;
err_desc.append("post '/contacts/del/ 结果异常:")
.append("异常原因:")
.append(rep.base().err_desc());
throw ContactException(err_desc);
}
std::cout << "-------删除联系人成功-------" << std::endl;
std::cout << "删除联系人ID:" << rep.uid() << std::endl;
}
// 查找联系人
void findContact()
{
httplib::Client cli(CONTACTS_IP, CONTACTS_PORT);
request::FindOneContactRequest 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("findContact:序列化失败");
}
auto res = cli.Post("/contacts/find-all", req_str, "application/protobuf");
if (!res)
{
std::string err_desc;
err_desc.append("findContact: /contacts/find-one 链接错误!错误信息:")
.append(std::to_string(res.error()));
throw ContactException(err_desc);
}
// 反序列化响应
response::FindOneContactResponse rep;
bool parse = rep.ParseFromString(res->body);
// 处理异常
if (res->status != 200 && !parse)
{
std::string err_desc;
err_desc.append("findContact: 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(rep.base().err_desc());
throw ContactException(err_desc);
}
else if (!rep.base().success())
{
// 结果异常
std::string err_desc;
err_desc.append("findContact: post '/contacts/find-one' 结果异常:")
.append("异常原因:")
.append(rep.base().err_desc());
throw ContactException(err_desc);
}
std::cout << "-------查找联系人成功-------" << std::endl;
std::cout << "联系人uid: " << rep.uid() << std::endl;
printFindoneContactsResponse(rep);
}
// 查找通讯录
void findContacts()
{
httplib::Client cli(CONTACTS_IP, CONTACTS_PORT);
auto res = cli.Get("/contacts/find-all");
if (!res)
{
std::string err_desc;
err_desc.append("findContacts: /contacts/find-all 链接错误!错误信息:")
.append(std::to_string(res.error()));
throw ContactException(err_desc);
}
// 反序列化response
response::FindAllContactsResponse rep;
bool parse = rep.ParseFromString(res->body);
if (res->status != 200 && !parse)
{
std::string err_desc;
err_desc.append("findContacts: 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(rep.base().err_desc());
throw ContactException(err_desc);
}
else if (!rep.base().success())
{
// 结果异常
std::string err_desc;
err_desc.append("findContacts: post '/contacts/find-all' 结果异常:")
.append("异常原因:")
.append(rep.base().err_desc());
throw ContactException(err_desc);
}
// 正常返回,打印结果
printFindAllContactsResponse(rep);
}
};
客户端主程序
客户端的主程序负责打印选择菜单。用户选择响应的选项后,即可调用对应的接口。
void menu()
{
std::cout << "-----------------------------------------------------" << std::endl
<< "--------------- 请选择对通讯录的操作 ----------------" << std::endl
<< "------------------ 1、新增联系⼈ --------------------" << std::endl
<< "------------------ 2、删除联系⼈ --------------------" << std::endl
<< "------------------ 3、查看联系⼈列表 ----------------" << std::endl
<< "------------------ 4、查看联系⼈详细信息 ------------" << std::endl
<< "------------------ 0、退出 --------------------------" << std::endl
<< "-----------------------------------------------------" << std::endl;
}
int main()
{
enum OPERATE
{
ADD = 1,
DEL,
FIND_ALL,
FIND_ONE
};
ContactClinet client;
int choose;
while (true)
{
menu();
std::cout << "----请输入对通讯录的操作----" << std::endl;
std::cin >> choose;
std::cin.ignore(256, '\n');
try
{
switch (choose)
{
case OPERATE::ADD:
client.addContact();
break;
case OPERATE::DEL:
client.delContact();
break;
case OPERATE::FIND_ALL:
client.findContacts();
break;
case OPERATE::FIND_ONE:
client.findContact();
break;
case 0:
std::cout << "---> 程序已退出" << std::endl;
return 0;
default:
std::cout << "---> ⽆此选项,请重新选择!" << std::endl;
break;
}
}
catch (const ContactException &e)
{
std::cerr << "---> 操作通讯录时发现异常!!!" << std::endl
<< "---> 异常信息:" << e.what() << std::endl;
}
catch (const std::exception &e)
{
std::cerr << "---> 操作通讯录时发现异常!!!" << std::endl
<< "---> 异常信息:" << e.what() << std::endl;
}
}
return 0;
}
服务端编写
通过介绍客户端的请求,调用对应的接口,返回响应信息。
服务端持久化存储实现
主要目的:将客户端添加的个人信息,联系人等数据写入到文件中。
通讯录序列化文件:Contacts.proto
syntax = "proto3";
package contacts;
message Phone {
string number = 1; // 电话号码
enum PhoneType {
MP = 0; // 移动电话
TEL = 1; // 固定电话
}
PhoneType type = 2; // 类型
}
// 联系人
message PeopleInfo {
string uid = 1; // 联系人ID
string name = 2; // 姓名
int32 age = 3; // 年龄
repeated Phone phone = 4; // 电话
map<string, string> remark = 5; // 备注
oneof gender{
string male=6;
string female=7;
}
}
// 通讯录
message Contacts {
map<string, PeopleInfo> contacts = 1;
}
**ContactsMapper.h:持久化存储通讯录⽅法定义 **
ContactsMapper的主要功能如下:读取通讯录内容、将二进制数据写入到文件中。
#pragma once
#define DATA_PATH "contacts.bin"
#include <iostream>
#include <fstream>
#include"ContactException.hpp"
/*
持久化方式,将数据存储到文件中
*/
#include "Contacts.pb.h"
class ContactMapper
{
private:
public:
ContactMapper();
~ContactMapper();
//读取一个通讯录
void SelectContact(contacts::Contacts *contacts) const
{
std::fstream input(DATA_PATH, std::ios::in | std::ios::binary);
if (!input)
{
std::cout << "---> (ContactsMapper::selectContacts) " << DATA_PATH << ":File not found.Creating a new file." << std::endl;
}
if(!contacts->ParseFromIstream(&input)){
input.close();
throw ContactException("SelectContact: failed to parse Contacts");
}
input.close();
}
//插入一个通讯录
void InsertContact(contacts::Contacts &contacts) const
{
std::fstream output(DATA_PATH,std::ios::out|std::ios::binary);
//将通讯录序列化,并输入文件
if(!contacts.SerializePartialToOstream(&output)){
output.close();
throw ContactException("(ContactsMapper::insertContacts) Failed towrite contacts.");
}
output.close();
}
};
服务端工具类Utlis实现
主要功能为:生成唯一ID
生成随机数函数
static unsigned int random_char()
{
std::random_device rd;
// mt19937是c++11新特性,它是⼀种随机数算法,⽤法与rand()函数类似,但是mt19937具有速度快,周期⻓的特点
// 作⽤是⽣成伪随机数
std::mt19937 gen(rd());
// 随机⽣成⼀个整数i 范围[0, 255]
std::uniform_int_distribution<> dis(0, 255);
return dis(gen);
}
生成唯一UID
// 生成UID,唯一键
static std::string generate_hex(const unsigned int len)
{
std::stringstream ss;
// ⽣成 len 个16进制随机数,将其拼接⽽成
for (auto 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();
}
完整代码
#include <sstream>
#include <random>
#include <google/protobuf/map.h>
class Utlis
{
private:
public:
Utlis() {}
~Utlis() {}
// 生成随机值
static unsigned int random_char()
{
std::random_device rd;
// mt19937是c++11新特性,它是⼀种随机数算法,⽤法与rand()函数类似,但是mt19937具有速度快,周期⻓的特点
// 作⽤是⽣成伪随机数
std::mt19937 gen(rd());
// 随机⽣成⼀个整数i 范围[0, 255]
std::uniform_int_distribution<> dis(0, 255);
return dis(gen);
}
// 生成UID,唯一键
static std::string generate_hex(const unsigned int len)
{
std::stringstream ss;
// ⽣成 len 个16进制随机数,将其拼接⽽成
for (auto 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});
}
}
};
服务端server服务定义
主要提供添加联系人,删除联系人,查找联系人,查找通讯录接口
- 添加联系人:add
- 删除联系人:del
- 查找联系人:findone
- 查找通讯录:findall
完整代码:ContactServer.hpp
class ContactServer
{
public:
void printAddContactRequest(request::AddContactRequest &request) const
{
std::cout << "---> (ContactsServer::add) AddContactRequest:" << std::endl;
std::cout << "姓名:" << request.name() << std::endl;
std::cout << "年龄:" << request.age() << std::endl;
switch (request.gender_case())
{
case request::AddContactRequest::kMale:
std::cout << "性别:male" << std::endl;
break;
case request::AddContactRequest::kFemale:
std::cout << "性别:female" << std::endl;
break;
case request::AddContactRequest::GENDER_NOT_SET:
std::cout << "性别:未设置" << std::endl;
break;
default:
std::cout << "未知错误" << std::endl;
break;
}
for (auto &phone : request.phones())
{
int j = 1;
std::cout << "电话" << j++ << ": " << phone.number();
std::cout << " (" << phone.PhoneType_Name(phone.type()) << ")" << std::endl;
}
if (request.remarks_size())
{
std::cout << "备注信息: " << std::endl;
}
for (auto it = request.remarks().cbegin(); it != request.remarks().cend();
++it)
{
std::cout << " " << it->first << ": " << it->second << std::endl;
}
}
void buildPeopleInfo(contacts::PeopleInfo *people, request::AddContactRequest &request) const
{
// 生成唯一UID
std::string uid = Utlis::generate_hex(10);
people->set_uid(uid);
people->set_name(request.name());
people->set_age(request.age());
switch (request.gender_case())
{
case request::AddContactRequest::kMale:
people->set_male("male");
break;
case request::AddContactRequest::kFemale:
people->set_female("female");
break;
default:
break;
}
for (auto &phone : request.phones())
{
request::Phone *phone_ptr = request.add_phones();
phone_ptr->set_number(phone.number());
switch (phone.type())
{
case request::Phone::PhoneType::Phone_PhoneType_MP:
phone_ptr->set_type(request::Phone::PhoneType::Phone_PhoneType_MP);
break;
case request::Phone::PhoneType::Phone_PhoneType_TEL:
phone_ptr->set_type(request::Phone::PhoneType::Phone_PhoneType_TEL);
break;
default:
break;
}
}
// 拷贝备注信息
Utlis::map_copy(people->mutable_remark(), request.remarks());
}
void buildFindOneContactResponse(const contacts::PeopleInfo &people, response::FindOneContactResponse *response) const
{
if (response == nullptr)
{
return;
}
response->mutable_base()->set_success(true);
response->set_uid(people.uid());
response->set_name(people.name());
response->set_age(people.age());
switch (people.gender_case())
{
case contacts::PeopleInfo::kMale:
response->set_male("male");
break;
case contacts::PeopleInfo::kFemale:
response->set_female("female");
break;
;
default:
break;
}
// 添加电话信息
for (auto &sphone : people.phone())
{
response::Phone *phone_ptr = response->add_phones();
phone_ptr->set_number(sphone.number());
switch (sphone.type())
{
case response::Phone::PhoneType::Phone_PhoneType_MP:
phone_ptr->set_type(response::Phone::PhoneType::Phone_PhoneType_MP);
break;
case request::Phone::PhoneType::Phone_PhoneType_TEL:
phone_ptr->set_type(response::Phone::PhoneType::Phone_PhoneType_TEL);
break;
default:
break;
}
}
// 拷贝备注信息
Utlis::map_copy(response->mutable_marks(), people.remark());
}
void buildFindAllContactsResponse(contacts::Contacts &contacts, response::FindAllContactsResponse *rsp) const
{
if (rsp == nullptr)
{
return;
}
rsp->mutable_base()->set_success(true);
for (auto it = contacts.contacts().cbegin(); it !=contacts.contacts().cend();++it)
{
response::PeopleInfo *people = rsp->add_contacts();
people->set_uid(it->first);
people->set_name(it->second.name());
}
}
ContactMapper conmapper; // 通讯录持久化
// 添加一个联系人
void add(request::AddContactRequest &req, response::AddContactResponse *rep) const
{
// 打印日志
printAddContactRequest(req);
// 读取contacts.bin文件
contacts::Contacts contacts;
conmapper.SelectContact(&contacts);
// 转化为存入文件的对象
// 为新存入文件的数据开辟空间
google::protobuf::Map<std::string, contacts::PeopleInfo> *contact_map = contacts.mutable_contacts();
contacts::PeopleInfo people;
// 构建people结构
buildPeopleInfo(&people, req);
contact_map->insert({people.uid(), people});
// 向磁盘写入新的文件
conmapper.InsertContact(contacts);
// 构建响应信息
rep->set_uid(people.uid());
rep->mutable_base()->set_success(true);
rep->mutable_base()->set_err_desc("添加联系人成功");
std::cout << "---> (ContactsServer::add) Success to write contacts." << std::endl;
}
// 删除一个联系人
void del(request::DelContactRequest &req, response::DelContactRequest *rep) const
{
// 打印⽇志
std::cout << "---> (ContactsServer::del) DelContactRequest: uid: " << req.uid() << std::endl;
// 读取contacts.bin文件
contacts::Contacts contacts;
conmapper.SelectContact(&contacts);
// 删除数据
// 判断是否存在相应的UID,如果不存在就构建响应
if (contacts.contacts().find(req.uid()) == contacts.contacts().end())
{
std::cout << "---> (ContactsServer::del) not find uid: " << req.uid() << std::endl;
rep->set_uid(req.uid());
rep->mutable_base()->set_success(false);
rep->mutable_base()->set_err_desc("not find uid");
return;
}
// 如果存在就删除用户
contacts.mutable_contacts()->erase(rep->uid());
// 写入到磁盘中
conmapper.InsertContact(contacts);
// 构建响应
rep->set_uid(req.uid());
rep->mutable_base()->set_success(true);
rep->mutable_base()->set_err_desc("删除成功");
// 打印⽇志
std::cout << "---> (ContactsServer::del) Success to del contact, uid: " << req.uid() << std::endl;
}
// 查找一个联系人
void findone(request::FindOneContactRequest &req, response::FindOneContactResponse *rep) const
{
// 打印⽇志
std::cout << "---> (ContactsServer::findOne) FindOneContactRequest: uid: " << req.uid() << std::endl;
// 读取contacts.bin文件
contacts::Contacts contacts;
conmapper.SelectContact(&contacts);
// 判断是否存在相应的UID,如果不存在就构建响应
if (contacts.contacts().find(req.uid()) == contacts.contacts().end())
{
std::cout << "---> (ContactsServer::del) not find uid: " << req.uid() << std::endl;
rep->set_uid(req.uid());
rep->mutable_base()->set_success(false);
rep->mutable_base()->set_err_desc("not find uid");
return;
}
// 如果有对应的uid
const google::protobuf::Map<std::string, contacts::PeopleInfo> &contacts_map = contacts.contacts();
auto it = contacts_map.find(req.uid());
buildFindOneContactResponse(it->second, rep);
// 打印⽇志
std::cout << "---> (ContactsServer::findOne) find uid: " << req.uid() << std::endl;
}
void findall(response::FindAllContactsResponse *rep) const
{
// 打印⽇志
std::cout << "---> (ContactsServer::findAll) " << std::endl;
// 读取contacts.bin文件
contacts::Contacts contacts;
conmapper.SelectContact(&contacts);
buildFindAllContactsResponse(contacts, rep);
}
};
服务器主程序
通过httplib库对对应的端口、请求进行监听:main.cc
int main()
{
std::cout << "----服务启动----" << std::endl;
ContactServer conserver;
httplib::Server svr;
// 新添联系人
svr.Post("/contacts/add", [conserver](const httplib::Request &req, httplib::Response &rep)
{
request::AddContactRequest requ;
response::AddContactResponse resp;
try {
//序列化请求
if(!requ.ParseFromString(req.body)){
throw ContactException("parse AddContactRequest error!");
}
conserver.add(requ,&resp);
std::string rep_str;
//反序列化响应
//构建响应
if(!resp.SerializeToString(&rep_str)){
throw ContactException("Serialize AddContactResponse error");
}
rep.body=rep_str;
rep.set_header("Content-Type", "application/protobuf");
rep.status=200;
}
catch(const ContactException& e){
std::cerr << "---> /contacts/add 发现异常!!!" << std::endl<< "---> 异常信息:" << e.what() << std::endl;
rep.status=500;
response::BaseResponse* base=resp.mutable_base();
base->set_success(false);
base->set_err_desc(e.what());
std::string response_str;
if(resp.SerializeToString(&response_str)){
rep.body=response_str;
rep.set_header("Content-Type", "application/protobuf");
}
} });
svr.Post("/contacts/del", [conserver](const httplib::Request &req, httplib::Response &rep)
{
request::DelContactRequest requ;
response::DelContactRequest resp;
try
{
if (!requ.ParseFromString(req.body))
{
throw ContactException("parse AddContactRequest error!");
}
// 删除联系人
conserver.del(requ, &resp);
std::string rep_str;
// 反序列化响应
// 构建响应
if (!resp.SerializeToString(&rep_str))
{
throw ContactException("Serialize DelContactResponse error");
}
rep.body = rep_str;
rep.status = 200;
rep.set_header("Content-Type", "application/protobuf");
}
catch (const ContactException &e)
{
std::cerr << "---> /contacts/del 发现异常!!!" << std::endl
<< "---> 异常信息:" << e.what() << std::endl;
rep.status = 500;
response::BaseResponse *base = resp.mutable_base();
base->set_success(false);
base->set_err_desc(e.what());
std::string response_str;
if (resp.SerializeToString(&response_str))
{
rep.body = response_str;
rep.set_header("Content-Type", "application/protobuf");
}
} });
svr.Post("/contacts/find-one", [conserver](const httplib::Request &req, httplib::Response &rep)
{
request::FindOneContactRequest requ;
response::FindOneContactResponse resp;
try
{
if (!requ.ParseFromString(req.body))
{
throw ContactException("Parse FindOneContactRequest error!");
}
// 查找一个联系人
conserver.findone(requ, &resp);
std::string rep_str;
// 反序列化响应
// 构建响应
if (!resp.SerializeToString(&rep_str))
{
throw ContactException("Serialize FindOneContactResponseerror");
}
rep.body = rep_str;
rep.status = 200;
rep.set_header("Content-Type", "application/protobuf");
}
catch (const ContactException &e)
{
std::cerr << "---> /contacts/find-one 发现异常!!!" << std::endl
<< "---> 异常信息:" << e.what() << std::endl;
rep.status = 500;
response::BaseResponse *base =resp.mutable_base();
base->set_success(false);
base->set_err_desc(e.what());
std::string response_str;
if (resp.SerializeToString(&response_str))
{
rep.body = response_str;
rep.set_header("Content-Type", "application/protobuf");
}
} });
svr.Get("/contacts/find-all", [conserver](const httplib::Request &req, httplib::Response &rep)
{
response::FindAllContactsResponse resp;
ContactServer conserver;
try{
//查找所有联系人
conserver.findall(&resp);
std::string resp_str;
//反序列化响应
//构建响应
if (!resp.SerializeToString(&resp_str)) {
throw ContactException("Serialize FindAllContactsResponseerror");
}
rep.body=resp_str;
rep.status=200;
rep.set_header("Content-Type", "application/protobuf");
}
catch(const ContactException& e){
std::cerr << "---> /contacts/find-all 发现异常!!!" <<std::endl<< "---> 异常信息:" << e.what() << std::endl;
rep.status = 500;
response::BaseResponse* base =resp.mutable_base();
base->set_success(false);
base->set_err_desc(e.what());
std::string response_str;
if (resp.SerializeToString(&response_str)) {
rep.body = response_str;
rep.set_header("Content-Type", "application/protobuf");
}
} });
//监听端口
svr.listen(CONTACTS_IP,CONTACTS_PORT);
}
通讯录的目录结构和运行
通过tree程序,我们可以查看通讯录的目录结构以及依赖关系。
程序运行
当程序出现报错信息:terminate called after throwing an instance of ‘std::regex_error’ 时,一般是我们没有进行gcc或者g++。
升级g++
sudo yum -y install centos-release-scl # 1. 安装scl的yum源
sudo yum -y install devtoolset-8 # 2. 安装工具集
scl enable devtoolset-7 bash # 3. 启动工具集
source /opt/rh/devtoolset-7/enable # 4. 加载工具集(出错执行)
注意:我们安装的是工具集,并没有卸载原来的gcc,工具集的启动仅本次会话有效,如果我们想启动自动生效可以添加到~/.bash_profile
中,这个是登录的时候默认启动的登录脚本
添加:scl enable devtoolset-7 bash
使用:gcc -v可以查看gcc版本
重新编译client程序和server程序并重新运行程序:
总结
序列化协议 | 通⽤性 | 格式 | 可读性 | 序列化⼤ ⼩ | 序列化性能 | 适⽤场景 |
---|---|---|---|---|---|---|
JSON | 通⽤(json、 xml已成为多种 ⾏业标准的编 写⼯具) | ⽂本格式 | 好 | 轻量(使 ⽤键值对 ⽅式,压 缩了⼀定 的数据空 间) | 中 | web项⽬。因为浏览 器对于json数据⽀持 ⾮常好,有很多内建 的函数⽀持。 |
XML | 通⽤ | ⽂本格式 | 好 | 重量(数 据冗余, 因为需要 成对的闭 合标签) | 低 | XML作为⼀种扩展标 记语⾔,衍⽣出了 HTML、RDF/RDFS, 它强调数据结构化的 能⼒和可读性。 |
ProtoBuf | 独⽴ (Protobuf只 是Google公司 内部的⼯具) | ⼆进制格式 | 差(只能 反序列化 后得到真 正可读的 数据) | 轻量(⽐ JSON更轻 量,传输 起来带宽 和速度会 有优化) | ⾼ | 适合⾼性能,对响应 速度有要求的数据传 输场景。Protobuf⽐ XML、JSON更⼩、 更快。 |
总结
-
XML、JSON、ProtoBuf都具有数据结构化和数据序列化的能⼒。
-
XML、JSON更注重数据结构化,关注可读性和语义表达能⼒。ProtoBuf更注重数据序列化,关注
效率、空间、速度,可读性差,语义表达能⼒不⾜,为保证极致的效率,会舍弃⼀部分元信息。 -
ProtoBuf的应⽤场景更为明确,XML、JSON的应⽤场景更为丰富。