当前位置: 首页 > news >正文

利用ProtoBuf 实现网络版通讯录

目录

  • 1. 网络版通讯录概述
  • 2. 环境搭建
  • 3. 约定双端交互接口
  • 4. 约定双端交互req/resp
  • 5. 客户端代码实现
  • 6. 服务端代码实现
  • 7 序列化能力对比验证
  • 8. 总结

1. 网络版通讯录概述

(1)Protobuf 还常用于通讯协议、服务端数据交换场景。那么在这个是例中,我们将实现⼀个网络版本的通讯录,模拟实现客户端与服务端的交互,通过 Protobuf 来实现各端之间的协议序列化。 需求如下:

  • 客户端可以选择对通讯录进行以下操作:
    • 新增⼀个联系人。
    • 删除⼀个联系人。
    • 查询通讯录列表。
    • 查询⼀个联系⼈的详细信息。
  • 服务端提供 增 删 查 能力,并需要持久化通讯录。
  • 客户端、服务端间的交互数据使用 Protobuf 来完成。

2. 环境搭建

(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

(2)CentOS 下编写的注意事项:

  • 如果使用 CentOS 环境,yum源带的 g++ 最新版本是4.8.5,发布于2015年,年代久远。编译该项目会出现异常。将 gcc/g++ 升级为更高版本可解决问题。
# 升级参考:https://juejin.cn/post/6844903873111392263
# 安装gcc 8版本
yum install -y devtoolset-8-gcc devtoolset-8-gcc-c++
# 启⽤版本
source /opt/rh/devtoolset-8/enable
# 查看版本已经变成gcc 8.3.1
gcc -v

3. 约定双端交互接口

(1)新增一个联系人:

[请求]Post /contacts/add AddContactRequestContent-Type: application/protobuf
[响应]AddContactResponseContent-Type: application/protobuf

(2)删除一个联系人:

[请求]Post /contacts/del DelContactRequestContent-Type: application/protobuf
[响应]DelContactResponseContent-Type: application/protobuf

(3)查询通讯录列表:

[请求]GET /contacts/find-all
[响应]FindAllContactsResponseContent-Type: application/protobuf

(4)查询一个联系人的详细信息:

[请求]Post /contacts/find-one FindOneContactRequestContent-Type: application/protobuf
[响应]FindOneContactResponseContent-Type: application/protobuf

4. 约定双端交互req/resp

(1)base_response.proto:

syntax = "proto3";
package base_response;message BaseResponse {bool success = 1; // 返回结果string error_desc = 2; // 错误描述
}

(2)add_contact_request.proto:

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; // 备注
}

(3)add_contact_response.proto:

syntax = "proto3";
package add_contact_resp;
import "base_response.proto"; // 引⼊base_responsemessage AddContactResponse {base_response.BaseResponse base_resp = 1;string uid = 2;
}

(4)del_contact_request.proto:

syntax = "proto3";
package del_contact_req;// 删除⼀个联系⼈ req
message DelContactRequest {string uid = 1; // 联系⼈ID
}

(5)del_contact_response.proto:

syntax = "proto3";
package del_contact_resp;
import "base_response.proto"; // 引⼊base_response// 删除⼀个联系⼈ resp
message DelContactResponse {base_response.BaseResponse base_resp = 1;string uid = 2;
}

(6)find_one_contact_request.proto:

syntax = "proto3";
package find_one_contact_req;// 查询⼀个联系⼈ req
message FindOneContactRequest {string uid = 1; // 联系⼈ID
}

(7)find_one_contact_response.proto:

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; // 备注
}

(8)find_all_contacts_response.proto:

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;
}

5. 客户端代码实现

(1)main.cpp:

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};ContactsServer contactsServer;while (true) {menu();std::cout << "---> 请选择:";int choose;std::cin >> choose;std::cin.ignore(256, '\n');try {switch (choose) {case OPERATE::ADD:contactsServer.addContact();break;case OPERATE::DEL:contactsServer.delContact();break;case OPERATE::FIND_ALL:contactsServer.findContacts();break;case OPERATE::FIND_ONE:contactsServer.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;}}
}

(2)ContactException.h:定义异常类。

// ⾃定义异常类
class ContactException
{
private:std::string message;
public:ContactException(std::string str = "A problem") :message{str} {}std::string what() const { return message; }
};

(3)ContactsServer.h:客户端通讯录服务定义。

class ContactsServer
{
public:void addContact();void delContact();void findContacts();void findContact();private:void buildAddContactRequest(add_contact_req::AddContactRequest* req);void printFindOneContactResponse(find_one_contact_resp::FindOneContactResponse& resp);void printFindAllContactsResponse(find_all_contacts_resp::FindAllContactsResponse& resp);
};

(4)ContactsServer.cc:客户端通讯录服务实现。

#define CONTACTS_IP "43.138.218.166"
#define CONTACTS_PORT 8123void ContactsServer::addContact() 
{httplib::Client cli(CONTACTS_IP, CONTACTS_PORT);// 构建 request 请求add_contact_req::AddContactRequest req;buildAddContactRequest(&req);// 序列化 requeststd::string req_str;if (!req.SerializeToString(&req_str)) {throw ContactException("AddContactRequest序列化失败!");}// 发起 post 请求auto res = cli.Post("/contacts/add", req_str, "application/protobuf");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;
}void ContactsServer::delContact() 
{httplib::Client cli(CONTACTS_IP, CONTACTS_PORT);// 构建 request 请求del_contact_req::DelContactRequest 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("DelContactRequest序列化失败!");}// 发起 post 请求auto res = cli.Post("/contacts/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);}// 反序列化 responsedel_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;
}void ContactsServer::findContacts() 
{httplib::Client cli(CONTACTS_IP, CONTACTS_PORT);// 发起 get 请求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);}// 反序列化 responsefind_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);
}void ContactsServer::findContact() 
{httplib::Client cli(CONTACTS_IP, CONTACTS_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序列化失败!");}// 发起 post 请求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);}// 反序列化 responsefind_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);
}void ContactsServer::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 ContactsServer::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 ContactsServer::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;}
}

6. 服务端代码实现

(1)服务端存储通讯录结构定义:contacts.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; // 备注
}
// 通讯录
message Contacts {map<string, PeopleInfo> contacts = 1;
}

(2)main.cpp:

using std::cout;
using std::endl;
using std::cerr;
using namespace httplib;int main() 
{cout << "---> 服务启动..." << endl;Server srv; // 创建服务端对象ContactsServer contactsServer;srv.Post("/contacts/add", [contactsServer](const Request& req, Response& res) {add_contact_req::AddContactRequest request;add_contact_resp::AddContactResponse response;try {// 反序列化 requestif (!request.ParseFromString(req.body)) {throw ContactException("Parse AddContactRequest error!");}// 新增联系⼈contactsServer.add(request, &response);// 序列化 respstd::string response_str;if (!response.SerializeToString(&response_str)) {throw ContactException("Serialize AddContactResponse error");}res.body = response_str;res.set_header("Content-Type", "application/protobuf");res.status = 200;} catch (ContactException &e) {cerr << "---> /contacts/add 发现异常!!!" << endl << "---> 异常信息:" << e.what() << endl;res.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)) {res.body = response_str;res.set_header("Content-Type", "application/protobuf");}}});srv.Post("/contacts/del", [contactsServer](const Request& req, Response& res) {del_contact_req::DelContactRequest request;del_contact_resp::DelContactResponse response;try {// 反序列化 requestif (!request.ParseFromString(req.body)) {throw ContactException("Parse DelContactRequest error!");}// 删除联系⼈contactsServer.del(request, &response);// 序列化 responsestd::string response_str;if (!response.SerializeToString(&response_str)) {throw ContactException("Serialize DelContactResponse error");}res.body = response_str;res.set_header("Content-Type", "application/protobuf");res.status = 200;} catch (ContactException &e) {cerr << "---> /contacts/del 发现异常!!!" << endl << "---> 异常信息:" << e.what() << endl;res.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)) {res.body = response_str;res.set_header("Content-Type", "application/protobuf");}}});srv.Post("/contacts/find-one", [contactsServer](const Request& req, Response& res) {find_one_contact_req::FindOneContactRequest request;find_one_contact_resp::FindOneContactResponse response;try {// 反序列化 requestif (!request.ParseFromString(req.body)) {throw ContactException("Parse FindOneContactRequest error!");}// 查询联系⼈详细信息contactsServer.findOne(request, &response);// 序列化 responsestd::string response_str;if (!response.SerializeToString(&response_str)) {throw ContactException("Serialize FindOneContactResponseerror");}res.body = response_str;res.set_header("Content-Type", "application/protobuf");res.status = 200;} catch (ContactException &e) {cerr << "---> /contacts/find-one 发现异常!!!" << endl<< "---> 异常信息:" << e.what() << endl;res.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)) {res.body = response_str;res.set_header("Content-Type", "application/protobuf");}}});srv.Get("/contacts/find-all", [contactsServer](const Request& req, Response& res) {find_all_contacts_resp::FindAllContactsResponse response;try {// 查询所有联系⼈contactsServer.findAll(&response);// 序列化 responsestd::string response_str;if (!response.SerializeToString(&response_str)) {throw ContactException("Serialize FindAllContactsResponse error");}res.body = response_str;res.set_header("Content-Type", "application/protobuf");res.status = 200;} catch (ContactException &e) {cerr << "---> /contacts/find-all 发现异常!!!" << endl << "---> 异常信息:" << e.what() << endl;res.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)) {res.body = response_str;res.set_header("Content-Type", "application/protobuf");}}});srv.listen("0.0.0.0", 8123);
}

(3)ContactException.h:定义异常类。

// ⾃定义异常类
class ContactException
{
private:std::string message;public:ContactException(std::string str = "A problem") : message{str} {}std::string what() const { return message; }
}

(4)ContactsServer.h:通讯录服务定义。

using namespace httplib;class ContactsServer 
{
public:ContactsMapper contactsMapper;public:void add(add_contact_req::AddContactRequest& request,add_contact_resp::AddContactResponse* response) const;void del(del_contact_req::DelContactRequest& request,del_contact_resp::DelContactResponse* response) const;void findOne(find_one_contact_req::FindOneContactRequest request, find_one_contact_resp::FindOneContactResponse* response) const;void findAll(find_all_contacts_resp::FindAllContactsResponse* rsp) const;private:void printAddContactRequest(add_contact_req::AddContactRequest& request) const;void buildPeopleInfo(contacts::PeopleInfo* people, add_contact_req::AddContactRequest& request) const;void buildFindOneContactResponse(const contacts::PeopleInfo& people, find_one_contact_resp::FindOneContactResponse* response) const;void buildFindAllContactsResponse(contacts::Contacts& contacts, find_all_contacts_resp::FindAllContactsResponse* rsp) const;
};

(5)ContactsServer.cpp:通讯录服务实现。

using std::cout;
using std::endl;void ContactsServer::add(add_contact_req::AddContactRequest& request, add_contact_resp::AddContactResponse* response) const 
{// 打印日志printAddContactRequest(request);      // 先读取已存在的 contactscontacts::Contacts contacts;contactsMapper.selectContacts(&contacts);// 转换为存入文件的消息对象  google::protobuf::Map<std::string, contacts::PeopleInfo>* map_contacts = contacts.mutable_contacts();contacts::PeopleInfo people;buildPeopleInfo(&people, request);   map_contacts->insert({people.uid(), people});   // 向磁盘文件写入新的 contactscontactsMapper.insertContacts(contacts);response->set_uid(people.uid());response->mutable_base_resp()->set_success(true);// 打印日志cout << "---> (ContactsServer::add) Success to write contacts." << endl;
}void ContactsServer::del(del_contact_req::DelContactRequest& request,del_contact_resp::DelContactResponse* response) const 
{// 打印日志cout << "---> (ContactsServer::del) DelContactRequest: uid: " << request.uid() << endl;  // 先读取已存在的 contactscontacts::Contacts contacts;contactsMapper.selectContacts(&contacts);// 不含uid直接返回if (contacts.contacts().find(request.uid()) == contacts.contacts().end()) {  cout << "---> (ContactsServer::del) not find uid: " << request.uid() << endl; response->set_uid(request.uid());response->mutable_base_resp()->set_success(false);response->mutable_base_resp()->set_error_desc("not find uid");return;}// 删除用户contacts.mutable_contacts()->erase(request.uid());// 向磁盘文件写入新的 contactscontactsMapper.insertContacts(contacts);// 构造respresponse->set_uid(request.uid());response->mutable_base_resp()->set_success(true);// 打印日志cout << "---> (ContactsServer::del) Success to del contact, uid: " << request.uid() << endl;
}void ContactsServer::findOne(find_one_contact_req::FindOneContactRequest request, find_one_contact_resp::FindOneContactResponse* response) const 
{// 打印日志cout << "---> (ContactsServer::findOne) FindOneContactRequest: uid: " << request.uid() << endl;  // 获取通讯录contacts::Contacts contacts;contactsMapper.selectContacts(&contacts);// 转换resp消息对象const google::protobuf::Map<std::string, contacts::PeopleInfo>& map_contacts = contacts.contacts();auto it = map_contacts.find(request.uid());// 查找的联系人不存在if (it == map_contacts.end()) {cout << "---> (ContactsServer::findOne) not find uid: " << request.uid() << endl;response->mutable_base_resp()->set_success(false);response->mutable_base_resp()->set_error_desc("uid not exist");return;}// 构建respbuildFindOneContactResponse(it->second, response);// 打印日志cout << "---> (ContactsServer::findOne) find uid: " << request.uid() << endl;
}void ContactsServer::findAll(find_all_contacts_resp::FindAllContactsResponse* rsp) const 
{// 打印日志cout << "---> (ContactsServer::findAll) " << endl;  // 获取通讯录contacts::Contacts contacts;contactsMapper.selectContacts(&contacts);// 转换resp消息对象buildFindAllContactsResponse(contacts, rsp);
}void ContactsServer::buildFindAllContactsResponse(contacts::Contacts& contacts, find_all_contacts_resp::FindAllContactsResponse* rsp) const 
{if (nullptr == rsp) {return;}rsp->mutable_base_resp()->set_success(true);for (auto it = contacts.contacts().cbegin(); it != contacts.contacts().cend(); ++it) {find_all_contacts_resp::PeopleInfo* people = rsp->add_contacts();people->set_uid(it->first);people->set_name(it->second.name());}
}void ContactsServer::buildFindOneContactResponse(const contacts::PeopleInfo& people, find_one_contact_resp::FindOneContactResponse* response) const 
{if (nullptr == response) {return;}response->mutable_base_resp()->set_success(true);response->set_uid(people.uid());response->set_name(people.name());response->set_age(people.age());for (auto& phone : people.phone()) {find_one_contact_resp::FindOneContactResponse_Phone* resp_phone = response->add_phone();resp_phone->set_number(phone.number());switch (phone.type()) {case contacts::PeopleInfo_Phone_PhoneType::PeopleInfo_Phone_PhoneType_MP:resp_phone->set_type(find_one_contact_resp::FindOneContactResponse_Phone_PhoneType::FindOneContactResponse_Phone_PhoneType_MP);break;case contacts::PeopleInfo_Phone_PhoneType::PeopleInfo_Phone_PhoneType_TEL:resp_phone->set_type(find_one_contact_resp::FindOneContactResponse_Phone_PhoneType::FindOneContactResponse_Phone_PhoneType_TEL);break;default:break;}}Utils::map_copy(response->mutable_remark(), people.remark());
}void ContactsServer::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 ContactsServer::buildPeopleInfo(contacts::PeopleInfo* people, add_contact_req::AddContactRequest& request) const 
{std::string uid = Utils::generate_hex(10);people->set_uid(uid);people->set_name(request.name());people->set_age(request.age());for (auto& phone : request.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;}}Utils::map_copy(people->mutable_remark(), request.remark());
}

(6)Utils.h:定义工具类。

#include <sstream>
#include <random>
#include <google/protobuf/map.h>class Utils
{
public: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);}// 生成 UUID (通用唯一标识符)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});}  }
};

(7)ContactsMapper.h:持久化存储通讯录方法定义。

class ContactsMapper 
{
public:void selectContacts(contacts::Contacts* contacts) const;void insertContacts(contacts::Contacts& contacts) const;
}

(8)ContactsMapper.cc:持久化存储通讯录方法实现。

  • 注意:本应该存如数据库中,在这里为了简化流程,将通讯录存入本地文件。
#define TEXT_NAME "contacts.bin"
using std::ios;
using std::cout;
using std::endl;// 本应该存⼊数据库中,在这⾥为了简化流程,将通讯录存⼊本地⽂件
void ContactsMapper::selectContacts(contacts::Contacts* contacts) const
{std::fstream input(TEXT_NAME, ios::in | ios::binary);if (!input) {cout << "---> (ContactsMapper::selectContacts) " << TEXT_NAME << ":File not found. Creating a new file." << endl;}else if (!contacts->ParseFromIstream(&input)){input.close();throw ContactException("(ContactsMapper::selectContacts) Failed to parse contacts.");}input.close();
}void ContactsMapper::insertContacts(contacts::Contacts& contacts) const 
{std::fstream output(TEXT_NAME, ios::out | ios::trunc | ios::binary);if (!contacts.SerializeToOstream(&output)) {output.close();throw ContactException("(ContactsMapper::insertContacts) Failed to write contacts.");}output.close();
}

7 序列化能力对比验证

(1)在这里让我们分别使用 PB 与 JSON 的序列化与反序列化能力, 对值完全相同的⼀份结构化数据进行不同次数的性能测试。

  • 为了可读性,下面这一份文本使用 JSON 格式展示了需要被进行测试的结构化数据内容:
{"age" : 20,"name" : "张珊","phone" :[{"number" : "110112119","type" : 0},{"number" : "110112119","type" : 0},{"number" : "110112119","type" : 0},{"number" : "110112119","type" : 0},{"number" : "110112119","type" : 0}],"qq" : "95991122","address" :{"home_address" : "陕西省西安市⻓安区","unit_address" : "陕西省西安市雁塔区"},"remark" :{"key1" : "value1","key2" : "value2","key3" : "value3","key4" : "value4","key5" : "value5"}
}

(2)开始进行测试代码编写,我们在新的目录下新建 contacts.proto⽂件,内容如下:

syntax = "proto3";
package compare_serialization;
import "google/protobuf/any.proto"; // 引⼊ any.proto ⽂件// 地址
message Address{string home_address = 1; // 家庭地址string unit_address = 2; // 单位地址
}// 联系⼈
message PeopleInfo {string name = 1; // 姓名int32 age = 2; // 年龄message Phone {string number = 1; // 电话号码enum PhoneType {MP = 0; // 移动电话TEL = 1; // 固定电话}PhoneType type = 2; // 类型}repeated Phone phone = 3; // 电话google.protobuf.Any data = 4;oneof other_contact { // 其他联系⽅式:多选⼀string qq = 5;string weixin = 6;}map<string, string> remark = 7; // 备注
}

(3)使用 protoc 命令编译文件后,新建性能测试文件 compare.cc,我们分别对相同的结构化数据进行 100 、 1000 、 10000 、 100000 次的序列化与反序列化,分别获取其耗时与序列化后的大小。内容如下:

#include <iostream>
#include <sys/time.h>
#include <jsoncpp/json/json.h>
#include "contacts.pb.h"using namespace std;
using namespace compare_serialization;
using namespace google::protobuf;#define TEST_COUNT 100
void createPeopleInfoFromPb(PeopleInfo *people_info_ptr);
void createPeopleInfoFromJson(Json::Value& root);int main(int argc, char *argv[])
{struct timeval t_start,t_end;double time_used;int count;string pb_str, json_str;// ------------------------------Protobuf 序列化------------------------------------{PeopleInfo pb_people;createPeopleInfoFromPb(&pb_people);count = TEST_COUNT;gettimeofday(&t_start, NULL);// 序列化count次while ((count--) > 0) {pb_people.SerializeToString(&pb_str);}gettimeofday(&t_end, NULL);time_used = 1000000*(t_end.tv_sec - t_start.tv_sec) + t_end.tv_usec - t_start.tv_usec;cout << TEST_COUNT << "次 [pb序列化]耗时:" << time_used/1000 << "ms." << " 序列化后的⼤⼩:" << pb_str.length() << endl;}// ------------------------------Protobuf 反序列化------------------------------------{PeopleInfo pb_people;count = TEST_COUNT;gettimeofday(&t_start, NULL);// 反序列化count次while ((count--) > 0) {pb_people.ParseFromString(pb_str);}gettimeofday(&t_end, NULL);time_used = 1000000*(t_end.tv_sec - t_start.tv_sec) + t_end.tv_usec - t_start.tv_usec;cout << TEST_COUNT << "次 [pb反序列化]耗时:" << time_used / 1000 << "ms." << endl;}// ------------------------------JSON 序列化------------------------------------{Json::Value json_people;createPeopleInfoFromJson(json_people);Json::StreamWriterBuilder builder;count = TEST_COUNT;gettimeofday(&t_start, NULL);// 序列化count次while ((count--) > 0) {json_str = Json::writeString(builder, json_people);}gettimeofday(&t_end, NULL);// 打印序列化结果// cout << "json: " << endl << json_str << endl;time_used = 1000000*(t_end.tv_sec - t_start.tv_sec) + t_end.tv_usec - t_start.tv_usec;cout << TEST_COUNT << "次 [json序列化]耗时:" << time_used/1000 << "ms." << " 序列化后的⼤⼩:" << json_str.length() << endl;}// ------------------------------JSON 反序列化------------------------------------{Json::CharReaderBuilder builder;unique_ptr<Json::CharReader> reader(builder.newCharReader());Json::Value json_people;count = TEST_COUNT;gettimeofday(&t_start, NULL);// 反序列化count次while ((count--) > 0) {reader->parse(json_str.c_str(), json_str.c_str() + json_str.length(), &json_people, nullptr);}gettimeofday(&t_end, NULL);time_used=1000000*(t_end.tv_sec - t_start.tv_sec) + t_end.tv_usec -t_start.tv_usec;cout << TEST_COUNT << "次 [json反序列化]耗时:" << time_used/1000 << "ms." << endl;}return 0;
}/**
* 构造pb对象
*/
void createPeopleInfoFromPb(PeopleInfo *people_info_ptr)
{people_info_ptr->set_name("张珊");people_info_ptr->set_age(20);people_info_ptr->set_qq("95991122");for(int i = 0; i < 5; i++) {PeopleInfo_Phone* phone = people_info_ptr->add_phone();phone->set_number("110112119");phone->set_type(PeopleInfo_Phone_PhoneType::PeopleInfo_Phone_PhoneType_MP);}Address address;address.set_home_address("陕西省西安市⻓安区");address.set_unit_address("陕西省西安市雁塔区");google::protobuf::Any * data = people_info_ptr->mutable_data();data->PackFrom(address);people_info_ptr->mutable_remark()->insert({"key1", "value1"});people_info_ptr->mutable_remark()->insert({"key2", "value2"});people_info_ptr->mutable_remark()->insert({"key3", "value3"});people_info_ptr->mutable_remark()->insert({"key4", "value4"});people_info_ptr->mutable_remark()->insert({"key5", "value5"});
}/**
* 构造json对象
*/
void createPeopleInfoFromJson(Json::Value& root) 
{root["name"] = "张珊";root["age"] = 20;root["qq"] = "95991122";for(int i = 0; i < 5; i++) {Json::Value phone;phone["number"] = "110112119";phone["type"] = 0;root["phone"].append(phone);}Json::Value address;address["home_address"] = "陕西省西安市⻓安区";address["unit_address"] = "陕西省西安市雁塔区";root["address"] = address;Json::Value remark;remark["key1"] = "value1";remark["key2"] = "value2";remark["key3"] = "value3";remark["key4"] = "value4";remark["key5"] = "value5";root["remark"] = remark;
}

(4)Makefile :

compare:compare.cc contacts.pb.ccg++ -o $@ $^ -std=c++11 -lprotobuf -ljsoncpp.PHONY:clean
clean:rm -f compare

(5)测试结果如下:

100[pb序列化]耗时:0.342ms. 序列化后的⼤⼩:278
100[pb反序列化]耗时:0.435ms.
100[json序列化]耗时:1.306ms. 序列化后的⼤⼩:567
100[json反序列化]耗时:0.926ms.1000[pb序列化]耗时:3.59ms. 序列化后的⼤⼩:278
1000[pb反序列化]耗时:5.069ms.
1000[json序列化]耗时:11.582ms. 序列化后的⼤⼩:567
1000[json反序列化]耗时:9.289ms.10000[pb序列化]耗时:34.386ms. 序列化后的⼤⼩:278
10000[pb反序列化]耗时:45.96ms.
10000[json序列化]耗时:115.76ms. 序列化后的⼤⼩:567
10000[json反序列化]耗时:91.046ms.100000[pb序列化]耗时:349.937ms. 序列化后的⼤⼩:278
100000[pb反序列化]耗时:428.366ms.
100000[json序列化]耗时:1150.54ms. 序列化后的⼤⼩:567
100000[json反序列化]耗时:904.58ms.

(6)由实验结果可得:

  • 编解码性能:ProtoBuf 的编码解码性能,比 JSON 高出 2-4 倍。
  • 内存占用:ProtoBuf 的内存278,而JSON到达567,ProtoBuf的内存占用只有JSON的1/2。
  • 注意:以上结论的数据只是根据该项实验得出。因为受不同的字段类型、字段个数等影响,测出的数据会有所差异。
  • 该实验有很多可待优化的地方。但其实这种粗略的测试,也能看出来 ProtoBuf 的优势。

8. 总结

(1)对比表格如下:

序列化协议通用性格式可读性序列化大小序列化性能适用场景
JSON通用 (json、xml已成为多种行业标准的编写工具)文本格式轻量(使用键值对方式,压缩了⼀定的数据空间)web项目。因为浏览器对于json数据支持非常好,有很多内建的函数支持。
XML通用文本格式重量(数据冗余,因为需要成对的闭合标签)XML 作为⼀种扩展标记语言,衍生出了 HTML、RDF/RDFS,它强调数据结构化的能力和可读性。
ProtoBuf独立(Protobuf只是Google公司内部的工具)二进制格式差(只能反序列化后得到真正可读的数据)轻量(比JSON更轻量,传输起来带宽和速度会有优化)适合高性能,对响应速度有要求的数据传输场景。Protobuf比XML、JSON 更小、更快。

(2)小结:

  1. XML、JSON、ProtoBuf 都具有数据结构化和数据序列化的能力。
  2. XML、JSON 更注重数据结构化,关注可读性和语义表达能力。ProtoBuf 更注重数据序列化,关注效率、空间、速度,可读性差,语义表达能力不足,为保证极致的效率,会舍弃一部分元信息。
  3. ProtoBuf 的应用场景更为明确,XML、JSON 的应用场景更为丰富。

相关文章:

  • SpringAI 1.0.0 正式版——利用Redis存储会话(ChatMemory)
  • 电脑开不了机,主板显示67码解决过程
  • MySQL数据库表设计与索引优化终极指南
  • 如何理解机器人课程的技术壁垒~壁垒和赚钱是两件不同的事情
  • [蓝桥杯]迷宫与陷阱
  • 黄晓明新剧《潜渊》定档 失忆三面间谍开启谍战新维度
  • 驱控边界在哪里?知名舵机品牌伟创动力CNTE2025展带来答案
  • 生成大得矢量图
  • transformer和 RNN以及他的几个变体区别 改进
  • shell--文件操作
  • Java并发包中的管程:Lock和Condition
  • echarts树状图与vue3
  • 微软推出SQL Server 2025技术预览版,深化人工智能应用集成
  • “一代更比一代强”:现代 RAG 架构的演进之路
  • 408第一季 - 数据结构 - 栈与队列
  • Python读取阿里法拍网的html+解决登录cookie
  • 创客匠人:如何通过创始人IP打造实现知识变现与IP变现的长效增长?
  • 《DeepSeek R1-0528与ChatGPT o3对比分析》
  • clickhouse 学习总结
  • 第十届电子技术和信息科学国际学术会议(ICETIS 2025)
  • 永久免费建网站/aso关键词覆盖优化
  • 网站安全解决方案/定制开发公司
  • 生态农业网站模板/百度公司全称
  • 建站什么程序好/seo指的是什么意思
  • 网站外链建设可以提升网站权重对还是错/未来网络营销的发展趋势
  • 军人可以做网站吗/无忧软文网