protobuf 格式分析

protobuf 是谷歌出品一款高性能序列化框架,优点序列化后报文数据小,支持多种多种编程语言(c/c++,java,php,python等主流语言),缺点二进制不可读这倒不重要。

一. 安装

下载源码编译

二. 开发流程

2.1 准备helloworld.proto文件

package com;

message helloworld{
    required int32 id = 1;
    required string str = 3;
    optional int32 age = 2;
} 

Required 必须字段

Optional 可选字段,用的较多,便于后期平滑升级系统

Repeated 重复字段,相当于传递一个数组

Num 1,2,3 字段序号,不能重复

package 包名,c++中对应namespace,java对应包名

常用数据类型,与c/c++数据结构比较对应

bool

int32/uint32 int64/uint32

float double

string 只能处理ASCII字符

bytes 用于处理多字节语言字符,如中文

enum 枚举

2.2 生成各语言bundle

protoc -I=. --cpp_out=. helloworld.proto
protoc -I=. --java_out=./java helloworld.proto

2.3 网络测试

protobuf 最大的优势是跨语言,下面通过C++ udp server来处理java客户端消息。

C++ UDP Server:  

/**
 * C++ Udp server
 */ 
void udpServer()
{
        int s;
        struct sockaddr_in addr_serv;
        struct sockaddr_in client;

        s = socket(AF_INET, SOCK_DGRAM, 0);

        memset(&addr_serv, 0, sizeof(addr_serv));
        addr_serv.sin_family = AF_INET;
        addr_serv.sin_addr.s_addr = htonl(INADDR_ANY);
        addr_serv.sin_port = htons(PORT_SERV);

        bind(s, (struct sockaddr*)&addr_serv, sizeof(addr_serv));

        int n;
        char buff[BUFF_LEN];
        socklen_t len;

        while(1)
        {
                len = sizeof(client);
                n = recvfrom(s, buff, BUFF_LEN, 0, (struct sockaddr*)&client, &len);

                // unserialize
                helloworld rmsg;
                rmsg.ParseFromArray( buff, BUFF_LEN );
                printf( "Recv: %s\n", rmsg.DebugString().c_str() );
        }
}

Java UDP Client:

/**
 * UDP 发送pb数据包
 */
public static void sendPbPacket() {
	// Builder 
	Helloworld.helloworld.Builder builder = Helloworld.helloworld.newBuilder();
	builder.setId(505100).setStr("hello world");
	builder.setAge(18);
	
	// Make object
	Helloworld.helloworld hw = builder.build();
	System.out.println( hw.toString() );

	// 序列化
	byte[] buf = hw.toByteArray();
	try {
		// 反序列化
		Helloworld.helloworld hw1 = Helloworld.helloworld.parseFrom(buf);
		System.out.println( hw1.toString() );
	} catch (InvalidProtocolBufferException e) {
		e.printStackTrace();
	}

	// UDP发送
	DatagramSocket client = null;
		
	try {
		client = new DatagramSocket();

		InetAddress addr = InetAddress.getByName(host);
		DatagramPacket sendPacket = new DatagramPacket(buf, buf.length, addr, port);
		client.send(sendPacket);
	} catch (Exception e) {
		e.printStackTrace();
	}finally{
		client.close();
	}
}

服务端打印:

Recv: id: 505100
str: "hello world"
age: 18

   可见 protobuf 能完美跨语言间进行序列化过程。

三. protobuf 格式分析

protobuf编码其实类似tlv(tag length value)编码,其内部就是(tag, length, value)的组合,其中tag由(field_number<<3)|wire_type计算得出,field_number由我们在proto文件中定义。

tlv

  

Wireshark将上述通信过程抓包。数据包:

 pb data

数据段,共19字节:

08 8c ea 1e 12 0b 68 65 6c 6c 6f 20 77 6f 72 6c 64 18 12 

1. int id = 505100 

08 08 = (1<<3)|0,id序号,从上表查int32对应的Type为0  

8c ea 1e 三字节表示数字505100

505100为什么为0x8cea1e呢,下面是转换过程:

十进制: 505100 

二进制: 1111011010100001100

按照每7位拆: 001 1110 110 1010 000 1100

交换高低位,填充高位(1或0):1000 1100 1110 1010 0001 1110

十六进制:   0x08 0x0c 0xe  0xa  0x1  0xe

2. string str = "hello world";

12 0x12 = (2<<3)|2 

0b 长度为11

68 65 6c 6c 6f 20 77 6f 72 6c 64 hello world

3. int age = 18

18 0x18 = (3<<3)|0

12 十进制18 

  

猜你喜欢

转载自tcspecial.iteye.com/blog/2376748
今日推荐