Google.ProtocolBuffers.dll 之.Net应用(一)
原创文章,转载必需注明出处:http://www.cnblogs.com/wu-jian/
http://www.cnblogs.com/wu-jian/archive/2011/02/22/1961104.html
核心提示:Google Protocol Buffers是google出品的一个协议生成工具,特点就是跨平台,Google Protocol Buffers 快速入门(带生成C#源码的方法),效率高,速度快,只有 AddressBookProtos.cs有用,将这个文件连同Google.ProtocolBuffers.dll一起.
前言
最近接到一个跨平台的测试项目,服务端Linux,是Java开发的一系列Socket接口,客户端Windows,所以准备用.Net。本想这种跨主流平台的Socket通信应该不成问题,但随着代码进程,随着一次次反复调试,我发现我错了。花了一周时间至今两者仍呈现北方网通和南方电信的姿态。
不过总有意外惊喜,过程中认识了Protocol Buffer,比XML、比JSON更为强悍,语言无关、平台无关、更小的存储、更少的歧义、更高的性能,其实Google一直在贡献,不论是Copy Left的还是Copy Right的,回头看看我们的百度,抄IM抄商城抄游戏抄视频抄房地产,还有搜索永远排第一却打不开的百度文库,印象中JQuery盛行N久之后百度开源了一个JS库,记忆里这也是百度为中国互联网技术做的唯一贡献,大公司的责任呐,好了,再说就偏离主题了。
Protocal Buffer官方站点:http://code.google.com/p/protobuf/,遗憾的是不支持.Net,但社区的力量不容忽视,MySQL最近还推出社区版呢,从这个链接可以看到Protobuf的社区阵营:http://code.google.com/p/protobuf/wiki/ThirdPartyAddOns
OK,本文主要描述自己在.Net中基于应用层面使用Protobuf的一些体会,作为学习笔记与大家分享,个人能力有限,不足之处还请及时指正。
需求
Java为服务端,.Net为客户端,Socket通信,使用Protobuf进行数据封装和传输,如下图:
DEMO中构造了3个简单的.proto文件供各客户端使用:
message MyRequest {
//版本号
required int32 version =1;
//姓名
required string name =2;
//个人网站
optional string website =3[default="http://www.paotiao.com/"];
//附加数据
optional bytes data =4;
}
message MyResponse {
//版本号
required int32 version =1;
//响应结果
required int32 result =2;
}
message MyData {
//个人简介
optional string resume =1[default="I'm goodman"];
}
其中MyRequest为客户端的请求,MyResponse为服务端的响应,MyData作为一个属性附加在MyRequest的data字段中,提醒注意这个byte类型的data字段,为此花费了最多时间并最终导至放弃Protobuf-net来做跨平台的应用。
Protobuf-net
官方站点:http://code.google.com/p/protobuf-net/
Protobuf-net是第三方中最强大应用最广泛的一个,支持.Net、C#、WCF、VB,并且DEMO丰富,网上可查到的资料也最多。
生成.CS类文件
安装后通过 protogen.exe 就可将.proto文件生成.cs文件(Demo中我将命令封装在/tools/getCS.bat中):
echo on
protogen -i:ProtoMyRequest.proto -o:ProtoMyRequest.cs
protogen -i:ProtoMyResponse.proto -o:ProtoMyResponse.cs
protogen -i:ProtoMyData.proto -o:ProtoMyData.cs
接着将生成的3个.cs文件包含在项目中,同时在项目中引用protobuf-net.dll
代码示例(服务端与客户端)
using System; using System.IO; using System.Text; using System.Threading; using System.Net; using System.Net.Sockets; using System.Runtime.Serialization; using System.Runtime.Serialization.Formatters.Binary; using ProtoBuf; //自定义 using ProtoMyData; using ProtoMyRequest; using ProtoMyResponse; namespace protobuf_net { class Program { private static ManualResetEvent allDone = new ManualResetEvent( false ); static void Main( string [] args) { beginDemo(); } private static void beginDemo() { //启动服务端 TcpListener 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 { TcpListener server = (TcpListener)result.AsyncState; using (TcpClient client = server.EndAcceptTcpClient(result)) using (NetworkStream stream = client.GetStream()) { //获取 Console.WriteLine( "SERVER : 客户端已连接,读取数据 ---" ); //proto-buf 使用 Base128 Varints 编码 MyRequest myRequest = Serializer.DeserializeWithLengthPrefix<MyRequest>(stream, PrefixStyle.Base128); //使用C# BinaryFormatter IFormatter formatter = new BinaryFormatter(); MyData myData = (MyData)formatter.Deserialize( new MemoryStream(myRequest.data)); //MyData.MyData mydata = Serializer.DeserializeWithLengthPrefix<MyData.MyData>(new MemoryStream(request.data), PrefixStyle.Base128); Console.WriteLine( "SERVER : 获取成功, myRequest.version={0}, myRequest.name={1}, myRequest.website={2}, myData.resume={3}" , myRequest.version, myRequest.name, myRequest.website, myData.resume); //响应(MyResponse) MyResponse myResponse = new MyResponse(); myResponse.version = myRequest.version; myResponse.result = 99; Serializer.SerializeWithLengthPrefix(stream, myResponse, PrefixStyle.Base128); Console.WriteLine( "SERVER : 响应成功 ---" ); //DEBUG //int final = stream.ReadByte(); //if (final == 123) //{ // Console.WriteLine("SERVER: Got client-happy marker"); //} //else //{ // Console.WriteLine("SERVER: OOPS! Something went wrong"); //} Console.WriteLine( "SERVER: 关闭连接 ---" ); stream.Close(); client.Close(); } } finally { allDone.Set(); } } //客户端请求 private static void runClient( object state) { try { //构造MyData MyData myData = new MyData(); myData.resume = "我的个人简介" ; //构造MyRequest MyRequest myRequest = new MyRequest(); myRequest.version = 1; myRequest.name = "吴剑" ; myRequest.website = "www.paotiao.com" ; //使用C# BinaryFormatter using (MemoryStream ms = new MemoryStream()) { IFormatter formatter = new BinaryFormatter(); formatter.Serialize(ms, myData); //Serializer.Serialize(ms, mydata); myRequest.data = ms.GetBuffer(); ms.Close(); } Console.WriteLine( "CLIENT : 对象构造完毕 ..." ); using (TcpClient client = new TcpClient()) { client.Connect( new IPEndPoint(IPAddress.Parse( "127.0.0.1" ), 9527)); Console.WriteLine( "CLIENT : socket 连接成功 ..." ); using (NetworkStream stream = client.GetStream()) { //发送 Console.WriteLine( "CLIENT : 发送数据 ..." ); ProtoBuf.Serializer.SerializeWithLengthPrefix(stream, myRequest, PrefixStyle.Base128); //接收 Console.WriteLine( "CLIENT : 等待响应 ..." ); MyResponse myResponse = ProtoBuf.Serializer.DeserializeWithLengthPrefix<MyResponse>(stream, PrefixStyle.Base128); Console.WriteLine( "CLIENT : 成功获取结果, version={0}, result={1}" , myResponse.version, myResponse.result); //DEBUG client-happy marker //stream.WriteByte(123); //关闭 stream.Close(); } client.Close(); Console.WriteLine( "CLIENT : 关闭 ..." ); } } catch (Exception error) { Console.WriteLine( "CLIENT ERROR : {0}" , error.ToString()); } } } //end class } |
从代码中可以发现protobuf-net已考虑的非常周到,不论是客户端发送对象还是服务端接收对象,均只需一行代码就可实现:
//客户端发送对象
ProtoBuf.Serializer.SerializeWithLengthPrefix(stream, myRequest, PrefixStyle.Base128);
//服务端接收对象
MyRequest myRequest = Serializer.DeserializeWithLengthPrefix<MyRequest>(stream, PrefixStyle.Base128);
所以如果Server与Client均使用.Net,Protobuf-net会是理想选择。
但我的项目需要跨平台,同时项目中又恰恰使用了byte类型字段,经过反复调试比较,发现一个关键问题:假使proto脚本和对象属性值完全一样,但只要包含byte类型的字段,那么通过Java序列化的二进制与C#序列化的二进制结果一定不同。而Protobuf中Google原生支持Java,那么几乎可以确定Protobuf-net对Protobuf的支持存在瑕疵。
后来在使用Protobuf-csharp-sport后验证了这一点,Protobuf-net使用C#的byte[]来实现bytes,而Java以及Protobuf-csharp-port均使用ByteString,前者是无符号的,后者是有符号的,语言的基本差异导致两者无法兼容,所以最终我只能放弃Protobuf-net。
Protobuf-csharp-port
官方站点:http://code.google.com/p/protobuf-csharp-port/
Protobuf-csharp-port的文档资料、DEMO、应用范围都不如Protobuf-net,但Protobuf-csharp-port更遵循Google的Protobuf,甚至应用和代码都几乎一样,所以跨平台,Protobuf-csharp-port是不二之选。
生成.CS类文件
先直接使用Google的 protoc.exe 生成二进制文件。
然后通过 protogen.exe 将二进制文件生成C#类文件(Demo中我将命令封装在/tools/getCS.bat中):
echo on
protoc --descriptor_set_out=ProtoMyRequest.protobin --include_imports ProtoMyRequest.proto
protoc --descriptor_set_out=ProtoMyResponse.protobin --include_imports ProtoMyResponse.proto
protoc --descriptor_set_out=ProtoMyData.protobin --include_imports ProtoMyData.proto protogen ProtoMyRequest.protobin
protogen ProtoMyResponse.protobin
protogen ProtoMyData.protobin
接着将生成的3个.cs文件包含在项目中,同时在项目中引用Google.ProtocolBuffers.dll
代码示例(服务端与客户端)
using System; using System.IO; using System.Net; using System.Net.Sockets; using System.Threading; using Google.ProtocolBuffers; namespace protobuf_csharp_sport { class Program { private static ManualResetEvent allDone = new ManualResetEvent( false ); static void Main( string [] args) { beginDemo(); } private static void beginDemo() { //启动服务端 TcpListener server = new TcpListener(IPAddress.Parse( "127.0.0.1" ), 9528); 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 { TcpListener server = (TcpListener)result.AsyncState; using (TcpClient client = server.EndAcceptTcpClient(result)) { using (NetworkStream stream = client.GetStream()) { //获取 Console.WriteLine( "SERVER : 客户端已连接,数据读取中 --- " ); byte [] myRequestBuffer = new byte [49]; int myRequestLength = 0; do { myRequestLength = stream.Read(myRequestBuffer, 0, myRequestBuffer.Length); } while (stream.DataAvailable); MyRequest myRequest = MyRequest.ParseFrom(myRequestBuffer); MyData myData = MyData.ParseFrom(myRequest.Data); Console.WriteLine( "SERVER : 获取成功, myRequest.Version={0}, myRequest.Name={1}, myRequest.Website={2}, myData.Resume={3}" , myRequest.Version, myRequest.Name, myRequest.Website, myData.Resume); //响应(MyResponse) MyResponse.Builder myResponseBuilder = MyResponse.CreateBuilder(); myResponseBuilder.Version = myRequest.Version; myResponseBuilder.Result = 99; MyResponse myResponse = myResponseBuilder.Build(); myResponse.WriteTo(stream); Console.WriteLine( "SERVER : 响应成功 ---" ); Console.WriteLine( "SERVER: 关闭连接 ---" ); stream.Close(); } client.Close(); } } finally { allDone.Set(); } } //客户端请求 private static void runClient( object state) { try { //构造MyData MyData.Builder myDataBuilder = MyData.CreateBuilder(); myDataBuilder.Resume = "我的个人简介" ; MyData myData = myDataBuilder.Build(); //构造MyRequest MyRequest.Builder myRequestBuilder = MyRequest.CreateBuilder(); myRequestBuilder.Version = 1; myRequestBuilder.Name = "吴剑" ; myRequestBuilder.Website = "www.paotiao.com" ; //注:直接支持ByteString类型 myRequestBuilder.Data = myData.ToByteString(); MyRequest myRequest = myRequestBuilder.Build(); Console.WriteLine( "CLIENT : 对象构造完毕 ..." ); using (TcpClient client = new TcpClient()) { client.Connect( new IPEndPoint(IPAddress.Parse( "127.0.0.1" ), 9528)); Console.WriteLine( "CLIENT : socket 连接成功 ..." ); using (NetworkStream stream = client.GetStream()) { //发送 Console.WriteLine( "CLIENT : 发送数据 ..." ); myRequest.WriteTo(stream); //接收 Console.WriteLine( "CLIENT : 等待响应 ..." ); byte [] myResponseBuffer = new byte [4]; int myResponseLength = 0; do { myResponseLength = stream.Read(myResponseBuffer, 0, myResponseBuffer.Length); } while (stream.DataAvailable); MyResponse myResponse = MyResponse.ParseFrom(myResponseBuffer); Console.WriteLine( "CLIENT : 成功获取结果, myResponse.Version={0}, myResponse.Result={1}" , myResponse.Version, myResponse.Result); //关闭 stream.Close(); } client.Close(); Console.WriteLine( "CLIENT : 关闭 ..." ); } } catch (Exception error) { Console.WriteLine( "CLIENT ERROR : {0}" , error.ToString()); } } } //end class } |
Protobuf#
官方站点:http://code.google.com/p/protosharp/
暂未测试Protobuf#
结束语
基本花了一周时间了解和学习了Google Protobuf在.NET下的应用,也找到了Protobuf跨平台的方案,但好事多魔,C# Socket发送的protobuf数据包在Java Netty 中怎么也获取不到,我想也许是平台差异,但对Java知之甚少,如有知情人士还请指点迷津。
DEMO
DEMO下载:http://files.cnblogs.com/wu-jian/ProtobufDemo.rar
DEMO运行环境:.Net Framework 4.0, VS2010
<全文完>
作者:吴剑
出处:http://www.cnblogs.com/wu-jian/
本文版权归作者和博客园共有,欢迎转载,但必需注明出处,并且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。
Google.ProtocolBuffers.dll 之.Net应用(一)的更多相关文章
- 《炉石传说》建筑设计欣赏(7):采用Google.ProtocolBuffers处理网络消息
这一次,琢磨了一下Unity3D网络游戏发展的网络信息处理.服务器的网络游戏一般都是自主研发,因此,相应的网络消息处理应该培养自己.client/现在使用的邮件服务器之间的价差JSON和Google. ...
- 在Unity3d中使用Google.ProtocolBuffers
通过Nuget下载Google.ProtocolBuffers,在目录中找到net35下的文件,放入unity3d中作为插件 PersonMessage.Builder personBuilder = ...
- ProtocolBuffers (二) android与PC,C#与Java 利用protobuf 进行无障碍通讯【Socket】
protobuf 是什么? Protocol buffers是一种编码方法构造的一种有效而可扩展的格式的数据. 谷歌使用其内部几乎RPC协议和文件格式的所有协议缓冲区. 参考文档 http://c ...
- Google Protocol Buffers 快速入门(带生成C#源码的方法)
Google Protocol Buffers是google出品的一个协议生成工具,特点就是跨平台,效率高,速度快,对我们自己的程序定义和使用私有协议很有帮助. Protocol Buffers入门: ...
- Gvr SDK for Unity 分析(二)
前言 关于google vr sdk的具体使用,传送门 Gvr SDK for Unity 分析(一) Google Daydream平台已经整合进Google VR SDK 本文环境:Unity5. ...
- 初用protobuf-csharp-port
下面这个用法是参照protobuf-csharp-port的官方wiki,参见: https://code.google.com/p/protobuf-csharp-port/wiki/Getting ...
- (二)Protobuf的C#使用
[转]http://blog.csdn.net/shantsc/article/details/50729402 protobuf c#版本分成两个版本,一个是protobuf-net,另一个是pr ...
- HBase(一): c#访问hbase组件开发
HDP2.4安装系列介绍了通过ambari创建hbase集群的过程,但工作中一直采用.net的技术路线,如何去访问基于Java搞的Hbase呢? Hbase提供基于Java的本地API访问,同时扩展了 ...
- Protobuf实现Android Socket通讯开发教程
本节为您介绍Protobuf实现Android Socket通讯开发教程,因此,我们需要先了理一下protobuf 是什么? Protocol buffers是一种编码方法构造的一种有效而可扩展的格式 ...
随机推荐
- Css中如何使英文和拼音变成全大写、全小写和首字母大写?
想要实现英文和中文拼音变成全大写.全小写和首个字母大写,需要用到 css中text-transform样式属性,接下来介绍一下 1.text-transform的值 1)Capitalize:英文拼音 ...
- Awk 从入门到放弃(3) —- 内置变量
转:http://www.zsythink.net/archives/1374 NF :当前行的字段个数 NR: 行号 FNR: 各文件分别计数的行号 RS: 输入行分隔符 ORS:输出行分隔符 内 ...
- json批量设置DIV属性
<!doctype html> <html> <head> <meta charset="utf-8"> <title> ...
- numpy 小示例
import numpy as np 生成 3*4 的由 0 组成的二维数组 >>> np.zeros((3,4)) array([[0., 0., 0., 0.], [0., 0 ...
- L1-020 帅到没朋友
当芸芸众生忙着在朋友圈中发照片的时候,总有一些人因为太帅而没有朋友.本题就要求你找出那些帅到没有朋友的人. 输入格式: 输入第一行给出一个正整数N(≤100),是已知朋友圈的个数:随后N行,每行首先给 ...
- Swift网络封装库Moya中文手册之Targets
Targets 使用Moya,我们首先需要定义一个target - 这通常是继承 TargetType 协议的 枚举 变量.接下来,你的app只需要处理这些targets,也就是一些你希望调用API完 ...
- AE项目打包
Holinz AE项目打包 打包详细信息:Setup Factory 7.0打包软件,VS2005+AE92下的Winform项目1.依赖项: Dot Net Framework20 AO ...
- 给迷茫的你学习Node.js最好的方法
这是真事儿,在3w咖啡整理书稿,然后小弟梁过来了,聊聊他的现状,一副很不好的样子,在天津我曾带过他大半年,总不能不管,我给他的建议是:“每天看10个npm模块” 对于学习Node.js迷茫的人来说,这 ...
- net start mysql启动mysql,提示发生系统错误 5 拒绝访问 解决方法
解决问题方法如下: 在dos下运行net start mysql 不能启动mysql!提示发生系统错误 5:拒绝访问!切换到管理员模式就可以启动了.所以我们要以管理员身份来运行cmd程序来启动mys ...
- Python 文件修改
# 需求: 把好人换成sb # 必须: # 1. 先从文件中读取内容 # 2. 把要修改的内容进行修改 # 3. 把修改好的内容写人一个新文件 # 4. 删除掉原来的文件 # 5. 把新文件重命名成原 ...