Protobuf实战:通讯录

news2024/11/24 20:23:39

网络版通讯录

需求

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程序,我们可以查看通讯录的目录结构以及依赖关系。

image-20230612020938377

程序运行

image-20230612021011419

当程序出现报错信息: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更⼩、 更快。

总结

  1. XML、JSON、ProtoBuf都具有数据结构化和数据序列化的能⼒。

  2. XML、JSON更注重数据结构化,关注可读性和语义表达能⼒。ProtoBuf更注重数据序列化,关注
    效率、空间、速度,可读性差,语义表达能⼒不⾜,为保证极致的效率,会舍弃⼀部分元信息。

  3. ProtoBuf的应⽤场景更为明确,XML、JSON的应⽤场景更为丰富。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/635633.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

电子科技大学编译原理复习笔记(七):自下而上语法分析

目录 前言 重点一览 引言 自下而上分析 分析方法 规范规约&#xff08;最左规约&#xff0c;对应最右推导&#xff09; 算符优先分析法 算符优先文法 最左素短语 举个例子 优先关系表的构造 规范规约与算符优先分析 LR分析法 概述 LR&#xff08;0&#xff09…

系统架构设计师 2:计算机基础

一、计算机硬件 1 处理器&#xff08;CPU&#xff09; 处理器是计算机系统运算和控制的核心部件。 1.1 指令集 处理器的指令集按照其复杂程度可分为复杂指令集&#xff08;CISC&#xff09;与精简指令集&#xff08;RISC&#xff09;。 随着研究的深入&#xff0c;RISC已经…

基于深度学习的高精度安全背心检测识别系统(PyTorch+Pyside6+YOLOv5模型)

摘要&#xff1a;基于深度学习的高精度安全背心检测识别系统可用于日常生活中或野外来检测与定位安全背心目标&#xff0c;利用深度学习算法可实现图片、视频、摄像头等方式的安全背心目标检测识别&#xff0c;另外支持结果可视化与图片或视频检测结果的导出。本系统采用YOLOv5…

2.25 sigprocmask函数使用 2.26sigaction信号捕捉函数 2.27SIGCHILD信号

2.25 sigprocmask函数使用 阻塞信号集有时称作信号掩码。 联想&#xff1a;fcntl函数可以修改fd属性。 ./sigprocmask & //将程序设置为后台运行&#xff0c;输入ls可以同步有输出 fg //将程序恢复到前台运行#include <stdio.h> #include <signal.…

动态规划dp —— 21.乘积最大子数组

1.状态表示 是什么&#xff1f;dp表中里的值所表示的含义就是状态表示 因为要考虑负数情况&#xff0c;负数乘以最大数就等于最小数了&#xff0c;负数乘以最小数就是最大数了 f[i]表示&#xff1a;以i位置为结尾的所以子数组中最大乘积 g[i]表会&#xff1a;以i位置为结尾…

Java 实现删除顺序表中第一次出现的某个元素

一、思路 1.顺序表不能是空的&#xff0c;如果顺序表是空的就肯定无法删除第一次出现的 key 元素. 2.定义一个key变量来传入要删除的元素&#xff0c;这个元素要求是第一次出现的. 3.删除之前要先找到第一次出现的key的下标. 4.找到位置之后就开始删除. 5.删除过程是从key下标位…

Linux - struct file与缓冲区

​​​​​​​ ​​​​​​​ 感谢各位 点赞 收藏 评论 三连支持 本文章收录于专栏【Linux系统编程】 ❀希望能对大家有所帮助❀ 本文章由 风君子吖 原创 ​​​​​​​ ​​​​​​​ ​​​​​​​ 前言 对于文件&#x…

DTS迁移Oracle至DM与MySQL至DM

目录 DTS迁移Oracle至DM... 3 一、前期准备... 3 二、DTS迁移... 4 1、新建工程... 5 2、新建迁移... 6 3、填写源库信息&#xff0c;使用指定驱动并自定义URL连接... 7 4、填写目标库信息... 8 5、填写迁移选项... 8 6、指定迁移模式及模式对象... 9 7、执行迁移...…

http长连接与会话保持

"我们半推半就的人生&#xff0c;没有和你一样被眷顾的未来!" 一、Http长连接 (1) 为什么需要长连接 如上展示的是一个常规得并不能再常规的http服务&#xff0c;从本地拉取远端linux上的本地文件上传至浏览器上&#xff0c;经过浏览器的渲染展示成如今的样子。唔&a…

【实战】体验训练Geneface

一.环境 conda activate geneface export PYTHONPATH./ CUDA_VISIBLE_DEVICES0 python tasks/run.py --configegs/datasets/lrs3/lm3d_syncnet.yaml --exp_namelrs3/syncnet 训练这篇出过的一些奇奇怪怪的问题基本上都记录在【环境搭建】40系一些奇奇怪怪的环境问题_weixin_50…

aigc - 文化衫设计

团队要用aigc设计个文化衫&#xff0c;就是给些提示词&#xff0c;然后让ai自动生成能够包含这些提示词的文化衫出来 二、第二版 思路&#xff1a;收集了30多张文化衫&#xff0c;然后用两种方式生成提升词&#xff1a;一个是自然语言描述这件t-short&#xff0c;一个是全名词…

IDEA常用插件Top18

前言&#xff1a;精心推荐给大家的一些日常开发中最常用的IDEA效率插件。 1、Alibaba Java Coding Guidelines代码规范检查工具 Alibaba Java Coding Guidelines ,阿里的一款强大的代码规范检查工具&#xff0c;可以让自己写出易读性更高的代码&#xff0c;可以让团队代码风格…

Keepalived+LVS

这里写目录标题 一、Keepalived及其工作原理1.1Keepalived体系主要模块及其作用1.3VRRP &#xff08;虚拟路由冗余协议&#xff09; 二、keepalived实验部署主服务器&#xff1a;备用服务器配置节点 一配置节点二 三、测试 一、Keepalived及其工作原理 Keepalived 是一个基于V…

Linux编译器 gcc、g++

绪论 你热爱生命吗&#xff1f;那幺别浪费时间&#xff0c;由于时间是组成生命的材料。——富兰克林 。 本篇文章写了主要写了Linux下编译器&#xff0c;以及编译器是如何实现编译的过程。 话不多说安全带系好&#xff0c;发车啦&#xff08;建议电脑观看&#xff09;。 附&…

2.30 守护进程(1) 2.31 守护进程(2)

2.30 守护进程&#xff08;1&#xff09; 终端 echo $$//查看当前终端的的pid tty//查看当前终端设备控制终端可以操作某一个进程。 进程组 会话 进程组、会话、控制终端之间的关系 find/2 查看2重定向到dev/null设备上&#xff0c;|管道&#xff08;创建子进程&#xff09…

RK3588平台开发系列讲解(USB篇)USB Device端口组合配置过程

文章目录 一、configfs二、configfs 配置过程2.1、使能相关的宏2.2、挂载configfs2.3、创建名为g1的usb复合设备2.4、配置PID和VID2.5、创建并配置strings子目录2.6、创建configuration和字符串2.7、创建functions2.8、将functions和configuration关联起来2.9、绑定到UDC,使能…

数据的存储练习题 -- (解题思路+代码)

目录 前言 知识补充 有符号和无符号的区别 练习一 练习二 练习三 练习四 练习五 练习六 练习七 前言 书接上回&#xff0c;我们学习了整形数据在内存中是怎么存储的。本篇我们就利用这些知识来做一些练习题目&#xff0c;把这些知识很好的消化掉。 知识补充 对…

简单的TCP网络程序·单进程

目录 文件1&#xff1a;tcpServer.cc 文件2&#xff1a;tcpServer.hpp 1.提出日志概念 -- 在后续完善 日志格式 -- 暂定简单的打印功能 2.创建套接字 SOCK_STREAM -- socket参数 3.bind自己的套接字 4.设置socket 为监听状态 * 新接口1&#xff1a;listen 函数1&…

IIC协议总结

IIC&#xff08;Inter-Integrated Circuit) &#xff0c;简单说就是IC&#xff08;芯片&#xff09;之间通信的总线。所谓总线&#xff0c;就是各个器件都并联到一组公共的线路上&#xff0c;然后共用这条线路来传输数据。总线的英文名为BUS&#xff0c;这是个形象的名字&#…

chatgpt赋能python:Python如何取三位数的每一位

Python如何取三位数的每一位 作为一门广泛应用的编程语言&#xff0c;Python在数值相关处理方面也十分得心应手。取三位数的每一位是一个常见的需求&#xff0c;那么在Python中该如何实现呢&#xff1f; 1. 数字字符串切片 Python中的字符串有索引和切片功能&#xff0c;我们…