对于Socket应用来说,如何序列化和反序列化消息一直是比较头痛的问题,C#提供了自动序列化的功能(类似AS3中的AMF),但是唯一的缺点就是前后端都必须是C#实现,如果前后端语言不一致该怎么办?
Google的Protobuf很好的解决了这个问题,支持类似C++、Java等主流语言,但是官方版本未提供C#语言的实现,但是不用担心,有很多开发者已经帮助我们实现了C#的Protobuf,其中应用得最多的是Protobuf-net,下载地址是:http://code.google.com/p/protobuf-net/
当然如果404了也不用担心,我提供了一个百度网盘的下载:http://pan.baidu.com/s/1o6qTFEa
解压后即可使用,下面我们创建两个协议示例文件来展示一下Protobuf的用法,其中一个文件用到了另一个文件定义的消息体:
test1.proto:
1 //这里定义基础的消息体或枚举 2 3 package Test.Base; 4 5 //定义枚举 6 7 enum Sex 8 { 9 MALE = 0; 10 FEMALE = 1; 11 } 12 13 //定义消息体 14 15 message People 16 { 17 required string name = 1; 18 required Sex sex = 2; 19 optional int32 age = 3; 20 } 21 22 message Hero 23 { 24 required People people = 1; 25 optional string skill = 2; 26 }
test2.proto:
1 //有用到 test1 的东西需要导入 test1 2 3 import "test1.proto"; 4 5 //这里定义直接使用的消息体 6 7 package Test.App; 8 9 //定义消息体 10 11 message SuperHero 12 { 13 required Test.Base.Hero hero = 1; 14 required string superSkill = 2; 15 }
那么该如何生成可以使用的代码呢?
一般通过命令行进行生成,但是如果每次修改了协议都敲一通肯定会累死人的,下面在test1.proto和test2.proto的同一目录下新建一个名为gen.cmd的文件,内容如下:
1 D:project oolprotobuf-netProtoGenprotogen.exe -i:test1.proto -i:test2.proto -o:test.cs 2 pause
当前protogen.exe的路径以你自己的为准,双击会在本目录下生成test.cs文件。
生成的test.cs内容如下:
1 //------------------------------------------------------------------------------ 2 // <auto-generated> 3 // This code was generated by a tool. 4 // 5 // Changes to this file may cause incorrect behavior and will be lost if 6 // the code is regenerated. 7 // </auto-generated> 8 //------------------------------------------------------------------------------ 9 10 // Generated from: test1.proto 11 namespace Test.Base 12 { 13 [global::System.Serializable, global::ProtoBuf.ProtoContract(Name=@"People")] 14 public partial class People : global::ProtoBuf.IExtensible 15 { 16 public People() {} 17 18 private string _name; 19 [global::ProtoBuf.ProtoMember(1, IsRequired = true, Name=@"name", DataFormat = global::ProtoBuf.DataFormat.Default)] 20 public string name 21 { 22 get { return _name; } 23 set { _name = value; } 24 } 25 private Test.Base.Sex _sex; 26 [global::ProtoBuf.ProtoMember(2, IsRequired = true, Name=@"sex", DataFormat = global::ProtoBuf.DataFormat.TwosComplement)] 27 public Test.Base.Sex sex 28 { 29 get { return _sex; } 30 set { _sex = value; } 31 } 32 private int _age = default(int); 33 [global::ProtoBuf.ProtoMember(3, IsRequired = false, Name=@"age", DataFormat = global::ProtoBuf.DataFormat.TwosComplement)] 34 [global::System.ComponentModel.DefaultValue(default(int))] 35 public int age 36 { 37 get { return _age; } 38 set { _age = value; } 39 } 40 private global::ProtoBuf.IExtension extensionObject; 41 global::ProtoBuf.IExtension global::ProtoBuf.IExtensible.GetExtensionObject(bool createIfMissing) 42 { return global::ProtoBuf.Extensible.GetExtensionObject(ref extensionObject, createIfMissing); } 43 } 44 45 [global::System.Serializable, global::ProtoBuf.ProtoContract(Name=@"Hero")] 46 public partial class Hero : global::ProtoBuf.IExtensible 47 { 48 public Hero() {} 49 50 private Test.Base.People _people; 51 [global::ProtoBuf.ProtoMember(1, IsRequired = true, Name=@"people", DataFormat = global::ProtoBuf.DataFormat.Default)] 52 public Test.Base.People people 53 { 54 get { return _people; } 55 set { _people = value; } 56 } 57 private string _skill = ""; 58 [global::ProtoBuf.ProtoMember(2, IsRequired = false, Name=@"skill", DataFormat = global::ProtoBuf.DataFormat.Default)] 59 [global::System.ComponentModel.DefaultValue("")] 60 public string skill 61 { 62 get { return _skill; } 63 set { _skill = value; } 64 } 65 private global::ProtoBuf.IExtension extensionObject; 66 global::ProtoBuf.IExtension global::ProtoBuf.IExtensible.GetExtensionObject(bool createIfMissing) 67 { return global::ProtoBuf.Extensible.GetExtensionObject(ref extensionObject, createIfMissing); } 68 } 69 70 [global::ProtoBuf.ProtoContract(Name=@"Sex")] 71 public enum Sex 72 { 73 74 [global::ProtoBuf.ProtoEnum(Name=@"MALE", Value=0)] 75 MALE = 0, 76 77 [global::ProtoBuf.ProtoEnum(Name=@"FEMALE", Value=1)] 78 FEMALE = 1 79 } 80 81 } 82 // Generated from: test2.proto 83 // Note: requires additional types generated from: test1.proto 84 namespace Test.App 85 { 86 [global::System.Serializable, global::ProtoBuf.ProtoContract(Name=@"SuperHero")] 87 public partial class SuperHero : global::ProtoBuf.IExtensible 88 { 89 public SuperHero() {} 90 91 private Test.Base.Hero _hero; 92 [global::ProtoBuf.ProtoMember(1, IsRequired = true, Name=@"hero", DataFormat = global::ProtoBuf.DataFormat.Default)] 93 public Test.Base.Hero hero 94 { 95 get { return _hero; } 96 set { _hero = value; } 97 } 98 private string _superSkill; 99 [global::ProtoBuf.ProtoMember(2, IsRequired = true, Name=@"superSkill", DataFormat = global::ProtoBuf.DataFormat.Default)] 100 public string superSkill 101 { 102 get { return _superSkill; } 103 set { _superSkill = value; } 104 } 105 private global::ProtoBuf.IExtension extensionObject; 106 global::ProtoBuf.IExtension global::ProtoBuf.IExtensible.GetExtensionObject(bool createIfMissing) 107 { return global::ProtoBuf.Extensible.GetExtensionObject(ref extensionObject, createIfMissing); } 108 } 109 110 }
当前如果要生成两个文件而非一个时可以这么写:
1 D:project oolprotobuf-netProtoGenprotogen.exe -i:test1.proto -o:test1.cs 2 D:project oolprotobuf-netProtoGenprotogen.exe -i:test2.proto -o:test2.cs 3 pause
生成的test1.cs:
1 //------------------------------------------------------------------------------ 2 // <auto-generated> 3 // This code was generated by a tool. 4 // 5 // Changes to this file may cause incorrect behavior and will be lost if 6 // the code is regenerated. 7 // </auto-generated> 8 //------------------------------------------------------------------------------ 9 10 // Generated from: test1.proto 11 namespace Test.Base 12 { 13 [global::System.Serializable, global::ProtoBuf.ProtoContract(Name=@"People")] 14 public partial class People : global::ProtoBuf.IExtensible 15 { 16 public People() {} 17 18 private string _name; 19 [global::ProtoBuf.ProtoMember(1, IsRequired = true, Name=@"name", DataFormat = global::ProtoBuf.DataFormat.Default)] 20 public string name 21 { 22 get { return _name; } 23 set { _name = value; } 24 } 25 private Test.Base.Sex _sex; 26 [global::ProtoBuf.ProtoMember(2, IsRequired = true, Name=@"sex", DataFormat = global::ProtoBuf.DataFormat.TwosComplement)] 27 public Test.Base.Sex sex 28 { 29 get { return _sex; } 30 set { _sex = value; } 31 } 32 private int _age = default(int); 33 [global::ProtoBuf.ProtoMember(3, IsRequired = false, Name=@"age", DataFormat = global::ProtoBuf.DataFormat.TwosComplement)] 34 [global::System.ComponentModel.DefaultValue(default(int))] 35 public int age 36 { 37 get { return _age; } 38 set { _age = value; } 39 } 40 private global::ProtoBuf.IExtension extensionObject; 41 global::ProtoBuf.IExtension global::ProtoBuf.IExtensible.GetExtensionObject(bool createIfMissing) 42 { return global::ProtoBuf.Extensible.GetExtensionObject(ref extensionObject, createIfMissing); } 43 } 44 45 [global::System.Serializable, global::ProtoBuf.ProtoContract(Name=@"Hero")] 46 public partial class Hero : global::ProtoBuf.IExtensible 47 { 48 public Hero() {} 49 50 private Test.Base.People _people; 51 [global::ProtoBuf.ProtoMember(1, IsRequired = true, Name=@"people", DataFormat = global::ProtoBuf.DataFormat.Default)] 52 public Test.Base.People people 53 { 54 get { return _people; } 55 set { _people = value; } 56 } 57 private string _skill = ""; 58 [global::ProtoBuf.ProtoMember(2, IsRequired = false, Name=@"skill", DataFormat = global::ProtoBuf.DataFormat.Default)] 59 [global::System.ComponentModel.DefaultValue("")] 60 public string skill 61 { 62 get { return _skill; } 63 set { _skill = value; } 64 } 65 private global::ProtoBuf.IExtension extensionObject; 66 global::ProtoBuf.IExtension global::ProtoBuf.IExtensible.GetExtensionObject(bool createIfMissing) 67 { return global::ProtoBuf.Extensible.GetExtensionObject(ref extensionObject, createIfMissing); } 68 } 69 70 [global::ProtoBuf.ProtoContract(Name=@"Sex")] 71 public enum Sex 72 { 73 74 [global::ProtoBuf.ProtoEnum(Name=@"MALE", Value=0)] 75 MALE = 0, 76 77 [global::ProtoBuf.ProtoEnum(Name=@"FEMALE", Value=1)] 78 FEMALE = 1 79 } 80 81 }
生成的test2.cs:
1 //------------------------------------------------------------------------------ 2 // <auto-generated> 3 // This code was generated by a tool. 4 // 5 // Changes to this file may cause incorrect behavior and will be lost if 6 // the code is regenerated. 7 // </auto-generated> 8 //------------------------------------------------------------------------------ 9 10 // Generated from: test2.proto 11 // Note: requires additional types generated from: test1.proto 12 namespace Test.App 13 { 14 [global::System.Serializable, global::ProtoBuf.ProtoContract(Name=@"SuperHero")] 15 public partial class SuperHero : global::ProtoBuf.IExtensible 16 { 17 public SuperHero() {} 18 19 private Test.Base.Hero _hero; 20 [global::ProtoBuf.ProtoMember(1, IsRequired = true, Name=@"hero", DataFormat = global::ProtoBuf.DataFormat.Default)] 21 public Test.Base.Hero hero 22 { 23 get { return _hero; } 24 set { _hero = value; } 25 } 26 private string _superSkill; 27 [global::ProtoBuf.ProtoMember(2, IsRequired = true, Name=@"superSkill", DataFormat = global::ProtoBuf.DataFormat.Default)] 28 public string superSkill 29 { 30 get { return _superSkill; } 31 set { _superSkill = value; } 32 } 33 private global::ProtoBuf.IExtension extensionObject; 34 global::ProtoBuf.IExtension global::ProtoBuf.IExtensible.GetExtensionObject(bool createIfMissing) 35 { return global::ProtoBuf.Extensible.GetExtensionObject(ref extensionObject, createIfMissing); } 36 } 37 38 }
下面我们看看工程中该如何使用生成的代码文件,先在VS中添加protobuf-net.dll的引用,文件在解压后的目录Full中,然后将生成的cs文件也包含到vs中。
下面是将对象序列化为二进制文件和从二进制文件中反序列化出对象的代码:
1 using ProtoBuf; 2 using System; 3 using System.Collections.Generic; 4 using System.IO; 5 using System.Linq; 6 using System.Text; 7 using System.Threading.Tasks; 8 using Test.App; 9 using Test.Base; 10 11 namespace ProtobufTest 12 { 13 class Program 14 { 15 static void Main(string[] args) 16 { 17 ObjectToFile(); 18 FileToObject(); 19 Console.ReadKey(); 20 } 21 22 private static void ObjectToFile() 23 { 24 //创建对象 25 SuperHero sh = new SuperHero(); 26 sh.hero = new Hero(); 27 sh.superSkill = "天狼巽闪"; 28 29 sh.hero.people = new People(); 30 sh.hero.skill = "疾鹰七痕剑"; 31 32 sh.hero.people.name = "赛特"; 33 sh.hero.people.sex = Sex.MALE; 34 sh.hero.people.age = 30; 35 36 //序列化 37 using(MemoryStream stream = new MemoryStream()) 38 { 39 //获取二进制数据 40 Serializer.Serialize<SuperHero>(stream, sh); 41 byte[] bytes = stream.ToArray(); 42 stream.Close(); 43 44 //写入数据 45 using(FileStream fs = File.Open("D:\test.bytes", FileMode.OpenOrCreate)) 46 { 47 fs.Write(bytes, 0, bytes.Length); 48 fs.Flush(); 49 fs.Close(); 50 } 51 } 52 53 Console.WriteLine("文件已经生成!"); 54 } 55 56 private static void FileToObject() 57 { 58 //读取数据 59 using(FileStream fs = File.Open("D:\test.bytes", FileMode.Open)) 60 { 61 byte[] bytes = new byte[fs.Length]; 62 fs.Read(bytes, 0, (int)fs.Length); 63 fs.Close(); 64 65 //反序列化 66 using(MemoryStream stream = new MemoryStream(bytes)) 67 { 68 SuperHero sh = Serializer.Deserialize<SuperHero>(stream); 69 stream.Close(); 70 71 Console.WriteLine("姓名:{0},性别:{1},年龄:{2},绝技:{3},终极绝技:{4}", sh.hero.people.name, sh.hero.people.sex, sh.hero.people.age, sh.hero.skill, sh.superSkill); 72 } 73 } 74 } 75 } 76 }
程序输出:
1 文件已经生成! 2 姓名:赛特,性别:MALE,年龄:30,绝技:疾鹰七痕剑,终极绝技:天狼巽闪