Protobuf简单使用及其抓包分析

早之前就用过Google的Protobuf做数据编码,一直没有深入理解其中的原理,最近做了一次通讯抓包,发现其中很多Protobuf编码的数据包,于是决定分析一下其中的数据包及其编码。

一、Protobuf的使用

首先来简单介绍一下Protobuf的使用,这里以windows下java开发为例,几个步骤:编写*.proto ->使用google提供的protoc.exe生成*.java->项目中导入protobuf的.jar包进行开发即可。先看这里的*.proto文件:

package com;

message CMsg
{
    required string msghead = 1;
    required string msgbody = 2;
}

message CMsgHead
{
    required int32 msglen = 1;
    required int32 msgtype = 2;
    required int32 msgseq = 3;
    required int32 termversion = 4;
    required int32 msgres = 5;
    required string termid = 6;
}

message CMsgReg
{
    optional int32 area = 1;
    optional int32 region = 2;
    optional int32 shop = 3;
    optional int32 ret = 4;
    optional string termid = 5;
}

使用protoc.exe生成java文件,命令如下:

将生成的Msg.java及protobuf-java-2.3.0.jar导入项目中进行开发,这里写一个服务器端ProtobufServer及客户端ProtobufClient

package com;

import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.ServerSocket;
import java.net.Socket;

import com.Msg.CMsg;
import com.Msg.CMsgHead;
import com.Msg.CMsgReg;

public class ProtoServer implements Runnable {

	@Override
	public void run() {
		try {
			System.out.println("beign:");
			ServerSocket serverSocket = new ServerSocket(12345);
			while (true) {
				System.out.println("等待接收用户连接:");
				// 接受客户端请求
				Socket client = serverSocket.accept();

				DataOutputStream dataOutputStream;
				DataInputStream dataInputStream;

				try {
					InputStream inputstream = client.getInputStream();
					dataOutputStream = new DataOutputStream(
							client.getOutputStream());

					byte len[] = new byte[1024];
					int count = inputstream.read(len);
					byte[] temp = new byte[count];
					for (int i = 0; i < count; i++) {
						temp[i] = len[i];
					}

					CMsg msg = CMsg.parseFrom(temp);

					CMsgHead head = CMsgHead.parseFrom(msg.getMsghead()
							.getBytes());
					System.out.println("==len===" + head.getMsglen());
					System.out.println("==res===" + head.getMsgres());
					System.out.println("==seq===" + head.getMsgseq());
					System.out.println("==type===" + head.getMsgtype());
					System.out.println("==Termid===" + head.getTermid());
					System.out.println("==Termversion==="
							+ head.getTermversion());

					CMsgReg body = CMsgReg.parseFrom(msg.getMsgbody()
							.getBytes());
					System.out.println("==area==" + body.getArea());
					System.out.println("==Region==" + body.getRegion());
					System.out.println("==shop==" + body.getShop());

					sendProtoBufBack(dataOutputStream);
					inputstream.close();

				} catch (Exception ex) {
					System.out.println(ex.getMessage());
					ex.printStackTrace();
				} finally {
					client.close();
					System.out.println("close");
				}
			}
			
		} catch (IOException e) {
			System.out.println(e.getMessage());
		}
	}

	private byte[] getProtoBufBack() {

		// head
		CMsgHead head = CMsgHead.newBuilder().setMsglen(10).setMsgtype(21)
				.setMsgseq(32).setTermversion(43).setMsgres(54)
				.setTermid("Server:head").build();

		// body
		CMsgReg body = CMsgReg.newBuilder().setArea(11).setRegion(22)
				.setShop(33).setRet(44).setTermid("Server:body").build();

		// Msg
		CMsg msg = CMsg.newBuilder()
				.setMsghead(head.toByteString().toStringUtf8())
				.setMsgbody(body.toByteString().toStringUtf8()).build();

		return msg.toByteArray();
	}

	private void sendProtoBufBack(DataOutputStream dataOutputStream) {

		byte[] backBytes = getProtoBufBack();
		
		// Integer len2 = backBytes.length;
		
		// byte[] cmdHead2 = BytesUtil.IntToBytes4(len2);

		try {
			// dataOutputStream.write(cmdHead2, 0, cmdHead2.length);
			dataOutputStream.write(backBytes, 0, backBytes.length);
			dataOutputStream.flush();
		} catch (IOException e) {
			e.printStackTrace();
		}
	}

	public static void main(String[] args) {
		Thread desktopServerThread = new Thread(new ProtoServer());
		desktopServerThread.start();
	}

}

package com;

import java.io.InputStream;
import java.net.Socket;

import com.Msg.CMsg;
import com.Msg.CMsgHead;
import com.Msg.CMsgReg;

public class ProtoClient {

	public static void main(String[] args) {
		ProtoClient pc=new ProtoClient();
		System.out.println("beign:");
		pc.runget();
	}

	public void runget() {
		Socket socket = null;
		try {
			//socket = new Socket("localhost", 12345);
			socket = new Socket("192.168.85.152", 12345);
			// head
			CMsgHead head = CMsgHead.newBuilder().setMsglen(5).setMsgtype(1)
					.setMsgseq(3).setTermversion(41).setMsgres(5)
					.setTermid("Client:head").build();

			// body
			CMsgReg body = CMsgReg.newBuilder().setArea(11).setRegion(22)
					.setShop(33).setRet(44).setTermid("Clent:body").build();
			
			// Msg
			CMsg msg = CMsg.newBuilder()
					.setMsghead(head.toByteString().toStringUtf8())
					.setMsgbody(body.toByteString().toStringUtf8()).build();

			// 向服务器发送信息
			System.out.println("sendMsg...");
			msg.writeTo(socket.getOutputStream());

			// 接受服务器的信息
			InputStream input = socket.getInputStream();

			System.out.println("recvMsg:");
			byte[] by = recvMsg(input);
			printMsg(CMsg.parseFrom(by));

			input.close();
			socket.close();
		} catch (Exception e) {
			System.out.println(e.toString());
		}
	}

	public void printMsg(CMsg g) {

		try {
			CMsgHead h = CMsgHead.parseFrom(g.getMsghead().getBytes());
			StringBuffer sb = new StringBuffer();
			if (h.hasMsglen())
				sb.append("==msglen===" + h.getMsglen() + "\n");
			if (h.hasMsgres())
				sb.append("==msgres===" + h.getMsgres() + "\n");
			if (h.hasMsgseq())
				sb.append("==msgseq===" + h.getMsgseq() + "\n");
			if (h.hasMsgtype())
				sb.append("==msgtype===" + h.getMsgtype() + "\n");
			if (h.hasTermid())
				sb.append("==termid===" + h.getTermid() + "\n");
			if (h.hasTermversion())
				sb.append("==termversion===" + h.getTermversion() + "\n");

			CMsgReg bo = CMsgReg.parseFrom(g.getMsgbody().getBytes());
			if (bo.hasArea())
				sb.append("==area==" + bo.getArea() + "\n");
			if (bo.hasRegion())
				sb.append("==region==" + bo.getRegion() + "\n");
			if (bo.hasShop())
				sb.append("==shop==" + bo.getShop() + "\n");
			if (bo.hasRet())
				sb.append("==ret==" + bo.getRet() + "\n");
			if (bo.hasTermid())
				sb.append("==termid==" + bo.getTermid() + "\n");

			System.out.println(sb.toString());

		} catch (Exception e) {
			e.printStackTrace();
		}

	}

	public byte[] recvMsg(InputStream inpustream) {
		byte[] temp = null;
		try {

			byte len[] = new byte[1024];
			int count = inpustream.read(len);

			temp = new byte[count];
			for (int i = 0; i < count; i++) {
				temp[i] = len[i];
			}
			return temp;
		} catch (Exception e) {
			System.out.println(e.toString());
			return temp;
		}
	}
}

运行结果:

二、抓包分析

在上面socket通信过程中我使用了wireshark对其进行抓包,结果分析如下图

由上图我们可以很清楚的看到,protobuf编码其实类似tlv(tag length value)编码,其内部就是(tag, length, value)的组合,其中tag由(field_number<<3)|wire_type计算得出,field_number由我们在proto文件中定义,wire_type由protobuf根据proto中定义的字段类型决定,length长度采用一种叫做Varint 的数字表示方法,它是一种紧凑的表示数字的方法,用一个或多个字节来表示一个数字,值越小的数字使用越少的字节数,具体细节可以谷歌Varint。总之Protobuf 序列化后所生成的二进制消息非常紧凑,这得益于 Protobuf 采用了上面的 Encoding 方法。

参考文献:http://www.ibm.com/developerworks/cn/linux/l-cn-gpb/

源码下载:http://download.csdn.net/detail/wangqiuyun/8294015

转载请注明:http://blog.csdn.net/wangqiuyun/article/details/42119835



  • 7
    点赞
  • 19
    收藏
    觉得还不错? 一键收藏
  • 6
    评论
ProtobufProtocol Buffers)是一种轻量级的数据序列化格式,用于结构化数据的存储和交换。它具有跨平台、高效、可扩展等特点,被广泛应用于分布式系统、通信协议、数据存储等领域。 要使用Protobuf插件,首先需要安装Protobuf编译器(protoc)和相应的插件。然后,按照以下步骤进行使用: 1. 定义消息结构:使用Protobuf语言定义消息的结构,包括字段名称、类型和标签等信息。例如,可以创建一个名为`person.proto`的文件,并在其中定义一个`Person`消息类型。 2. 编写消息定义:在`person.proto`文件中,使用Protobuf语言编写消息的定义。例如,可以定义一个包含姓名和年龄字段的`Person`消息类型。 3. 编译消息定义:使用Protobuf编译器将消息定义文件编译成目标语言的代码。例如,可以使用以下命令将`person.proto`文件编译成Python代码: ``` protoc --python_out=. person.proto ``` 4. 使用生成的代码:根据目标语言生成的代码,可以在应用程序中使用Protobuf消息进行序列化和反序列化操作。例如,在Python中可以导入生成的代码,并使用`Person`类创建和操作消息对象。 相关问题: 1. Protobuf是什么? 2. Protobuf有哪些特点? 3. 如何定义Protobuf消息结构? 4. 如何使用Protobuf编译器生成代码? 5. 如何在应用程序中使用生成的代码进行序列化和反序列化操作?
评论 6
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值