Protobuf实战:通讯录

网络版通讯录

需求

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

//删除一个联系人 
message DelContactRequest {
    string uid=1;
}

// 查询⼀个联系⼈ 
message FindOneContactRequest {
    string uid = 1; // 联系⼈ID
}

客户端编写

客户端主要实现四个功能:新增联系人、删除联系人、查找一个联系人、查找通讯录的所有联系人。

异常类定义

主要用于异常捕获,用于定位错误。

#include
#include
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 remark = 5;   // 备注
  oneof gender{
    string male=6;
    string female=7;
  }
}

// 通讯录
message Contacts {
  map contacts = 1;
}

**ContactsMapper.h:持久化存储通讯录⽅法定义 **

ContactsMapper的主要功能如下:读取通讯录内容、将二进制数据写入到文件中。

#pragma once
#define DATA_PATH "contacts.bin"
#include 
#include 
#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 
#include 
#include 

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

Protobuf实战:通讯录_第1张图片

程序运行

Protobuf实战:通讯录_第2张图片

当程序出现报错信息: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版本

Protobuf实战:通讯录_第3张图片

重新编译client程序和server程序并重新运行程序:

Protobuf实战:通讯录_第4张图片

总结

序列化协议 通⽤性 格式 可读性 序列化⼤ ⼩ 序列化性能 适⽤场景
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的应⽤场景更为丰富。

你可能感兴趣的:(服务器,java,servlet)