Protobuf数据交互实战

Protobuf数据交互实战_第1张图片

"no one gonna make me down" 

        在之前呢,我们介绍了什么protobuf以及它的语法、数据类型。 一句老话说得好,"多说不练,假把式!"。因此,本篇会选择以protobuf的语法,完成一个简易的通讯录,一个是文件版的,一个是网络版的。这样才能让我亲切地感受到,protobuf以及和它拥有类似功能的Json、xml这些数据交换格式,它们是如何运作的。

---前言


一、通讯录1

(1) 通讯录格式设计

Protobuf数据交互实战_第2张图片

Protobuf数据交互实战_第3张图片

(2) 通讯录功能实现

        那么要编写文件版的demo代码,首先就需要两个.cc文件,其中一个文件是用来写入(out),一个是读取(in),而Protobuf的数据格式最终是以二进制码转换存储的。

write.cc:

#include 
#include 
#include 

#include "contact.pb.h"

using namespace std;

const std::string file_name = "./contact.txt";

// 添加联系人操作 之后实现
void AddContact() {}

int main()
{
    contacts::Contact con;
    // 可能需要先加载本地文件
    fstream input(file_name, ios::in | ios::binary);
    // 从打开的文件流里进行序列化到con里
    if (!input)
    {
        cout << "contacts.bin not find, create new file!" << endl;
    }

    if (con.ParseFromIstream(&input) == false)
    {
        cerr << "Parse error!" << endl;
        input.close();
        return -1;
    }

    // 可以进行 增添联系人
    AddContact();

    // 联系人添加完毕后 写回文件

    fstream output(file_name, ios::out | ios::binary);
    if (!con.SerializeToOstream(&output))
    {
        cerr << "write error!" << endl;
        input.close();
        output.close();
        return -1;
    }

    cout << "write success!" << endl;
    input.close();
    output.close();
    return 0;
}

        

read.cc:

#include 
#include 
#include 

#include "contact.pb.h"
using namespace std;

const std::string file_name = "./contact.txt";

// 打印通讯录
void PrintContact() {}

int main()
{
    contacts::Contact con;

    fstream input(file_name, ios::in | ios::binary);
    if (!con.ParseFromIstream(&input))
    {
        cerr << "parse error!" << endl;
        input.close();
        return -1;
    }

    // 打印通讯录列表
    PrintContact();
    input.close();
    return 0;
}

增加一个联系人:

姓名、年龄、电话设置:
Protobuf数据交互实战_第4张图片

设置地址字段:

Protobuf数据交互实战_第5张图片

设置其他联系方式和备注:

Protobuf数据交互实战_第6张图片

write.cc代码:

#include 
#include 
#include 

#include "contact.pb.h"

using namespace std;

const std::string file_name = "./contact.txt";

// 添加联系人操作
void AddContact(contacts::PeopleInfo *people)
{
    cout << "-------------新增联系⼈-------------" << endl;
    string name;
    cout << "请输入联系人姓名: ";
    getline(cin, name);
    people->set_name(name);

    int age;
    cout << "请输入联系人年龄: ";
    cin >> age;
    people->set_age(age);
    cin.ignore(256, '\n'); // 设置清空

    for (int i = 0;; i++)
    {
        contacts::PeopleInfo_Phone *phone = people->add_phone();
        cout << "请输入联系人电话" << i + 1 << "(只输⼊回⻋完成电话新增):";
        string number;
        getline(cin, number);
        if (number.empty() == true)
        {
            cout << "电话信息填充完毕" << endl;
            break;
        }
        phone->set_number(number);
        cout << "请输入该电话类型(1、移动电话   2、固定电话): ";
        int type;
        cin >> type;

        switch (type)
        {
        case 1:
            phone->set_type(contacts::PeopleInfo_Phone_PhoneType::PeopleInfo_Phone_PhoneType_MP);
            break;
        case 2:
            phone->set_type(contacts::PeopleInfo_Phone_PhoneType::PeopleInfo_Phone_PhoneType_TEL);
        default:
            cout << "选择有误!" << endl;
            break;
        }
        cin.ignore(256, '\n');
    }
    
    // 录入地址
    // 先将数据存入 消息字段address中
    contacts::Address address;
    cout << "请输入联系人家庭地址:";
    string home_address;
    getline(cin, home_address);
    address.set_home_address(home_address);
    cout << "请输入联系人单位地址:";
    string unit_address;
    getline(cin, unit_address);
    address.set_unit_address(unit_address);
    // 转为Any字段
    // mutable_data: 这里会为Any开辟内存空间 并返回这个内存空间的地址
    people->mutable_data()->PackFrom(address);

    cout << "请选择要添加的其他联系方式(1、qq     2、微信号):";
    int other_contact;
    cin >> other_contact;
    cin.ignore(256, '\n');
    if (1 == other_contact)
    {
        cout << "请输入联系人qq号: ";
        string qq;
        getline(cin, qq);
        people->set_qq(qq);
    }
    else if (2 == other_contact)
    {
        cout << "请输入联系人微信号: ";
        string wechat;
        getline(cin, wechat);
        people->set_wechat(wechat);
    }
    else
    {
        cout << "选择有误,未成功设置其他联系方式!" << endl;
    }

    for (int i = 0;; i++)
    {
        cout << "请输入备注" << i + 1 << "标题(只输入回车完成备注新增):";
        string remark_key;
        getline(cin, remark_key);
        if (remark_key.empty())
        {
            break;
        }

        cout << "请输入备注" << i + 1 << "内容: ";
        string remark_value;
        getline(cin, remark_value);
        people->mutable_remark()->insert({remark_key, remark_value});
    }

    cout << "-----------添加联系⼈成功-----------" << endl;
}

int main()
{
    contacts::Contact con;
    // 可能需要先加载本地文件
    fstream input(file_name, ios::in | ios::binary);
    // 从打开的文件流里进行序列化到con里
    if (!input)
    {
        cout << "contacts.bin not find, create new file!" << endl;
    }

    if (con.ParseFromIstream(&input) == false)
    {
        cerr << "Parse error!" << endl;
        input.close();
        return -1;
    }

    // 可以进行 增添联系人
    AddContact(con.add_con());

    // 联系人添加完毕后 写回文件
    fstream output(file_name, ios::out | ios::binary);
    if (!con.SerializeToOstream(&output))
    {
        cerr << "write error!" << endl;
        input.close();
        output.close();
        return -1;
    }

    cout << "write success!" << endl;

    input.close();
    output.close();
    return 0;
}

打印联系人信息:

 打印姓名、年龄、电话:
Protobuf数据交互实战_第7张图片

地址信息:
Protobuf数据交互实战_第8张图片

其他联系方式与备注:
Protobuf数据交互实战_第9张图片

read.cc完整代码:

#include 
#include 
#include 

#include "contact.pb.h"
using namespace std;

const std::string file_name = "./contact.txt";

// 打印通讯录
void PrintContact(contacts::Contact &con)
{
    for (int i = 0; i < con.con_size(); ++i)
    {
        cout << "---------------联系人" << i + 1 << "---------------" << endl;
        const contacts::PeopleInfo &people = con.con(i);

        cout << "联系人姓名: " << people.name() << std::endl;
        cout << "联系人年龄: " << people.age() << std::endl;

        for (int j = 0; j < people.phone_size(); j++)
        {
            const contacts::PeopleInfo_Phone &phone = people.phone(j);
            // 联系人电话1:1311111  (MP)
            cout << "联系人电话" << j + 1 << ":" << phone.number();
            cout << "   (" << phone.PhoneType_Name(phone.type()) << ")"
                 << " ";
        }
        cout << endl;

        // 对Any字段 可以通过查看是否设置来确认打印情况
        if (people.has_data() && people.data().Is())
        {
            contacts::Address address;
            // adress -> any
            // people.data().PackFrom()

            // any -> address
            people.data().UnpackTo(&address);
            if (!address.home_address().empty())
            {
                cout << "联系人家庭地址:" << address.home_address() << endl;
            }
            else if (!address.unit_address().empty())
            {
                cout << "联系人家庭地址:" << address.home_address() << endl;
            }
        }

        switch (people.other_contact_case())
        {
        case contacts::PeopleInfo::OtherContactCase::kQq:
            cout << "联系人qq: " << people.qq() << endl;
            break;
        case contacts::PeopleInfo::OtherContactCase::kWechat:
            cout << "联系人微信号: " << people.wechat() << endl;
            break;
        default:
            cout << " " << endl;
            break;
        }

        if (people.remark_size())
        {
            cout << "备注信息:" << endl;
        }
        for (auto it = people.remark().cbegin(); it != people.remark().cend(); it++)
        {
            cout << "   " << it->first << ": " << it->second << endl;
        }
        
    }
}

int main()
{
    contacts::Contact con;

    fstream input(file_name, ios::in | ios::binary);
    if (!con.ParseFromIstream(&input))
    {
        cerr << "parse error!" << endl;
        input.close();
        return -1;
    }

    // 打印通讯录列表
    PrintContact(con);
    input.close();
    return 0;
}

(3) 测试 

        代码编写的部分已经完成了,那么来看看简易版的通讯录是怎样的?Protobuf数据交互实战_第10张图片

         我们先对文件进行写入,因为存在二进制,很多信息是可读性不高。Protobuf数据交互实战_第11张图片

         我们此时调用read进程,将文件里的内容读出来,

Protobuf数据交互实战_第12张图片

         那么此时原安静躺在磁盘上的二进制文件的内容,是能被可读性强地展示出来。


二、基于Protbuf的网络实战版通讯录

(1) 环境搭建

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

镜像仓库: https://gitcode.net/mirrors/yhirose/cpp-httplib?utm_source=csdn_github_accelerator

        值得注意的是,在大多数Centos的环境下,yum自带的gcc/g++编译器的最新版本是4.8.5发布于2015年,年代久远。译该项⽬会出现异常。将gcc/g++升级为更⾼版本可解决问题。当然,同时安装这两款编译器是不会冲突的。

# 安装gcc 8版本
yum install -y devtoolset-8-gcc devtoolset-8-gcc-c++

# 启⽤版本 否则还是会默认使用原g++4.8.5的版本
source /opt/rh/devtoolset-8/enable

        

如何使用httplib?

        这里写了一份小的demo代码来搭建一个简易的http服务器。

  server:

Protobuf数据交互实战_第13张图片

#include 
// 只需要将该httplib.h文件引入即可
#include "../../cpp-httplib/httplib.h"

using namespace std;
using namespace httplib;

int main()
{
  cout << "---服务器启动---" << endl;
  Server server;
  
  // 服务端接收到Post请求  这里传了个Lambda表达式,自动填写响应状态码
  server.Post("/test-Post", [](const Request &req, Response &resp)
   {
    cout << "接收到Post请求" << endl;
    resp.status = 200; 
   });

  // 服务端接收到Get请求  这里传了个Lambda表达式,自动填写响应状态码
  server.Get("/test-Get", [](const Request &req, Response &resp)
  {
      cout << "接收到Post请求" << endl;
      resp.status = 200; 
  });

  // bind端口
  server.listen("0.0.0.0",8085);
  return 0;
}

     

client:

Protobuf数据交互实战_第14张图片

#include 
#include "../../cpp-httplib/httplib.h"
using namespace httplib;
using namespace std;
#define CONTACTS_HOST "127.0.0.1"
#define CONTACTS_PORT 8085

int main()
{
    Client cli(CONTACTS_HOST, CONTACTS_PORT);

    Result res1 = cli.Post("/test-Post");
    if (res1->status == 200)
    {
        cout << "调用post成功!" << endl;
    }

    Result res2 = cli.Get("/test-Get");
    if (res2->status == 200)
    {
        cout << "调用Get成功!" << endl;
    }

    return 0;
}

        我们分别启动服务器,进行基于http协议通信:Protobuf数据交互实战_第15张图片 

(2) 业务逻辑 

        下面就开始代码实战!

(3) Server服务端

实用的小程序功能块:        

        随机数生成 与 备注信息插入

#include 
#include 
#include 

class Util
{
public:
    static unsigned int random_char()
    {
        // 获取随机数种子
        std::random_device rd;
        // mt19937C++11的新特性,类似于rand(),但是速度跟快周期长
        std::mt19937 gen(rd());
        // 限制范围生成i [0,255]
        std::uniform_int_distribution<> dis(0, 255);
        return dis(gen);
    }

    // 生成唯一标识UID
    static std::string generate_uid(const unsigned int len = 4)
    {
        std::stringstream ss;
        for (int 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 *target, const google::protobuf::Map &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});
        }
    }
};

        异常类,简单的打印信息:

#pragma once
// 自定义异常类
#include 
#include 
class ContactException
{
private:
    std::string msg;
public:
    ContactException(std::string str ):msg(str){}
    std::string& what(){
        msg += "A problem happend";
        return msg;
    }
};

功能实现的proto:

        对于客户端而言,它会有四个选项来发送不同的req,并且得到不同的resp。对于服务端而言,它也应该根据网络拿到客户端的req,并且将处理结果resp给客户端。如何进行这些数据的交互呢?当然是我们正在讲的.proto文件!

 response:

// base 默认携带 退出码和错误描述
syntax = "proto3";
package base_response;

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


// add_contact_resp
syntax = "proto3";
package add_contact_resp;

import "base_response.proto";

message AddContactResponse{
    base_response.BaseResponse base_resp = 1;
    string uid = 2;
}   


// del_contact_resp
syntax = "proto3";
package del_contact_resp;

import "base_response.proto";   // 引入base_response

// 删除一个联系人 resp
message DelContactResponse {
  base_response.BaseResponse base_resp = 1;
  string uid = 2;
}


// find_all_contacts_resp
syntax = "proto3";
package find_all_contacts_resp;

import "base_response.proto";  // 引入base_response

// 联系人摘要信息
message PeopleInfo {
  string uid = 1;                   // 联系人ID
  string name = 2;                  // 姓名
}

// 查询所有联系人 resp
message FindAllContactsResponse {
  base_response.BaseResponse base_resp = 1;
  repeated PeopleInfo contacts = 2;
}


// find_one_contact_resp
syntax = "proto3";
package find_one_contact_resp;

import "base_response.proto";  // 引入base_response
// 查询一个联系人 resp
message FindOneContactResponse {
  base_response.BaseResponse base_resp = 1;

  string uid = 2;                   // 联系人ID
  string name = 3;                  // 姓名
  int32 age = 4;                    // 年龄

  message Phone {
    string number = 1;     // 电话号码
    enum PhoneType {
      MP = 0;    // 移动电话
      TEL = 1;   // 固定电话
    }
    PhoneType type = 2;    // 类型
  }
  
  repeated Phone phone = 5;         // 电话
  map remark = 6;   // 备注
}

        

request:

// add_contact_req
syntax = "proto3";
package add_contact_req;
// 新增联系人 req
message AddContactRequest {
  string name = 1;                  // 姓名
  int32 age = 2;                    // 年龄

  message Phone {
    string number = 1;     // 电话号码
    enum PhoneType {
      MP = 0;    // 移动电话
      TEL = 1;   // 固定电话
    }
    PhoneType type = 2;    // 类型
  }
  
  repeated Phone phone = 3;         // 电话
  map remark = 4;   // 备注
}

// del_contact_req
syntax = "proto3";
package del_contact_req;

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

// find_one_contact_req
syntax = "proto3";
package find_one_contact_req;

// 查询一个联系人 req
message FindOneContactRequest {
  string uid = 1;                    // 联系人ID
}

        对于request就不需要为find_all_contact定制一个req请求,因为一旦收到这个指示,直接返回

find_all_contact的resp就行了。

Protobuf数据交互实战_第16张图片

          那么以上是"Client端"(之后就不再细讲了)和Server端都需要具备的数据交换格式信息。

入口函数main.cc:

Protobuf数据交互实战_第17张图片

Server_Storage:

         如何将数据进行持久化存储,其实也是需要考究的。在这里呢,我们不打算复杂化,介入Mysql数据库来管理这些通讯录信息,而是继续采用本地持续化存储的策略。

Contact.proto: 存储格式

syntax = "proto3";
package contacts;

// 联系人
message PeopleInfo {
  string uid = 1;                    // 联系人ID
  string name = 2;                  // 姓名
  int32 age = 3;                    // 年龄

  message Phone {
    string number = 1;     // 电话号码
    enum PhoneType {
      MP = 0;    // 移动电话
      TEL = 1;   // 固定电话
    }
    PhoneType type = 2;    // 类型
  }
  
  repeated Phone phone = 4;         // 电话
  map remark = 5;   // 备注
}

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

 ContactMapper: 写入、读取文件的模块

#include "contact.pb.cc"
#include "../common/ContactException.h"
#include 
#define CONTACT_TXT "contacts.txt"
class ContactsMapper
{
public:
    void selectContacts(contacts::Contacts *contacts) const;
    void insertContacts(contacts::Contacts &contacts) const;
};

#include "ContactsMapper.h"
// 文件操作
void ContactsMapper::selectContacts(contacts::Contacts *con) const
{
    std::fstream input(CONTACT_TXT, std::ios::in | std::ios::binary);

    if(!con->ParseFromIstream(&input)){
        input.close();
        throw ContactException("ContactsMapper::selectContacts) Failed to parse contacts.");
    }

    input.close();
}

void ContactsMapper::insertContacts(contacts::Contacts &con) const
{
    // 写入
    std::fstream output(CONTACT_TXT,std::ios::out | std::ios::binary);
    if(!con.SerializeToOstream(&output)){
        output.close();
        throw ContactException("ContactsMapper::insertContacts) Failed to write contacts.");
    }

    output.close();
}

        

Server_Control:真正后台操控数据存储、读取的功能模块。

        我们使用的Client端、Server端互相定义的协议格式(req.proto\resp.proto),在这里进行处理。

add\buildPeopleInfo\printAddContactRequest:

// 仅仅用作 Server端查看
void ContactServer::printAddContactRequest(add_contact_req::AddContactRequest &request) const
{
    cout << "---> (ContactsServer::add) AddContactRequest:" << endl;
    cout << "姓名:" << request.name() << endl;
    cout << "年龄:" << request.age() << endl;
    for (auto &phone : request.phone())
    {
        int j = 1;
        cout << "电话" << j++ << ": " << phone.number();
        cout << "  (" << phone.PhoneType_Name(phone.type()) << ")" << endl;
    }

    if (request.remark_size())
    {
        cout << "备注信息: " << endl;
    }
    for (auto it = request.remark().cbegin(); it != request.remark().cend(); ++it)
    {
        cout << "    " << it->first << ": " << it->second << endl;
    }
}

void ContactServer::buildPeopleInfo(contacts::PeopleInfo *people, add_contact_req::AddContactRequest &req) const
{
    // 生成随机数 种子
    std::string uid = Util::generate_uid(10);
    // 简单的数据更新
    people->set_uid(uid);
    people->set_name(req.name());
    people->set_age(req.age());

    for (auto &phone : req.phone())
    {
        contacts::PeopleInfo_Phone *peo_phone = people->add_phone();
        peo_phone->set_number(phone.number());

        switch (phone.type())
        {
        case add_contact_req::AddContactRequest_Phone_PhoneType::AddContactRequest_Phone_PhoneType_MP:
            peo_phone->set_type(contacts::PeopleInfo_Phone_PhoneType::PeopleInfo_Phone_PhoneType_MP);
            break;
        case add_contact_req::AddContactRequest_Phone_PhoneType::AddContactRequest_Phone_PhoneType_TEL:
            peo_phone->set_type(contacts::PeopleInfo_Phone_PhoneType::PeopleInfo_Phone_PhoneType_TEL);
            break;
        default:
            break;
        }
    }

    // 拷贝备注信息
    Util::map_copy(people->mutable_remark(), req.remark());
}

void ContactServer::add(add_contact_req::AddContactRequest &req, add_contact_resp::AddContactResponse *resp) const
{
    // 0.先打印提交上来的信息(可以忽略)
    printAddContactRequest(req);

    // 1.先读取已经存在的
    contacts::Contacts con;
    contactsMapper.selectContacts(&con);

    // 2.转换为存入文件的消息对象
    google::protobuf::Map *map_contact = con.mutable_contacts(); // 通过con 开辟新的空间
    contacts::PeopleInfo people;
    // 通过req 构建新的people字段
    buildPeopleInfo(&people, req);
    // {uid,message}
    map_contact->insert({people.uid(), people});

    // 3.向磁盘里写入
    contactsMapper.insertContacts(con);
    resp->set_uid(people.uid());
    resp->mutable_base_resp()->set_success(true);

    // 打印日志
    cout << "---> (ContactsServer::add) Success to write contacts." << endl;
}

del:

void ContactServer::del(del_contact_req::DelContactRequest &req, del_contact_resp::DelContactResponse *resp) const
{
    // 删除哪条记录
    cout << "---> (ContactsServer::del) DelContactRequest: uid: " << req.uid() << endl;
    // 1.先读取contacts 方便比对删除
    contacts::Contacts con;
    contactsMapper.selectContacts(&con);
    // 不含uid 直接返回
    if (con.contacts().find(req.uid()) == con.contacts().end())
    {
        cout << "---> (ContactsServer::del) not find uid: " << req.uid() << endl;
        resp->set_uid(-1);
        // 设置返回报头
        resp->mutable_base_resp()->set_success(false);
        resp->mutable_base_resp()->set_error_desc("not be found");
        return;
    }

    // 含uid 删除用户
    con.mutable_contacts()->erase(req.uid());

    // 写回磁盘
    contactsMapper.insertContacts(con);

    // 构造resp
    resp->mutable_base_resp()->set_success(true);
    // 日志打印
    cout << "---> (ContactsServer::del) Success to del contact, uid: " << req.uid() << endl;
}

find_one\buildFindOneContactResponse:

void ContactServer::buildFindOneContactResponse(const contacts::PeopleInfo &people,
                                                find_one_contact_resp::FindOneContactResponse *resp) const
{
    if (nullptr == resp)
    {
        return;
    }
    // 设置信息
    resp->mutable_base_resp()->set_success(true);
    resp->set_uid(people.uid());
    resp->set_name(people.name());
    resp->set_age(people.age());

    // 将people里的phone 设置进resp中
    for (auto &phone : people.phone())
    {
        find_one_contact_resp::FindOneContactResponse_Phone* resp_phone = resp->add_phone();    
        resp_phone->set_number(phone.number());

        switch(phone.type())
        {
            case find_one_contact_resp::FindOneContactResponse_Phone_PhoneType::FindOneContactResponse_Phone_PhoneType_MP:
                resp_phone->set_type(find_one_contact_resp::FindOneContactResponse_Phone_PhoneType::FindOneContactResponse_Phone_PhoneType_MP);
                break;
            case find_one_contact_resp::FindOneContactResponse_Phone_PhoneType::FindOneContactResponse_Phone_PhoneType_TEL:
                resp_phone->set_type(find_one_contact_resp::FindOneContactResponse_Phone_PhoneType::FindOneContactResponse_Phone_PhoneType_TEL);
        }
    }
}

void ContactServer::find_one(find_one_contact_req::FindOneContactRequest &req, find_one_contact_resp::FindOneContactResponse *resp) const
{
    // 读取原contact
    contacts::Contacts con;
    contactsMapper.selectContacts(&con);

    // 获取map地图
    const google::protobuf::Map &map_contacts = con.contacts();
    auto it = map_contacts.find(req.uid());
    // 不含uid 返回

    if (it == map_contacts.end())
    {
        cout << "---> (ContactServer::find_one) not found uid" << req.uid() << endl;
        resp->set_uid(req.uid());
        resp->mutable_base_resp()->set_success(false);
        resp->mutable_base_resp()->set_error_desc("查无此人");
        return;
    }

    // 含uid 构建resp
    buildFindOneContactResponse(it->second, resp);
    return;
}

find_all\buildFindAllContactsResponse:

void ContactServer::buildFindAllContactsResponse(contacts::Contacts &con,
                                                 find_all_contacts_resp::FindAllContactsResponse *resp) const
{
    if (resp == nullptr)
    {
        return;
    }

    resp->mutable_base_resp()->set_success(true);
    // 遍历 con内部
    for (auto it = con.contacts().cbegin(); it != con.contacts().cend(); ++it)
    {
        find_all_contacts_resp::PeopleInfo *people = resp->add_contacts();
        people->set_uid(it->first);
        people->set_name(it->second.name());
    }
}

void ContactServer::find_all(find_all_contacts_resp::FindAllContactsResponse *resp) const
{
    // 打印日志
    cout << "---> (ContactsServer::findAll) " << endl;

    // 获取通讯录
    contacts::Contacts con;
    contactsMapper.selectContacts(&con);

    // 转换为resp对象
    buildFindAllContactsResponse(con, resp);
}


        我们将Server功能实现模块的代码工作完成后,就可以填补Server_Entry的缺漏代码了。完成一次完整的客户端代码函数调用、响应的流程可以认为会走这几步流程:

● 创建req、resp对象(输出型参数)

● 将Request的内容进行反序列化到req当中

● 调用功能函数,传入(req,resp)

● resp的内容序列化到一个容器里

● 最后填入返回resp剩余信息 

#include 
#include "../../../../../../cpp-httplib/httplib.h"
#include "../server/ContactsServer.h"
#include "../common/ContactException.h"

#include "Request/add_contact_request.pb.h"
#include "Request/del_contact_request.pb.h"
#include "Request/find_one_contact_request.pb.h"

#include "Response/add_contact_response.pb.h"
#include "Response/find_one_contact_response.pb.h"
#include "Response/find_all_contacts_response.pb.h"
#include "Response/del_contact_response.pb.h"

using std::cerr;
using std::cout;
using std::endl;
using namespace httplib;

int main()
{
    cout << "---> 服务启动..." << endl;
    Server srv; // 创建服务器对象
    ContactServer contact_server; // 创建功能函数控制

    srv.Post("/contacts/add", [&contact_server](const Request &req, Response &resp) 
    {
        // 1.创建 输出型对象
        add_contact_req::AddContactRequest request;
        add_contact_resp::AddContactResponse response;
        // 功能函数可能出现异常情况
        try
        {
            // 2.反序列化
            if(!request.ParseFromString(req.body)){
                throw ContactException("Parse AddContactRequest error!");
            }

            // 3. 调用功能函数
            contact_server.add(request,&response);

            // 4.resp的内容序列化到一个容器里 
            std::string resp_str;
            if(!response.SerializeToString(&resp_str)){
                throw ContactException("Serialize AddContactResponse error");
            }

            resp.body = resp_str;
            resp.set_header("Content-Type","application/protobuf");
            resp.status = 200;
        }
        catch(ContactException& e)
        {
            cerr << "---> /contacts/add 发现异常!!!" << endl;
            std::cout << e.what() << std::endl; 
            // 设置报头
            resp.status = 500;
            base_response::BaseResponse* baseResponse = response.mutable_base_resp();
            baseResponse->set_success(false);
            baseResponse->set_error_desc(e.what());

            std::string response_str;
            if(response.SerializeToString(&response_str))
            {
                // 写进正文
                resp.body = response_str;
                resp.set_header("Content-Type","application/protobuf");
            }
        }
    });

    srv.Post("/contacts/del", [&contact_server](const Request &req, Response &resp) 
    {
        del_contact_req::DelContactRequest request;
        del_contact_resp::DelContactResponse response;
        try
        {
            // 2.反序列化
            if(!request.ParseFromString(req.body)){
                throw ContactException("Parse DelContactRequest  error!");
            }

            // 3. 调用功能函数
            contact_server.del(request,&response);

            // 4.resp的内容序列化到一个容器里 
            std::string resp_str;
            if(!response.SerializeToString(&resp_str)){
                throw ContactException("Serialize DelContactRequest error");
            }

            resp.body = resp_str;
            resp.set_header("Content-Type","application/protobuf");
            resp.status = 200;
        }
        catch(ContactException& e)
        {
            cerr << "---> /contacts/del 发现异常!!!" << endl;
            std::cout << e.what() << std::endl; 
            // 设置报头
            resp.status = 500;
            base_response::BaseResponse* baseResponse = response.mutable_base_resp();
            baseResponse->set_success(false);
            baseResponse->set_error_desc(e.what());

            std::string response_str;
            if(response.SerializeToString(&response_str))
            {
                // 写进正文
                resp.body = response_str;
                resp.set_header("Content-Type","application/protobuf");
            }
        }

    });

    srv.Get("/contacts/fine-one", [&contact_server](const Request &req, Response &resp) 
    {
        find_one_contact_req::FindOneContactRequest request;
        find_one_contact_resp::FindOneContactResponse response;
        try
        {
            // 2.反序列化
            if(!request.ParseFromString(req.body)){
                throw ContactException("Parse FindOneContactRequest  error!");
            }

            // 3. 调用功能函数
            contact_server.find_one(request,&response);

            // 4.resp的内容序列化到一个容器里 
            std::string resp_str;
            if(!response.SerializeToString(&resp_str)){
                throw ContactException("Serialize FindOneContactRequest  error");
            }

            resp.body = resp_str;
            resp.set_header("Content-Type","application/protobuf");
            resp.status = 200;
        }
        catch(ContactException& e)
        {
            cerr << "---> /contacts/find_one 发现异常!!!" << endl;
            std::cout << e.what() << std::endl; 
            // 设置报头
            resp.status = 500;
            base_response::BaseResponse* baseResponse = response.mutable_base_resp();
            baseResponse->set_success(false);
            baseResponse->set_error_desc(e.what());

            std::string response_str;
            if(response.SerializeToString(&response_str))
            {
                // 写进正文
                resp.body = response_str;
                resp.set_header("Content-Type","application/protobuf");
            }
        }
    });

    srv.Get("/contacts/find-all", [&contact_server](const Request &req, Response &resp) 
    {
        find_all_contacts_resp::FindAllContactsResponse response;
        try
        {
            // 3. 调用功能函数
            contact_server.find_all(&response);

            // 4.resp的内容序列化到一个容器里 
            std::string resp_str;
            if(!response.SerializeToString(&resp_str)){
                throw ContactException("Serialize FindAllContactsResponse  error");
            }

            resp.body = resp_str;
            resp.set_header("Content-Type","application/protobuf");
            resp.status = 200;
        }
        catch(ContactException& e)
        {
            cerr << "---> /contacts/find_all 发现异常!!!" << endl;
            std::cout << e.what() << std::endl; 
            // 设置报头
            resp.status = 500;
            base_response::BaseResponse* baseResponse = response.mutable_base_resp();
            baseResponse->set_success(false);
            baseResponse->set_error_desc(e.what());

            std::string response_str;
            if(response.SerializeToString(&response_str))
            {
                // 写进正文
                resp.body = response_str;
                resp.set_header("Content-Type","application/protobuf");
            }
        }
    });

    srv.listen("0.0.0.0", 8085);
    return 0;
}


(4) Client端

        Client端入口,很明显写了一个很挫的交互页面。Protobuf数据交互实战_第18张图片 

Client_Control:

addContact/buildAddContactRequest:

void ContactServer::buildAddContactRequest(add_contact_req::AddContactRequest *req)
{
    std::cout << "请输入联系人姓名: ";
    std::string name;
    getline(std::cin, name);
    req->set_name(name);

    std::cout << "请输入联系人年龄: ";
    int age;
    std::cin >> age;
    req->set_age(age);
    std::cin.ignore(256, '\n');

    for (int i = 1;; i++)
    {
        std::cout << "请输入联系人电话" << i << "(只输入回车完成电话新增): ";
        std::string number;
        getline(std::cin, number);
        if (number.empty())
        {
            break;
        }
        add_contact_req::AddContactRequest_Phone *phone = req->add_phone();
        phone->set_number(number);

        std::cout << "选择此电话类型 (1、移动电话   2、固定电话) : ";
        int type;
        std::cin >> type;
        std::cin.ignore(256, '\n');
        switch (type)
        {
        case 1:
            phone->set_type(add_contact_req::AddContactRequest_Phone_PhoneType::AddContactRequest_Phone_PhoneType_MP);
            break;
        case 2:
            phone->set_type(add_contact_req::AddContactRequest_Phone_PhoneType::AddContactRequest_Phone_PhoneType_TEL);
            break;
        default:
            std::cout << "----非法选择,使用默认值!" << std::endl;
            break;
        }
    }

    for (int i = 1;; i++)
    {
        std::cout << "请输入备注" << i << "标题 (只输入回车完成备注新增): ";
        std::string remark_key;
        getline(std::cin, remark_key);
        if (remark_key.empty())
        {
            break;
        }

        std::cout << "请输入备注" << i << "内容: ";
        std::string remark_value;
        getline(std::cin, remark_value);
        req->mutable_remark()->insert({remark_key, remark_value});
    }
}

void ContactServer::addContact()
{
    // 进行http的连接建立
    Client cli(port);

    // 构建 request 请求
    add_contact_req::AddContactRequest req;
    buildAddContactRequest(&req);

    // 序列化
    std::string req_str;
    if (!req.SerializeToString(&req_str))
    {
        throw ContactException("AddContactRequest序列化失败!");
    }

    // 发起Post请求
    auto res = cli.Post("contact/add",req_str,"applicationi/protbuf");
    if (!res)
    {
        std::string err_desc;
        err_desc.append("/contacts/add 链接错误!错误信息:")
            .append(httplib::to_string(res.error()));
        throw ContactException(err_desc);
    }

    // 反序列化Response
    add_contact_resp::AddContactResponse resp;
    bool parse = resp.ParseFromString(res->body);

    // 处理异常
    if (res->status != 200 && !parse)
    {
        std::string err_desc;
        err_desc.append("post '/contacts/add/' 失败:")
            .append(std::to_string(res->status))
            .append("(")
            .append(res->reason)
            .append(")");
        throw ContactException(err_desc);
    }
    else if (res->status != 200)
    {
        // 处理服务异常
        std::string err_desc;
        err_desc.append("post '/contacts/add/' 失败 ")
            .append(std::to_string(res->status))
            .append("(")
            .append(res->reason)
            .append(")  错误原因:")
            .append(resp.base_resp().error_desc());
        throw ContactException(err_desc);
    }
    else if (!resp.base_resp().success())
    {
        // 处理结果异常
        std::string err_desc;
        err_desc.append("post '/contacts/add/' 结果异常:")
            .append("异常原因:")
            .append(resp.base_resp().error_desc());
        throw ContactException(err_desc);
    }

    // 正常返回,打印结果
    std::cout << "---> 新增联系人成功,联系人ID: " << resp.uid() << std::endl;
}

del:

void ContactServer::delContact()
{
    httplib::Client cli(port);

    del_contact_req::DelContactRequest 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("DelContactRequest序列化失败!");
    }

    // 发起Post请求
    auto res = cli.Post("contact/del", req_str, "application/protobuf");
    if (!res)
    {
        std::string err_desc;
        err_desc.append("/contacts/del 链接错误!错误信息:")
            .append(httplib::to_string(res.error()));
        throw ContactException(err_desc);
    }

    // 反序列化
    del_contact_resp::DelContactResponse resp;
    bool parse = resp.ParseFromString(res->body);
    // 处理异常
    if (res->status != 200 && !parse)
    {
        std::string err_desc;
        err_desc.append("post '/contacts/del' 失败:")
            .append(std::to_string(res->status))
            .append("(")
            .append(res->reason)
            .append(")");
        throw ContactException(err_desc);
    }
    else if (res->status != 200)
    {
        std::string err_desc;
        err_desc.append("post '/contacts/del' 失败 ")
            .append(std::to_string(res->status))
            .append("(")
            .append(res->reason)
            .append(")  错误原因:")
            .append(resp.base_resp().error_desc());
        throw ContactException(err_desc);
    }
    else if (!resp.base_resp().success())
    {
        // 结果异常
        std::string err_desc;
        err_desc.append("post '/contacts/del' 结果异常:")
            .append("异常原因:")
            .append(resp.base_resp().error_desc());
        throw ContactException(err_desc);
    }

     // 正常返回,打印结果
    std::cout << "---> 成功删除联系人,被删除的联系人ID为:" << resp.uid() << std::endl;
}

find_all_Contact\printFindAllContactsResponse:

void ContactServer::printFindAllContactsResponse(find_all_contacts_resp::FindAllContactsResponse &resp)
{
    if (0 == resp.contacts_size())
    {
        std::cout << "还未添加任何联系人" << std::endl;
        return;
    }
    for (auto contact : resp.contacts())
    {
        std::cout << "联系人姓名: " << contact.name() << " 联系人ID: " << contact.uid() << std::endl;
    }
}

void ContactServer::find_all_Contact()
{
    httplib::Client cli(port);

    // 查询所有联系人没有需要填写的 req报头
    auto res = cli.Get("/contacts/find-all");
    if (!res)
    {
        std::string err_desc;
        err_desc.append("/contacts/find-all 链接错误!错误信息:")
            .append(httplib::to_string(res.error()));
        throw ContactException(err_desc);
    }

    find_all_contacts_resp::FindAllContactsResponse resp;
    bool parse = resp.ParseFromString(res->body);
    // 处理异常
    if (res->status != 200 && !parse)
    {
        std::string err_desc;
        err_desc.append("get '/contacts/find-all' 失败:")
            .append(std::to_string(res->status))
            .append("(")
            .append(res->reason)
            .append(")");
        throw ContactException(err_desc);
    }
    else if (res->status != 200)
    {
        // 服务端异常
        std::string err_desc;
        err_desc.append("post '/contacts/find-all' 失败 ")
            .append(std::to_string(res->status))
            .append("(")
            .append(res->reason)
            .append(")  错误原因:")
            .append(resp.base_resp().error_desc());
        throw ContactException(err_desc);
    }
    else if (!resp.base_resp().success())
    {
        // 结果异常
        std::string err_desc;
        err_desc.append("post '/contacts/find-all' 结果异常:")
            .append("异常原因:")
            .append(resp.base_resp().error_desc());
        throw ContactException(err_desc);
    }

    // 正常返回,打印结果
    printFindAllContactsResponse(resp);
}

find_one_Contact\printFindOneContactResponse:

void ContactServer::printFindOneContactResponse(find_one_contact_resp::FindOneContactResponse &resp)
{
    std::cout << "姓名:" << resp.name() << std::endl;
    std::cout << "年龄:" << resp.age() << std::endl;
    for (auto &phone : resp.phone())
    {
        int j = 1;
        std::cout << "电话" << j++ << ": " << phone.number();
        std::cout << "  (" << phone.PhoneType_Name(phone.type()) << ")" << std::endl;
    }
    if (resp.remark_size())
    {
        std::cout << "备注信息: " << std::endl;
    }
    for (auto it = resp.remark().cbegin(); it != resp.remark().cend(); ++it)
    {
        std::cout << "    " << it->first << ": " << it->second << std::endl;
    }
}

void ContactServer::find_one_Contact()
{
    httplib::Client cli(port);
    // 构建 request 请求
    find_one_contact_req::FindOneContactRequest req;
    std::cout << "请输入要查询的联系人id: ";
    std::string uid;
    getline(std::cin, uid);
    req.set_uid(uid);

    // 序列化 request
    std::string req_str;
    if (!req.SerializeToString(&req_str))
    {
        throw ContactException("FindOneContactRequest序列化失败!");
    }

    auto res = cli.Post("/contacts/find-one", req_str, "application/protobuf");
    if (!res)
    {
        std::string err_desc;
        err_desc.append("/contacts/find-one 链接错误!错误信息:")
            .append(httplib::to_string(res.error()));
        throw ContactException(err_desc);
    }

    find_one_contact_resp::FindOneContactResponse resp;
    bool parse = resp.ParseFromString(res->body);
    // 处理异常
    if (res->status != 200 && !parse)
    {
        std::string err_desc;
        err_desc.append("post '/contacts/find-one' 失败:")
            .append(std::to_string(res->status))
            .append("(")
            .append(res->reason)
            .append(")");
        throw ContactException(err_desc);
    }
    else if (res->status != 200)
    {
        std::string err_desc;
        err_desc.append("post '/contacts/find-one' 失败 ")
            .append(std::to_string(res->status))
            .append("(")
            .append(res->reason)
            .append(")  错误原因:")
            .append(resp.base_resp().error_desc());
        throw ContactException(err_desc);
    }
    else if (!resp.base_resp().success())
    {
        // 结果异常
        std::string err_desc;
        err_desc.append("post '/contacts/find-one' 结果异常:")
            .append("异常原因:")
            .append(resp.base_resp().error_desc());
        throw ContactException(err_desc);
    }

    // 正常返回,打印结果
    std::cout << "---> 查询到联系人ID为: " << resp.uid() << " 的信息:" << std::endl;
    printFindOneContactResponse(resp);
}

(5) 测试

        我们分别编译完client与server文件。

Protobuf数据交互实战_第19张图片

Protobuf数据交互实战_第20张图片

        我们举例插入一名通讯录人员小王,

server端:

Protobuf数据交互实战_第21张图片

 

client端:

Protobuf数据交互实战_第22张图片

        我们可以通过其id查找到该用户信息。

Protobuf数据交互实战_第23张图片

        再次新增或用户后,查看详细联系人列表。

        Protobuf数据交互实战_第24张图片 

        同样,也可以按照id对用户进行删除     Protobuf数据交互实战_第25张图片 

         本代码功能演示也就这样。


        

本篇到此结束,感谢你的阅读。

祝你好运,向阳而生~

Protobuf数据交互实战_第26张图片

你可能感兴趣的:(数据交换格式,protobuf,c++)