注册 登录  
 加关注
   显示下一条  |  关闭
温馨提示!由于新浪微博认证机制调整,您的新浪微博帐号绑定已过期,请重新绑定!立即重新绑定新浪微博》  |  关闭

勇敢的劳尤条

 
 
 

日志

 
 

【转】跨平台网络通信(结构体还是流序列?)  

2014-03-27 15:49:00|  分类: 网络编程知识积累 |  标签: |举报 |字号 订阅

  下载LOFTER 我的照片书  |
网络通信通常分为基于数据结构的和基于流的。HTTP协议就是后者的一个例子。
    有时为了提高程序的处理速度和数据处理的方便,会使用基于数据结构的通信(不需要对流进行解析)。但是,当需要在多平台间进行通信时,基于数据结构的通信,往往要十分注意以下几个方面:
[1] 字节序
[2] 变量长度
[3] 内存对齐

    在常见的系统架构中(Linux X86,Windows),非单字节长度的变量类型,都是低字节在前,而在某些特定系统中,如Soalris Sparc平台,高字节在前。如果在发送数据前不进行处理,那么由Linux X86发向Soalris Sparc平台的数据值,势必会有极大的偏差,进而程序运行过程中无法出现预计的正常结果,更严重时,会导致段错误。
    对于此种情况,我们往往使用同一的字节序。在系统中,有ntohXXX(), htonXXX()等函数,负责将数据在网络字节序和本地字节序之间转换。虽然每种系统的本地字节序不同,但是对于所有系统来说,网络字节序是固定的-----高字节在前。所以,可以以网络字节序为通信的标准,发送前,数据都转换为网络字节序。
    转换的过程,也建议使用ntohXXX(), htonXXX()等标准函数,这样代码可以轻松地在各平台间进行移植(像通信这种很少依赖系统API的代码,做成通用版本是不错的选择)。

    变量的长度,在不同的系统之间会有差别,如同是Linux2.6.18的平台,在64位系统中,指针的长度为8个字节,而在32位系统中,指针又是4个字节的长度---此处只是举个例子,很少有人会将指针作为数据发送出去。下面是我整理的在64位Linux系统和32位Linux系统中,几种常见C语言变量的长度:
                short    int    long    long long    ptr    time_t
32位           2         4       4             8         4        4
64位           2         4       8             8         8        8
    在定义通信用的结构体时,应该考虑使用定长的数据类型,如uint32_t,4字节的固定长度,并且这属于标准C库(C99),在各系统中都可使用。

    内存对齐的问题,也与系统是64位还是32位有关。如果你手头有32位和64位系统,不妨写个简单的程序测试一下,你就会看到同一个结构体,即便使用了定长的数据类型,在不同系统中的大小是不同的对齐往往是以4字节或8字节为准的,只要你写的测试程序,变量所占空间没有对齐到4或8的倍数即可,举个简单的测试用的结构体的例子吧:
struct student
{
    char name[7];
    uint32_t id;
    char subject[5];
};
    在每个系统上看下这个结构体的长度吧。
    内存对齐,往往是由编译器来做的,如果你使用的是gcc,可以在定义变量时,添加__attribute__,来决定是否使用内存对齐,或是内存对齐到几个字节,以上面的结构体为例:
 1)到4字节,同样可指定对齐到8字节。
struct student
{
    char name[7];
    uint32_t id;
    char subject[5];
} __attribute__ ((aligned(4))); 

2)不对齐,结构体的长度,就是各个变量长度的和
struct student
{
    char name[7];
    uint32_t id;
    char subject[5];
} __attribute__ ((packed));


虽然,网络编程里面的数据传送推荐用序列化,但我不用,还是选择结构体(返璞归真),有以下几点理由:
1.跨平台问题:
序列化确实可以很好的跨语言平台,可大多数网络游戏不需要跨语言平台

2.别以为有了序列化就不需要结构体
表面上序列化代码量小,按顺序读和写char int short LPCSTR ... 就好,逻辑对象写不写都无所谓,那就是大错而特错了
待序列化的对象发送前的结构还是不可省略的,序列化的过程就是 object->(按一定顺序拆分)write->bytes->(按拆分顺序组装)read->object的过程
其实object还是不能省略,很多人写网络程序不注重逻辑对象结构,收到的一堆bytes按一定顺序读和写就完事了,这样虽然灵活,但缺乏结构,容易造成混乱,调试起来是灾难
所以结构体(或类)还是省略不了的,所以:
别以为有了序列化,就不需要结构体了。

3.结构体存在内存对齐和CPU不兼容的问题,可以避免
的确结构体是有内存对齐的问题,存在兼容性问题,我可以选择pack(1)把内存对齐给关闭掉,避免兼容性问题,既然选择了iocp就不打算跨平台了,可以避免结构体平台兼容的问题

4.结构体调试起来方便很多,减少内存拷贝,效率高
不用序列化可write和read的过程就不需要过多考虑(少写太多代码了),read write 就好像现代社会每个人每天都要穿衣服和脱衣服一样,原始社会需要吗?其实人类进化到原始裸奔状态才是最爽快的:)
但还是要说句公道话:有人说序列化编码解码read write 需要耗费资源, 诚然这个过程基本等于赋值和内存拷贝,那点效率损失主要还在内存拷贝上,这点效率损失很小,不能作为序列化的缺点,当然如果涉及到数据加密那将是另外一个话题

5.结构体貌似呆板,发送数据限制多,发送变长数据就不方便,数据组织起来也不灵活
我想这是很多人抛弃结构体,选择用序列化方式发送和接受数据的一个很重要的原因
但:其实对于变长结构(子结构也是变长)的问题,用结构体来实现的确很麻烦,但并不代表不能实现
我已经实现了,而且读和写变长子结构体嵌套任意多层都不成问题,可以存储复杂变长的数据结构,
数据就如同能自动序列化一样方便,这个应该是技术难点,但细心去做是可以实现的

6.关于结构体指针
游戏里面
要发送的数据内存事先分配好的,不存在指针,深度复制更不用考虑,所以内存拷贝不会出错
如果用到指针即使用序列化来实现也会面临同样的问题也占不了多少便宜,由于C++这们语言的特点,
不象java那样有个标准实现,对于序列化本身没有一个统一的标准,所以可想而知,有人说:boost有它的序列化的实现
其实那个实现不见得就合适你自己,如果真要做序列化,编码和解码的仿照那个过程自己写才最为牢靠,
哪些指针对应的内存需要序列化那些不需要序列化,是个逻辑结构,需要自己说了算才好(好像扯远了点)
说回游戏数据,既然不用需要他用到指针,结构体用来发送数据也没问题的

7 平台扩充问题
退一万步的说:
换了语言就基本上换了客户端,客户端的数据组织形式都要重写
实在不行还可以考虑用xml json 编码等等一些跨平台的解决方案,现在所写的结构体是可以用来做数据接收的,只是发送的不再是结构体而已

8.综上所述

如果需要跨语言平台,不用序列化(二进制流或xml, json文本等等)根本无法实现

序列化的优点还是非常多的.如果主要是跨平台和语言自定义读写规则,根据需要读写对象的某一部分数据,

空间浪费少,不存在内存对齐问题等诸多优点,缺点就是拐弯抹角,代码量大,调试不方便

【参考资料】
  评论这张
 
阅读(179)| 评论(0)
推荐 转载

历史上的今天

评论

<#--最新日志,群博日志--> <#--推荐日志--> <#--引用记录--> <#--博主推荐--> <#--随机阅读--> <#--首页推荐--> <#--历史上的今天--> <#--被推荐日志--> <#--上一篇,下一篇--> <#-- 热度 --> <#-- 网易新闻广告 --> <#--右边模块结构--> <#--评论模块结构--> <#--引用模块结构--> <#--博主发起的投票-->
 
 
 
 
 
 
 
 
 
 
 
 
 
 

页脚

网易公司版权所有 ©1997-2017