3d Game Walkman

3d图形渲染,网络引擎 — tonykee's Blog
随笔 - 45, 文章 - 0, 评论 - 309, 引用 - 0
数据加载中……

返璞归真,网络传输——结构体还是序列化?

虽然,网络编程里面的数据传送推荐用序列化,但我不用,还是选择结构体(返璞归真),有以下几点理由:
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文本等等)根本无法实现
  序列化的优点还是非常多的.如果主要是跨平台和语言自定义读写规则,根据需要读写对象的某一部分数据,
  空间浪费少,不存在内存对齐问题等诸多优点,缺点就是拐弯抹角,代码量大,调试不方便


权衡了良久
  数据如果能组织的合理,而且没有跨语言平台的要求,用结构体也未尝不可,毕竟数据发送直来直去还是方便些,减少内存拷贝,效率也高了很多
  特别是调试起来容易太多了,衡量利弊我还是放弃了序列化,选择了原始的结构体,只是难在数据的组织(好在基本已经克服了)

我知道:序列化很好很强大,很多网络程序高手根本不屑于直接发一个逻辑结构体,用这种方式就好象是旁门左道,狗肉上不了大雅之堂一样,狗肉还是很多人喜欢吃的嘛,:)。

我还是返璞归真选择了结构体

一句话:物尽其用,用的恰当,够用就好。

如果有什么不对,敬请拍砖,莫要客气

posted on 2008-02-17 12:53 李侃 阅读(8196) 评论(30)  编辑 收藏 引用 所属分类: 网络模块

评论

# re: 返璞归真,网络传输——结构体还是序列化?  回复  更多评论   

缺点就是拐弯抹角,代码量大?
我的结论恰恰相反, 使用序列化的最大好处是简化编程, 以及将来的可扩展性,
至于调试, 对于这种网络程序,如果再加上多线程,是很难调试的,而这种你所说的结构体简直是bug的温床, 所以为了保证可靠性,一般都是用命令录制回放来进行测试的.
2008-02-17 13:48 | eXile

# re: 返璞归真,网络传输——结构体还是序列化?  回复  更多评论   

只要封装的好,怎么会是BUG的温床呢?
我的结构体测试过了,作为容器,内部嵌套子容器,再套N个变长结构体或子结构体都没问题,比序列化方便多了。

因为有了这个实现,我才有这个底气写这篇文的,要不然我早用传统方式了
2008-02-17 14:38 | 李侃

# re: 返璞归真,网络传输——结构体还是序列化?  回复  更多评论   

呵呵, 那就具体探讨一下, 比如发送一个用户名列表, 这是一个字符串的容器, 用序列化大致实现起来就是以下样子:
std::vector<std::string> user_names;
archive ar;
if (ar.serialize(user_names)) socket.send(ar.data(), ar.size());

不知道你是怎么实现的, 欢迎探讨.
2008-02-17 15:52 | eXile

# re: 返璞归真,网络传输——结构体还是序列化?  回复  更多评论   

CollectionPack collection;

StringPack p1;
strcpy(p.buffer, "jack");

StringPack p2;
strcpy(p2.buffer, "cat");

collection.appendPack(p1);
collection.appendPack(p2);

socket.send(collection, collection.size());

遍历collection代码大致如下:

void visttest(CollectionPack * c)
{
BasePack *p = 0;
while(c->next(&p))
{
if(p->type==ROLESIMPLEINFO_PACK)
{
RoleInfoSimplePack * aaa = (RoleInfoSimplePack *) p;
printf("%s \r\n", aaa->username);
} else if(p->type==LOGIN_PACK)
{
LoginPack * aaa = (LoginPack *) p;
printf("%s \r\n", aaa->username);
} else if(p->type == STRING_PACK)
{
StringPack *aaa = (StringPack *) p;
printf("%s \r\n", aaa->buffer);
}
else if(p->type == COLLECTION_PACK) //带嵌套的COLLECTION_PACK
{
CollectionPack *t=(CollectionPack *)p;
visttest(t); //递归方式
// return;
}
}
}
2008-02-17 17:14 | 李侃

# re: 返璞归真,网络传输——结构体还是序列化?[未登录]  回复  更多评论   

扯什么蛋
序列化局部上还是结构体。
有什么区别。
2008-02-17 17:23 | 小白

# re: 返璞归真,网络传输——结构体还是序列化?[未登录]  回复  更多评论   

我觉得没事干的话,最好写一些有技术含量的东西。。。。
2008-02-17 17:24 | 小白

# re: 返璞归真,网络传输——结构体还是序列化?  回复  更多评论   


object->(按一定顺序拆分)write->bytes->(按拆分顺序组装)read->object

这才是序列化,和结构体直接发送的区别你都不知道?

楼上的正如你的名字“小白”一个
2008-02-17 17:29 | 李侃

# re: 返璞归真,网络传输——结构体还是序列化?  回复  更多评论   

其实你的设计就是最简单的序列化实现.
不过用较好的序列化库对于接收时就简单多了:

std::vector<std::string> user_names;
in_archive ar(received_data, received_size);
if (ar.serialize(user_names)) visit(user_names);

在serialize函数里包含了各种异常情况处理, 比如错误的数据, 字符串长度异常,而像你的要完全自己处理, 那太复杂了, 这正是我所说的容易产生bug的地方.
2008-02-17 18:16 | eXile

# re: 返璞归真,网络传输——结构体还是序列化?  回复  更多评论   

结构体还是序列化
不是一个概念.
2008-02-17 18:19 | WXX

# re: 返璞归真,网络传输——结构体还是序列化?  回复  更多评论   

@WXX
其实lz所说的结构体指的把网络数据看作是POD数据类型来处理, 这样来简化处理过程(当然在我看来, 这只能把处理过程搞得更复杂), 而序列化把网络数据看作二进制流或文本流. 从这个角度来谈论这个问题的.
2008-02-17 18:51 | eXile

# re: 返璞归真,网络传输——结构体还是序列化?  回复  更多评论   

我得要把标题改一下才好。

网络数据,以结构体的方式直接传送还是转化成特定字节顺序形式来传送。
我当然知道传统方式是采用楼上的方法。我只是想打破常规。

我的出发点是想自定义一个容器,相当于一个SmartStruct能装下各种各样的东西,其实这本身就相当于在序列化了,只是和复杂的序列化过程还是有些的差异而已
说来说去就是数据的字节组织形式是按原样收发,还是变个特定顺序收发
真不好意思,这篇文章确实是有些小小的混淆概念。



至于eXile 所说,数据验证这些目前还没有去考虑
2008-02-17 19:09 | 李侃

# re: 返璞归真,网络传输——结构体还是序列化?  回复  更多评论   

最好的方法还是模拟rpc的解决方案,利用idl解析器自动生成序列化,只是这需要用lex/yacc方面的咚咚,
tao,ice等都有原代码可借鉴。
2008-02-18 17:32 | 游客

# re: 返璞归真,网络传输——结构体还是序列化?  回复  更多评论   

简单的结构是可以,如果结构中有指针呢?或者有带虚函数的object呢?
2008-02-18 18:00 | giscn

# re: 返璞归真,网络传输——结构体还是序列化?  回复  更多评论   

游戏里面发送的数据,并不需要包含指针和虚函数的vtable,所以我才敢这样去做,仅仅是针对游戏数据通讯的需求而已。

不过这几天,再三思量,看来不能图一时痛快,太早这样断言
我的做法,前人应该也用过,目前可能够用,将来也未必如此。

为了扩展性和高质量的代码,我现在试试用自定义序列化的方式来做。
这两天认真阅读了看了前人写的一些服务器端的代码,感受颇多

只是觉得最大的问题是为了要提高效率还得要尽可能减少内存拷贝和动态内存的创建和销毁这也是个挑战。如果搞的不好,看似强大的序列化过程在负载严重的服务器上就没效率可言了。

我之前面的做法虽然不优雅,但读数据就是对象,少了解码就不存在内存拷贝的过程了,也无需额外再去创建一个对象,效率方面应该是很高的

所以...,还是想再好好想想一个折中的方案,结合各自的优点。

2008-02-19 08:41 | 李侃

# re: 返璞归真,网络传输——结构体还是序列化?  回复  更多评论   

如果没有指针和vtable, 两者的差别也不是特别大了,传结构体是可行的,你后面所说的如何减少动态创建和销毁开销确实是要点,也有办法解决。

折中的办法已经有啊:用序列化的语义,内部直接传内存块
2008-02-19 10:05 | giscn

# re: 返璞归真,网络传输——结构体还是序列化?  回复  更多评论   

跨机器传object始终都要用一种序列化的方式,直接传内存块只是实现细节,方法之一。你说的SmartStruct应该是一种OO的思路,基本的Object 就可作为SmartStruct,但这样做避免不了new delete, 如果将smartStruct 设计成union, 很显然局限性很大。传统的C语言的做法最直接,只要将繁琐的switch去掉即可
2008-02-19 10:21 | giscn

# re: 返璞归真,网络传输——结构体还是序列化?  回复  更多评论   

用序列化的语义,内部直接传内存块?

是不是:接收数据的时候,省略掉重新组装成对象这一步?这也是个思路

效率是高了,只是缺少了最终接收的对象,以字节的方式处理字节块的语意的逻辑就不很直观了,也是件繁琐的事情。
2008-02-20 23:32 | 李侃

# re: 返璞归真,网络传输——结构体还是序列化?  回复  更多评论   

这两天,我把序列化的库重新写了一遍,已经能支持基本数据类型,字符串和Stl的list vector set map 等等了...方便是方便,但想想,还是存在上面的问题,最终接收成对象的时候,少不了分配内存(创建对象) -> 数据拷贝->处理对象->释放对象 (我想这是个可能会成为将来的一个性能瓶颈),对此我真的是耿耿于怀

如果直接去处理语意的内存块,真的又很是麻烦。
2008-02-20 23:38 | 李侃

# re: 返璞归真,网络传输——结构体还是序列化?  回复  更多评论   

数据拷贝是不可避免的,而且一般对效率影响不大,主要还是内存管理上作一些优化,比如使用内存池。
2008-02-20 23:38 | eXile

# re: 返璞归真,网络传输——结构体还是序列化?  回复  更多评论   

恩,也是,最重要的是要找到重大的性能的瓶颈在哪里,也许我想太多了。
明天我再从群里找几个人来帮我看看这个帖,希望能多些经验方面的交流
2008-02-20 23:54 | 李侃

# re: 返璞归真,网络传输——结构体还是序列化?  回复  更多评论   

按需编程
结构体,简单快速
序列化,需要写序列化代码, 效率也不差
还有另外一种, 就是简单重新计算一下长度,
对于只有最后一个变长的包, 这样又快又简单

简单写个例子:
pk1
{
int a;
}

pk2
{
int a;
int b_len;
char b[10000];
}

pk3
{
AA a; //AA是复杂的变长因素类
}

pk1适合于结构体传送
pk2适合于改变一下包长度
pk3适合于序列化

那不如稍加改造
pk1
{
int a;
}

pk2
{
int a;
int b_len;
char b[10000];
int GetLength()
{
return sizeof(*this) - sizeof(b) + b_len;
}
}

pk3
{
AA a; //AA是复杂的变长因素类
void Serialize(Stream& s);
bool DeSerialize((Stream& s);
}

那么发消息函数为
template<T>
SendMsg (T* pkt)
{
__if_exist(T::Serialize)
{
Stream a;
pkt->Serialize(&a);
Send(a->GetBuffer(), a->GetBufferLength());
}
__if_not_exist(T::Serialize)
{
__if_exist(T::CalcLength)
{
Send(Pkt,pkt->GetLength());
}
__if_not_exist(T::CalcLength)
{
Send(Pkt, sizeof(T));
}
}
}
接受的时候反序列化就不写了, 类似的

这样不是又快有简单吗?
2009-01-11 13:06 | llxisdsh

# re: 返璞归真,网络传输——结构体还是序列化?  回复  更多评论   

补充一下:
上面的
Stream a;
可以写成静态变量 或者 线程静态变量, 这样就没有构建开销了

2009-01-11 13:21 | llxisdsh

# re: 返璞归真,网络传输——结构体还是序列化?  回复  更多评论   

如何方便健壮就怎么写,各有优缺,权衡不宜罢了.
2009-01-17 17:35 | timlly

# re: 返璞归真,网络传输——结构体还是序列化?  回复  更多评论   

这帖子快一年了,回头看看,还真是感触颇多啊
后来我还是自己写了一套序列化的库,完全支持stl + zlib字节压缩的的序列化库,最终还是没有采用结构体的方式

自己写的这套库工作了很久了,也是我的IO和网络流的底层,用起来还是很方便的,足够了
2009-01-18 10:55 | 李侃

# re: 返璞归真,网络传输——结构体还是序列化?  回复  更多评论   

随便搜索看到的这个文章,本意是想找个 Java CLIENT 和 C++服务端(使用结构体) 的 通信解决方案。

我都不明白,你们为什么在考虑 什么 效率??你们明白效率的 瓶颈在什么地方???难道你的效率 瓶颈不在 网卡处理能力,不在使用的网络带宽,反而在 内存上面???难道你的效率 是 SBB 的使用 单机完成的???高流量的应用,哪一个不是 分布式,冗余式??在 基础编程 水平一致的情况下。。你损失的那么点内存效率。。算什么,性能能相差多少??

搞不懂,程序是 人写的,是给人用的。不去讲究 快速开发,不去讲究 调试灵活,不去考虑设计是否能 迅速的在需求改变的时候 进行调整。不去考虑稳定性。偏偏非得考虑 杂七杂八的莫名其妙的因素。。

你可能有很好的编程水平。不过,你不是一个好的程序员。因为你考虑的方向错误了。而没有正确思维方式去写程序,充其量只是说是一个 高级码工。
2009-11-25 12:00 | XXXMAN

# re: 返璞归真,网络传输——结构体还是序列化?  回复  更多评论   

我也认为使用结构体传输不错。
2010-01-16 19:28 | KongQue

# re: 返璞归真,网络传输——结构体还是序列化?  回复  更多评论   

将数据包序列化后,怎么知道序列化后的长度。接收方有可能遇到粘包问题,怎么解决呢?是不是发送方在发送序列化数据之前还要在做些处理?
2010-06-13 22:04 | JustCodeIT

# re: 返璞归真,网络传输——结构体还是序列化?[未登录]  回复  更多评论   

XXXMAN 就是一个2B
2010-07-14 16:35 | TH

# re: 返璞归真,网络传输——结构体还是序列化?  回复  更多评论   

小弟看了上面各位大虾的评论,受益匪浅.但是我还是想不明白,如果按楼主说,只是为了效率,为什么在游戏中定义对像呢,直接用原始结构体+函数效率此不是很好,本人觉得效率是重要,但维护更重要的.
2010-10-10 22:45 | 倾风

# re: 返璞归真,网络传输——结构体还是序列化?[未登录]  回复  更多评论   

误人子弟。你不手动序列化但实现了ISerializible的对象在网络传输时还是会自动序列化的。比如在使用webservices数据传输时,大数据量的数据在手动序列化后的传输效率要比自动序列化快很多。
2011-06-30 10:23 | yuan

只有注册用户登录后才能发表评论。
网站导航: 博客园   IT新闻   BlogJava   知识库   博问   管理