听过 PHPRPC 吗?试试我的 Hign!

颤菊大师 2011-04-06 04:17:29
加精
原帖:听过 PHPRPC 吗?试试我的 Hign!

------------------------------------------------------------------

·〉前言
在企业级应用程序中,常常需要将某个类(可能复杂、组合、等等)进行本地化。当然,个人感觉微软所提供的 Binary 序列化是最“保险”的方式。可惜这是一个略有遗憾的序列化器。常见问题如程序集版本的问题(虽然有 Binder 可以解决),以及致命的序列化的效率和用时令人不敢恭维。而 XML 序列化仅仅适用于简单对象的本地化。

无意之中,在网络中寻找到了PHPRPC。这是一个非常轻型,非常棒的序列化器。

PHPRPC,它的商业版本是Hprose。 PHPRPC 采用的序列化方式是 PHP 序列化。你可以认为它是一种半文本格式,因为文本和二进制字节流的特征它都具有。 比如表示数字时,它采用的是数字的文本格式,而不是二进制的机器存储格式,这样它就可以在不同的平台上传递而无须担心字节序引起的解析问题。而它又像二进制字节流那样不允许在数据当中插入多余的空白(空格、回车、换行等字符),并且对于字符串、数组、字典、对象还有长度描述信息。这些特征决定了它既有纯文本格式的通用性,又具有二进制字节流的快速解析能力。

PHPRPC For .NET 无论是效率,还是大小,都是非常棒的。如果读者对 PHPRPC 还不是很了解,或者对他的性能表示怀疑,可以阅读这篇文章(http://www.cnblogs.com/xlai/archive/2010/10/19/1855736.html),这篇文章详细的测试 PHPRPC 和其他序列化器的性能。毋庸置疑,在没有找到其他序列化器,这是一个非常好的选择。据闻,PHPRPC 有一个商业版,性能和效率更胜一筹,可惜无缘尝试。

但是,PHPRPC也不是全能的(至少 For .NET 是这样的)。例如对于十分复杂的组合对象,PHPRPC的序列化是抛出异常的。更要命的是,对于object类型的解析,PHPRPC显得过于2B了。例如对于List<object>、Dictionary<object,object>的处理更是一塌糊涂。虽然官方扯蛋的说:支持复杂对象传输的。

幸好,PHPRPC是一个开源的项目。翻开源码后,有一种热血立即涌上胸口!

——自己写一个。

·〉PHPRPC的不足

PHPRP的思路是非常美妙的,比如对字符串的长度描述,复杂对象的“Reference”特性等,在自己写序列化器的过程中,PHPRPC的源码令人受益匪浅。现在主要列举几个PHPRPC亟待解决的问题(可能不影响正常的使用),再一一列出解决方案。
1、对ICollection、IList和IDictionary的类型支持不够,我想应当是PHPRPC为了缩短序列化后的大小,特意对这三个常用的类型进行特别处理,无奈的是,PHPRPC处理的方式并不是最佳的,比如前文所提到的“List<object>”,如果在“List<object>”中存有一个字符串,那么反序列化,就会成了一个 byte[]数组。通过观赏代码,BUG是在【PHPReader文件的private AssocArray ReadAssocArray(Stream stream, ArrayList objectContainer)函数】。

2、也正因为PHPRPC对IDictionary、ICollection、ILIst的特殊处理,导致了对于实现三个接口任意对象,不序列化其自定义属性,而只序列化集合所包含的元素。

3、PHPRPC并不支持多维数组。如果你的维数大于1,那么就会抛出【throw new RankException("Only single dimension arrays are supported here.")】的异常。虽然本人对多维数组用的也是相当的少,但二维数组,还是有用到的。

4、性能。对,没错,PHPRPC的性能仍然无法令我满意。最明显的是反射,这个竟然没有做任何优化。

5、不支持值类型的对象。显然,PHPRPC并没有考虑这么多。

相对第3条、第4条和第5条,第1、2条显得十分致命,在许多应用程序的开发中,对Dictionary`2或List`1的继承与扩展是十分频繁的。综合PHPRPC的诸多显著性的缺点,我即将展示我的方案。

·〉我的方案

十分感谢您可以阅读到这里,虽然我前面不断的批评PHPRPC,但它的强大和奇妙,是无法褪去的。尤其是随着自己的序列化器第一版的落幕,充分体会到PHPRPC其“牺牲”与“得到”(为什么PHPRPC要这样做,而不要那样做)。

只不过——它可以更好。

通过一周左右的钻研,我发现:任意对象若想要序列化,他无限分解后,只会剩下:基础数据类型(int、string、DateTime等)和数组,而其余的,皆为浮云。

不过这样的方案是有代价的,尤其是对集合(如Dictionary)的序列化,哪怕集合为空,也无法潇洒的简短。我曾常识对Dictionary进行分解,分成两个部分,一部分是元素部分,一部分是派生类的属性部分,但最终否决了这样的方案(考虑集合有可能仅仅是对IDictionary的实现,还有待再次尝试)。

支持集合的object类型。强大的功能的背后总是性能的牺牲。为了支持一切对象,不得不再次牺牲序列化的大小,在每一个集合的元素前都写入其类型。这样的好处是:List<BaseClass>里含有 DerivedClass,在序列化与反序列化后不存在任何差异。
在牺牲序列化后的大小,得到的是——任意对象的复制!

为了减少序列化后的大小,我特意增加了一个不影响性能的特性(实际上性能反而令我吃惊的提升了),就是“简化限定名”(包含对程序集描述进行简化),例如对于int类型的则为“i”,System.Collections.Generic.List`1则为“scg.List”,支持限定名的命名空间并不多,但是基本覆盖了常用的,可以大大的简短序列化后的字符串长度。

此次公开Sofire的核心模块之一:Utils源码(Sofire.Utils,基于.NET 3.5),这个核心框架已经编写了将近两年,它包含了几个核心的功能:Result特性,Emit’s Dynamic Obect、Serialization、Security。如果您有兴趣,可以对它进行研究。此次开源仅仅是为了分享,希望可以帮助朋友,也希望路过的朋友留下宝贵的意见。接下来附上一张测试图(PS:测试结果是针对 100000 个对象进行内存流序列化,并且 PHPRPC已经是个人进行优化的后的版本(抛弃传统反射带来的性能损失)。
Sofire是一套基于.NET的开发辅助框架。在此段任务忙完以后,可能会针对个人所研发的Sofire Platfrom(信息化综合平台)进行讲解。

附图:

源码(VS2010):请访问博客地址。
...全文
1351 113 打赏 收藏 转发到动态 举报
写回复
用AI写文章
113 条回复
切换为时间正序
请发表友善的回复…
发表回复
tht2009 2011-04-23
  • 打赏
  • 举报
回复
怎么序列化窗体类的数组、TList、记录、自定义类对象


求教
jamesstudy001 2011-04-12
  • 打赏
  • 举报
回复
不错 呵呵 、支持开源 支持共享
andot 2011-04-11
  • 打赏
  • 举报
回复
那个自动属性对应的字段在hprose 1.2中无法序列化的问题找到了,是因为那些自动属性对应的字段都是私有属性,而Emit中的动态方法构造函数在Silverlight中只有进行JIT可见性检查的版本,所以为了兼容Silverlight,所以统统采用了JIT可见性检查的动态方法构造函数。在mrlen的提醒下,新的hprose 1.3版本中,对于非Silverlight的.net 2.0以上的版本,使用忽略JIT可见性检查的动态方法构造函数,在跨平台性不变的情况下,可以进行更快的序列化和反序列化了。
颤菊大师 2011-04-11
  • 打赏
  • 举报
回复
[Quote=引用 102 楼 fangxinggood 的回复:]

自定义序列化的确能在性能上能够有所改善,但目标应该是跨语言支持。

貌似Hprose可以成为Remoting,WCF以外的又一种分布式系统架构方式。学习下。
[/Quote]

我相信一个序列化,是分布式的核心。
机器人 2011-04-11
  • 打赏
  • 举报
回复
自定义序列化的确能在性能上能够有所改善,但目标应该是跨语言支持。

貌似Hprose可以成为Remoting,WCF以外的又一种分布式系统架构方式。学习下。
andot 2011-04-11
  • 打赏
  • 举报
回复
[Quote=引用 100 楼 mrlen 的回复:]
可惜我没有1.2 的版本。没办法进行更详细的比较。不知道能否分享1.2的试用版?
[/Quote]

已经给你发短信息啦,你查收一下。:)
颤菊大师 2011-04-11
  • 打赏
  • 举报
回复
[Quote=引用 99 楼 andot 的回复:]

顺便说一下:

1.1.4.0 这个版本号的意思是:1.1是hprose的版本号,4.0对应的是.net的版本号。

而你用的那个1.1.4.0试用版(也就是官方发布的测试版)实际上是1.2.4.0的序列化+1.1.4.0的其它部分代码的混淆版。

在 hprose 1.2 中,序列化部分针对不同的 .net 版本做了不同的优化,对于 .net 3.5、.net 4.0 的优化是最好……
[/Quote]

可惜我没有1.2 的版本。没办法进行更详细的比较。不知道能否分享1.2的试用版?
andot 2011-04-11
  • 打赏
  • 举报
回复
顺便说一下:

1.1.4.0 这个版本号的意思是:1.1是hprose的版本号,4.0对应的是.net的版本号。

而你用的那个1.1.4.0试用版(也就是官方发布的测试版)实际上是1.2.4.0的序列化+1.1.4.0的其它部分代码的混淆版。

在 hprose 1.2 中,序列化部分针对不同的 .net 版本做了不同的优化,对于 .net 3.5、.net 4.0 的优化是最好的,.net 2.0的优化次之,.net 1.0、.net 1.1 和 SilverLight 版本再次之,他们在优化上的差别主要在于是否有并发容器的使用和不同.net版本在Emit支持的功能上的差别。.net cf 版本是优化最少的,主要是因为 .net cf 没有对 Emit 的支持。

hprose 1.2的这些优化都是针对性能的优化,在通用性上都是相同的。

在 hprose 1.3 中还会增加对数据契约方式定义的类型对象的特殊序列化,之前的hprose版本中所有的可序列化对象都是需要标记[Serializable]的,但hprose 1.3中对.net 3.5、.net 4.0中的[DataContract]方式定义的类型,也提供序列化支持,包括对[DataMember]这种标记的支持。这种定义的类型虽然在.net其它版本中不存在,但是通过hprose 1.3,可以实现这种定义与之前版本的[Serializable]方式定义的数据类型的互通,只要保证序列化的字段或属性名称保持一致即可。
颤菊大师 2011-04-11
  • 打赏
  • 举报
回复
[Quote=引用 97 楼 andot 的回复:]

我又重新测试了一下按字段序列化,1.1.4.0的原版不会出现你说的那个异常,而是得到上面的结果,但是发布的测试版(是代码混淆版)会出现你说的那个异常。新的1.2.4.0版本也会出现那个异常,所以我想应该是这些特殊的字段在采用特殊方式反射时(用Emit实现的快速反射),不能解析造成的。

不过因为hprose是为了跨平台传输设计的,所以这种自动属性的特殊字段在跨平台时是不考虑的,因此,对于类中……
[/Quote]
恩。对 Hprose 的写入部分非常感兴趣。
在效率上,读取的速度已经和Hprose持衡,甚至更甚一筹。

可是写入的速度是在差距太大了。三倍。
andot 2011-04-11
  • 打赏
  • 举报
回复
我又重新测试了一下按字段序列化,1.1.4.0的原版不会出现你说的那个异常,而是得到上面的结果,但是发布的测试版(是代码混淆版)会出现你说的那个异常。新的1.2.4.0版本也会出现那个异常,所以我想应该是这些特殊的字段在采用特殊方式反射时(用Emit实现的快速反射),不能解析造成的。

不过因为hprose是为了跨平台传输设计的,所以这种自动属性的特殊字段在跨平台时是不考虑的,因此,对于类中需要序列化的数据都是自动属性的对象来说,建议按照属性序列化方式进行序列化,一是可以保证跨平台,二是数据量会小很多。
andot 2011-04-11
  • 打赏
  • 举报
回复
[Quote=引用 94 楼 mrlen 的回复:]

引用 91 楼 andot 的回复:

HproseFormatter.Serialize(value);

这种序列化方式是按照字段序列化,而你现在定义的这些类中,可序列化的部分都是属性,所以应该选择按属性序列化,因此正确的调用是:

HproseFormatter.Serialize(value, HproseMode.PropertyMode);

这样就可以正确的按照属性……
[/Quote]

自动属性确实是对应一个字段,但这个字段名是比较特殊的,我刚才测试了一下,如果按照字段序列化,也不会出现你说的那个异常,同样会得到序列化后的结果,只是结果有所不同,序列化结果中,可以看到那些特殊的字段名:

c6"Family"4{s27"<FamilyName>k__BackingField"s23"<Father>k__BackingField"s23"<Mot
her>k__BackingField"s20"<Sun>k__BackingField"}o0{s4"LOVE"c4"Male"8{s23"<Family>k
__BackingField"s21"<Name>k__BackingField"s20"<Age>k__BackingField"s20"<Sex>k__Ba
ckingField"s22"<Money>k__BackingField"s23"<Height>k__BackingField"s23"<Weight>k_
_BackingField"s22"<Title>k__BackingField"}o1{r4;s6"LiTonk"i40;uMd12345654321;d17
5;d75;s6"Father"}c6"Female"8{s23"<Family>k__BackingField"s21"<Name>k__BackingFie
ld"s20"<Age>k__BackingField"s20"<Sex>k__BackingField"s22"<Money>k__BackingField"
s23"<Height>k__BackingField"s23"<Weight>k__BackingField"s22"<Title>k__BackingFie
ld"}o2{r4;s9"HanMeiMei"i36;uFd456541;d165;d50;s6"Mother"}o1{r4;s5"LiLei"i16;uMd1
21;d170;d54;s3"Sun"}}
颤菊大师 2011-04-11
  • 打赏
  • 举报
回复
[Quote=引用 91 楼 andot 的回复:]

HproseFormatter.Serialize(value);

这种序列化方式是按照字段序列化,而你现在定义的这些类中,可序列化的部分都是属性,所以应该选择按属性序列化,因此正确的调用是:

HproseFormatter.Serialize(value, HproseMode.PropertyMode);

这样就可以正确的按照属性进行序列化啦。
[/Quote]
这句话我不能理解。自动属性只是一个语法糖。每个属性都对应一个字段吧?
颤菊大师 2011-04-11
  • 打赏
  • 举报
回复
[Quote=引用 106 楼 andot 的回复:]

继续补充,在mrlen的帮助下,SilverLight版本的hprose也支持忽略JIT可见性检查的快速序列化和反序列化啦。
[/Quote]

感谢 andot 的无私帮助。下一个版本将会更加高效!
andot 2011-04-11
  • 打赏
  • 举报
回复
继续补充,在mrlen的帮助下,SilverLight版本的hprose也支持忽略JIT可见性检查的快速序列化和反序列化啦。
andot 2011-04-10
  • 打赏
  • 举报
回复
如果把三个类的定义放在ConsoleApplication1这个名空间之外的话,那运行结果如下:

c6"Family"4{s10"familyName"s6"father"s6"mother"s3"sun"}o0{s4"LOVE"c4"Male"8{s6"f
amily"s4"name"s3"age"s3"sex"s5"money"s6"height"s6"weight"s5"title"}o1{r4;s6"LiTo
nk"i40;uMd12345654321;d175;d75;s6"Father"}c6"Female"8{s6"family"s4"name"s3"age"s
3"sex"s5"money"s6"height"s6"weight"s5"title"}o2{r4;s9"HanMeiMei"i36;uFd456541;d1
65;d50;s6"Mother"}o1{r4;s5"LiLei"i16;uMd121;d170;d54;s3"Sun"}}

没有任何问题。
andot 2011-04-10
  • 打赏
  • 举报
回复

static void Main(string[] args)
{
var value = CreateLiLeiFamily();
MemoryStream s = HproseFormatter.Serialize(value, HproseMode.PropertyMode);
Console.WriteLine(UTF8Encoding.UTF8.GetString(s.ToArray()));
Console.ReadKey();

}


下面是序列化之后的输出结果:

c26"ConsoleApplication1_Family"4{s10"familyName"s6"father"s6"mother"s3"sun"}o0{s
4"LOVE"c24"ConsoleApplication1_Male"8{s6"family"s4"name"s3"age"s3"sex"s5"money"s
6"height"s6"weight"s5"title"}o1{r4;s6"LiTonk"i40;uMd12345654321;d175;d75;s6"Fath
er"}c26"ConsoleApplication1_Female"8{s6"family"s4"name"s3"age"s3"sex"s5"money"s6
"height"s6"weight"s5"title"}o2{r4;s9"HanMeiMei"i36;uFd456541;d165;d50;s6"Mother"
}o1{r4;s5"LiLei"i16;uMd121;d170;d54;s3"Sun"}}
andot 2011-04-10
  • 打赏
  • 举报
回复
HproseFormatter.Serialize(value);

这种序列化方式是按照字段序列化,而你现在定义的这些类中,可序列化的部分都是属性,所以应该选择按属性序列化,因此正确的调用是:

HproseFormatter.Serialize(value, HproseMode.PropertyMode);

这样就可以正确的按照属性进行序列化啦。
颤菊大师 2011-04-10
  • 打赏
  • 举报
回复
补充上面:使用的是试用版 1.1.4.0
颤菊大师 2011-04-10
  • 打赏
  • 举报
回复
        #region 复杂的关系对象

static Male CreateFather(Family family)
{
Male father = new Male();
father.Family = family;
father.Name = "LiTonk";
father.Age = 40;
father.Sex = 'M';
father.Money = 12345654321M;
father.Weight = 75D;
father.Height = 175D;
father.Title = "Father";
return father;
}

static Female CreateMother(Family family)
{
Female mother = new Female();
mother.Family = family;
mother.Name = "HanMeiMei";
mother.Age = 36;
mother.Sex = 'F';
mother.Money = 456541M;
mother.Weight = 50D;
mother.Height = 165D;
mother.Title = "Mother";
return mother;
}

static Male CreateSun(Family family)
{
Male sun = new Male();
sun.Family = family;
sun.Name = "LiLei";
sun.Age = 16;
sun.Sex = 'M';
sun.Money = 121M;
sun.Weight = 54D;
sun.Height = 170D;
sun.Title = "Sun";
return sun;
}

static Family CreateLiLeiFamily()
{
Family family = new Family();
family.FamilyName = "LOVE";
family.Father = CreateFather(family);
family.Mother = CreateMother(family);
family.Sun = CreateSun(family);
return family;
}

#endregion


最终需要序列化的对象:
var value = CreateLiLeiFamily();


抛出的异常处:
HproseFormatter.Serialize(value);


抛出的异常描述:
The field value can't be serialized.

我无法理解哪一个字段无法序列化了?
颤菊大师 2011-04-10
  • 打赏
  • 举报
回复
[Quote=引用 82 楼 andot 的回复:]

你确认使用的是Hprose,而不是PHPRPC?你确认你的代码没有问题吗?如果是的话,你可以把那个那个对象的定义贴上来,我想试一下。非常感谢!


引用 74 楼 mrlen 的回复:

引用 71 楼 andot 的回复:

引用 63 楼 mrlen 的回复:

Hprose 的效率的确令人吃惊。
但是令人郁闷的是,他不支持复杂的对象。


Hprose 跟 php……
[/Quote]

类声明:


[Serializable]
public abstract class Person
{
public Family Family { get; set; }
public string Name { get; set; }
public int Age { get; set; }
public char Sex { get; set; }
public decimal Money { get; set; }
public double Height { get; set; }
public double Weight { get; set; }
public string Title { get; set; }
public abstract void SayHello();
}

[Serializable]
public class Male : Person
{
public override void SayHello()
{
Console.WriteLine("I am a Male.The " + this.Title + " with " + this.Family.FamilyName + "'s Family");
}
}

[Serializable]
public class Female : Person
{
public override void SayHello()
{
Console.WriteLine("I am a Female.The " + this.Title + " with " + this.Family.FamilyName + "'s Family");
}
}

[Serializable]
public class Family
{
public string FamilyName { get; set; }
public Male Father { get; set; }
public Female Mother { get; set; }
public Male Sun { get; set; }
}
加载更多回复(48)

110,524

社区成员

发帖
与我相关
我的任务
社区描述
.NET技术 C#
社区管理员
  • C#
  • Web++
  • by_封爱
加入社区
  • 近7日
  • 近30日
  • 至今
社区公告

让您成为最强悍的C#开发者

试试用AI创作助手写篇文章吧