> 文章列表 > C/C++ Linux protobuf2 简单用法记录

C/C++ Linux protobuf2 简单用法记录

C/C++ Linux protobuf2 简单用法记录

简单记录以下protobuf2的用法,以后忘记了可以回顾一下!(这是谷歌的一个库)


一、protobuf安装

mirrors / google / protobuf · GitCodeProtocol Buffers - Google's data interchange format 🚀 Github 镜像仓库 🚀 源项目地址 ⬇...https://gitcode.net/mirrors/google/protobuf?utm_source=csdn_github_accelerator点击上面的连接去下载!

然后执行命令安装必要条件:

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

centos系统将apt-get 改为 yum

依次执行以下命令安装:(进入root用户) 

unzip protobuf-master.zipcd protobuf-master/./autogen.sh./configuremakemake checkmake installldconfig

头文件所在路径:/usr/local/include/

库所在路径:/usr/local/lib

 安装完毕!


二、编写.proto文件

编写按照如下格式去编写:

message 名字
{
    required 类型 变量名   = 1;   
    required 类型 变量名  = 2;   
    optional 类型 变量名  = 3;   
}

message是固定的,开头需要写上;

required是属性,一共有三种属性,分别是,required、optional、repeated

  • required:表示该值是必须要设置的;
  • optional:消息格式中该字段可以有0个或1个值(不超过1个);即可以不用设置它;
  • repeated:在一个格式良好的消息中,该值可以被设置多个值;

也就是说,设置了required,就必须给他设置值;

repeated int32 code= 4 [packed=true];        // 定义时可以这样,效率会高点

设置了optional,可以不用给他设置值,但要设置默认值,如下:

optional std::string data = 3 [default = 10];

设置了repeated,(可以说是枚举)也要给他设置值,且它可以被设置多个值;

类型可以是以下:(在其他博客截图的)

后面赋值 1, 2, 3,根据顺序赋值,从1开始,自增赋值即可!

如下编写案例:

ptb.proto

syntax = "proto2";package tutorial;message response
{required int32 code   = 1;   required int32 icode  = 2;   optional string data  = 3;   
}message list_account_records_response
{required int32   code   = 1;    optional string  desc   = 2;    message account_record{required int32  type      = 1; required int32  limit     = 2; required uint64 timestamp = 3; }repeated account_record records = 3;
}

开头一定要写上:

syntax = "proto2";        // 用的是protobuf2,所以这里写proto2

package tutorial;        // 这个是命名空间


三、编译.proto文件

编译语法:

protoc -I=$SRC_DIR --cpp_out=$DST_DIR  ptb.proto

SRC_DIR 表示proto文件所在的目录,cpp_out指定了生成的代码的路径, ptb.proto指proto文件名。

protoc -I=./ --cpp_out=./ ptb.proto

这样在当前目录生成了ptb.pb.cc和ptb.pb.h两个文件。


四、代码使用

接下来使用代码去操作。

set_变量名();        // 设置属性值

变量名();                // 获取值

SerializeToString();        // 将类格式化为stirng类型字符处

 ParseFromString();        // 将字符串转换为类

 add_变量名();                // 生成一个repeated 对象返回

编译命令:g++ -std=c++11 test.cc ptb.pb.cc -lprotobuf

test.cc 

#include "ptb.pb.h"
#include <string>
#include <iostream>using namespace std;
using namespace tutorial;int main(void) {/* 1、 */{std::string data;    // 存储序列化的消息// 模拟客户端发送请求{response res;res.set_code(200);res.set_icode(123);res.set_data("字符串");// 将类格式化为stirng类型字符处res.SerializeToString(&data);// 客户端将data发送给服务器}// 模拟服务器接受请求{response res;// 将字符串转换为res.ParseFromString(data);std::cout << "code = " << res.code() << std::endl;std::cout << "icode = " << res.icode() << std::endl;std::cout << "data = " << res.data() << std::endl;}   }printf("-------------华丽的分隔符-------------\\n");/* 2、 */{std::string data;    // 存储序列化的消息// 模拟客户端发送请求{list_account_records_response larr;larr.set_code(200);larr.set_desc("ok");for (int i = 0; i < 2; i++) {// 分配一个对象list_account_records_response_account_record *ar = larr.add_records();ar->set_type(i);ar->set_limit(i * 100);ar->set_timestamp(time(NULL));}// 输出records的个数printf("client:recoreds size : %d\\n", larr.records_size());// 将类格式化为stirng类型字符处larr.SerializeToString(&data);// 客户端将data发送给服务器}// 模拟服务器接受请求{list_account_records_response larr;larr.ParseFromString(data);// 输出records的个数printf("server:recoreds size : %d\\n", larr.records_size());printf("code: %d\\n", larr.code());printf("desc: %s\\n", larr.desc().c_str());for (int i = 0; i < 2; i++) {const list_account_records_response_account_record &ar = larr.records(i);printf("type: %d\\n", ar.type());printf("limit: %d\\n", ar.limit());printf("timestamp: %lu\\n", ar.timestamp());}}}return 0;
}


五、protobuf与libevent结合使用

client.cc

g++ -std=c++11 client.cc ptb.pb.cc -lprotobuf -levent -o client.exe 

#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <errno.h>
#include <unistd.h>#include <stdio.h>
#include <string.h>
#include <stdlib.h>#include <event.h>
#include <event2/util.h>#include "ptb.pb.h"
#include <string>
#include <time.h>using namespace std;
using namespace tutorial;#define COUNT 3int connect_server(const char *server_ip, int port);
void cmd_read_data(int fd, short events, void *arg);
void socket_read_data(int fd, short events, void *arg);int main(int argc, char **argv) {if (argc < 3) {printf("please input 2 parameters!\\n");return -1;}// 两个参数依次是服务器的IP地址和端口号int sockfd = connect_server(argv[1], atoi(argv[2]));if (-1 == sockfd) {perror("tcp_connect error!");return -1;}printf("connect to server successfully\\n");struct event_base *base = event_base_new();// 监听服务端发送的消息struct event *ev_sockfd = event_new(base, sockfd, EV_READ | EV_PERSIST, socket_read_data, NULL);event_add(ev_sockfd, NULL);// 监听终端输入事件struct event *ev_cmd = event_new(base, STDIN_FILENO, EV_READ | EV_PERSIST, cmd_read_data, (void *)&sockfd);event_add(ev_cmd, NULL);// 事件循环event_base_dispatch(base);event_base_free(base);printf("finished\\n");return 0;
}void cmd_read_data(int fd, short events, void *arg) {char msg[1024] = { '\\0' };std::string data = "";int ret = read(fd, msg, sizeof(msg) - 1);if (0 == ret) {printf("connection close. exit!\\n");exit(1);}if (ret < 0) {perror("read failed!");exit (1);}int sockfd = *((int *)arg);if (msg[ret - 1] == '\\n') {msg[ret - 1] = '\\0';} else {msg[ret] = '\\0';}{list_account_records_response larr;larr.set_code(200);larr.set_desc(msg);for (int i = 0; i < COUNT; i++) {// 分配一个对象list_account_records_response_account_record *ar = larr.add_records();ar->set_type(i + 1);ar->set_limit((i+1)*10);ar->set_timestamp(time(NULL));}//printf("recoreds size: %d\\n", larr.records_size());// 将类格式化为stiring类型字符串larr.SerializeToString(&data);}// 把终端的消息发送给服务器端,客户端忽略性能考虑,直接利用阻塞方式发送//printf("write to server >>> %s\\n", msg);ret = write(sockfd, data.c_str(), data.length());if (ret == -1) {perror("write to server failed!");exit(1);}//printf("ret  =  %d\\n", ret);if (strncmp(msg, "exit", 4) == 0) {   memset(msg, 0, sizeof(msg));write(sockfd, msg, sizeof(msg));usleep(100000); // 100msclose(sockfd);exit(1);}}void socket_read_data(int fd, short events, void *arg) {char msg[1024] = { '\\0' };// 不考虑一次读不完数据的情况int len = read(fd, msg, sizeof(msg) - 1);if (0 == len) {printf("connection close. exit!\\n");exit(1);} else if (len < 0) {perror("read failed!");return ;}msg[len] = '\\0';std::string data = msg;list_account_records_response larr;// 字符串转化为对象larr.ParseFromString(data);printf("code: %d\\n", larr.code());printf("desc: %s\\n", larr.desc().c_str());for (int i = 0; i < COUNT; i++) {const list_account_records_response_account_record &ar = larr.records(i);printf("type: %d\\n", ar.type());printf("limit: %d\\n", ar.limit());printf("time: %lu\\n", ar.timestamp());}//printf("recv from server <<< %s\\n", msg);
}typedef struct sockaddr SA;
int connect_server(const char *server_ip, int port) {int sockfd, status, save_errno;struct sockaddr_in server_addr;memset(&server_addr, 0, sizeof(server_addr));server_addr.sin_family = AF_INET;server_addr.sin_port = htons(port);status = inet_aton(server_ip, &server_addr.sin_addr);if (0 == status) {errno = EINVAL;return -1;}sockfd = socket(PF_INET, SOCK_STREAM, 0);status = connect(sockfd, (SA *)&server_addr, sizeof(server_addr));if (-1 == status) {save_errno = errno;close(sockfd);errno = save_errno;     // the close may be errorreturn -1;}return sockfd;
}

  

server.cc

g++ -std=c++11 server.cc ptb.pb.cc -lprotobuf -levent -o server.exe 

#include <stdio.h>
#include <string.h>
#include <errno.h>
#include <stdlib.h>
#include <unistd.h>
#include <event.h>
#include <event2/event.h>
#include <event2/listener.h>
#include <assert.h>
#include <arpa/inet.h>#include <string>
#include <time.h>#include "ptb.pb.h"using namespace std;
using namespace tutorial;#define COUNT	3
#define BUFLEN  1024typedef struct _ConnectStat {struct bufferevent *bev;char buf[BUFLEN];
}ConnectStat;ConnectStat *stat_init(struct bufferevent *bev);
void do_echo_request(struct bufferevent *bev, void *arg);           // 读数据
void do_echo_response(struct bufferevent *bev, void *arg);          // 写数据
void event_cb(struct bufferevent *bev, short event, void *arg);     // 出错处理函数
int tcp_server_init(int port, int listen_num);
void listener_cb(struct evconnlistener *listener, evutil_socket_t fd, struct sockaddr *sock, int socklen, void *arg);   // 监听函数struct event_base *base;int main(int argc, char **argv) {struct sockaddr_in sin;memset(&sin, 0, sizeof(struct sockaddr_in));sin.sin_family = AF_INET;sin.sin_port = htons(9999);//server_addr.sin_addr.s_addr = inet_addr("127.0.0.1");base = event_base_new();// 创建socket,绑定、监听、接受链接// 创建监听对象,在指定的地址上监听接下来的TCP连接// listen、connect、bind、accept;  LEV_OPT_REUSEABLE:可重用,LEV_OPT_CLOSE_ON_FREE:自动关闭struct evconnlistener *listener = evconnlistener_new_bind(base, listener_cb, base,LEV_OPT_REUSEABLE | LEV_OPT_CLOSE_ON_FREE,1024, (struct sockaddr *)&sin,sizeof(struct sockaddr_in));// 监听集合中的事件event_base_dispatch(base);// 释放evconnlistener_free(listener);event_base_free(base);return 0;
}ConnectStat *stat_init(struct bufferevent *bev) {ConnectStat *temp = NULL;temp = (ConnectStat *)malloc(sizeof(ConnectStat));if (!temp) {fprintf(stderr, "malloc failed. reason: %s\\n", strerror(errno));return NULL;}memset(temp, '\\0', sizeof(ConnectStat));temp->bev = bev;return temp;
}void do_echo_request(struct bufferevent *bev, void *arg) {ConnectStat *stat = (ConnectStat *)arg;char *msg = stat->buf;std::string data = "";// 从缓冲区中获取数据size_t len = bufferevent_read(bev, msg, BUFLEN);if (0 == len) {return;}msg[len] = '\\0';data = msg;{list_account_records_response larr;// 将字符串转换larr.ParseFromString(data);
//printf("recoreds size: %d\\n", larr.records_size());// 输出接收到的数据printf("code: %d\\n", larr.code());printf("desc: %s\\n", larr.desc().c_str());for (int i = 0; i < COUNT; i++) {const list_account_records_response_account_record &ar = larr.records(i); printf("type: %d\\n", ar.type());printf("limit: %d\\n", ar.limit());printf("time: %lu\\n", ar.timestamp());}larr.set_desc(larr.desc() + "123");}//printf("recv from client <<< %s\\n", msg);// 将数据添加到缓冲区bufferevent_write(bev, msg, strlen(msg));
}void do_echo_response(struct bufferevent *bev, void *arg) {return ;
}void event_cb(struct bufferevent *bev, short event, void *arg) {ConnectStat *stat = (ConnectStat *)arg;if (event & BEV_EVENT_EOF) {printf("connect cloase\\n");} else if (event & BEV_EVENT_ERROR) {printf("some other error\\n");}// 自动close套接字和free读写缓冲区bufferevent_free(bev);// 释放bufferevent对象free(stat);
}typedef struct sockaddr SA;
int tcp_server_init(int port, int listen_num) {int errno_save;evutil_socket_t listener;   // int listenerlistener = socket(AF_INET, SOCK_STREAM, 0);if (-1 == listener) {return -1;}// 允许多次绑定同一个地址,要用在socket和bind之间evutil_make_listen_socket_reuseable(listener);struct sockaddr_in sin;sin.sin_family = AF_INET;sin.sin_addr.s_addr = 0;sin.sin_port = htons(port);if (bind(listener, (SA *)&sin, sizeof(sin)) < 0) {errno_save = errno;evutil_closesocket(listener);errno = errno_save;return -1;}if (listen(listener, listen_num) < 0) {errno_save = errno;evutil_closesocket(listener);errno = errno_save;return -1;}// 跨平台统一接口,将套接字设置为非阻塞状态evutil_make_socket_nonblocking(listener);return listener;
}// 一个客户端连接上服务器此函数就会被调用;当此函数被调用时,libevent已经帮我们accept了这个客户端
void listener_cb(struct evconnlistener *listener, evutil_socket_t fd, struct sockaddr *sock, int socklen, void *arg) {printf("accept a client %d \\n", fd);struct event_base *base = (struct event_base *)arg;// 针对已经存在的socket创建bufferevent对象// BEV_OPT_CLOSE_ON_FREE:如果释放bufferevent对象,则关闭连接struct bufferevent *bev = bufferevent_socket_new(base, fd, BEV_OPT_CLOSE_ON_FREE);// BEV_OPT_CLOSE_ON_FREE:释放bufferevent时关闭底层传输端口。这将关闭底层套接字,释放底层bufferevent等。ConnectStat *stat = stat_init(bev);// 给bufferevent设置回调函数// bufferevent对象、读事件回调函数、写事件回调函数、其他事件回调函数、参数bufferevent_setcb(bev, do_echo_request, do_echo_response, event_cb, stat);  // evnet_setbufferevent_enable(bev, EV_READ | EV_PERSIST);  // evnet_add,使bufferevent 生效
}


六、总结

简单用法介绍完毕,现阶段学习中,我是这样去使用的;日后如果接触的项目有用到再来更新。