技术收藏首页投稿(暂停使用,暂停投稿)程序员

Protocol buffers(protobuf)入门简介及性

2016-09-28  本文已影响3600人  cjzhao

XML、JSON程序员应该都已经再熟悉不过了,不管你使用什么开发语言,解析xml、json是必须玩过的,而且肯定也曾经让你烧过脑,还好大部分开发语言都有很成熟的包来帮你打理这么复杂的事。

XML和Json已经经历了数十年的历史,也是时候做些改变了,Google在08年就推出了一个全新的结构化数据序列化(交换)机制,发展到今天,伴随着微服务架构的到来,已经开始在逐步普及,它就是今天我们要聊的话题是Protocol buffers或简称protobuf。

Protocol buffers是一个灵活,高效,自动化的结构化数据序列化机制,类似于XML,但是更小,更快,更简单。你可以一次定义你希望你的数据结构,使用工具很方便的生成从各种数据流读写结构化数据的代码,支持多种开发语言。你可以在不重新编译部署程序的情况下更新你的数据结构,支持向前和向后兼容。

多的不说了,直接上码吧,类似xml和json,我们先来看看如何来定义数据结构吧:


syntax = "proto3"; 

option go_package = "user";
option java_package = "com.venusource.protobuf";

message ProtobufUser {
 int32 id = 1;
 string name = 2;
 message Phone{
   enum PhoneType {
     HOME = 0;
     WORK = 1;
     OTHER = 2;   
   }
   PhoneType phoneType = 1;
   string phoneNumber = 2;
 }
 repeated Phone phones = 3;
}

protobuf定义详细的语法请到官网自学:https://developers.google.com/protocol-buffers/docs/proto3(你懂的,需要翻墙)。

这里简单介绍一下,message关键字定义一种消息类型,int32、string相当于数据类型,后面的1、2、3相当于字段的索引,在序列化的时候,只使用索引来标识相应的字段,能节省存储空间,emum定义了枚举类型,repeated关键字可以理解为一个数组。

整个结构相当于定义了一个ProtobufUser类,包括id、name、phones三个子段,phones是一个Phone类型的数组,Phone包括两个字段,phoneType和phoneNumber,其中phoneType是枚举类型,包括HOME、WORK、OTHER三种电话类型。

上面的描述相当于下面的json:

{
 "Id":1,
 "Name":"cjzhao",
 "Phones":[
 {
 "PhoneType":0,
 "PhoneNumber":"01088888888"
 },
 {
 "PhoneType":1,
 "PhoneNumber":"15588888888"
 }
 ]
}

怎么用呢?我们以go语言为例,先安装Protobuf编译器,可以从github下载最新版本,地址:https://github.com/google/protobuf/releases

安装方法和大部分软件类似,下载安装包解压,配置bin目录到环境变量,这里就不详述了,自己搞吧,如果是mac用户,也可以用brew安装。如果没记错的话安装命令应该是:

brew install protobuf

接下来我们安装go语言相关的工具包和插件,执行如下命令:

go get -u github.com/golang/protobuf/{proto,protoc-gen-go}

OK,到此安装已经结束,接下来就是怎么用了。

protobuf使用包括以下几个步骤:

刚才我们已经定义了数据结构,执行下面的命令可以用成go语言相关的工具类:

mkdir user
protoc --go_out=user ProtobufUser.proto

可以看到在user目录下,生成一个ProtobufUser.pb.go,这个就是编译器生成的类,这里就不贴代码了,太多。

来看看我们怎么使用吧:

func testProtobuf(rw http.ResponseWriter, req *http.Request) {
    t := &user.ProtobufUser{Id: 1, Name: "cjzhao", 
Phones: []*user.ProtobufUser_Phone{
{PhoneType: user.ProtobufUser_Phone_HOME, PhoneNumber: "01088888888"},
 {PhoneType: user.ProtobufUser_Phone_WORK, PhoneNumber: "15588888888"}}}
    data, err := proto.Marshal(t)
    if err != nil {
        log.Fatal("marshaling error: ", err)
    }
    rw.Write(data)
}

注:本文结尾有本文涉及到的完整代码

很简单,直接定义一个ProtobufUser对象,然后序列化后返回给客户端。

再来看看客户端的代码:

    client := &http.Client{}
    resp, err := client.Get("http://localhost:8080/protoc")
    if err != nil {
        log.Println(err)
    }
    defer resp.Body.Close()
    data, err := ioutil.ReadAll(resp.Body)
    if err != nil {
        log.Println(err)
    }
    user := &user.ProtobufUser{}
    proto.Unmarshal(data, user)
    fmt.Printf("user id=%d, name=%s\n", user.Id, user.Name)
    for _, phone := range user.Phones {
        fmt.Printf("%s:%s\n", phone.PhoneType, phone.PhoneNumber)
    }

也很简单,将服务器端返回的数据直接反序列化即得到我们的user对象。

整个过程protobuf就和xml和json一样,承担了结构化数据的数据交换。

最后再来看看java客户端如何使用吧,执行如下命令,生成java版本的数据结构类:


protoc --java_out=. ProtobufUser.proto

会生成一个ProtobufUser的java类,我们在客户端直接使用即可,客户端代码如下:


package com.venusource.protobuf;

import java.io.IOException;
import org.apache.http.HttpEntity;
import org.apache.http.client.ClientProtocolException;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.util.EntityUtils;
import com.venusource.protobuf.ProtobufUserOuterClass.ProtobufUser;
import com.venusource.protobuf.ProtobufUserOuterClass.ProtobufUser.Phone;

public class ProtobufClient {
    public static void main(String args[]){
        CloseableHttpClient httpclient = HttpClients.createDefault(); 
        HttpGet httpget = new HttpGet("http://localhost:8080/protoc");
        CloseableHttpResponse response = null;
        // 执行get请求.    
        try {
            response = httpclient.execute(httpget);
            HttpEntity entity = response.getEntity();
            ProtobufUser user = ProtobufUser.parseFrom(EntityUtils.toByteArray(entity));
            System.out.printf("user: id=%d, name=%s \n",user.getId(), user.getName());
            for(Phone phone:user.getPhonesList()){
                System.out.printf("%s:%s\n", phone.getPhoneType(), phone.getPhoneNumber());
            }
        } catch (ClientProtocolException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        } catch (IOException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }finally{
            try {
                response.close();
                httpclient.close();
            } catch (IOException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
        }
    }
}

输出结果如下:

user: id=1, name=cjzhao 
HOME:01088888888
WORK:15588888888

怎么样,是不是跨语言、跨平台,和xml和json效果一样。

最后我们来看看和xml和json对比的情况吧,主要包括性能和数据传输包的大小。

同样的数据结构,测试结果如下:

protobuf_test.png

可以看出protobuf的性能最好、xml最差,但相差都不是很大。

数据包大小:

protobuf_curl.png

可以看出,protobuf最小,42字节,xml最大200字节,相当于5倍。

怎么样?是时候使用protobuf了吧?

最后给大家奉上本文涉及到的代码:

go语言版的性能测试项目:https://github.com/ChangjunZhao/protobuf-test

java版本的客户端代码:https://github.com/ChangjunZhao/protobuf-java-client

本文将是微服务入门系列的第一篇,请在简书关注我哦(cjzhao)。

看完文章有收获的话记得打赏、关注、点赞!


CJ推荐:
IOS APP开发常用的几个命令行工具
使用GitLab来实现IOS项目的持续集成CI
互联网+时代的全新软件(产品)交付模式
程序员的编辑器-VIM(爱就是爱)
向开源社区贡献您的代码
在github上写博客
DevOps是什么东东?
js依赖管理工具bower
JS模块化编程-requirejs

上一篇下一篇

猜你喜欢

热点阅读