ProtoBuf实战之网络版通讯录

news2024/11/14 11:57:53

目录

网络版通讯录需求

实现网络版通讯录

搭建服务端客户端

协议约定

客户端菜单功能

服务端代码


Protobuf 还常用于 通讯协议、服务端数据交换 的场景,接下来,我们将实现一个网络版本的通讯录,模拟实现客户端与服务端的交互,通过 Protobuf 来实现各端之间的协议序列化

网络版通讯录需求

客户端可以选择对通讯录进行以下操作:

  • 新增一个联系人

  • 删除一个联系人

  • 查询通讯录列表

  • 查询一个联系人的详细信息

服务端:
提供:增、删、查能力,并需要持久化通讯录

客户端、服务端间的交互数据使用 Protobuf 来完成


下面分析具体需要做的事情:

  • 因为是网络传输,所以需要 req和resp,首先给客户端和服务端的 req和resp 创建对应的 message

  • 客户端选择需要做的事情后,放入 req 中,进行网络传输前 序列化req

  • 调用接口 req

  • 在 服务端 反序列化req

  • 接着调用对应的代码,并保证持久化存储 通讯录

  • 完成后 序列化resp,进行网络传输

  • 客户端接收到 resp 后,反序列化 resp,整个网络通信结束


实现网络版通讯录

由于新增、删除、查询联系人,只是实现逻辑不同,而网络通信的步骤是相同的,所以下面只实现 新增联系人 的功能,帮我们更好的了解网络通信

搭建服务端客户端

首先需要使用 cpp-httplib 库,只需要克隆到服务器上后,将 httplib.h 放入项目路径下,包含即可使用

点击进入对应gitee

搭建服务端

服务端有以下文件:

makefile:

server:*.cc
	g++ -o $@ $^ -std=c++11 -lpthread -lprotobuf

.PHNOY:clean
clean:
	rm -f server

main.cc:

#include <iostream>
#include "httplib.h"

using namespace std;
using namespace httplib;

int main()
{
    cout << "-----------服务启动-----------";
    Server server;

    server.Post("/test-post", [](const Request& req, Response& res){
        cout << "接收到Post请求!" << endl;
        res.status = 200;
    });

    server.Get("/test-get", [](const Request& req, Response& res){
        cout << "接收到Get请求!" << endl;
        res.status = 200;
    });
    // 绑定8081端口,并将端口对外开放
    server.listen("0.0.0.0", 8081);
    return 0;
}

搭建客户端

此时再另一台主机上搭建客户端,客户端同样是这三个文件:

makefile:

client:*.cc
	g++ -o $@ $^ -std=c++11 -lpthread -lprotobuf
 
.PHNOY:clean
clean:
	rm -f client

main.cc:

#include <iostream>
#include "httplib.h"

using namespace std;
using namespace httplib;

int main()
{
    Client cli("127.0.0.1", 8081);

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

此时服务端和客户端都编译后启动,初步验证了当前的服务端和客户端是可以跨网络传输的


协议约定

在服务端和客户端中,各自创建一个 add_contact.proto文件,内容是:

syntax = "proto3";
package add_contact;

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; // 电话信息
}

message AddContactResponse
{
    bool success = 1;       // 客户端调用是否成功
    string error_desc = 2;  // 错误原因
    string uid = 3;         // 代表唯一的一个联系人
}

客户端菜单功能

在某些方法中可能出现一些错误的行为,所以我们可以自定义一个异常类,如果在客户端序列化 req 时,失败了,我们就可以直接抛一个异常

在 main函数 中接收到异常后,将异常捕获,再进行错误信息的打印

所以这里重新写一个文件:ContactsException.h

#pragma once
#include <iostream>
#include <string>

class ContactsException
{
private:
    std::string message;
public:
    ContactsException(std::string str = "A problem") : message(str)
    {}
    std::string what() const
    {
        return message;
    }
};

客户端首先需要一个菜单:

在 main函数 中肯定需要使用 Switch case,不想 case 1 这样使用,所以创建一个枚举类型:

main函数 的整体框架是:
其中 2/3/4 都不是实现,只实现 新增

 


main.cc代码:

#include <iostream>
#include "httplib.h"
#include "ContactsException.h"
#include "add_contact.pb.h"

using namespace std;
using namespace httplib;

void addContact();

void menu()
{
    cout << "------------------------------------" << endl;
    cout << "---------请选择对通讯录的操作---------" << endl;
    cout << "------------1. 新增联系人------------" << endl;
    cout << "------------2. 删除联系人------------" << endl;
    cout << "------------3. 查看联系人列表--------" << endl;
    cout << "------------4. 查看联系人详细信息----" << endl;
    cout << "------------0. 退出-----------------" << endl;
    cout << "------------------------------------" << endl;
}

enum OPTION
{
    QUIT = 0,
    NEW,
    DEL,
    FIND_ALL,
    FIND_ONE
};

int main()
{
    while (true)
    {
        menu();
        cout << "--->请选择: ";
        int choose;
        cin >> choose;
        cin.ignore(256, '\n');
        try
        {
            switch (choose)
            {
            case OPTION::QUIT:
                cout << "程序退出" << endl;
                return 0;
            case OPTION::NEW:
                addContact();
                break;
            case OPTION::DEL:
            case OPTION::FIND_ALL:
            case OPTION::FIND_ONE:
                break;
            default:
                cout << "选择有误, 请重新选择!" << endl;
                break;
            }
        }
        catch (const ContactsException &e)
        {
            cout << "--->操作通讯录时发送异常" << endl;
            cout << "--->异常信息: " << e.what() << endl;
        }
    }
    return 0;
}

// 从前面实现的通讯录版本的 write.cc 中拷贝过来即可, 大体逻辑类似
void buildAddContactRequest(add_contact::AddContactRequest* req)
{
    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 number;
        getline(cin, number);
        if(number.empty())
        {
            break;
        }
        add_contact::AddContactRequest_Phone* phone = req->add_phone();
        phone->set_number(number);

        cout << "请输入该电话类型(1.移动电话 2.固定电话): ";
        int type;
        cin >> type;
        cin.ignore(256, '\n');
        switch(type)
        {
        case 1:
            phone->set_type(add_contact::AddContactRequest_Phone_PhoneType::AddContactRequest_Phone_PhoneType_MP);
            break;
        case 2:
            phone->set_type(add_contact::AddContactRequest_Phone_PhoneType::AddContactRequest_Phone_PhoneType_TEL);
            break;
        default:
            cout << "输入有误!" << endl;
            break;
        }
    }
}

void addContact()
{
    Client cli("0.0.0.0", 8080);
    // 构造 req
    add_contact::AddContactRequest req;
    buildAddContactRequest(&req);

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

    // 发起 Post 调用
    auto res = cli.Post("/contacts/add", req_str, "application/protobuf");
    if(!res)
    {
        // httplib::to_string() 可以将错误的枚举类型转化了枚举对应的描述
        string err_desc;
        err_desc.append("/contacts/add 链接失败! 错误信息: ")
                .append(httplib::to_string(res.error()));
        throw ContactsException(err_desc);
    }

    // 反序列化 resp
    add_contact::AddContactResponse resp;
    bool parse = resp.ParseFromString(res->body);
    if (res->status != 200 && !parse)
    {
        string err_desc;
        err_desc.append("/contacts/add 调用失败")
                .append(std::to_string(res->status))
                .append("(").append(res->reason).append(")");
        throw ContactsException(err_desc);
    }
    else if(res->status != 200)
    {
        string err_desc;
        err_desc.append("/contacts/add 调用失败")
                .append(std::to_string(res->status))
                .append("(").append(res->reason).append(")错误原因:")
                .append(resp.error_desc());
        throw ContactsException(err_desc);
    }
    else if(!resp.success())
    {
        string err_desc;
        err_desc.append("/contacts/add 结果异常")
                .append("异常原因:")
                .append(resp.error_desc());
        throw ContactsException(err_desc);
    }

    // 结果打印
    cout << "新增联系人成功, 联系人ID: " << resp.uid() << endl;
}

服务端代码

main.cc完整代码:

#include <iostream>
#include <string>
#include "httplib.h"
#include "add_contact.pb.h"

using namespace std;
using namespace httplib;

class ContactsException
{
private:
    std::string message;
public:
    ContactsException(std::string str = "A problem") : message(str)
    {}
    std::string what() const
    {
        return message;
    }
};

void printContact(add_contact::AddContactRequest& req)
{
    cout << "联系人姓名: " << req.name() << endl;
    cout << "联系人年龄: " << req.age() << endl;
    for(int j = 0; j < req.phone_size(); j++)
    {
        const add_contact::AddContactRequest_Phone& phone = req.phone(j);
        cout << "联系人电话" << j+1 << ":" << phone.number();
        // 格式为: 联系人电话1:123456   (MP)
        cout << "   (" << phone.PhoneType_Name(phone.type()) << ")" << endl;
    }
}

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);
 }
 // ⽣成 UUID (通⽤唯⼀标识符) 
 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();
 }


int main()
{
    cout << "-----------服务启动-----------" << endl;;
    Server server;

    server.Post("/contacts/add", [](const Request& req, Response& res){
        cout << "接收到Post请求!" << endl;
        // 反序列化 request: req.body
        add_contact::AddContactRequest request;
        add_contact::AddContactResponse response;
        try
        {
            if(!request.ParseFromString(req.body))
            {
                throw ContactsException("AddcontactRequest反序列化失败!");
            }

            // 新增联系人,打印新增的联系人信息
            printContact(request);

            // 构造 response: res.body
            response.set_success(true);
            response.set_uid(generate_hex(10));

            // res.body (序列化response)
            string response_str;
            if(!response.SerializeToString(&response_str))
            {
                throw ContactsException("AddcontactResponse列化失败!");
            }
            res.status =200;
            res.body = response_str;
            res.set_header("content-Type", "application/protobuf");
        }
        catch(const ContactsException& e)
        {
            res.status =500;
            response.set_success(false);
            response.set_error_desc(e.what());
            string response_str;
            if(response.SerializeToString(&response_str))
            {
                res.body = response_str;
                res.set_header("Content-Type", "application/protobuf");
            }
            cout << "/contacts/add 发生异常,异常信息:" << e.what() << endl;
        }
    });

    // 绑定8080端口,并将端口对外开放
    server.listen("0.0.0.0", 8080);
    return 0;
}

网络版通讯录实现完毕

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

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

相关文章

动态规划理论基础和习题【力扣】【算法学习day.26】

前言 ###我做这类文档一个重要的目的还是给正在学习的大家提供方向&#xff08;例如想要掌握基础用法&#xff0c;该刷哪些题&#xff1f;&#xff09;我的解析也不会做的非常详细&#xff0c;只会提供思路和一些关键点&#xff0c;力扣上的大佬们的题解质量是非常非常高滴&am…

Sqoop学习

目录 一、Soop简介 二、Sqoop的安装 1. 上传压缩包到/opt/install目录下 2.解压 3.修改文件名 4.拷贝sqoop-1.4.7.bin__hadoop-2.6.0目录下的sqoop-1.4.7.jar包到/opt/soft/sqoop147目录下 5.拷贝sqoop-1.4.7.bin__hadoop-2.6.0/lib目录下该jar包到sqoop/lib目录下 6.复…

关于随身wifi,看了再决定要不要买!2024年最受欢迎的随身wifi品牌推荐!

话费、流量费缴纳起来肉疼&#xff0c;毕竟不是每个月都有很大需求&#xff0c;主打一个该省省该花花。特别是短租人群、在校学生、出差或旅游的人群、追求高性价比的人群&#xff0c;随身Wifi特别实用&#xff0c;出门当WiFi&#xff0c;在家当宽带&#xff0c;两不耽误&#…

[vulnhub] DarkHole: 1

https://www.vulnhub.com/entry/darkhole-1,724/ 端口扫描主机发现 探测存活主机&#xff0c;184是靶机 nmap -sP 192.168.75.0/24 Starting Nmap 7.94SVN ( https://nmap.org ) at 2024-11-08 09:59 CST Nmap scan report for 192.168.75.1 Host is up (0.00027s latency). MA…

iPhone 微信传大文件到QQ

问题 解决方法 在微信里打开要拷贝的文件 选择“...” 选择“用其他应用打开” 长按QQ 选择“拷贝到App”&#xff08;有些版本是“在App中打开”&#xff09;

【学习笔记】网络设备(华为交换机)基础知识 11 —— 信息中心 ② 配置案例

提示&#xff1a;学习华为交换机信息中心配置案例&#xff0c;包含配置输出Log信息 &#xff08; 输出到Log缓冲区、控制台、日志文件、终端 &#xff09;、配置输出Trap信息 &#xff08; 输出到Trap缓冲区、控制台、日志文件、终端 &#xff09;、 配置输出Debug信息 &#x…

电脑管家实时监控软件下载 | 六款知名又实用的电脑监控软件推荐!(珍藏篇)

在当今的商业环境&#xff0c;企业对于员工在工作期间的行为监控需求越来越强烈。 尤其是在网络化和信息化程度不断提高的今天&#xff0c;电脑管家实时监控软件是企业管理员工工作行为、提高工作效率、防止信息泄露的重要工具。 本文&#xff0c;将为您推荐六款知名又实用的电…

PICO+Unity MR空间网格

官方链接&#xff1a;空间网格 | PICO 开发者平台 注意&#xff1a;该功能只能打包成APK在PICO 4 Ultra上真机运行&#xff0c;无法通过串流或PICO developer center在PC上运行。使用之前要开启视频透视。 在 Inspector 窗口中的 PXR_Manager (Script) 面板上&#xff0c;勾选…

Spring Boot中集成MyBatis操作数据库详细教程

目录 前言1. 项目依赖配置1.1 引入MyBatis和数据库驱动依赖1.2 数据源配置 2. 创建数据库映射实体类3. 创建Mapper层接口4. 创建Service层4.1 定义Service接口4.2 实现Service接口 5. 创建Controller层6. 运行和测试项目6.1 启动项目6.2 测试接口 7. 总结 前言 在Java开发中&a…

DirectShow过滤器开发-写AVI视频文件过滤器

下载本过滤器DLL 本过滤器将视频流和音频流写入AVI视频文件。 过滤器信息 过滤器名称&#xff1a;写AVI 过滤器GUID&#xff1a;{2EF49957-37DF-4356-A2A0-ECBC52D1984B} DLL注册函数名&#xff1a;DllRegisterServer 删除注册函数名&#xff1a;DllUnregisterServer 过滤器有…

使用 API 和离线库查询 IP 地址方法详解

目录 一、IP 地址查询能获取哪些信息1.地理位置信息2.网络信息3.网络类型 二、IP 地址查询方法&#xff0c;附代码1.在线查询 IP 地址方法2.使用 API 进行 IP 地址查询3.使用离线库进行 IP 地址查询 互联网监管部门要求公开 IP 归属地&#xff0c;引起了很大热度&#xff0c;但…

机器学习在时间序列预测中的应用与实现——以电力负荷预测为例(附代码)

&#x1f4dd;个人主页&#x1f339;&#xff1a;一ge科研小菜鸡-CSDN博客 &#x1f339;&#x1f339;期待您的关注 &#x1f339;&#x1f339; 1. 引言 随着数据采集技术的发展&#xff0c;时间序列数据在各个领域中的应用越来越广泛。时间序列预测旨在基于过去的时间数据来…

语音识别ic赋能烤箱,离线对话操控,引领智能厨房新体验

一、智能烤箱产品的行业背景 随着科技的飞速发展&#xff0c;智能家居已经成为现代家庭的新宠。智能烤箱作为智能家居的重要组成部分&#xff0c;正逐渐从高端市场走向普通家庭。消费者对于烤箱的需求不再仅仅局限于基本的烘焙功能&#xff0c;而是更加注重其智能化、便捷化和…

【C++】异常处理机制(对运行时错误的处理)

&#x1f308; 个人主页&#xff1a;谁在夜里看海. &#x1f525; 个人专栏&#xff1a;《C系列》《Linux系列》 ⛰️ 天高地阔&#xff0c;欲往观之。 目录 引言 1.编译器可以处理的错误 2.编译器不能处理的错误 3.传统的错误处理机制 assert终止程序 返回错误码 一、…

Python基础学习-03逻辑分支语句、循环

目录 1、记住逻辑关系 2、逻辑分支语句 3、for-loop循环 4、while-loop 5、break 和 continue 6、本节总结 1、记住逻辑关系 • 逻辑关系 1&#xff09; True&#xff08;真&#xff09; 和 False&#xff08;假&#xff09; 2&#xff09;逻辑关系有 and&#xff08;与…

【Windows】Android Studio 上cmd 换为Powershell 终端

最近在Windows 环境下Android Studio 的Terminal 终端&#xff0c;低版本默认用的是cmd.exe&#xff0c;好多linux 命令不支持&#xff0c;有时候一不小心就记忆错了&#xff1b;干脆直接换成Windows PowerShell 得了。 下载Powershell&#xff1a;https://aka.ms/PSWindows 选…

javascript实现sha512和sha384算法(支持微信小程序),可分多次计算

概述&#xff1a; 本人前端需要实现sha512和sha384计算的功能&#xff0c;最好是能做到分多次计算。 本文所写的代码在现有sha512和sha384的C代码&#xff0c;反复测试对比计算过程参数&#xff0c;成功改造成sha512和sha384的javascript代码&#xff0c;并成功验证好分多次计算…

Pr 视频过渡:沉浸式视频

效果面板/视频过渡/沉浸式视频 Video Transitions/Immersive Video Adobe Premiere Pro 的视频过渡效果中&#xff0c;沉浸式视频 Immersive Video效果组主要用于 VR 视频剪辑之间的过渡。 自动 VR 属性 Auto VR Properties是所有 VR 视频过渡效果的通用选项。 默认勾选&#x…

Ascend C的编程模型

1 并发执行 Ascend C和cudnn相似&#xff0c;都是一种多核心编程的范式。想要了解Ascend C&#xff0c;必须得先掌握这种“多核”是怎么实现得。 多核执行&#xff0c;说白了就是使用CPU/GPU/Ascend的物理多核并发去执行一段流程&#xff0c;一般情况下&#xff0c;可以通过以…

商品,订单风控业务梳理二

订单风控流程 业务风控系统