【ProtoBuf 实战训练】网络版通讯录

news2026/4/4 6:13:31
文章目录1. 通讯录 4.0 实现网络版2. 环境搭建2.1 搭建服务端2.2 搭建客户端2.3 运行结果3. 新增联系人功能3.1 协议约定3.2 协议接口定义 (.proto)3.2.1 AddContactRequest请求消息3.2.2 AddContactResponse响应消息3.3 完成客户端代码3.3.1 菜单函数实现3.3.2 main 函数实现3.3.3 addContact 函数3.3.4 buildAddContactRequest 函数3.4 完成服务端代码3.4.1 main 函数实现3.4.2 生成 uid3.4.3 辅助功能说明4. 功能演示5. 总结1. 通讯录 4.0 实现网络版Protobuf 还常用于通讯协议、服务端数据交换场景。那么在这个示例中我们将实现一个网络版本的通讯录模拟实现客户端与服务端的交互通过 Protobuf 来实现各端之间的协议序列化。需求如下客户端可以选择对通讯录进行以下操作新增一个联系人删除一个联系人查询通讯录列表查询一个联系人的详细信息服务端提供增删查能力并需要持久化通讯录。客户端、服务端间的交互数据使用 Protobuf 来完成。2. 环境搭建Httplib 库cpp-httplib 是个开源的库并且是用 C 封装的 http 库使用这个库可以在 linux、windows 平台下完成 http 客户端、http 服务端的搭建。使用起来非常方便只需要包含头文件httplib.h即可。编译程序时需要带上-lpthread选项。源码库地址cpp-httplib直接把 httplib.h 这个文件复制到服务器上即可。另外如果使用 centOS 环境yum 源带的 g 最新版本是 4.8.5发布于 2015 年年代久远。那么使用最新版本的 cpp-httplib 编译该项目会出现异常。所以必须将 gcc/g 升级为更高版本可解决问题。# 安装gcc 8版本yuminstall-ydevtoolset-8-gcc devtoolset-8-gcc-c# 启用版本source/opt/rh/devtoolset-8/enable# 查看版本已经变成gcc 8.3.1gcc-v下面是一个简单的服务端和客户端 Demo 示例主要是演示一下 cpp-httplib 库是如何使用的。代码结构如下2.1 搭建服务端先编写服务端的主函数代码#includeiostream#includehttplib.husingstd::cout;usingstd::endl;usingstd::cerr;usingnamespacehttplib;intmain(){cout-----------服务启动----------endl;Server server;server.Post(/test-post,[](constRequestreq,Responseres){cout接收到post请求!endl;res.status200;});server.Get(/test-get,[](constRequestreq,Responseres){cout接收到get请求!endl;res.status200;});// 绑定 8123 端口并且将端口号对外开放server.listen(0.0.0.0,8123);return0;}再编写 makefileservice:*.cc g-o$$^-stdc11-lpthread-lprotobuf.PHONY:clean clean:rm-fservice2.2 搭建客户端先编写客户端的主函数代码#includeiostream#includehttplib.husingnamespacestd;usingnamespacehttplib;#defineCONTACTS_HOST172.19.61.204#defineCONTACTS_PORT8123intmain(){Clientcli(CONTACTS_HOST,CONTACTS_PORT);Result res1cli.Post(/test-post);if(res1-status200){cout调用post成功!endl;}Result res2cli.Get(/test-get);if(res2-status200){cout调用get成功!endl;}return0;}再编写 makefileservice:*.cc g-o$$^-stdc11-lpthread-lprotobuf.PHONY:clean clean:rm-fservice2.3 运行结果编译运行的结果如下以上就是 httplib 库的一个基本用法。3. 新增联系人功能在本篇文章中我们只针对【新增一个联系人】这个功能进行实现完整代码可以移步到代码仓库。3.1 协议约定对于新增一个联系人我们在 Client 客户端需要发起 POST 请求调用。格式如下[请求]Post/contacts/add AddContactRequest Content-Type:application/protobuf同时在服务端需要进行响应格式如下[响应]AddContactResponse Content-Type:application/protobuf3.2 协议接口定义 (.proto)接着开始编写 .proto 文件客户端和服务器一样这份.proto文件是定义客户端和服务端通信协议的 “合同”。它规定了数据将以什么样的格式进行打包和传输。3.2.1 AddContactRequest请求消息用于封装新增联系人的完整信息name (string)映射为 C 中的std::string。age (int32)映射为 C 中的int。Phone (嵌套消息)定义了电话详情包含号码number和枚举type。PhoneType (枚举)包含MP(0, 移动电话) 和TEL(1, 固定电话)。repeated Phone表示一个动态数组列表在 C 代码中通过add_phone()动态增加条目通过phone_size()获取数量。代码如下所示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; // 电话信息 }3.2.2 AddContactResponse响应消息用于服务端向客户端反馈执行结果success (bool)标识服务器业务处理是否成功。error_desc (string)当success为false时存储具体的报错信息如反序列化失败。uid (string)业务成功后返回服务端生成的唯一标识符。代码如下syntax proto3; package add_contact; message AddContactResponse { bool success 1; // 服务调用是否成功 string error_desc 2; // 错误原因 string uid 3; }3.3 完成客户端代码3.3.1 菜单函数实现功能描述该函数是一个纯显示型的辅助函数负责在终端界面输出美化后的操作菜单。实现要点用户引导清晰地列出了当前通讯录系统支持的五大功能选项新增、删除、查看列表、查看详情、退出。交互边界定义了合法输入的范围0-4为 main 函数中的逻辑判断提供视觉依据。代码如下所示voidmenu(){std::cout-----------------------------------------------------std::endl--------------- 请选择对通讯录的操作 ----------------std::endl------------------ 1、新增联系人 --------------------std::endl------------------ 2、删除联系人 --------------------std::endl------------------ 3、查看联系人列表 ----------------std::endl------------------ 4、查看联系人详细信息 ------------std::endl------------------ 0、退出 --------------------------std::endl-----------------------------------------------------std::endl;}3.3.2 main 函数实现功能描述客户端的逻辑控制中心负责驱动主循环、分发用户指令并处理全局异常。核心逻辑语义化选项通过枚举 OPTION 将数字输入0 - 4映射为具体业务如 ADD、QUIT提高代码可读性。交互循环使用 while(true) 配合 menu() 持续显示菜单直到用户输入 0 触发 return 0 退出。缓冲区清理在 cin choose 后立即执行 cin.ignore()防止残留的换行符干扰后续的字符串输入。统一容错通过 try-catch 捕获整个业务流中的 ContactsException。这样即便网络或序列化出错程序也能打印错误并安全返回主菜单不会直接崩溃。代码如下// 自定义异常类classContactsException{private:std::string message;public:ContactsException(std::string strA problem):message(str){}std::stringwhat()const{returnmessage;}};intmain(){enumOPTION{QUIT0,ADD,DEL,FIND_ALL,FIND_ONE};while(true){menu();cout---请选择;intchoose;cinchoose;cin.ignore(256,\n);try{switch(choose){caseOPTION::QUIT:cout---程序退出endl;return0;caseOPTION::ADD:addContact();break;caseOPTION::DEL:caseOPTION::FIND_ALL:caseOPTION::FIND_ONE:break;default:cout选择有误请重新选择!endl;break;}}catch(constContactsExceptione){cout---操作通讯录时发生异常endl---异常信息e.what()endl;}}return0;}3.3.3 addContact 函数功能描述该函数负责执行添加联系人的完整业务流。它是一个上层调用函数串联了本地交互、数据转换、网络通信及错误处理。核心逻辑步骤对象初始化创建 HTTP 客户端实例和空的 AddContactRequest 请求对象。调用构造逻辑调用 buildAddContactRequest 函数填充数据。序列化使用 SerializeToString() 将结构化对象转为二进制字节流以满足 Protobuf 传输要求。同步请求向服务器路径/contacts/add发起 POST 请求。特别指定了application/protobuf的 Content-Type告知服务端数据格式。多层级校验传输层检查 HTTP 请求是否送达if (!res)。协议层检查 HTTP 状态码是否为 200并尝试解析响应体。业务层解析成功后检查resp.success()字段确认服务端业务逻辑是否执行成功。结果反馈业务成功后从响应对象中获取并显示服务端分配的uid。代码如下voidaddContact(){// 定义客户端Clientcli(CONTACTS_HOST,CONTACTS_PORT);// 构造 reqAddContactRequest req;buildAddContactRequest(req);// 序列化 reqstring req_str;if(!req.SerializeToString(req_str)){throwContactsException(AddContactRequest序列化失败!);}// 发起 post 调用autorescli.Post(/contacts/add,req_str,application/protobuf);if(!res){string err_desc;err_desc.append(/contacts/add 链接失败错误信息).append(httplib::to_string(res.error()));throwContactsException(err_desc);}// 反序列化 respAddContactResponse resp;boolparseresp.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());throwContactsException(err_desc);}elseif(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());throwContactsException(err_desc);}elseif(!resp.success()){string err_desc;err_desc.append(/contacts/add 结果异常).append(异常原因).append(resp.error_desc());throwContactsException(err_desc);}// 结果打印cout新增联系人成功,联系人ID: resp.uid()endl;}3.3.4 buildAddContactRequest 函数功能描述该函数负责构造请求对象。它通过标准的输入输出流cin/cout与用户进行交互将用户输入的原始数据填充到 Protobuf 协议定义的结构体中。实现细节字段填充逐个提示用户输入 “姓名” 和 “年龄”并调用 Protobuf 自动生成的set_方法完成赋值。流清理在读取整数年龄后使用cin.ignore()清除缓冲区中的换行符确保后续getline能够正常读取字符串。重复字段处理利用for循环支持输入多个电话号码。每增加一个号码通过add_phone()动态创建一个电话子消息对象。输入校验通过if (number.empty())判断用户是否直接按下回车以此作为结束电话输入录入的信号。代码如下voidbuildAddContactRequest(AddContactRequest*req){cout请输入联系人姓名;string name;getline(cin,name);req-set_name(name);cout请输入联系人年龄;intage;cinage;req-set_age(age);cin.ignore(256,\n);for(inti0;;i){cout请输入联系人电话i1(只输入回车完成电话新增);string number;getline(cin,number);if(number.empty()){break;}AddContactRequest_Phone*phonereq-add_phone();phone-set_number(number);cout请输入该电话类型(1、移动电话 2、固定电话);inttype;cintype;cin.ignore(256,\n);switch(type){case1:phone-set_type(AddContactRequest_Phone_PhoneType::AddContactRequest_Phone_PhoneType_MP);break;case2:phone-set_type(AddContactRequest_Phone_PhoneType::AddContactRequest_Phone_PhoneType_TEL);break;default:cout选择有误endl;break;}}}3.4 完成服务端代码服务端采用了 cpp-httplib 作为 HTTP 框架结合 Protocol Buffers 处理数据的接收与响应并实现了简单的模拟持久化控制台打印以及 uuid 生成逻辑。3.4.1 main 函数实现功能描述main 函数是服务端的入口负责初始化 HTTP 服务器、注册路由回调逻辑并启动监听。实现逻辑解析路由注册通过server.Post(/contacts/add, ...)监听特定的 POST 请求路径。数据反序列化从req.body中提取二进制数据并使用 request.ParseFromString() 将其还原为 AddContactRequest 对象。业务处理调用 printContact() 函数将接收到的联系人姓名、年龄及电话列表打印到控制台模拟数据持久化过程。设置响应状态将 success 标记为 true。生成响应调用 generate_hex() 为新联系人生成一个唯一的 uid并将整个 AddContactResponse 对象序列化为二进制流。异常处理使用 try-catch 块捕获自定义异常 ContactsException。若发生错误如解析失败将 HTTP 状态码设为500并在响应体中封装具体的错误描述error_desc返回给客户端。服务启动调用server.listen(0.0.0.0, 8123)使服务在 8123 端口上运行并接受任何 IP 的访问。代码如下intmain(){cout-----------服务启动----------endl;Server server;server.Post(/contacts/add,[](constRequestreq,Responseres){cout接收到post请求!endl;// 反序列化 request: req.bodyadd_contact::AddContactRequest request;add_contact::AddContactResponse response;try{if(!request.ParseFromString(req.body)){throwContactsException(AddContactRequest反序列化失败!);}// 新增联系人持久化存储通讯录----》 打印新增的联系人信息printContact(request);// 构造 responseres.bodyresponse.set_success(true);response.set_uid(generate_hex(10));// res.body (序列化response)string response_str;if(!response.SerializeToString(response_str)){throwContactsException(AddContactResponse序列化失败!);}res.status200;res.bodyresponse_str;res.set_header(Content-Type,application/protobuf);}catch(constContactsExceptione){res.status500;response.set_success(false);response.set_error_desc(e.what());string response_str;if(response.SerializeToString(response_str)){res.bodyresponse_str;res.set_header(Content-Type,application/protobuf);}cout/contacts/add 发生异常异常信息e.what()endl;}});// 绑定 8123 端口并且将端口号对外开放server.listen(0.0.0.0,8123);return0;}3.4.2 生成 uid功能描述由于本示例未接入数据库服务端通过generate_hex函数手动生成一个 10 字节20 个字符的十六进制字符串作为联系人的唯一标识符UID。随机数引擎使用了 C11 标准的random库。std::random_device用于获取硬件随机种子。std::mt19937梅森旋转算法引擎具有随机性好、速度快的特点。std::uniform_int_distribution确保生成的随机整数均匀分布在[0, 255]范围内。十六进制转换利用std::stringstream和std::hex操纵符将随机生成的字节值转换为十六进制字符串。补位处理通过(hex.length() 2 ? 0 hex : hex)确保每个字节都占据两位字符例如将0x5转换为05从而保证生成的 uid 长度固定。代码如下voidprintContact(add_contact::AddContactRequestreq){cout联系人姓名req.name()endl;cout联系人年龄req.age()endl;for(intj0;jreq.phone_size();j){constadd_contact::AddContactRequest_Phonephonereq.phone(j);cout联系人电话j1phone.number();// 联系人电话1:1311111 (MP)cout (phone.PhoneType_Name(phone.type()))endl;}}staticunsignedintrandom_char(){// ⽤于随机数引擎获得随机种⼦std::random_device rd;// mt19937是c11新特性它是⼀种随机数算法⽤法与rand()函数类似但是mt19937具有速度快周期⻓的特点// 作⽤是⽣成伪随机数std::mt19937gen(rd());// 随机⽣成⼀个整数i 范围[0, 255]std::uniform_int_distributiondis(0,255);returndis(gen);}3.4.3 辅助功能说明ContactsException 类自定义异常类专门用于捕获并传递 Protobuf 处理过程中的序列化/反序列化错误。代码如下// 自定义异常类classContactsException{private:std::string message;public:ContactsException(std::string strA problem):message(str){}std::stringwhat()const{returnmessage;}};printContact 函数该函数展示了如何遍历 Protobuf 中的 repeated 字段电话列表并利用 PhoneType_Name() 接口将枚举数值转换为易读的字符串标签。代码如下voidprintContact(add_contact::AddContactRequestreq){cout联系人姓名req.name()endl;cout联系人年龄req.age()endl;for(intj0;jreq.phone_size();j){constadd_contact::AddContactRequest_Phonephonereq.phone(j);cout联系人电话j1phone.number();// 联系人电话1:1311111 (MP)cout (phone.PhoneType_Name(phone.type()))endl;}}4. 功能演示对于客户端和服务器都需要先编译 proto 文件protoc--cpp_out. add_contact.proto接着再进行 make 编译 cpp 代码文件即可。make新增一个联系人的结果如下所示5. 总结下面将展示网络版通讯录的完整功能。新增第一个联系人如下图所示新增第二个联系人如下图所示查看联系人列表如下图所示查看联系人详细信息如下图所示删除联系人 张三如下图所示此时通讯录只剩李四这一个联系人了对于完整的通讯录代码可以移步到我的代码仓库Online-Address-Book

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

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

相关文章

SpringBoot-17-MyBatis动态SQL标签之常用标签

文章目录 1 代码1.1 实体User.java1.2 接口UserMapper.java1.3 映射UserMapper.xml1.3.1 标签if1.3.2 标签if和where1.3.3 标签choose和when和otherwise1.4 UserController.java2 常用动态SQL标签2.1 标签set2.1.1 UserMapper.java2.1.2 UserMapper.xml2.1.3 UserController.ja…

wordpress后台更新后 前端没变化的解决方法

使用siteground主机的wordpress网站,会出现更新了网站内容和修改了php模板文件、js文件、css文件、图片文件后,网站没有变化的情况。 不熟悉siteground主机的新手,遇到这个问题,就很抓狂,明明是哪都没操作错误&#x…

网络编程(Modbus进阶)

思维导图 Modbus RTU(先学一点理论) 概念 Modbus RTU 是工业自动化领域 最广泛应用的串行通信协议,由 Modicon 公司(现施耐德电气)于 1979 年推出。它以 高效率、强健性、易实现的特点成为工业控制系统的通信标准。 包…

UE5 学习系列(二)用户操作界面及介绍

这篇博客是 UE5 学习系列博客的第二篇,在第一篇的基础上展开这篇内容。博客参考的 B 站视频资料和第一篇的链接如下: 【Note】:如果你已经完成安装等操作,可以只执行第一篇博客中 2. 新建一个空白游戏项目 章节操作,重…

IDEA运行Tomcat出现乱码问题解决汇总

最近正值期末周,有很多同学在写期末Java web作业时,运行tomcat出现乱码问题,经过多次解决与研究,我做了如下整理: 原因: IDEA本身编码与tomcat的编码与Windows编码不同导致,Windows 系统控制台…

利用最小二乘法找圆心和半径

#include <iostream> #include <vector> #include <cmath> #include <Eigen/Dense> // 需安装Eigen库用于矩阵运算 // 定义点结构 struct Point { double x, y; Point(double x_, double y_) : x(x_), y(y_) {} }; // 最小二乘法求圆心和半径 …

使用docker在3台服务器上搭建基于redis 6.x的一主两从三台均是哨兵模式

一、环境及版本说明 如果服务器已经安装了docker,则忽略此步骤,如果没有安装,则可以按照一下方式安装: 1. 在线安装(有互联网环境): 请看我这篇文章 传送阵>> 点我查看 2. 离线安装(内网环境):请看我这篇文章 传送阵>> 点我查看 说明&#xff1a;假设每台服务器已…

XML Group端口详解

在XML数据映射过程中&#xff0c;经常需要对数据进行分组聚合操作。例如&#xff0c;当处理包含多个物料明细的XML文件时&#xff0c;可能需要将相同物料号的明细归为一组&#xff0c;或对相同物料号的数量进行求和计算。传统实现方式通常需要编写脚本代码&#xff0c;增加了开…

LBE-LEX系列工业语音播放器|预警播报器|喇叭蜂鸣器的上位机配置操作说明

LBE-LEX系列工业语音播放器|预警播报器|喇叭蜂鸣器专为工业环境精心打造&#xff0c;完美适配AGV和无人叉车。同时&#xff0c;集成以太网与语音合成技术&#xff0c;为各类高级系统&#xff08;如MES、调度系统、库位管理、立库等&#xff09;提供高效便捷的语音交互体验。 L…

(LeetCode 每日一题) 3442. 奇偶频次间的最大差值 I (哈希、字符串)

题目&#xff1a;3442. 奇偶频次间的最大差值 I 思路 &#xff1a;哈希&#xff0c;时间复杂度0(n)。 用哈希表来记录每个字符串中字符的分布情况&#xff0c;哈希表这里用数组即可实现。 C版本&#xff1a; class Solution { public:int maxDifference(string s) {int a[26]…

【大模型RAG】拍照搜题技术架构速览:三层管道、两级检索、兜底大模型

摘要 拍照搜题系统采用“三层管道&#xff08;多模态 OCR → 语义检索 → 答案渲染&#xff09;、两级检索&#xff08;倒排 BM25 向量 HNSW&#xff09;并以大语言模型兜底”的整体框架&#xff1a; 多模态 OCR 层 将题目图片经过超分、去噪、倾斜校正后&#xff0c;分别用…

【Axure高保真原型】引导弹窗

今天和大家中分享引导弹窗的原型模板&#xff0c;载入页面后&#xff0c;会显示引导弹窗&#xff0c;适用于引导用户使用页面&#xff0c;点击完成后&#xff0c;会显示下一个引导弹窗&#xff0c;直至最后一个引导弹窗完成后进入首页。具体效果可以点击下方视频观看或打开下方…

接口测试中缓存处理策略

在接口测试中&#xff0c;缓存处理策略是一个关键环节&#xff0c;直接影响测试结果的准确性和可靠性。合理的缓存处理策略能够确保测试环境的一致性&#xff0c;避免因缓存数据导致的测试偏差。以下是接口测试中常见的缓存处理策略及其详细说明&#xff1a; 一、缓存处理的核…

龙虎榜——20250610

上证指数放量收阴线&#xff0c;个股多数下跌&#xff0c;盘中受消息影响大幅波动。 深证指数放量收阴线形成顶分型&#xff0c;指数短线有调整的需求&#xff0c;大概需要一两天。 2025年6月10日龙虎榜行业方向分析 1. 金融科技 代表标的&#xff1a;御银股份、雄帝科技 驱动…

观成科技:隐蔽隧道工具Ligolo-ng加密流量分析

1.工具介绍 Ligolo-ng是一款由go编写的高效隧道工具&#xff0c;该工具基于TUN接口实现其功能&#xff0c;利用反向TCP/TLS连接建立一条隐蔽的通信信道&#xff0c;支持使用Let’s Encrypt自动生成证书。Ligolo-ng的通信隐蔽性体现在其支持多种连接方式&#xff0c;适应复杂网…

铭豹扩展坞 USB转网口 突然无法识别解决方法

当 USB 转网口扩展坞在一台笔记本上无法识别,但在其他电脑上正常工作时,问题通常出在笔记本自身或其与扩展坞的兼容性上。以下是系统化的定位思路和排查步骤,帮助你快速找到故障原因: 背景: 一个M-pard(铭豹)扩展坞的网卡突然无法识别了,扩展出来的三个USB接口正常。…

未来机器人的大脑:如何用神经网络模拟器实现更智能的决策?

编辑&#xff1a;陈萍萍的公主一点人工一点智能 未来机器人的大脑&#xff1a;如何用神经网络模拟器实现更智能的决策&#xff1f;RWM通过双自回归机制有效解决了复合误差、部分可观测性和随机动力学等关键挑战&#xff0c;在不依赖领域特定归纳偏见的条件下实现了卓越的预测准…

Linux应用开发之网络套接字编程(实例篇)

服务端与客户端单连接 服务端代码 #include <sys/socket.h> #include <sys/types.h> #include <netinet/in.h> #include <stdio.h> #include <stdlib.h> #include <string.h> #include <arpa/inet.h> #include <pthread.h> …

华为云AI开发平台ModelArts

华为云ModelArts&#xff1a;重塑AI开发流程的“智能引擎”与“创新加速器”&#xff01; 在人工智能浪潮席卷全球的2025年&#xff0c;企业拥抱AI的意愿空前高涨&#xff0c;但技术门槛高、流程复杂、资源投入巨大的现实&#xff0c;却让许多创新构想止步于实验室。数据科学家…

深度学习在微纳光子学中的应用

深度学习在微纳光子学中的主要应用方向 深度学习与微纳光子学的结合主要集中在以下几个方向&#xff1a; 逆向设计 通过神经网络快速预测微纳结构的光学响应&#xff0c;替代传统耗时的数值模拟方法。例如设计超表面、光子晶体等结构。 特征提取与优化 从复杂的光学数据中自…