美文网首页Protobuf
跨平台数据通信的选择:Google ProtoBuf

跨平台数据通信的选择:Google ProtoBuf

作者: Bobby0322 | 来源:发表于2017-08-15 16:13 被阅读794次

跨平台数据通信的选择:Google ProtoBuf

简称protobuf,google开源项目,是一种数据交换的格式,google 提供了多种语言的实现:php、JavaScript、java、c#、c++、go 和 python等。 由于它是一种二进制的格式,比使用 xml, json 进行数据交换快许多。以上描述太官方不好理解,通俗点来解释一下,就是通过protobuf定义好数据结构生成一个工具类,这个工具类可以把数据封装成二进制数据来进行传输,在另一端收到二进制数据再用工具类解析成正常的数据。

protobuf是google的一个开源项目,可用于以下两种用途:

  • 数据的存储(序列化和反序列化),类似于xml、json等;
  • 制作网络通信协议。

正宗(Google 自己内部用的)的protobuf支持三种语言:Java 、c++和Pyton,很遗憾的是并不支持.Net 或者 Lua 等语言,但社区的力量是不容忽视的,由于protobuf确实比Json、XML有速度上的优势和使用的方便,并且可以做到向前兼容、向后兼容等众多特点,所以protobuf社区又弄了个protobuf.net的组件并且还支持众多语言。

.net 版的protobuf来源于proto社区,有两个版本。一个版本叫protobuf-net,官方站点:http://code.google.com/p/protobuf-net/ 写法上比较符合c#一贯的写法。另一个版本叫protobuf-csharp-sport ,
官方站点:http://code.google.com/p/protobuf-csharp-port/ 写法上跟java上的使用极其相似,比较遵循Google 的原生态写法,所以做跨平台还是选择第二版本吧。因为你会发现几乎和java的写法没啥两样。

ProtoBuf的原理

Socket通信中,客户端与服务器之间传递的是字节流。而在现实的应用中我们需要传递有一定含义的结构,使得通信的双方都能够识别该结构。实现对象(Class和Struct)Socket传输的关键就在于Class或Struct的序列和反序列化。
 Protobuf是google制定的一种对象序列化格式,开源地址:ProtoBuf:http://code.google.com/p/protobuf/
googleProtobuf的官方开源实现由Java。c++和Python。而在.net下的实现有protobuf-net.(官方站点:http://code.google.com/p/protobuf-net/)而protobuf-net在序列化方面有着出色的性能,效率是.net二进制序列化几倍,而序列化后所占的空间也少于.net二进制序列化;除了以上两个优势外Protobuf有着一个更大的优势就是和其他平台交互的兼容性,在现有大部分流行的语言平台中基本都有Protobuf的实现.因此采用protobuf进行对象序列化是个不错的选择.

使用protobuf协议

定义protobuf协议必须创建一个以.proto为后缀的文件,下面是一个proto脚本的简单例子:

message MyData {
    //个人简介
    optional string resume = 1[default="I'm goodman"];
}
message MyRequest {
    //版本号
    required int32 version = 1;
    //姓名
    required string name = 2;
    //个人网站
    optional string website = 3[default="http://www.apache.org/"];
    //附加数据
    optional bytes data = 4;
}
message MyResponse {
    //版本号
    required int32 version = 1;
    //响应结果
    required int32 result = 2;
}

requied是必须有的字段、optional是可有可无的字段、repeated是可以重复的字段(数组或列表),同时枚举字段都必须给出默认值。
 接下来就可以使用ProgoGen来根据proto脚本生成源代码cs文件了,命令行如下:

 protogen -i:ProtoMyData.proto -o:ProtoMyData.cs -ns:MyProtoBuf
 protogen -i:ProtoMyRequest.proto -o:ProtoMyRequest.cs -ns:MyProtoBuf
 protogen -i:ProtoMyResponse.proto -o:ProtoMyResponse.cs -ns:MyProtoBuf

-i指定了输入,-o指定了输出,-ns指定了生成代码的namespace,上面的proto脚本生成的源码如下:

//------------------------------------------------------------------------------
// <auto-generated>
//     This code was generated by a tool.
//
//     Changes to this file may cause incorrect behavior and will be lost if
//     the code is regenerated.
// </auto-generated>
//------------------------------------------------------------------------------

// Generated from: ProtoMyData.proto
namespace MyProtoBuf
{
  [global::System.Serializable, global::ProtoBuf.ProtoContract(Name=@"MyData")]
  public partial class MyData : global::ProtoBuf.IExtensible
  {
    public MyData() {}
    

    private string _resume = @"I'm goodman";
    [global::ProtoBuf.ProtoMember(1, IsRequired = false, Name=@"resume", DataFormat = global::ProtoBuf.DataFormat.Default)]
    [global::System.ComponentModel.DefaultValue(@"I'm goodman")]
    public string resume
    {
      get { return _resume; }
      set { _resume = value; }
    }
    private global::ProtoBuf.IExtension extensionObject;
    global::ProtoBuf.IExtension global::ProtoBuf.IExtensible.GetExtensionObject(bool createIfMissing)
      { return global::ProtoBuf.Extensible.GetExtensionObject(ref extensionObject, createIfMissing); }
  }
  
}
//------------------------------------------------------------------------------
// <auto-generated>
//     This code was generated by a tool.
//
//     Changes to this file may cause incorrect behavior and will be lost if
//     the code is regenerated.
// </auto-generated>
//------------------------------------------------------------------------------

// Generated from: ProtoMyRequest.proto
namespace MyProtoBuf
{
  [global::System.Serializable, global::ProtoBuf.ProtoContract(Name=@"MyRequest")]
  public partial class MyRequest : global::ProtoBuf.IExtensible
  {
    public MyRequest() {}
    
    private int _version;
    [global::ProtoBuf.ProtoMember(1, IsRequired = true, Name=@"version", DataFormat = global::ProtoBuf.DataFormat.TwosComplement)]
    public int version
    {
      get { return _version; }
      set { _version = value; }
    }
    private string _name;
    [global::ProtoBuf.ProtoMember(2, IsRequired = true, Name=@"name", DataFormat = global::ProtoBuf.DataFormat.Default)]
    public string name
    {
      get { return _name; }
      set { _name = value; }
    }

    private string _website = @"http://www.apache.org/";
    [global::ProtoBuf.ProtoMember(3, IsRequired = false, Name=@"website", DataFormat = global::ProtoBuf.DataFormat.Default)]
    [global::System.ComponentModel.DefaultValue(@"http://www.apache.org/")]
    public string website
    {
      get { return _website; }
      set { _website = value; }
    }

    private byte[] _data = null;
    [global::ProtoBuf.ProtoMember(4, IsRequired = false, Name=@"data", DataFormat = global::ProtoBuf.DataFormat.Default)]
    [global::System.ComponentModel.DefaultValue(null)]
    public byte[] data
    {
      get { return _data; }
      set { _data = value; }
    }
    private global::ProtoBuf.IExtension extensionObject;
    global::ProtoBuf.IExtension global::ProtoBuf.IExtensible.GetExtensionObject(bool createIfMissing)
      { return global::ProtoBuf.Extensible.GetExtensionObject(ref extensionObject, createIfMissing); }
  }
  
}
//------------------------------------------------------------------------------
// <auto-generated>
//     This code was generated by a tool.
//
//     Changes to this file may cause incorrect behavior and will be lost if
//     the code is regenerated.
// </auto-generated>
//------------------------------------------------------------------------------

// Generated from: ProtoMyResponse.proto
namespace MyProtoBuf
{
  [global::System.Serializable, global::ProtoBuf.ProtoContract(Name=@"MyResponse")]
  public partial class MyResponse : global::ProtoBuf.IExtensible
  {
    public MyResponse() {}
    
    private int _version;
    [global::ProtoBuf.ProtoMember(1, IsRequired = true, Name=@"version", DataFormat = global::ProtoBuf.DataFormat.TwosComplement)]
    public int version
    {
      get { return _version; }
      set { _version = value; }
    }
    private int _result;
    [global::ProtoBuf.ProtoMember(2, IsRequired = true, Name=@"result", DataFormat = global::ProtoBuf.DataFormat.TwosComplement)]
    public int result
    {
      get { return _result; }
      set { _result = value; }
    }
    private global::ProtoBuf.IExtension extensionObject;
    global::ProtoBuf.IExtension global::ProtoBuf.IExtensible.GetExtensionObject(bool createIfMissing)
      { return global::ProtoBuf.Extensible.GetExtensionObject(ref extensionObject, createIfMissing); }
  }
  
}

代码示例

接着将生成的3个.cs文件包含在项目中,代码示例(服务端与客户端)

using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Net;
using System.Net.Sockets;
using System.Runtime.Serialization;
using System.Runtime.Serialization.Formatters.Binary;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using MyProtoBuf;
using ProtoBuf;

namespace Tdf.ProtoBufDemo
{
    class Program
    {
        private static readonly ManualResetEvent AllDone = new ManualResetEvent(false);
        static void Main(string[] args)
        {
            // ProtoBufTest.TestMethod2();
            // ProtoBufTest.TestMethod3();
            // TestProtoBuf.TestMethod4();

            BeginDemo();

            Console.ReadLine();
        }

        private static void BeginDemo()
        {
            // 启动服务端
            var server = new TcpListener(IPAddress.Parse("127.0.0.1"), 9527);
            server.Start();
            server.BeginAcceptTcpClient(ClientConnected, server);
            Console.WriteLine("SERVER : 等待数据 ---");

            // 启动客户端
            ThreadPool.QueueUserWorkItem(RunClient);
            AllDone.WaitOne();

            Console.WriteLine("SERVER : 退出 ---");
            server.Stop();
        }

        // 服务端处理
        private static void ClientConnected(IAsyncResult result)
        {
            try
            {
                var server = (TcpListener)result.AsyncState;
                using (var client = server.EndAcceptTcpClient(result))
                using (var stream = client.GetStream())
                {
                    // 获取
                    Console.WriteLine("SERVER : 客户端已连接,读取数据 ---");

                    /*
                     * 服务端接收对象;
                     * 从代码中可以发现protobuf-net已考虑的非常周到,不论是客户端发送对象还是服务端接收对象,均只需一行代码就可实现:
                     * 
                     * proto-buf 使用 Base128 Varints 编码;
                     */
                    var myRequest = Serializer.DeserializeWithLengthPrefix<MyRequest>(stream, PrefixStyle.Base128);

                    // 使用C# BinaryFormatter
                    IFormatter formatter = new BinaryFormatter();
                    var myData = (MyData)formatter.Deserialize(new MemoryStream(myRequest.data));

                    Console.WriteLine($@"SERVER : 获取成功, myRequest.version={myRequest.version}, myRequest.name={myRequest.name}, myRequest.website={myRequest.website}, myData.resume={myData.resume}");

                    // 响应(MyResponse)
                    var myResponse = new MyResponse
                    {
                        version = myRequest.version,
                        result = 99
                    };
                    Serializer.SerializeWithLengthPrefix(stream, myResponse, PrefixStyle.Base128);
                    Console.WriteLine("SERVER : 响应成功 ---");

                    Console.WriteLine("SERVER: 关闭连接 ---");
                    stream.Close();
                    client.Close();
                }
            }
            finally
            {
                AllDone.Set();
            }
        }

        // 客户端请求
        private static void RunClient(object state)
        {
            try
            {
                // 构造MyData
                var myData = new MyData {resume = "我的个人简介"};

                // 构造MyRequest
                var myRequest = new MyRequest
                {
                    version = 1,
                    name = "Bobby",
                    website = "www.apache.org"
                };

                // 使用C# BinaryFormatter
                using (var ms = new MemoryStream())
                {
                    IFormatter formatter = new BinaryFormatter();
                    formatter.Serialize(ms, myData);
                    myRequest.data = ms.GetBuffer();
                    ms.Close();
                }
                Console.WriteLine("CLIENT : 对象构造完毕 ...");

                using (var client = new TcpClient())
                {
                    client.Connect(new IPEndPoint(IPAddress.Parse("127.0.0.1"), 9527));
                    Console.WriteLine("CLIENT : socket 连接成功 ...");

                    using (var stream = client.GetStream())
                    {
                        // 发送,客户端发送对象;
                        Console.WriteLine("CLIENT : 发送数据 ...");
                        ProtoBuf.Serializer.SerializeWithLengthPrefix(stream, myRequest, PrefixStyle.Base128);

                        // 接收
                        Console.WriteLine("CLIENT : 等待响应 ...");
                        var myResponse = ProtoBuf.Serializer.DeserializeWithLengthPrefix<MyResponse>(stream, PrefixStyle.Base128);

                        Console.WriteLine($"CLIENT : 成功获取结果, version={myResponse.version}, result={myResponse.result}");

                        // 关闭
                        stream.Close();
                    }
                    client.Close();
                    Console.WriteLine("CLIENT : 关闭 ...");
                }
            }
            catch (Exception error)
            {
                Console.WriteLine($@"CLIENT ERROR : {error.ToString()}");
            }
        }
    }
}

运行结果:

Tdf.ProtoBuf.png

相关文章

网友评论

    本文标题:跨平台数据通信的选择:Google ProtoBuf

    本文链接:https://www.haomeiwen.com/subject/zjlvrxtx.html