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点击上面的连接去下载!

C/C++ Linux protobuf2 简单用法记录_第1张图片

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

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

centos系统将apt-get 改为 yum

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

unzip protobuf-master.zip

cd protobuf-master/

./autogen.sh

./configure

make

make check

make install

ldconfig

头文件所在路径:/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,(可以说是枚举)也要给他设置值,且它可以被设置多个值;

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

C/C++ Linux protobuf2 简单用法记录_第2张图片

后面赋值 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 
#include 


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

C/C++ Linux protobuf2 简单用法记录_第3张图片


五、protobuf与libevent结合使用

client.cc

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

#include 
#include 
#include 
#include 
#include 
#include 

#include 
#include 
#include 

#include 
#include 

#include "ptb.pb.h"
#include 
#include 



using namespace std;
using namespace tutorial;

#define COUNT 3

int 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); // 100ms
        close(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 error
        return -1;
    }
    
    return sockfd;
}

  

server.cc

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

#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 

#include 
#include 

#include "ptb.pb.h"


using namespace std;
using namespace tutorial;


#define COUNT	3
#define BUFLEN  1024


typedef 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 listener
    
    listener = 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_set
    
    bufferevent_enable(bev, EV_READ | EV_PERSIST);  // evnet_add,使bufferevent 生效
}

C/C++ Linux protobuf2 简单用法记录_第4张图片


六、总结

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

你可能感兴趣的:(c++,linux,C/C++,protobuf)