Protobuf 的快速使用(四)

news2025/4/6 13:01:40
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

定义抛异常的类

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

private:
    std::string message;
};

client.cc

#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

#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

.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

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

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

相关文章

基于Spark的哔哩哔哩舆情数据分析系统

【Spark】基于Spark的哔哩哔哩舆情数据分析系统 &#xff08;完整系统源码开发笔记详细部署教程&#xff09;✅ 目录 一、项目简介二、项目界面展示三、项目视频展示 一、项目简介 本项目基于Python和Django框架进行开发&#xff0c;为了便于广大用户针对舆情进行个性化分析处…

【Linux】日志模块实现详解

&#x1f4e2;博客主页&#xff1a;https://blog.csdn.net/2301_779549673 &#x1f4e2;博客仓库&#xff1a;https://gitee.com/JohnKingW/linux_test/tree/master/lesson &#x1f4e2;欢迎点赞 &#x1f44d; 收藏 ⭐留言 &#x1f4dd; 如有错误敬请指正&#xff01; &…

Java基础:面向对象高级(四)

内部类&#xff08;类中五大成分之一&#xff09; 四种形式 成员内部类【了解】 静态内部类【了解】 局部内部类【了解】 匿名内部类【重点】 枚举 泛型 什么是泛型 泛型类-模拟ArrayList 泛型接口-操作学生&#xff0c;老师增删改查 泛型方法 泛型擦除和注意事项

easy-poi 一对多导出

1. 需求&#xff1a; 某一列上下两行单元格A,B值一样且这两个单元格&#xff0c; 前面所有列对应单元格值一样的话&#xff0c; 就对A,B 两个单元格进行纵向合并单元格 1. 核心思路&#xff1a; 先对数据集的国家&#xff0c;省份&#xff0c;城市...... id 身份证进行排序…

python通过调用海康SDK打开工业相机(全流程)

首先打开海康机器人-机器视觉-下载中心 下载最新版的 MVS 安装后打开目录找到 ...\MVS\Development\Samples\Python 将MvImport内所有文件拷贝至工作目录 然后到 C:\Program Files (x86)\Common Files\MVS\Runtime 找到适合自己系统的版本&#xff0c;将整个文件夹拷贝至工…

manim,制作专业的数学公式动画

manim是一个Python第三方库,全称是mathematical animation engine(数学动画引擎)。manim用于解说线性代数、微积分、神经网络、黎曼猜想、傅里叶变换以及四元数等数学概念。 manim使你能够以编程的方式创建精确的数学图形、动画和场景。与传统的几何画板等绘图软件不同,man…

小刚说C语言刷题——第15讲 多分支结构

1.多分支结构 所谓多分支结构是指在选择的时候有多种选择。根据条件满足哪个分支&#xff0c;就走对应分支的语句。 2.语法格式 if(条件1) 语句1; else if(条件2) 语句2; else if(条件3) 语句3; ....... else 语句n; 3.示例代码 从键盘输入三条边的长度&#xff0c;…

[ctfshow web入门] web6

前置知识 入口点(目录)爆破 还记得之前说过网站的入口的吗&#xff0c;我们输入url/xxx&#xff0c;其中如果url/xxx存在&#xff0c;那么访问成功&#xff0c;证明存在这样一个入口点&#xff1b;如果访问失败则证明不存在此入口点。所以我们可以通过遍历url/xxx&#xff0c;…

简单程序语言理论与编译技术·22 实现一个从AST到RISCV的编译器

本文是记录专业课“程序语言理论与编译技术”的部分笔记。 LECTURE 22&#xff08;实现一个从AST到RISCV的编译器&#xff09; 一、问题分析 1、完整的编译器&#xff08;如LLVM&#xff09;需先完成AST到IR的转换&#xff0c;并进行代码优化&#xff0c;再到汇编&#xff0…

lua和C的交互

1.C调用lua例子 #include <iostream> #include <lua.hpp>int main() {//用于创建一个新的lua虚拟机lua_State* L luaL_newstate();luaL_openlibs(L);//打开标准库/*if (luaL_dofile(L, "test.lua") ! LUA_OK) {std::cerr << "Lua error: &…

Css:如何解决绝对定位子元素内容被父级元素overflow:hidden属性剪裁

一、问题描述 今天小伙伴提了一个bug&#xff0c;在点击列表项的“…”按钮应该出现的悬浮菜单显示不完整&#xff1a; 二、问题排查 一般这种问题&#xff0c;是由于悬浮菜单采用的是绝对定位&#xff0c;而父级采用了overflow:hidden属性。但需要注意的是&#xff0c;这里的…

RoMo: Robust Motion Segmentation Improves Structure from Motion

前言 看起来像是一篇投稿CVPR的文章&#xff0c;不知道被哪个瞎眼审稿人拒了。同期还有一篇CVPR被接收的工作Segment Any Motion in Videos&#xff0c;看起来不如这篇直白&#xff08;也可能是因为我先看过spotlesssplats的缘故&#xff09;&#xff0c;后面也应该一并介绍了…

MCP 极简入门 - 三分钟 Cline + Smithery 运行 time 服务

文章目录 一、&#x1f680; 初识Smithery&#xff1a;AI服务的新大陆找到心仪的服务 二、Cline 编辑配置文件&#x1f527;1、打开配置文件2. 添加Time Server配置3. 验证配置效果 三、&#x1f4ac; 实战对话&#xff1a;让AI告诉你时间四、服务管理小技巧&#x1f504;&…

基本机动飞行性能

机动飞行时描述飞机在给定构型和发动机工作状态下改变飞行速度、飞行高度和飞行方向的能力 1. 水平加&#xff08;减&#xff09;速 水平加&#xff08;减&#xff09;速性能反映飞机在水平面内改变直线飞行速度的能力。描述水平加&#xff08;减&#xff09;速性能的参数包括…

【Linux】进程间通信、匿名管道、进程池

一.什么是通信 进程间通信(Inter-Process Communication&#xff0c;IPC),是指在操作系统中&#xff0c;不同进程之间进行数据交换和同步的机制。由于每个进程通常拥有独立的内存空间&#xff0c;进程间无法直接访问对方的内存&#xff0c;因此需要通过特定的机制来实现通信和…

【MATLAB定位例程】TDOA(到达时间差)的chan-tylor,三维环境,附完整代码

该代码实现了基于三维空间的动态目标TDOA定位,结合了Chan算法(解析解)与Taylor级数展开法(迭代优化)的双重优势。 文章目录 运行结果MATLAB代码代码讲解代码功能概述核心算法原理代码结构解析可视化与结果分析运行结果 定位示意图: 三轴状态曲线: 三轴误差曲线: MA…

数字化转型中的开源AI智能客服与S2B2C商城小程序的融合创新

摘要 数字经济时代&#xff0c;企业需通过技术重构用户交互与供应链体系。本文以“开源AI智能客服”“AI智能名片”及“S2B2C商城小程序”为核心&#xff0c;研究三者如何通过技术协同与场景化应用实现企业营销、客户服务与供应链管理的智能化升级。通过案例分析、技术架构设…

重生之我是去噪高手——diffusion model

diffusion model是如何运作的&#xff1f; 想象一下&#xff0c;你有一张清晰的图片。扩散模型的核心思想分为两个过程&#xff1a; 前向过程&#xff08;Forward Process / Diffusion Process&#xff09;&#xff1a;逐步加噪反向过程&#xff08;Reverse Process / Denois…

【C#】.net core 6.0 依赖注入常见问题之一,在构造函数使用的类,都需要注入到容器里,否则会提示如下报错,让DeepSeek找找原因,看看效果

&#x1f339;欢迎来到《小5讲堂》&#x1f339; &#x1f339;这是《C#》系列文章&#xff0c;每篇文章将以博主理解的角度展开讲解。&#x1f339; &#x1f339;温馨提示&#xff1a;博主能力有限&#xff0c;理解水平有限&#xff0c;若有不对之处望指正&#xff01;&#…

论文阅读笔记——RDT-1B: A DIFFUSION FOUNDATION MODEL FOR BIMANUAL MANIPULATION

RDT-1B 论文 模型表达与泛化能力&#xff1a;由于双臂操作中动作空间维度是单臂空间的两倍&#xff0c;传统方法难以建模其多模态分布。 数据&#xff1a;双臂数据少且不同机器人的物理结构和动作空间差异&#xff08;如关节数、运动范围&#xff09;导致数据分布不一致&#x…