跨平臺數(shù)據(jù)通信的選擇:Google ProtoBuf

跨平臺數(shù)據(jù)通信的選擇:Google ProtoBuf

簡稱protobuf,google開源項目谭确,是一種數(shù)據(jù)交換的格式帘营,google 提供了多種語言的實現(xiàn):php、JavaScript逐哈、java芬迄、c#、c++昂秃、go 和 python等禀梳。 由于它是一種二進制的格式,比使用 xml, json 進行數(shù)據(jù)交換快許多肠骆。以上描述太官方不好理解算途,通俗點來解釋一下,就是通過protobuf定義好數(shù)據(jù)結(jié)構(gòu)生成一個工具類蚀腿,這個工具類可以把數(shù)據(jù)封裝成二進制數(shù)據(jù)來進行傳輸嘴瓤,在另一端收到二進制數(shù)據(jù)再用工具類解析成正常的數(shù)據(jù)。

protobuf是google的一個開源項目莉钙,可用于以下兩種用途:

  • 數(shù)據(jù)的存儲(序列化和反序列化)廓脆,類似于xml、json等磁玉;
  • 制作網(wǎng)絡(luò)通信協(xié)議停忿。

正宗(Google 自己內(nèi)部用的)的protobuf支持三種語言:Java 、c++和Pyton蜀涨,很遺憾的是并不支持.Net 或者 Lua 等語言瞎嬉,但社區(qū)的力量是不容忽視的蝎毡,由于protobuf確實比Json、XML有速度上的優(yōu)勢和使用的方便氧枣,并且可以做到向前兼容沐兵、向后兼容等眾多特點,所以protobuf社區(qū)又弄了個protobuf.net的組件并且還支持眾多語言便监。

.net 版的protobuf來源于proto社區(qū)扎谎,有兩個版本。一個版本叫protobuf-net烧董,官方站點:http://code.google.com/p/protobuf-net/ 寫法上比較符合c#一貫的寫法毁靶。另一個版本叫protobuf-csharp-sport ,
官方站點:http://code.google.com/p/protobuf-csharp-port/ 寫法上跟java上的使用極其相似逊移,比較遵循Google 的原生態(tài)寫法预吆,所以做跨平臺還是選擇第二版本吧。因為你會發(fā)現(xiàn)幾乎和java的寫法沒啥兩樣胳泉。

ProtoBuf的原理

Socket通信中拐叉,客戶端與服務(wù)器之間傳遞的是字節(jié)流。而在現(xiàn)實的應(yīng)用中我們需要傳遞有一定含義的結(jié)構(gòu)扇商,使得通信的雙方都能夠識別該結(jié)構(gòu)凤瘦。實現(xiàn)對象(Class和Struct)Socket傳輸?shù)年P(guān)鍵就在于Class或Struct的序列和反序列化。
 Protobuf是google制定的一種對象序列化格式,開源地址:ProtoBuf:http://code.google.com/p/protobuf/
googleProtobuf的官方開源實現(xiàn)由Java案铺。c++和Python蔬芥。而在.net下的實現(xiàn)有protobuf-net.(官方站點:http://code.google.com/p/protobuf-net/)而protobuf-net在序列化方面有著出色的性能,效率是.net二進制序列化幾倍,而序列化后所占的空間也少于.net二進制序列化;除了以上兩個優(yōu)勢外Protobuf有著一個更大的優(yōu)勢就是和其他平臺交互的兼容性,在現(xiàn)有大部分流行的語言平臺中基本都有Protobuf的實現(xiàn).因此采用protobuf進行對象序列化是個不錯的選擇.

使用protobuf協(xié)議

定義protobuf協(xié)議必須創(chuàng)建一個以.proto為后綴的文件,下面是一個proto腳本的簡單例子:

message MyData {
    //個人簡介
    optional string resume = 1[default="I'm goodman"];
}
message MyRequest {
    //版本號
    required int32 version = 1;
    //姓名
    required string name = 2;
    //個人網(wǎng)站
    optional string website = 3[default="http://www.apache.org/"];
    //附加數(shù)據(jù)
    optional bytes data = 4;
}
message MyResponse {
    //版本號
    required int32 version = 1;
    //響應(yīng)結(jié)果
    required int32 result = 2;
}

requied是必須有的字段控汉、optional是可有可無的字段笔诵、repeated是可以重復(fù)的字段(數(shù)組或列表),同時枚舉字段都必須給出默認值暇番。
 接下來就可以使用ProgoGen來根據(jù)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文件包含在項目中恨课,代碼示例(服務(wù)端與客戶端)

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()
        {
            // 啟動服務(wù)端
            var server = new TcpListener(IPAddress.Parse("127.0.0.1"), 9527);
            server.Start();
            server.BeginAcceptTcpClient(ClientConnected, server);
            Console.WriteLine("SERVER : 等待數(shù)據(jù) ---");

            // 啟動客戶端
            ThreadPool.QueueUserWorkItem(RunClient);
            AllDone.WaitOne();

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

        // 服務(wù)端處理
        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 : 客戶端已連接舆乔,讀取數(shù)據(jù) ---");

                    /*
                     * 服務(wù)端接收對象;
                     * 從代碼中可以發(fā)現(xiàn)protobuf-net已考慮的非常周到剂公,不論是客戶端發(fā)送對象還是服務(wù)端接收對象希俩,均只需一行代碼就可實現(xiàn):
                     * 
                     * 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}");

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

                    Console.WriteLine("SERVER: 關(guān)閉連接 ---");
                    stream.Close();
                    client.Close();
                }
            }
            finally
            {
                AllDone.Set();
            }
        }

        // 客戶端請求
        private static void RunClient(object state)
        {
            try
            {
                // 構(gòu)造MyData
                var myData = new MyData {resume = "我的個人簡介"};

                // 構(gòu)造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 : 對象構(gòu)造完畢 ...");

                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())
                    {
                        // 發(fā)送纲辽,客戶端發(fā)送對象颜武;
                        Console.WriteLine("CLIENT : 發(fā)送數(shù)據(jù) ...");
                        ProtoBuf.Serializer.SerializeWithLengthPrefix(stream, myRequest, PrefixStyle.Base128);

                        // 接收
                        Console.WriteLine("CLIENT : 等待響應(yīng) ...");
                        var myResponse = ProtoBuf.Serializer.DeserializeWithLengthPrefix<MyResponse>(stream, PrefixStyle.Base128);

                        Console.WriteLine($"CLIENT : 成功獲取結(jié)果, version={myResponse.version}, result={myResponse.result}");

                        // 關(guān)閉
                        stream.Close();
                    }
                    client.Close();
                    Console.WriteLine("CLIENT : 關(guān)閉 ...");
                }
            }
            catch (Exception error)
            {
                Console.WriteLine($@"CLIENT ERROR : {error.ToString()}");
            }
        }
    }
}

運行結(jié)果:

Tdf.ProtoBuf.png
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末璃搜,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子鳞上,更是在濱河造成了極大的恐慌这吻,老刑警劉巖,帶你破解...
    沈念sama閱讀 211,348評論 6 491
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件篙议,死亡現(xiàn)場離奇詭異唾糯,居然都是意外死亡,警方通過查閱死者的電腦和手機鬼贱,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,122評論 2 385
  • 文/潘曉璐 我一進店門移怯,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人这难,你說我怎么就攤上這事舟误。” “怎么了姻乓?”我有些...
    開封第一講書人閱讀 156,936評論 0 347
  • 文/不壞的土叔 我叫張陵嵌溢,是天一觀的道長。 經(jīng)常有香客問我糖权,道長堵腹,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 56,427評論 1 283
  • 正文 為了忘掉前任星澳,我火速辦了婚禮疚顷,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘禁偎。我一直安慰自己腿堤,他們只是感情好,可當我...
    茶點故事閱讀 65,467評論 6 385
  • 文/花漫 我一把揭開白布如暖。 她就那樣靜靜地躺著笆檀,像睡著了一般。 火紅的嫁衣襯著肌膚如雪盒至。 梳的紋絲不亂的頭發(fā)上酗洒,一...
    開封第一講書人閱讀 49,785評論 1 290
  • 那天,我揣著相機與錄音枷遂,去河邊找鬼樱衷。 笑死,一個胖子當著我的面吹牛酒唉,可吹牛的內(nèi)容都是我干的矩桂。 我是一名探鬼主播,決...
    沈念sama閱讀 38,931評論 3 406
  • 文/蒼蘭香墨 我猛地睜開眼痪伦,長吁一口氣:“原來是場噩夢啊……” “哼侄榴!你這毒婦竟也來了雹锣?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 37,696評論 0 266
  • 序言:老撾萬榮一對情侶失蹤癞蚕,失蹤者是張志新(化名)和其女友劉穎蕊爵,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體涣达,經(jīng)...
    沈念sama閱讀 44,141評論 1 303
  • 正文 獨居荒郊野嶺守林人離奇死亡在辆,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,483評論 2 327
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了度苔。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片匆篓。...
    茶點故事閱讀 38,625評論 1 340
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖寇窑,靈堂內(nèi)的尸體忽然破棺而出鸦概,到底是詐尸還是另有隱情,我是刑警寧澤甩骏,帶...
    沈念sama閱讀 34,291評論 4 329
  • 正文 年R本政府宣布窗市,位于F島的核電站,受9級特大地震影響饮笛,放射性物質(zhì)發(fā)生泄漏咨察。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 39,892評論 3 312
  • 文/蒙蒙 一福青、第九天 我趴在偏房一處隱蔽的房頂上張望摄狱。 院中可真熱鬧,春花似錦无午、人聲如沸媒役。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,741評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽酣衷。三九已至,卻和暖如春次泽,著一層夾襖步出監(jiān)牢的瞬間穿仪,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,977評論 1 265
  • 我被黑心中介騙來泰國打工意荤, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留牡借,地道東北人。 一個月前我還...
    沈念sama閱讀 46,324評論 2 360
  • 正文 我出身青樓袭异,卻偏偏與公主長得像,于是被迫代替她去往敵國和親炬藤。 傳聞我的和親對象是個殘疾皇子御铃,可洞房花燭夜當晚...
    茶點故事閱讀 43,492評論 2 348

推薦閱讀更多精彩內(nèi)容