Protobuf 的快速使用(四)

Protobuf 还常⽤于通讯协议、服务端数据交换场景。那么在这个⽰例中,我们将实现⼀个⽹络版本的通讯录,模拟实现客⼾端与服务端的交互,通过 Protobuf 来实现各端之间的协议序列化。需求如下:
客⼾端可以选择对通讯录进⾏以下操作:

  • 新增⼀个联系⼈
  • 删除⼀个联系⼈
  • 查询通讯录列表
  • 查询⼀个联系⼈的详细信息

为了将代码简化,我们只具体实现新增联系人的功能, 服务端则相应提供增删查能⼒,并需要持久化通讯录(为了将代码简化, 这里只是将收到的需求序列化后打印出来),客⼾端、服务端间的交互数据使⽤ Protobuf 来完成。

搭建 Httplib 库

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

完整代码

protocol.proto

复制代码
syntax="proto3";
package protocol;

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

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;
}

将代码补充好后编译一下

复制代码
protoc --cpp_out=. protocol.proto

之后就在当前文件夹出现 protocol.pb.h 和 protocol.pb.cc 文件,还需要记得添加下载的 httplib.h 文件。

ContactsException.h

定义抛异常的类

cpp 复制代码
#include <string>
class ContactsException
{
public:
    ContactsException(std::string str = "problem")
        : message(str)
    {
    }
    std::string what() const
    {
        return message;
    }

private:
    std::string message;
};

client.cc

cpp 复制代码
#include <iostream>
#include "httplib.h"
#include "ContactsException.h"
#include "protocol.pb.h"
using namespace std;

const string server_ip = "127.0.0.1";
const int server_port = 8080;
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;
}

void buildAddContactRequest(protocol::AddContactRequest *req)
{
    cout << "-------------新增联系⼈-------------" << endl;
    cout << "请输⼊联系⼈姓名: ";
    string name;
    getline(cin, name);
    req->set_name(name);
    cout << "请输⼊联系⼈年龄: ";
    int age;
    cin >> age;
    req->set_age(age);
    cin.ignore(256, '\n');
    for (int i = 1;; i++)
    {
        cout << "请输⼊联系⼈电话" << i << "(只输⼊回⻋完成电话新增):";
        string numbers;
        getline(cin, numbers);
        if (numbers.empty())
            break;
        auto phone = req->add_phone();
        phone->set_number(numbers);

        cout << "选择此电话类型(1.移动电话 2.固定电话):";
        int type;
        cin >> type;
        cin.ignore(256, '\n');
        switch (type)
        {
        case 1:
            phone->set_type(protocol::AddContactRequest_Phone_PhoneType::AddContactRequest_Phone_PhoneType_MP);
            break;
        case 2:
            phone->set_type(protocol::AddContactRequest_Phone_PhoneType::AddContactRequest_Phone_PhoneType_TEL);
            break;
        default:
            cout << "非法选择,使用默认值!" << endl;
            break;
        }
    }
}
void addContact()
{
    httplib::Client client(server_ip, server_port);
    // 构造request
    protocol::AddContactRequest req;
    buildAddContactRequest(&req);

    // 序列化request
    string req_str;
    if (!req.SerializeToString(&req_str))
    {
        throw ContactsException("AddContactRequest序列化失败!");
    }
    // 发起post调用
    httplib::Result res = client.Post("/contacts/add", req_str, "application/protobuf");
    if (!res)
    {
        string err_desc = "/contacts/add 链接失败!错误信息: " + httplib::to_string(res.error());
        throw ContactsException(err_desc);
    }
    // 反序列化response
    protocol::Response rsp;
    bool parse = rsp.ParseFromString(res->body);
    if (res->status != 200 && parse == false)
    {
        string err_desc = "/contacts/add 调用失败,返回状态码为: " +
                          to_string(res->status) + "(" + res->reason + ")";
        throw ContactsException(err_desc);
    }
    else if (res->status != 200)
    {
        string err_desc = "/contacts/add 调用失败,返回状态码为: " +
                          to_string(res->status) + "(" + res->reason + "),错误原因: " + rsp.error_desc();
        throw ContactsException(err_desc);
    }
    else if (rsp.success() == false)
    {
        string err_desc = "/contacts/add 结果异常,异常原因: " + rsp.error_desc();
        throw ContactsException(err_desc);
    }

    // 结果打印
    cout << "新增联系人成功,联系人ID: " << rsp.uid() << endl;
}
int main()
{
    enum OPTION
    {
        QUIT = 0,
        ADD,
        DEL,
        FIND_ALL,
        FIND_ONE
    };
    while (true)
    {
        menu();
        cout << "--->请选择: ";
        int choose;
        cin >> choose;
        cin.ignore(256, '\n');
        try
        {
            switch (choose)
            {
            case OPTION::QUIT:
                cout << "程序退出!" << endl;
                return 0;
            case OPTION::ADD:
                cout << "新增联系人--->" << endl;
                addContact();
                break;
            case OPTION::DEL:
                cout << "删除联系人--->" << endl;
                break;
            case OPTION::FIND_ALL:
                cout << "查看联系⼈列表--->" << endl;
                break;
            case OPTION::FIND_ONE:
                cout << "查看联系⼈详细信息--->" << endl;
                break;
            default:
                cout << "选择错误,请重新选择!" << endl;
                break;
            }
        }
        catch (const ContactsException &e)
        {
            cout << "操作通讯录时发生异常,异常信息为: " << e.what() << endl;
        }
    }
    return 0;
}

server.cc

cpp 复制代码
#include <iostream>
#include "httplib.h"
#include "ContactsException.h"
#include "protocol.pb.h"
using namespace std;

unsigned int random_char()
{
    // ⽤于随机数引擎获得随机种⼦
    random_device rd;
    // mt19937是c++11新特性,它是⼀种随机数算法,⽤法与rand()函数类似,但是mt19937具有速度快,周期⻓的特点
    // 作⽤是⽣成伪随机数
    mt19937 gen(rd());
    // 随机⽣成⼀个整数i 范围[0, 255]
    uniform_int_distribution<> dis(0, 255);
    return dis(gen);
}
// ⽣成 UUID (通⽤唯⼀标识符)
string generate_hex(const unsigned int len)
{
    stringstream ss;
    // ⽣成 len 个16进制随机数,将其拼接⽽成
    for (auto i = 0; i < len; i++)
    {
        const auto rc = random_char();
        stringstream hexstream;
        hexstream << hex << rc;
        auto hex = hexstream.str();
        ss << (hex.length() < 2 ? '0' + hex : hex);
    }
    return ss.str();
}
void printContact(const protocol::AddContactRequest &proto_req)
{
    cout << "联系⼈姓名: " << proto_req.name() << endl;
    cout << "联系⼈年龄: " << proto_req.age() << endl;
    int j = 1;
    for (auto phone : proto_req.phone())
    {
        cout << "联系⼈电话" << j++ << ": " << phone.number();
        cout << " (" << phone.PhoneType_Name(phone.type()) << ")" << endl;
    }
}
int main()
{
    cout << "服务启动..." << endl;
    httplib::Server server;
    server.Post("/contacts/add", [](const httplib::Request &req, httplib::Response &rsp)
                {
                    cout << "收到post请求!" << endl;
                    protocol::AddContactRequest proto_req;
                    protocol::Response proto_rsp;
                    try
                    {
                        // 反序列化req,取出req的body
                        if(!proto_req.ParseFromString(req.body))
                        {
                            throw ContactsException("AddContactRequest反序列化失败");
                        }

                        // 新增联系人,持久化到文件或打印
                        printContact(proto_req);
                        // 构造proto_rsp
                        proto_rsp.set_success(true);
                        proto_rsp.set_uid(generate_hex(10));
                        // 序列化proto_rsp,构造rsp的body
                        string rsp_str;
                        if(!proto_rsp.SerializeToString(&rsp_str))
                        {
                            throw ContactsException("Response序列化失败");
                        }
                        rsp.status=200;
                        rsp.body=rsp_str;
                        rsp.set_header("Content-Type","application/protobuf");
                    }
                    catch (const ContactsException &e)
                    {
                        //返回异常的rsp
                        rsp.status=500;
                        proto_rsp.set_success(false);
                        proto_rsp.set_error_desc(e.what());
                        string rsp_str;
                        if(proto_rsp.SerializeToString(&rsp_str))
                        {
                            rsp.body=rsp_str;
                            rsp.set_header("Content-Type","application/protobuf");
                        }
                        cout<<"/contacts/add 发生异常,异常信息: "<<e.what()<<endl;
                    } });

    // 绑定端口
    server.listen("0.0.0.0", 8080);
    return 0;
}

makefile

cpp 复制代码
.PHONY:all
all:server client

server:server.cc protocol.pb.cc
	g++ -o $@ $^ -std=c++17 -lpthread -lprotobuf
client:client.cc protocol.pb.cc
	g++ -o $@ $^ -std=c++17 -lpthread -lprotobuf

.PHONY:clean
clean:
	rm -f server client
相关推荐
网络研究院17 分钟前
2025 年网络安全终极指南
网络·安全·环境·指南·数字·预防·措施
孙同学_22 分钟前
【Linux篇】基础IO - 文件描述符的引入
linux·运维·网络
梁下轻语的秋缘2 小时前
实验二 VLAN 的配置与应用
网络·学习·计算机网络·智能路由器
天才奇男子2 小时前
VLAN(虚拟局域网)
网络·网络协议
菜咖咖6 小时前
跨网连接vscode
网络·智能路由器
rufeike7 小时前
Rclone同步Linux数据到google云盘
linux·运维·服务器
Dream Algorithm8 小时前
什么是宽带拨号?
网络·智能路由器
愚戏师8 小时前
软件工程(应试版)图形工具总结(二)
数据结构·c++·python·软件工程
owde8 小时前
顺序容器 -forward list单链表
数据结构·c++·list
fanjinhong_85219 小时前
网络安全防御核心原则与实践指南
网络·安全·web安全