谷歌protobuf(Protocol buffers)的使用

本文详细介绍了Protocol Buffers的基本概念、安装步骤、限定符的作用、支持的数据类型,以及如何编译并利用protobuf进行文件编写、基础应用和嵌套消息传递。还展示了libevent与protobuf的协作示例。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

一、概述

  1. Protocol buffers 是 Google 的语言中立、平台中立、可扩展的结构化数据序列化机制——像 XML,但更小、更快、更简单。您可以定义一次数据的结构化方式,然后您可以使用特殊生成的源代码轻松地使用各种语言在各种数据流中写入和读取结构化数据。
  2. Photocol buffers 是一种比json和xml等序列化工具更加轻量和高效的结构化数据存储格式,性能比json和xml真的强很多。
  3. 原理图:
    protobuf 原理图

二、安装

# 如果在解压或安装时出现问题,执行
apt-get install autoconf automake libtool curl make g++ unzip
# 1. 从github上克隆protobuf
git clone https://github.com/protocolbuffers/protobuf
# 2. 解压下载好的文件
unzip protobuf-master.zip
# 3. 进入 protobuf-master 目录
cd protobuf-master/
# 4. 执行如下命令
./autogen.sh
./configure
make
make check
make install
ldconfig
# 5. 在 shell 下输入protoc ,如果出现 Usage: protoc [OPTION] PROTO_FILES 则成功。

三、protobuf中的限定符

限定符含义
required必填字段
optional可选字段
repeated可重复字段

四、protobuf支持的数据类型

protobuf数据类型代表C++数据类型描述
floatfloat
doubledouble
int32__int32使用变长编码,对于负值的效率很低,如果你的域有可能有负值,请使用sint64替代
uint32unsigned __int32使用变长编码
int64__int64使用变长编码
uint64unsigned __int64使用变长编码
sint32__int32使用变长编码,这些编码在负值时比int32高效的多
sint64__int64使用变长编码,有符号的整型值。编码时比通常的int64高效。
fixed32unsigned __int32总是4个字节,如果数值总是比228大的话,这个类型会比uint32高效。
fixed64unsigned __int64总是8个字节,如果数值总是比总是比256大的话,这个类型会比uint64高效。
sfixed32__int32总是4个字节
sfixed64__int64总是8个字节
boolbool
stringstd::string一个字符串必须是UTF-8编码或者7-bit ASCII编码的文本。
bytesstd::string可能包含任意顺序的字节数据。
enumenum枚举类型。如果要将两个枚举变量的值设为相等,那么需要添加如下代码,否则会报错:option allow_alias = true;

五、编译

1. 将proto文件编译成 C++ 文件

# SRC_DIR: proto文件(example.proto)所在目录
# cpp_out(DST_DIR): 制定了生成代码的路径
# example.proto: 指proto文件名
protoc -I=$SRC_DIR --cpp_out=$DST_DIR  example.proto

# 示例:
protoc -I=./ --cpp_out=./ example.proto
# 说明:在当前目录中查找 example.proto 并将生成的文件放在当前目录下

2. 将编译好的文件与代码一起编译执行

g++  -std=c++11   example.cc example.pb.cc -lprotobuf

六、应用

1. proto 文件的编写(文件后缀名:proto)

syntax = "proto2";

package example;

// 使用手机号登录获取验证码(请求端)
message get_code_requset {
	required	string	mobile	= 1;
}

// 使用手机号登录获取验证码(响应端)
message get_code_response {
	required	int32	code	= 1;	// 响应代号
	required	int32	icode	= 2;	// 验证码
	optional	string	reason	= 3;	// 失败原因
}

// 使用手机号登录(请求端)
message login_request {
	required	string	mobile	= 1;	// 手机号码
	required	int32	icode	= 2;	// 验证码
}

// 使用手机号登录(响应端)
message login_response {
	required	int32	code	= 1;	// 响应代号
	optional	string	reason	= 2;	// 失败原因
}

// 查询登录记录(请求端)
message login_record_request {
	required	string	mobile	= 1;	// 手机号码
}

// 查询登录记录(响应端)
message login_record_response {
	required	int32	code	= 1;	// 响应代号
	optional	string	reason	= 2;	// 失败原因
	
	message login_record {
		required	int32	timestamp	= 1;	// 时间戳
		required	string	device_name	= 2;	// 设备名
	}

	repeated	login_record	records	= 3;
}

// 将以上代码编译成 C++ 文件
// protoc -I=./ --cpp_out=./ example.proto

2. 基础应用

#include <string>
#include <iostream>
#include "example.pb.h"

int main(int argc, char *argv[]) {
	std::string data;	// 存储序列化之后的数据

	// 客户端发送请求
	{
		example::get_code_requset getCodeRequest;
		getCodeRequest.set_mobile("19912341234");

		getCodeRequest.SerializeToString(&data);
		std::cout << "serial[" << data.length() << "]:" << data << std::endl;
	}

	// 服务器端解析数据
	{
		example::get_code_requset parse;
		parse.ParseFromString(data);
		std::cout << "mobile: " << parse.mobile() << std::endl;
	}
	return 0;
}

/*********************************************************************************************
 * 使用如下语句在shell下编译:g++ -std=c++11 example.cc example.pb.cc -o example.exe -lprotobuf
**********************************************************************************************/

3. 嵌套应用(即message内嵌套message)

#include <time.h>
#include <string>
#include <iostream>
#include "example.pb.h"

int main(int argc, char *argv[]) {
	std::string data;	// 存储序列化之后的数据

	// 客户端发送请求
	{
		example::login_record_response response;
		response.set_code(200);
		response.set_reason("OK");

		// 插入登录数据
		time_t now = time(NULL);
		for (int i = 0; i < 5; ++i) {
			example::login_record_response_login_record* login = response.add_records();
			login->set_timestamp(now + i);
			login->set_device_name(std::string("phone-") + std::to_string(i));
		}

		std::cout << "record size: " << response.records_size() << std::endl;

		// 序列化
		response.SerializeToString(&data);
	}

	// 服务器端解析数据
	{
		example::login_record_response response;
		response.ParseFromString(data);

		std::cout << "code: " << response.code()
			<< "  reason:" << response.reason()
			<< "  parse size : " << response.records_size() << std::endl;

		for (int i = 0; i < response.records_size(); ++i) {
			const example::login_record_response_login_record& login = response.records(i);
			std::cout << "timestamp: " << login.timestamp()
				<< "  device_name: " << login.device_name() << std::endl;
		}

	}

	return 0;
}

/*********************************************************************************************
 * 使用如下语句在shell下编译:g++ -std=c++11 example.cc example.pb.cc -o example.exe -lprotobuf
**********************************************************************************************/

4. libevent 和 protobuf 协作

  1. 客户端
// 函数 cmd_read_data() 和 socket_read_data()
#include <stdio.h>
#include <errno.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#include <assert.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <event.h>
#include <event2/event.h>

#include <string>
#include "example.pb.h"

typedef struct sockaddr		sockaddr_t;
typedef struct sockaddr_in	sockaddr_in_t;

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) {
		fprintf(stderr, "please input [ipaddr][ipport]\n");
		return -1;
	}

	int sockfd = connect_server(argv[1], atoi(argv[2]));
	if (sockfd < 0) {
		fprintf(stderr, "connect_server(): failed!\n");
		return -2;
	}

	printf("connect server success!\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);

	printf("event add finished!\n");

	event_base_dispatch(base);

	printf("finished!\n");

	return 0;
}

int connect_server(const char* server_ip, int port) {
	int sockfd, status, save_errno;
	sockaddr_in_t server_addr;

	memset(&server_addr, 0, sizeof(sockaddr_in_t));

	server_addr.sin_family = AF_INET;
	server_addr.sin_port = htons(port);
	status = inet_aton(server_ip, &server_addr.sin_addr);

	if (!status) {
		errno = EINVAL;
		fprintf(stderr, "inet_aton() failed!\n");
		return -1;
	}

	sockfd = socket(PF_INET, SOCK_STREAM, 0);
	if (sockfd < 0) {
		fprintf(stderr, "socket() failed! reason: %s\n", strerror(errno));
		return sockfd;
	}

	status = connect(sockfd, (sockaddr_t*)(&server_addr), sizeof(server_addr));
	if (status < 0) {
		fprintf(stderr, "connect() failed! reason: %s\n", strerror(errno));
		save_errno = errno;
		close(sockfd);
		errno = save_errno;
		return -1;
	}

	// 不用设置非阻塞
	//evutil_make_socket_nonblocking(sockfd);

	return sockfd;
}

void cmd_read_data(int fd, short events, void* arg) {
	std::string data;
	char msg[1024] = { 0 };

	read(fd, msg, sizeof(msg) - 1);

	// 向服务器发送手机号
	example::get_code_requset request;
	request.set_mobile("19912344321");
	request.SerializeToString(&data);

	int sockfd = *((int*)arg);

	// 把终端消息发送给服务器端,客户端忽略性能考虑,直接使用阻塞发送
	write(sockfd, data.c_str(), data.length());
}

void socket_read_data(int fd, short events, void* arg) {
	char msg[1024];

	// 不考虑数据读了一半的情况
	int len = read(fd, msg, sizeof(msg) - 1);
	if (len == 0) {
		fprintf(stderr, "connection close!\n");
		exit(2);
	}
	else if (len < 0) {
		fprintf(stderr, "read failed!\n");
		exit(3);
	}

	msg[len] = '\0';

	example::get_code_response response;
	response.ParseFromString(std::string(msg));

	printf("code: %d, icode: %d, reason: %s\n", response.code(), response.icode(), response.reason().c_str());
}


  1. 服务器端
// 函数:do_recv_msg()
#include <string.h>
#include <stdlib.h>
#include <event.h>
#include <event2/event.h>
#include <event2/listener.h>
#include <event2/bufferevent.h>

#include <string>
#include <iostream>
#include "example.pb.h"

#define BUFFER_LENGTH 1024

typedef struct event_base		event_base_t;
typedef struct bufferevent		bufferevent_t;
typedef struct evconnlistener	evconnlistener_t;

typedef struct _connect_stat {
	bufferevent_t* bev;
	char buffer[BUFFER_LENGTH];
}connect_stat_t;

connect_stat_t* stat_new(bufferevent_t* bev);

void listener_cb(evconnlistener_t* listener, evutil_socket_t fd, struct sockaddr* addr, int addrlen, void* user_arg);

void do_recv_msg(bufferevent_t* bev, void* user_arg);
void event_cb(bufferevent_t* bev, short what, void* user_arg);

int main(int argc, char* argv[]) {
	struct sockaddr_in sin;
	memset(&sin, 0, sizeof(sin));
	sin.sin_family = AF_INET;
	sin.sin_port = htons(9696);	// 端口为 9696
	//sin.sin_addr.s_addr = htonl(INADDR_ANY);

	event_base_t* base = event_base_new();

	struct evconnlistener* listener = evconnlistener_new_bind(base, listener_cb, base,
		BEV_OPT_CLOSE_ON_FREE,
		10, (struct sockaddr*)(&sin), sizeof(sin));

	event_base_dispatch(base);

	evconnlistener_free(listener);
	event_base_free(base);

	return 0;
}

connect_stat_t* stat_new(bufferevent_t* bev) {
	connect_stat_t* p = (connect_stat_t*)malloc(sizeof(connect_stat_t));
	memset(p, 0, sizeof(connect_stat_t));
	p->bev = bev;

	return p;
}

void listener_cb(evconnlistener_t* listener, evutil_socket_t fd, struct sockaddr* addr, int addrlen, void* user_arg) {
	event_base_t* base = (event_base_t*)(user_arg);

	// 为客户端分配bufferevent
	bufferevent_t* bev = bufferevent_socket_new(base, fd, BEV_OPT_CLOSE_ON_FREE);

	connect_stat_t* stat = stat_new(bev);

	bufferevent_setcb(bev, do_recv_msg, NULL, event_cb, stat);
	bufferevent_enable(bev, EV_READ | EV_PERSIST);
}

void do_recv_msg(bufferevent_t* bev, void* user_arg) {
	connect_stat_t* stat = (connect_stat_t*)user_arg;

	char* msg = stat->buffer;

	size_t len = bufferevent_read(bev, msg, BUFFER_LENGTH - 1);
	msg[len] = '\0';

	example::get_code_requset request;
	request.ParseFromString(std::string(msg));

	std::cout << "mobile: " << request.mobile() << std::endl;

	// 响应
	std::string data;
	example::get_code_response response;
	int icode = rand() % 100000 + 100000;
	response.set_code(200);
	response.set_icode(icode);
	response.set_reason("OK");
	response.SerializeToString(&data);

	bufferevent_write(bev, data.c_str(), data.length());
}

void event_cb(bufferevent_t* bev, short what, void* user_arg) {
	connect_stat_t* stat = (connect_stat_t*)user_arg;

	if (what & BEV_EVENT_EOF) {
		printf("connection closed!\n");
	}
	else if (what & BEV_EVENT_ERROR) {
		printf("some other error!\n");
	}

	// 同时 关闭套接字 和 free 读写缓冲区
	bufferevent_free(bev);

	free(stat);
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值