【简易版】Linux下Protobuf 实现网络版通讯录--C++

2023-12-12 00:36

本文主要是介绍【简易版】Linux下Protobuf 实现网络版通讯录--C++,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

一、介绍

该项目的主要目的是用于熟悉protobuf的使用,体验数据在网络中序列化反序列化的形式,并非一个完整的项目。

该通讯录只实现了增加联系人的功能。服务器端接收到请求后会将联系人的信息打印。

二、环境搭建

使用Httplib库,可以快速完成客户端与服务器的搭建。

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

点击如图httplib.h文件,然后再点击下载该文件。
 

下载好后,在linux下使用rz指令将该头文件上传到linux的项目路径下。

注意:

如果使⽤centOS环境,yum源带的g++最新版本是4.8.5,发布于2015年,年代久远。编译该项⽬会出现异常。将gcc/g++升级为更⾼版本可解决问题。
可以通过指令gcc -v查看gcc的版本,如果以8.开头的版本,则需要更新一下。

更新方法可以参考这篇文章:CentOS 7上升级/安装gcc - 掘金

syntax = "proto3"; 
package add_contact;// 新增联系人 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; // 电话
}                                  message AddContactResponse{bool success = 1;  // 服务调用是否成功string error_desc = 2;  //错误原因    string uid = 3;                   
} 

五、自定义异常类

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

六、客户端

客户端使用前面下载的httplib.h中的Client类,来实现客户端的构造:

Client cli(CONTACTS_HOST, CONTACTS_PORT);

参数包含CONTACTS_HOST服务器的ip地址,CONTACTS_PORT服务器的端口号,来与服务器建立链接。

void addContact()
{Client cli(CONTACTS_HOST, CONTACTS_PORT); // 构造reqadd_contact::AddContactRequest req;buidAddContactRequest(&req);// 序列化string req_str;if(!req.SerializeToString(&req_str)){throw ContactException("AddContactRequest is fail");}// 发起post调用auto res = cli.Post("/contacts/add", req_str, "application/protobuf");if(!res)  // 失败{string err_desc;err_desc.append("/contacts/add 链接失败! 错误信息: ").append(httplib::to_string(res.error()));throw ContactException(err_desc);}//反序列化add_contact::AddContactResponse resp;bool parse = resp.ParseFromString(res->body);if(res->status != 200 && !parse){string err_desc;err_desc.append("/contact/add 调用失败").append(std::to_string(res->status)).append("(").append(res->reason).append(")");throw ContactException(err_desc);}else if(res->status != 200){string err_desc;err_desc.append("/contact/add 调用失败").append(std::to_string(res->status)).append("(").append(res->reason).append(") 错误原因").append(resp.error_desc());throw ContactException(err_desc);}else if(!resp.success()){string err_desc;err_desc.append("/contact/add 结果异常").append("异常原因:").append(resp.error_desc());throw ContactException(err_desc);}// 结果打印cout<< "新增联系人成功,联系人ID:"<<resp.uid()<<endl;
}

完整代码

#include <iostream>
#include "httplib.h"
#include "ContactsException.h"
#include "add_contact.pb.h"using namespace httplib;
using namespace std;#define CONTACTS_HOST "120.55.73.49"
#define CONTACTS_PORT 8080void addContact();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 OPTION{QUIT = 0,ADD,DEL,FIND_ONE,FIND_ALL};while(true){menu();cout<< "请选择:"<<endl;int choose;cin >> choose;cin.ignore(256, '\n');try{switch(choose){case OPTION::QUIT:cout<<"---程序退出---"<<endl;return 0;case OPTION::ADD:addContact();break;case OPTION::DEL:break;case OPTION::FIND_ONE:break;case OPTION::FIND_ALL:break;default:cout<<"选择错误,请重新选择:"<<endl;break;}}catch(const ContactException& e){cout<< "---操作通讯录时发生异常"<<endl<< "---异常信息:"<< e.what() <<endl;}}return 0;
}void buidAddContactRequest(add_contact::AddContactRequest* req)
{cout<< "--------新增联系人--------"<<endl;cout<< "请输入姓名: ";string name;getline(cin, name);req->set_name(name);cout<< "请输入年龄: ";int age;cin >> age;req->set_age(age);cin.ignore(256, '\n'); // 清除输入缓冲区里面内容// 输入电话号码for(int i=0; ; i++){cout<< "请输入联系人电话"<< i+1 <<"(只输入回车表示结束)"<<endl;string number;getline(cin, number);if(number.empty()){break;}add_contact::AddContactRequest_Phone* phone = req->add_phone();phone->set_number(number);cout<< "请输入电话类型(1.移动电话 2.座机): ";int type;cin>>type;cin.ignore(256, '\n');switch(type){case 1:phone->set_type(add_contact::AddContactRequest_Phone_PhoneType::AddContactRequest_Phone_PhoneType_MP);break;case 2:phone->set_type(add_contact::AddContactRequest_Phone_PhoneType::AddContactRequest_Phone_PhoneType_TEL);break;default:cout<<"输入有误"<<endl;break;}}
}void addContact()
{Client cli(CONTACTS_HOST, CONTACTS_PORT); // 构造reqadd_contact::AddContactRequest req;buidAddContactRequest(&req);// 序列化string req_str;if(!req.SerializeToString(&req_str)){throw ContactException("AddContactRequest is fail");}// 发起post调用auto res = cli.Post("/contacts/add", req_str, "application/protobuf");if(!res){string err_desc;err_desc.append("/contacts/add 链接失败! 错误信息: ").append(httplib::to_string(res.error()));throw ContactException(err_desc);}//反序列化add_contact::AddContactResponse resp;bool parse = resp.ParseFromString(res->body);if(res->status != 200 && !parse){string err_desc;err_desc.append("/contact/add 调用失败").append(std::to_string(res->status)).append("(").append(res->reason).append(")");throw ContactException(err_desc);}else if(res->status != 200){string err_desc;err_desc.append("/contact/add 调用失败").append(std::to_string(res->status)).append("(").append(res->reason).append(") 错误原因").append(resp.error_desc());throw ContactException(err_desc);}else if(!resp.success()){string err_desc;err_desc.append("/contact/add 结果异常").append("异常原因:").append(resp.error_desc());throw ContactException(err_desc);}// 结果打印cout<< "新增联系人成功,联系人ID:"<<resp.uid()<<endl;
}

七、服务器

void printContact(add_contact::AddContactRequest &req)打印req所包含的联系人信息

static unsigned int random_char();生成一个0~255范围内的随机数

static std::string generate_hex(const unsigned int len);⽣成 UUID (通⽤唯⼀标识符)

std::hex:将流中的整数以十六进制形式输出。

Server server:使用httplib.h中的Server类,构造服务器。

server.Post():参数中使用了lambda表达式,接收客户端的请求和响应。

#include <iostream>
#include "httplib.h"
#include "add_contact.pb.h"using namespace httplib;
using namespace std;// ⾃定义异常类
class ContactException
{
private:std::string message;public:ContactException(std::string str = "A problem") : message{str} {}std::string what() const { return message; }
};void printContact(add_contact::AddContactRequest &req)
{cout << "姓名:" << req.name() << endl;cout << "年龄:" << req.age() << endl;for (int j = 0; j < req.phone_size(); j++){const add_contact::AddContactRequest_Phone &phone = req.phone(j);cout << "联系电话:" << phone.number()<< " (" << phone.PhoneType_Name(phone.type()) << ")" << endl;// phone.type() 获取的是int值,phone.PhoneType_Name(phone.type())是根据int值转为对应的常量名(string)。}
}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;  // 生成的随机数转为16进制流auto hex = hexstream.str();  // 转为字符串形式ss << (hex.length() < 2 ? '0' + hex : hex);}return ss.str();
}int main()
{cout << "--------服务启动---------" << endl;Server server;server.Post("/contacts/add", [](const Request &req, Response &res){cout << "接收到post请求!" << endl;// 反序列化add_contact::AddContactRequest request;add_contact::AddContactResponse response;try{if (!request.ParseFromString(req.body)){throw ContactException("AddContactRequest 反序列化失败");}// 新增联系人持久化逻辑--》打印新增联系人信息printContact(request);// 构造返回结果 response.bodyresponse.set_success(true);response.set_uid(generate_hex(10));// res.body (序列化response)string response_str;if (!response.SerializeToString(&response_str)){throw ContactException("ADDcontactRequest序列化失败");}res.status = 200;res.body = response_str;res.set_header("Contact-Type", "application/protobuf");}catch (const ContactException &e){res.status = 500;response.set_success(false);response.set_error_desc(e.what());string response_str;if (response.SerializeToString(&response_str)){res.body = response_str;res.set_header("Content-type", "application/protobuf");}cout << "/contact/add 发生异常, 信息:" << e.what() << endl;} });// 绑定端口,且对外开放server.listen("0.0.0.0", 8080);return 0;
}

八、运行结果

这篇关于【简易版】Linux下Protobuf 实现网络版通讯录--C++的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/482633

相关文章

关于集合与数组转换实现方法

《关于集合与数组转换实现方法》:本文主要介绍关于集合与数组转换实现方法,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录1、Arrays.asList()1.1、方法作用1.2、内部实现1.3、修改元素的影响1.4、注意事项2、list.toArray()2.1、方

使用Python实现可恢复式多线程下载器

《使用Python实现可恢复式多线程下载器》在数字时代,大文件下载已成为日常操作,本文将手把手教你用Python打造专业级下载器,实现断点续传,多线程加速,速度限制等功能,感兴趣的小伙伴可以了解下... 目录一、智能续传:从崩溃边缘抢救进度二、多线程加速:榨干网络带宽三、速度控制:做网络的好邻居四、终端交互

从入门到精通C++11 <chrono> 库特性

《从入门到精通C++11<chrono>库特性》chrono库是C++11中一个非常强大和实用的库,它为时间处理提供了丰富的功能和类型安全的接口,通过本文的介绍,我们了解了chrono库的基本概念... 目录一、引言1.1 为什么需要<chrono>库1.2<chrono>库的基本概念二、时间段(Durat

java实现docker镜像上传到harbor仓库的方式

《java实现docker镜像上传到harbor仓库的方式》:本文主要介绍java实现docker镜像上传到harbor仓库的方式,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地... 目录1. 前 言2. 编写工具类2.1 引入依赖包2.2 使用当前服务器的docker环境推送镜像2.2

C++20管道运算符的实现示例

《C++20管道运算符的实现示例》本文简要介绍C++20管道运算符的使用与实现,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧... 目录标准库的管道运算符使用自己实现类似的管道运算符我们不打算介绍太多,因为它实际属于c++20最为重要的

Java easyExcel实现导入多sheet的Excel

《JavaeasyExcel实现导入多sheet的Excel》这篇文章主要为大家详细介绍了如何使用JavaeasyExcel实现导入多sheet的Excel,文中的示例代码讲解详细,感兴趣的小伙伴可... 目录1.官网2.Excel样式3.代码1.官网easyExcel官网2.Excel样式3.代码

Visual Studio 2022 编译C++20代码的图文步骤

《VisualStudio2022编译C++20代码的图文步骤》在VisualStudio中启用C++20import功能,需设置语言标准为ISOC++20,开启扫描源查找模块依赖及实验性标... 默认创建Visual Studio桌面控制台项目代码包含C++20的import方法。右键项目的属性:

Linux中SSH服务配置的全面指南

《Linux中SSH服务配置的全面指南》作为网络安全工程师,SSH(SecureShell)服务的安全配置是我们日常工作中不可忽视的重要环节,本文将从基础配置到高级安全加固,全面解析SSH服务的各项参... 目录概述基础配置详解端口与监听设置主机密钥配置认证机制强化禁用密码认证禁止root直接登录实现双因素

python实现对数据公钥加密与私钥解密

《python实现对数据公钥加密与私钥解密》这篇文章主要为大家详细介绍了如何使用python实现对数据公钥加密与私钥解密,文中的示例代码讲解详细,感兴趣的小伙伴可以跟随小编一起学习一下... 目录公钥私钥的生成使用公钥加密使用私钥解密公钥私钥的生成这一部分,使用python生成公钥与私钥,然后保存在两个文

浏览器插件cursor实现自动注册、续杯的详细过程

《浏览器插件cursor实现自动注册、续杯的详细过程》Cursor简易注册助手脚本通过自动化邮箱填写和验证码获取流程,大大简化了Cursor的注册过程,它不仅提高了注册效率,还通过友好的用户界面和详细... 目录前言功能概述使用方法安装脚本使用流程邮箱输入页面验证码页面实战演示技术实现核心功能实现1. 随机