一、介绍
该项目的主要目的是用于熟悉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);
// 构造req
add_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 8080
void 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);
// 构造req
add_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.body
response.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;
}