protobuf的简单使用
2018-11-10 本文已影响16人
ligang1109
Google的Protobuf是一个高效的二进制编解码机制,应用很广,很多RPC库都使用了它,包括gRPC。
为了让大家更好的理解它,今天我来用一个简单的例子和大家介绍下它的使用方式:
示例程序介绍
示例程序由三部分组成:
- person.proto:定义通信用的协议数据
- client.cc:客户端实现,生成通信二进制数据发送给server
- server.cc:服务端时间,解析客户端发送来的二进制数据,简单输出
下面分别说明:
person.proto
syntax = "proto2";
package tutorial; // 会生成namespace tutorial
message Person { // 会生成class Person
required string name = 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;
}
调用protoc工具,生成类代码:
ligang@vm-xubuntu ~/devspace/hogwarts/cppsimple/src/protobuf $ protoc -I ./ --cpp_out=./ person.proto
ligang@vm-xubuntu ~/devspace/hogwarts/cppsimple/src/protobuf $ ll
total 80
-rw-rw-r-- 1 ligang ligang 1992 11月 10 13:55 client.cc
-rw-rw-r-- 1 ligang ligang 590 11月 10 13:55 CMakeLists.txt
-rw-rw-r-- 1 ligang ligang 33288 11月 10 16:55 person.pb.cc // 生成的person类的实现代码
-rw-rw-r-- 1 ligang ligang 25548 11月 10 16:55 person.pb.h // 生成的person类的header文件
-rw-rw-r-- 1 ligang ligang 354 11月 10 13:55 person.proto // 定义协议
-rw-rw-r-- 1 ligang ligang 2749 11月 10 13:55 server.cc
client.cc
#include <sys/socket.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <iostream>
#include <cstring>
#include "person.pb.h"
const int kRwBufLen = 1024;
int ConnectToServer(uint16_t port) {
int connfd = socket(AF_INET, SOCK_STREAM, 0);
if (connfd == -1) {
perror("connfd fail");
return -1;
}
struct sockaddr_in serverAddr;
memset(&serverAddr, 0, sizeof(struct sockaddr_in));
serverAddr.sin_family = AF_INET;
if (inet_pton(AF_INET, "127.0.0.1", &(serverAddr.sin_addr)) != 1) {
std::cout << "inet_pton fail" << std::endl;
return -1;
}
serverAddr.sin_port = htons(port);
if (connect(connfd, (struct sockaddr *) &serverAddr, sizeof(serverAddr)) == -1) {
perror("connect failed");
return -1;
}
return connfd;
}
std::string ProtobufPersonSerialize() {
tutorial::Person person;
person.set_name("person1");
person.set_id(1);
person.set_email("p1@protobuf.com");
tutorial::Person_PhoneNumber *phone = person.add_phones();
phone->set_number("123");
phone->set_type(tutorial::Person_PhoneType::Person_PhoneType_MOBILE);
phone = person.add_phones();
phone->set_number("234");
phone->set_type(tutorial::Person_PhoneType::Person_PhoneType_HOME);
phone = person.add_phones();
phone->set_number("345");
phone->set_type(tutorial::Person_PhoneType::Person_PhoneType_WORK);
std::string person_serialize = person.SerializeAsString();
std::cout << person_serialize.size() << std::endl;
return person_serialize;
}
int main() {
GOOGLE_PROTOBUF_VERIFY_VERSION;
int connfd = ConnectToServer(9008); // 获得通信用socket
char buf[kRwBufLen];
memset(buf, 0, kRwBufLen);
std::string person_serialize = ProtobufPersonSerialize(); // 生成通信用的person的二进制数据
uint32_t dl = htonl(person_serialize.size());
size_t dlsize = sizeof dl;
memcpy(buf, &dl, dlsize); // 二进制数据的长度
memcpy(buf + dlsize, (char *) person_serialize.c_str(), person_serialize.size()); // 编码protobuf二进制数据
write(connfd, buf, dlsize + person_serialize.size()); // 组成数据后一起发送
return 0;
}
客户端运行后,输出编码发送的二进制数据长度为:55
server.cc
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <iostream>
#include <cstring>
#include "person.pb.h"
const int kRwBufLen = 1024;
int TcpBindAndListen(uint16_t port) {
int listenfd = socket(AF_INET, SOCK_STREAM, 0);
if (listenfd == -1) {
perror("listenfd fail");
return -1;
}
struct sockaddr_in serverAddr;
memset(&serverAddr, 0, sizeof(struct sockaddr_in));
serverAddr.sin_family = AF_INET;
if (inet_pton(AF_INET, "127.0.0.1", &(serverAddr.sin_addr)) != 1) {
std::cout << "inet_pton fail" << std::endl;
return -1;
}
serverAddr.sin_port = htons(port);
if (bind(listenfd, (struct sockaddr *) &serverAddr, sizeof(serverAddr)) == -1) {
perror("bind fail");
return -1;
}
if (listen(listenfd, 1024) == -1) {
perror("listen failed");
return -1;
}
return listenfd;
}
int ReadConn(int connfd, size_t len, char *buf) {
size_t readed = 0;
size_t remain = len - readed;
while (remain > 0) {
auto n = read(connfd, buf + readed, remain);
if (n < 0) {
if (errno == EINTR) {
continue;
}
perror("read error");
return -1;
}
readed += n;
remain = len - readed;
}
return 0;
}
void ReadProtobufPerson(int connfd, size_t dl) {
char buf[kRwBufLen];
int r = ReadConn(connfd, dl, buf);
if (r == -1) {
perror("read conn error");
return;
}
tutorial::Person person;
std::cout << "dl = " << dl << std::endl;
person.ParseFromArray(buf, dl); // 解码protobuf二进制数据
std::cout << person.id() << std::endl
<< person.name() << std::endl
<< person.email() << std::endl;
for (auto phone : person.phones()) {
std::cout << phone.number() << std::endl;
std::cout << phone.type() << std::endl;
}
}
void ProtobufServe(int connfd) {
char buf[kRwBufLen];
size_t dl_len = 4;
int r = ReadConn(connfd, dl_len, buf); // 先解析二进制数据的长度
if (r == -1) {
perror("read conn error");
return;
}
uint32_t dl;
memcpy(&dl, buf, dl_len);
std::cout << "before ntoh: " << dl << std::endl;
dl = ntohl(dl);
std::cout << "after ntoh: " << dl << std::endl;
ReadProtobufPerson(connfd, dl); // 解析二进制数据
}
int main() {
GOOGLE_PROTOBUF_VERIFY_VERSION;
int listenfd = TcpBindAndListen(9008); // listen用socket
if (listenfd < 0) {
return 1;
}
bool running = true;
while (running) {
struct sockaddr_in clientAddr;
memset(&clientAddr, 0, sizeof(struct sockaddr_in));
socklen_t clientLen;
int connfd = accept(listenfd, (struct sockaddr *) &clientAddr, &clientLen); // 客户端连接后获得通信用socket
if (connfd == -1) {
running = false;
perror("accept failed");
} else {
ProtobufServe(connfd); // 读取客户端发送来的二进制数据后解析输出
}
close(connfd);
}
return 0;
}
运行后,服务端输出客户端发送来的数据为:
dl = 55 // 数据长度
1 // id
person1 // name
p1@protobuf.com // email
123 // 这里开始是地址薄
0
234
1
345
2
结束语
这里只是为了示例protobuf的基本使用方法,更多的protobuf语法,请查看官方手册,谢谢大家!