第三方库介绍——Protobuf库(更高效的协议)

news/2024/11/8 20:37:08/

文章目录

  • protobuf综述
  • 传输协议与指令
    • 创建协议
    • 编译协议
  • 利用soctet实现客户端与服务端传输协议
    • Linux(Ubuntu)安装protoc步骤
    • 编写案例代码

protobuf综述

  • Protocol Buffers,是Google公司开发的一种数据格式,类似于XML和json,是一种用于数据传输时将数据序列化和反序列化的一个跨平台(支持目前主流的各种语言)工具库,可用于数据存储、通信协议等方面。它不依赖于语言和平台并且可扩展性极强。

protobuf的优点:

  • 更小、更快、更简单。

  • 支持多种编程语言 。

  • 解析速度快。

  • 可扩展性强。

传输协议与指令

创建协议

protobuf的核心是一个.proto文件,我们自定义一个.proto来创建我们的协议数据,然后使用protocol buffer 编译器工具编译生成两个"文件名.pb.cc""文件名.pb.h"的文件

例如我们创建一个addressbook.proto文件:

该文件定义为“地址簿”应用程序,可以在文件中读写联系人的详细信息。地址簿中的每个人都有姓名、ID、电子邮件地址和联系电话。

syntax = "proto3";
package myDemo;message Person {required string name 		= 1;//对每个字段需要带上一个编号,编号是从1开始的:required int32 id 			= 2;  optional string email 		= 3;enum PhoneType {MOBILE 			= 0;//移动电话HOME 			= 1;//家庭电话WORK 			= 2;//工作电话}message PhoneNumber {required string number 	= 1;optional PhoneType type 	= 2 [default = HOME];}repeated PhoneNumber phones = 4;message AddressBook{repeated Person people = 1;}
}
  • syntax为语法版本

    • 有proto2、proto3两个版本,语法上是存在区别的,proto3并不是完全兼容proto2的。同C/C++语言类似,.proto也规定了一些数据格式,如proto2的数据类型有:double 、 float、 int32 、 uint32 、 string、bool 等。
  • message为关键字

    • 修饰的Person 会对应生成C++中的Person 的结构体。
    • 支持嵌套,实际上生成了多个类,有几个message就生成几个类
  • required为前缀修饰【字段只能出现1次】

    • 表明该字段是必填字段,被修饰的变量必须要赋值。还有其它两个修饰关键字:

      • optional:被修饰的变量可以不赋值,未赋值则采用默认值,例如bool类型为false,int32为0,string为“”等【字段可以出现0次或1次】

      • repeated:作用是用来发送一个list的,当我们对某个变量发送的是一个list的时候,就需要给这个变量前面加上repeated【字段可以出现任意多次(包含0次)】

  • enum类型数据

    • enum编号是从0开始的
  • package xxx

    • 指定生成后.pd.h类的命名空间

对于proto3版本还有两个关键字:

  • singular 不是关键字,代表这个变量是一种类型,就和上面的例子一样,就是 类型+变量名的格式,在传输时,可以不给这种类型赋值,若没赋值则采用默认值,例如bool类型为false,int32为0,string为“”等
  • repeated 作用是用来发送一个list的,当我们对某个变量发送的是一个list的时候,就需要给这个变量前面加上repeated

编译协议

使用protocol buffer 编译器工具编译addressbook.proto文件的命令

protoc --cpp_out=. ./addressbook.proto

此时编译会生成addressbook.pb.cc、addressbook.pb.h两个文件。

在这里插入图片描述

我们可以看到student.pb.h里生成了一个协议数据结构体与操作该结构体的一些接口,包括组包与解包(序列化与反序列化)接口,对应的student.pb.cc里就是这些接口对应的实现。

序列化:通俗讲就是将类的实例化对象转成二进制数据然后通过TCP和UDP等传输
反序列化:通俗讲就是将序列化后的二进制数据转换成类的实例化对象,也就是将数据取出来并还原成原先的类的实例化对象

生成的"person.pb.h"中,有几个message就生成几个类

在这里插入图片描述

在这里插入图片描述

普通对象(没有用关键词修饰)可以调用set,get函数去操作

在这里插入图片描述

而用repeated修饰的对象,需要遍历,然后加入值时有个方法是add_对象名()

person.add_phones();

在这里插入图片描述

序列化与反序列化函数,例如:

person.SerializeToString

参考博文:protobuf学习笔记

利用soctet实现客户端与服务端传输协议

运行环境:ubuntu18.04,C++11,cmake3.15.5,protobuf3.17.3

Linux(Ubuntu)安装protoc步骤

安装protobuf需要依赖一些工具,需要先安装依赖:

sudo apt-get install autoconf automake libtool curl make g++ unzip

进入自定义的目录下安装proto3,解压并编译安装

wget -O protobuf-3.17.3.tar.gz https://codeload.github.com/protocolbuffers/protobuf/tar.gz/refs/tags/v3.17.3
tar -zxf protobuf-3.17.3.tar.gz
cd protobuf-3.17.3/cmake/
mkdir build && cd build
cmake .. -Dprotobuf_BUILD_TESTS=OFF
make -j4 install

安装好后使用 protoc --version命令看看是否出现版本号,出现则代表安装成功

编写案例代码

创建目录结构如下:

在这里插入图片描述
Cartoon.proto

syntax = "proto3";import "google/protobuf/timestamp.proto";message Cartoon{int32 Id                        = 1;string name                     = 2;string company                  = 3;google.protobuf.Timestamp time  = 4;
}message CartoonList{repeated Cartoon cartoonList    = 1;          //CartoonList
}message CartoonRequest{int32 query                     = 1;          //命令码 1->add 2->selectById 3->selectAllCartoonList cartoon             = 2;          //add 时一个Cartoon list, 默认放在  CartoonInf  文件中int32 selectById                = 3;          //select 时 Cartoon的Idbool selectAll                  = 4;          //查询所有的 Cartoon
}message CartoonResponse{string res                      = 1;CartoonList cartoon             = 2;          //返回的 select Cartoon的信息
}

tcpsocket.h

//
// Created by Aaj on 2021/8/9.
//#ifndef MYPROTOCOLBUF_TCPSOCKET_H
#define MYPROTOCOLBUF_TCPSOCKET_H#include <iostream>
#include <string>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include<unistd.h>
using std::cout;
using std::endl;
using std::cin;
using std::string;
inline void CHECK(bool operation) {if (operation == false) {exit(-1);}
}
class TcpSocket {
public:TcpSocket() : _sock(-1) {}void setNewSockFd(int newsockfd) {_sock = newsockfd;}// 创建套接字bool Socket() {_sock = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);if (_sock < 0) {perror("socket error");return false;}return true;}// 为套接字绑定地址信息bool Bind(string& ip, uint16_t& port) const {struct sockaddr_in addr;addr.sin_family = AF_INET;addr.sin_port = htons(port);addr.sin_addr.s_addr = inet_addr(ip.c_str());size_t len = sizeof(struct sockaddr_in);int binding = bind(_sock, (struct sockaddr*)&addr, len);if (binding < 0) {perror("bind error");return false;}return true;}// 服务端监听bool Listen(int backlog = 5) const {int listening = listen(_sock, backlog);if (listening < 0) {perror("listen error");return false;}return true;}// 客户端请求连接bool Connect(string& ip, uint16_t& port) const {struct sockaddr_in addr;addr.sin_family = AF_INET;addr.sin_port = htons(port);addr.sin_addr.s_addr = inet_addr(ip.c_str());size_t len = sizeof(struct sockaddr_in);int connecting = connect(_sock, (struct sockaddr*)&addr, len);if (connecting < 0) {perror("connect error");return false;}return true;}// 服务端接受客户端请求bool Accept(TcpSocket& cli_sock, struct sockaddr_in* cli_addr = NULL) {TcpSocket clisock;size_t len = sizeof(struct TcpSocket);int newsockfd = accept(_sock, (struct sockaddr*)&clisock, (socklen_t *)&len);if (newsockfd < 0) {perror("accept error");return false;}if (cli_addr != NULL) {memcpy(cli_addr, &clisock, len);cli_sock.setNewSockFd(newsockfd);}return true;}// 发送数据bool Send(string& buf) {size_t size = send(_sock, buf.c_str(), buf.size(), 0);if (size < 0) {perror("send error");return false;}return true;}// 接收数据bool Recv(string& buf) {char buf_tmp[4096] = {0};size_t size = recv(_sock, buf_tmp, sizeof(buf_tmp), 0);if (size < 0) {perror("recv error");return false;} else if (size == 0) {perror("peer shutdown");return false;}buf.assign(buf_tmp, size);return true;}void Close() {close(_sock);_sock = -1;}
private:int _sock;
};#endif //MYPROTOCOLBUF_TCPSOCKET_H

MyTcpsocket.h

//
// Created by Aaj on 2021/8/6.
//#ifndef MYPROTOCOLBUF_MYTCPSOCKET_H
#define MYPROTOCOLBUF_MYTCPSOCKET_H  1#include<string.h>
#include<iostream>
#include<string>
#include<sys/socket.h>
#include<arpa/inet.h>
#include<netinet/in.h>
#include<sys/types.h>
#include<unistd.h>using namespace std;void CHECK(bool res){if(res == false){exit(-1);}
}class MyTcpSocket{
private:int _sock;
public:MyTcpSocket() : _sock(-1){}void setNewSockFd(int newsockfd){_sock = newsockfd;}//1.创建套接字bool Socket(){//函数原型 int socket(int domain, int type, int protocol);//int domain(协议族) 参数为 AF_INET 表示 TCP/IP_IPv4//int type(套接字类型) 参数为SOCK_STREAM 表示 TPC流//int protocol 参数为0,因为 domain 和 type 已经确定_sock = socket(AF_INET, SOCK_STREAM, 0);if(_sock < 0){perror("socket error");return false;}return true;}//2.为套接字绑定地址信息bool Bind(string& ip,uint16_t& port) const{struct sockaddr_in addr;        //加上struct为c写法bzero(&addr, sizeof(addr));     //清零addr.sin_family = AF_INET;      //TCPaddr.sin_port = htons(port);    //htons是将整型变量从主机字节顺序转变成网络字节顺序, 就是整数在地址空间存储方式变为高位字节存放在内存的低地址处。addr.sin_addr.s_addr = inet_addr(ip.c_str()); //将IP转为二进制形式size_t len = sizeof(struct sockaddr_in);int binding = bind(_sock,(struct sockaddr*)&addr, len);if(binding < 0){perror("bind error");return false;}return true;}//3.服务端监听bool Listen(int backlog = 5) const {int listening = listen(_sock,backlog);if(listening < 0){perror("listen error");return false;}return true;}//4.客户端请求连接bool Connect(string& ip,uint16_t& port) const {struct sockaddr_in addr;bzero(&addr, sizeof(addr));addr.sin_family = AF_INET;addr.sin_port = htons(port);addr.sin_addr.s_addr = inet_addr(ip.c_str());size_t len = sizeof(struct sockaddr_in);int connecting = connect(_sock,(struct sockaddr*)& addr,len);if(connecting < 0){perror("connect error");return false;}return true;}//5.服务端接受客户端请求bool Accept(MyTcpSocket& cli_sock,struct sockaddr_in* cli_addr = NULL){MyTcpSocket clisock;size_t len = sizeof(struct MyTcpSocket);int newsockfd = accept(_sock, (struct sockaddr*)&clisock, (socklen_t *)&len);if(newsockfd < 0){perror("accept error");return false;}if (cli_addr != NULL) {memcpy(cli_addr, &clisock, len);cli_sock.setNewSockFd(newsockfd);}return true;}//6.发送数据bool Send(string& buf){size_t size = send(_sock, buf.c_str(),buf.size(), 0);if(size < 0){perror("send error");return false;}return true;}//7.接收数据bool Recv(string& buf){char buf_tmp[4096] = {0};size_t size = recv(_sock,buf_tmp, sizeof(buf_tmp),0);if(size < 0){perror("recv error");return false;}else if(size == 0){perror("peer shutdown");return false;}buf.assign(buf_tmp, size);return true;}void Close(){close(_sock);_sock = -1;}
};#endif //MYPROTOCOLBUF_MYTCPSOCKET_H

client.cpp

//
// Created by Aaj on 2021/8/6.
//#include "src/Cartoon.pb.h"
#include "src/MyTcpSocket.h"
#include <google/protobuf/util/time_util.h>#include<cstdio>
#include<iostream>
#include<fstream>
#include<ctime>
#include<list>
#include <netdb.h>using namespace std;
using google::protobuf::util::TimeUtil;void addCartoon(CartoonRequest &cartoonRequest){while(true){Cartoon *cartoon = cartoonRequest.mutable_cartoon()->add_cartoonlist();cout<<"输入动画Id: ";int Id;cin>>Id;//cout<<Id<<endl;cin.ignore(256,'\n');//忽略掉一个回车cartoon->set_id(Id);cout<<"输入动画名称: ";string name;getline(cin,name);//cout<<name<<endl;cartoon->set_name(name);cout<<"输入动画出品公司: ";string company;getline(cin,company);//cout<<company<<endl;cartoon->set_company(company);*cartoon->mutable_time() = TimeUtil::SecondsToTimestamp(time(NULL));string res;while(true){cout<<"输入 0 结束,1 继续输入"<<endl;cin>>res;if(res == "0" || res == "1")break;}if(res == "0"){
//            /*这里暂时写成直接写入CartoonInf文件方式*/
//            fstream output("CartoonInf",ios::out | ios::binary | ios::app); //app代表追加方式写入
//            CartoonList cartoonList = cartoonRequest.cartoon();
//            if(!cartoonList.SerializePartialToOstream(&output)){
//                cerr<<"无法写入CartoonInf\n";
//                exit(-1);
//            }return ;}}
}
void selectById(CartoonRequest &cartoonRequest){cout<<"请输入Id: ";int Id;cin>>Id;cin.ignore(256,'\n');//忽略掉一个回车cartoonRequest.set_selectbyid(Id);
}void selectAll(CartoonRequest &cartoonRequest){cartoonRequest.set_selectall(true);
}
int main(){//如果没有CartoonInf则先创建一个if(!fopen("CartoonInf","r")){fopen("CartoonInf","w");}string ip = "127.0.0.1";//本机uint16_t  port = 9999;MyTcpSocket client;CHECK(client.Socket());CHECK(client.Connect(ip,port));while(1){CartoonRequest cartoonRequest;cout<<"输入请求码 1->add 2->selectById 3->selectAll,0->退出:";int code;cin>>code;cin.ignore(256,'\n');//忽略掉一个回车//cout<<code<<endl;cartoonRequest.set_query(code);//addif(code == 1){addCartoon(cartoonRequest);//break;}//selectByIdelse if(code == 2){selectById(cartoonRequest);//break;}//selectAllelse if(code == 3){selectAll(cartoonRequest);//break;}else if(code == 0 ){cout<<"退出成功!"<<endl;break;}else{cout<<"请求码非法,重新输入!"<<endl;continue;}string data;cartoonRequest.SerializeToString(&data);CHECK(client.Send(data));data.clear();CHECK(client.Recv(data));CartoonResponse cartoonResponse;cartoonResponse.ParseFromString(data);cout<<cartoonResponse.res()<<endl;if(code == 2 || code == 3){for(int i=0;i<cartoonResponse.mutable_cartoon()->cartoonlist_size();++i){Cartoon cartoon = cartoonResponse.mutable_cartoon()->cartoonlist(i);cout<<"动画Id:"<<cartoon.id()<<endl;cout<<"动画名称:"<<cartoon.name()<<endl;cout<<"动画出品公司:"<<cartoon.company()<<endl;cout<<"***************************************"<<endl;}}}client.Close();return 0;
}

server.cpp

//
// Created by Aaj on 2021/8/6.
//#include"src/Cartoon.pb.h"
#include "src/MyTcpSocket.h"#include<iostream>
#include <fstream>
#include <string>using namespace std;void addCartoon(CartoonRequest &cartoonRequest){fstream output("CartoonInf",ios::out | ios::binary | ios::app); //app代表追加方式写入CartoonList cartoonList = cartoonRequest.cartoon();if(!cartoonList.SerializePartialToOstream(&output)){cerr<<"无法写入CartoonInf"<<endl;exit(-1);}return ;
}void selectByCartoonId(int Id,CartoonResponse &cartoonResponse){CartoonList cartoonList;fstream input("CartoonInf",ios::in | ios::binary);if(input && !cartoonList.ParseFromIstream(&input)){cerr<<"无法读取CartoonInf"<<endl;exit(-1);}//    /*这里暂时就直接显示出来*/
//    cout<<"*selectByCartoonId*"<<endl;
//    for(int i=0;i<cartoonList.cartoonlist_size();++i){
//        Cartoon cartoon = cartoonList.cartoonlist(i);
//        cout<<"动画Id:"<<cartoon.id()<<endl;
//        cout<<"动画名称:"<<cartoon.name()<<endl;
//        cout<<"动画出品公司:"<<cartoon.company()<<endl;
//        cout<<"***************************************"<<endl;
//    }for(int i=0;i<cartoonList.cartoonlist_size();++i){if(cartoonList.cartoonlist(i).id() == Id){Cartoon* cartoon = cartoonResponse.mutable_cartoon()->add_cartoonlist();cartoon->set_id(cartoonList.cartoonlist(i).id());cartoon->set_name(cartoonList.cartoonlist(i).name());cartoon->set_company(cartoonList.cartoonlist(i).company());break;}}return ;
}void selectAllCartoon(CartoonResponse &cartoonResponse){CartoonList cartoonList;fstream input("CartoonInf",ios::in | ios::binary);if(input && !cartoonList.ParseFromIstream(&input)) {cerr << "无法读取CartoonInf"<<endl;exit(-1);}//    /*这里暂时就直接显示出来*/
//    cout<<"*selectAllCartoon*"<<endl;
//    for(int i=0;i<cartoonList.cartoonlist_size();++i){
//        Cartoon cartoon = cartoonList.cartoonlist(i);
//        cout<<"动画Id:"<<cartoon.id()<<endl;
//        cout<<"动画名称:"<<cartoon.name()<<endl;
//        cout<<"动画出品公司:"<<cartoon.company()<<endl;
//        cout<<"***************************************"<<endl;
//    }for(int i=0;i<cartoonList.cartoonlist_size();++i){Cartoon* cartoon = cartoonResponse.mutable_cartoon()->add_cartoonlist();cartoon->set_id(cartoonList.cartoonlist(i).id());cartoon->set_name(cartoonList.cartoonlist(i).name());cartoon->set_company(cartoonList.cartoonlist(i).company());}return ;
}int main(){//如果没有CartoonInf则先创建一个if(!fopen("CartoonInf","r")){fopen("CartoonInf","w");}string ip = to_string(INADDR_ANY);uint16_t port = 9999;MyTcpSocket service;CHECK(service.Socket());CHECK(service.Bind(ip,port));CHECK(service.Listen());MyTcpSocket clisock;struct sockaddr_in cliaddr;CHECK(service.Accept(clisock, &cliaddr));//selectAllCartoon(); //测试用while(1){string data;CartoonRequest cartoonRequest;CHECK(clisock.Recv(data));cartoonRequest.ParseFromString(data);CartoonResponse cartoonResponse;if(cartoonRequest.query() == 1){cout<<"addCartoon"<<endl;addCartoon(cartoonRequest);cartoonResponse.set_res("addCartoon success!");}else if(cartoonRequest.query() == 2){cout<<"selectByCartoonId"<<endl;selectByCartoonId(cartoonRequest.selectbyid(),cartoonResponse);if(cartoonResponse.cartoon().cartoonlist_size()==0){cartoonResponse.set_res("Cartoons don't exist");}else{cartoonResponse.set_res("selectByCartoonId success!");}}else if(cartoonRequest.query() == 3){cout<<"selectAllCartoon"<<endl;selectAllCartoon(cartoonResponse);if(cartoonResponse.cartoon().cartoonlist_size()==0){cartoonResponse.set_res("Cartoons don't exist");}else{cartoonResponse.set_res("selectAllCartoon success!");}}else{cartoonResponse.set_res("query error!");}cartoonResponse.SerializeToString(&data);CHECK(clisock.Send(data));}service.Close();return 0;
}

CMakeList.tx:

cmake_minimum_required(VERSION 3.0)project(myProtocolBuf)
set(CMAKE_CXX_STANDARD 11)find_package(Protobuf REQUIRED)
include_directories(${Protobuf_INCLUDE_DIRS})
link_libraries(${Protobuf_LIBRARY})FILE(GLOB SRC ./src/*.cc ./src/*.h)add_executable(client client.cpp ${SRC})
add_executable(server server.cpp ${SRC})

进入src目录,编译生成相应的”.pb.cc“和“.pb.h”文件

protoc --cpp_out=. ./Cartoon.proto

进入build目录,执行命令:

cmake ..
make

运行服务端与客户端并查看结果;
在这里插入图片描述

参考博文:使用socket和TCP网络协议实现的客户端和服务端示例,采用谷歌的protobuf数据传输协议(类似json),部署在Linux上


参考博文:
嵌入式大杂烩 | Protobuf:一种更小、更快、更高效的协议

干货 | protobuf-c之嵌入式平台使用

干货 | 项目乏力?nanopb助你一臂之力

版本冲突解决


http://www.ppmy.cn/news/547589.html

相关文章

【面试题09】PHP中单引号’与双引号” 区别

文章目录 一、概览二、区别2.1 双引号支持变量解析2.2 双引号支持转义符&#xff0c;单引号不支持2.3 双引号比单引号更占用内存2.4 单引号比双引号快 总结 一、概览 本文已收录于PHP全栈系列专栏&#xff1a;PHP面试专区。 计划将全覆盖PHP开发领域所有的面试题&#xff0c;对…

代码随想录训练营第四十五天|70.爬楼梯、322.零钱兑换、279.完全平方数

70.爬楼梯 链接&#xff1a;LeetCode70.爬楼梯 首先回顾一下动态规划的解法 class Solution { public:int climbStairs(int n) {if(n<1) return 1;vector<int> dp(n1,1);for(int i2;i<n;i){dp[i]dp[i-1]dp[i-2];}return dp[n];} };把本题抽象成完全背包问题。 背…

Python基础算法训练——循环训练(16~20)

16.奶牛的数字游戏 【题目描述】 奶牛们又在玩一种无聊的数字游戏。输得很郁闷的贝茜想请你写个程序来帮她在开局时预测结果。在游戏的开始,每头牛都会得到一个数 N。此时奶牛们的分数均为 0。如果 N 是奇数,那么奶牛就会把它乘以 3 后再加 1。如果 N 是偶数,那么这个数就会…

BI-SQL丨角色和用户

角色和用户 在数仓的运维工作中&#xff0c;经常需要为用户开通不同权限的账号&#xff0c;使用户可以正常访问不同的数据&#xff0c;那么这就需要我们了解SQL Server的权限体系。 名词解释 登录名&#xff1a; 用来登录服务器的用户账号&#xff0c;例&#xff1a;sa&…

A1,A2,A3,A4,A5,A6,A7,A8纸张大小图解

A0 841 x 1189 单位&#xff1a;mm A1 594 x 841 A2 420 x 594 A3 297 x 420 A4 210 x 297 A5 148 x 210 A6 105 x 148 A7 74 x 105 A8 52 x 74

蓝桥杯每日一练:纸张尺寸

问题描述 在 ISO 国际标准中定义了 A0 纸张的大小为 1189mm 841mm, 将 A0 纸 沿长边对折后为 A1 纸, 大小为 841mm 594mm, 在对折的过程中长度直接取 下整 (实际裁剪时可能有损耗)。将 A1 纸沿长边对折后为 A2 纸, 依此类推。 输入纸张的名称, 请输出纸张的大小。 输入格式…

蓝桥杯—纸张尺寸

题目描述 在 ISO 国际标准中定义了 A0 纸张的大小为 1189mm 841mm&#xff0c;将 A0 纸沿长边对折后为 A1 纸&#xff0c;大小为 841mm 594mm&#xff0c;在对折的过程中长度直接取下整&#xff08;实际裁剪时可能有损耗&#xff09;。将 A1 纸沿长边对折后为 A2 纸&#xf…

蓝桥杯-纸张尺寸

问题描述 在 ISO 国际标准中定义了 A0 纸张的大小为 1189mm \times 841mm, 将 A0 纸 沿长边对折后为 A1 纸, 大小为 841mm \times 594mm, 在对折的过程中长度直接取 下整 (实际裁剪时可能有损耗)。将 A1 纸沿长边对折后为 A2 纸, 依此类推。 输入纸张的名称, 请输出纸张的大小。…