大宝(sodme)的专栏 http://blog.donews.com/sodme 专注于Win/Linux平台高性能服务器开发(ACE,EPOLL,IOCP) Fri, 17 Feb 2006 02:41:00 +0000 http://wordpress.org/?v=2.9.2 en hourly 1 测试一下 http://blog.donews.com/sodme/archive/2006/02/17/729923.aspx http://blog.donews.com/sodme/archive/2006/02/17/729923.aspx#comments Fri, 17 Feb 2006 02:40:00 +0000 大宝(sodme) http://blog.donews.com/sodme/archive/2006/02/17/729923.aspx 订阅 服务器编程邮件列表
电子邮件:
浏览存于 邮件列表上 的所有帖子 ]]>
http://blog.donews.com/sodme/archive/2006/02/17/729923.aspx/feed 0
也许是习惯问题 http://blog.donews.com/sodme/archive/2005/09/02/536908.aspx http://blog.donews.com/sodme/archive/2005/09/02/536908.aspx#comments Fri, 02 Sep 2005 02:14:00 +0000 大宝(sodme) http://blog.donews.com/sodme/archive/2005/09/02/536908.aspx
  我喜欢看的blog是那种首页有简短摘要,看全文要另外点击打开的,特讨厌直接将全文发到首页显示很长的页面给人看的,一是感觉这种作法太野蛮,二是觉得作者没有很好照顾到诸位看官的视觉感受。文章刚发时,看官确实是希望看到文章全文的,但对于评论看官也是经常回来看的,如果你一个全文占了首页N屏的位置,我光是想去找到那个“评论”的点击就要找半天,实在是不方便,所以,偶还是提倡blog首页摘要的简短化,切勿直接将全文显示在首页。

]]>
http://blog.donews.com/sodme/archive/2005/09/02/536908.aspx/feed 0
清静的感觉,真好 http://blog.donews.com/sodme/archive/2005/09/02/536883.aspx http://blog.donews.com/sodme/archive/2005/09/02/536883.aspx#comments Fri, 02 Sep 2005 01:22:00 +0000 大宝(sodme) http://blog.donews.com/sodme/archive/2005/09/02/536883.aspx
  没有人知道,我在这里还有个窝,夜深人静的时候,听着窗外的虫鸣,看着静静的donews,这种感觉,真的很好。

  抛去浮华,扎实走过每一步!

]]>
http://blog.donews.com/sodme/archive/2005/09/02/536883.aspx/feed 0
CSDN同步发表—初尝结对编程的甜头 http://blog.donews.com/sodme/archive/2005/09/02/536874.aspx http://blog.donews.com/sodme/archive/2005/09/02/536874.aspx#comments Fri, 02 Sep 2005 01:02:00 +0000 大宝(sodme) http://blog.donews.com/sodme/archive/2005/09/02/536874.aspx 本文作者:sodme
本文出处:http://blog.csdn.net/sodme
声明:本文可以不经作者同意任意转载、复制、传播,但任何对本文的引用都请保留作者、出处及本声明信息。谢谢!

  这一两个星期,一直在跟客户端作代码整合和调试的事。在跟客户端联调之前,服务器端我已经自己写了一个模拟客户端与我自己的服务器进行逻辑测试,在数据包的处理流程上基本已经跑通。所以,剩下的活就是跟客户端的人员一起联调他们的逻辑和代码。

  联调的方式,形式上有点象结对编程,我是那个坐着旁边“只说不作”的人,真正写代码的仍然是客户端的人员,我主要负责帮客户端人员理清他们的逻辑和思路,对现有的架构和代码提出修改建议,同时“监督”客户端人员的编码过程,帮助其及时纠正由于思维“盲角”导致的小错误。这一两个星期下来,感觉颇好,同事之间的合作既轻松也愉快,在效率上感觉要明显比以前更快了。

  在XP编程的相互协作中,有几点感触想说出来:
  1. 不管是说的那个人,还是作的那个人,两者都得要习惯这种编程方式,虽然从形式上看是两个人用一台电脑在写程序可能会造成公司的人力资源成本耗费,但从产品质量和工作效率上来看,这样子写出来的代码在安全性和易维护性上都要比一个人写的要强得多。公司的开发成本,并不会因此而大大增加,相反,而是减少。有条件的项目组,可以试试。

  2. 结对编程,不一定是贯穿于项目始终的,或许,我们只是在项目进展到某一个阶段时,而临时采用了结对编程的方式。比如我所遇到的这种客户端与服务器端的代码整合工作,这项工作是很繁琐的,客户端人员和服务器端人员必须通过充分的交流和沟通,才能在数据包的执行逻辑上达成广泛的一致,从而在游戏逻辑上实现默契的相互配合。在这种整合工作中,要绝对避免服务器端的人员写好自己的代码后,对客户端的编码情况不管不问的情况出现,不管是基于团队合作的需要,还是基于项目的正常进展,任何一方在完成手头已有任务的情况下,都要积极介入另一方的开发为队友提供帮助。好的团队,不是天生就有的,是靠每一个团队成员,用“心”去培养的。

  3. 平等交流的原则。作为有时间积极思考的一方“坐在旁边的那个人”,由于他并不担负具体的编码工作,所以他的思路可能相对于实施具体编码的那个人要更为清晰一些,看的层面也要更深、更广一些。当你的设计思路与编码人员有冲突时,要本着互相尊重的原则平等交流,把你的设计思路尽可能地解释清楚,尽可能客观地分析彼此设计方案的优缺点,作出正常的评估。不要在思想上先入为主,片面否定对方,胸襟要开阔。

  4. 知错便改的良好修养。作为实施编码的人员,从劳动量上来看,或许你的劳动量要比只说不作的那个人劳动量更大一点,或许你会时不时的抱怨对方提出的方案是“说起来轻巧,作起来难”,但只要对方说的是合理的,就要尽可能地去完善和修正,将来有一天你手头上的事比较少时,你也同样可以参与对方那个模块的结对编程,那时你也可以作一个“只说不作的人”,所以,不必心理不平衡。^&^ 当对方对你的设计方案提出质疑时,你要客观地想一想对方说得对不对,出于本能,你可能总是试图说服对方自己的方案是对的,尽管你心里可能已经在悄悄否定自己的设计方案了。如果你的内心有这种倾向,强烈建议你不要再死撑了,你应该及时纠正自己的缺点,将方案变得更完善和合理。有错本身,并不是什么大不了的,关键的是,知错要改。最重要的是,同样的错误,不要屡错屡犯。

  5. 设计风格是各人自己的事,不必强求。也许很多公司都有自己的所谓的编码规范,但写代码的老鸟,基本上都会沿袭自己长久以来的编码习惯,很少有人专门为了迎合公司的编码规范而刻意去写。只要代码是易读的,结构是清晰的,逻辑是正确的,我觉得各种设计风格是大家每个人自己的事,不必去强求。如果在这一点上都一定要钉是钉,铆是铆的话,可能会让彼此都郁闷得很。因为,你不能说哪种风格就是错的,最多,你只能说哪种风格是更为易读的和易维护的。结对编程的双方,在编码风格上,如果比较相近更好,如果相差比较大,那要看是不是满足我上面说的三点“代码是易读的,结构是清晰的,逻辑是正确的”,如果满足,就各安天命,你走你的阳光道,我过我的独木桥,你不必强求我,我也不必强求于你。

  总而言之,结对编程,在水平相差不是太大的二者之间进行实施的话,工作起来我觉得还是很快乐的,呵呵。愿大家每个人每天都能以快乐的心情写代码!

]]>
http://blog.donews.com/sodme/archive/2005/09/02/536874.aspx/feed 0
作一名Google的忠实追随者?! http://blog.donews.com/sodme/archive/2005/09/01/535315.aspx http://blog.donews.com/sodme/archive/2005/09/01/535315.aspx#comments Thu, 01 Sep 2005 00:34:00 +0000 大宝(sodme) http://blog.donews.com/sodme/archive/2005/09/01/535315.aspx
  俗话说,这萝卜白菜,各有所爱。你用你的baidu,我用我的google,咱井水不犯河水,各得清静。但偏偏这个世界上就是存在那一拨人,或是受人之托,或是受人钱财,整日里尽干着那些颠倒黑白,扭曲事实的勾当,这种人的无耻之行径,甚至到了一个普通的google使用者所无法容忍的程度。

  早就听说,网上有个反百度联盟。看到整日里那些对google的肆意毁谤,自觉也需要成立一个google爱好者联盟与之相衬罢!

  无论别人怎么说,看看到底哪一个给你带来的帮助更大,收益更多,事实胜于雄辩。

  google即将进军中国,有能耐,baidu你用事实来说明一切吧?OK?请别再背后捅刀子了,行吗?尽管我知道说了这句也白说。

]]>
http://blog.donews.com/sodme/archive/2005/09/01/535315.aspx/feed 1
CSDN同步发表—泡泡堂、QQ堂游戏通信架构分析 http://blog.donews.com/sodme/archive/2005/08/31/535247.aspx http://blog.donews.com/sodme/archive/2005/08/31/535247.aspx#comments Wed, 31 Aug 2005 15:39:00 +0000 大宝(sodme) http://blog.donews.com/sodme/archive/2005/08/31/535247.aspx 本文作者:sodme
本文出处:http://blog.csdn.net/sodme
声明:本文可以不经作者同意任意转载、复制、引用。但任何对本文的引用,均须注明本文的作者、出处以及本行声明信息。

  提笔注:扫地只是偶的表面工作,偶的真实身份是作MMORPG。^_^

  之前,我分析过QQ游戏(特指QQ休闲平台,并非QQ堂,下同)的通信架构(http://blog.csdn.net/sodme/archive/2005/06/12/393165.aspx),分析过魔兽世界的通信架构(http://blog.csdn.net/sodme/archive/2005/06/18/397371.aspx),似乎网络游戏的通信架构也就是这些了,其实不然,在网络游戏大家庭中,还有一种类型的游戏我认为有必要把它的通信架构专门作个介绍,这便是如泡泡堂、QQ堂类的休闲类竞技游戏。曾经很多次,被网友们要求能抽时间看看泡泡堂之类游戏的通信架构,这次由于被逼交作业,所以今晚抽了一点的时间截了一下泡泡堂的包,正巧昨日与网友就泡泡堂类游戏的通信架构有过一番讨论,于是,将这两天的讨论、截包及思考总结于本文中,希望能对关心或者正在开发此类游戏的朋友有所帮助,如果要讨论具体的技术细节,请到我的BLOG(http://blog.csdn.net/sodme)加我的MSN讨论。

  总体来说,泡泡堂类游戏(此下简称泡泡堂)在大厅到房间这一层的通信架构,其结构与QQ游戏相当,甚至要比QQ游戏来得简单。所以,在房间这一层的通信架构上,我不想过多讨论,不清楚的朋友请参看我对QQ游戏通信架构的分析文章(http://blog.csdn.net/sodme/archive/2005/06/12/393165.aspx)。可以这么说,如果采用与QQ游戏相同的房间和大厅架构,是完全可以组建起一套可扩展的支持百万人在线的游戏系统的。也就是说,通过负载均衡+大厅+游戏房间对游戏逻辑的分摊,完全可以实现一个可扩展的百万人在线泡泡堂。

  但是,泡泡堂与斗地主的最大不同点在于:泡泡堂对于实时性要求特别高。那么,泡泡堂是如何解决实时性与网络延迟以及大用户量之间矛盾的呢?

  阅读以下文字前,请确认你已经完全理解TCP与UDP之间的不同点。

  我们知道,TCP与UDP之间的最大不同点在于:TCP是可靠连接的,而UDP是无连接的。如果通信双方使用TCP协议,那么他们之前必须事先通过监听+连接的方式将双方的通信管道建立起来;而如果通信双方使用的是UDP通信,则双方不用事先建立连接,发送方只管向目标地址上的目标端口发送UDP包即可,不用管对方到底收没收到。如果要说形象点,可以用这样一句话概括:TCP是打电话,UDP是发电报。TCP通信,为了保持这样的可靠连接,在可靠性上下了很多功夫,所以导致了它的通信效率要比UDP差很多,所以,一般地,在地实时性要求非常高的场合,会选择使用UDP协议,比如常见的动作射击类游戏。

  通过载包,我们发现泡泡堂中同时采用了TCP和UDP两种通信协议。并且,具有以下特点:
  1.当玩家未进入具体的游戏地图时,仅有TCP通信存在,而没有UDP通信;
  2.进入游戏地图后,TCP的通信量远远小于UDP的通信量

  以上是两个表面现象,下面我们来分析它的本质和内在。^&^

  泡泡堂的游戏逻辑,简单地可以归纳为以下几个方面:
  1.玩家移动
  2.玩家埋地雷(如果你觉得这种叫法比较土,你也可以叫它:下泡泡,呵呵)
  3.地雷爆炸出道具或者地雷爆炸困住另一玩家
  4.玩家捡道具或者玩家消灭/解救一被困的玩家

  与MMORPG一样,在上面的几个逻辑中,广播量最大的其实是玩家移动。为了保持玩家画面同步,其他玩家的每一步移动消息都要即时地发给其它玩家。

  通常,网络游戏的逻辑控制,绝大多数是在服务器端的。有时,为了保证画面的流畅性,我们会有意识地减少服务器端的逻辑判断量和广播量,当然,这个减少,是以“不危及游戏的安全运行”为前提的。到底如何在效率、流畅性和安全性之间作取舍,很多时候是需要经验积累的,效率提高的过程,就是逻辑不断优化的过程。不过,有一个原则是可以说的,那就是:“关键逻辑”一定要放在服务器上来判断。那么,什么是“关键逻辑”呢?

  拿泡泡堂来说,下面的这个逻辑,我认为就是关键逻辑:玩家在某处埋下一颗地雷,地雷爆炸后到底能不能炸出道具以及炸出了哪些道具,这个信息,需要服务器来给。那么,什么又是“非关键逻辑”呢?

  “非关键逻辑”,在不同的游戏中,会有不同的概念。在通常的MMORPG中,玩家移动逻辑的判断,是算作关键逻辑的,否则,如果服务器端不对客户端发过来的移动包进行判断那就很容易造成玩家的瞬移以及其它毁灭性的灾难。而在泡泡堂中,玩家移动逻辑到底应不应该算作关键逻辑还是值得考虑的。泡泡堂中的玩家可以取胜的方法,通常是确实因为打得好而赢得胜利,不会因为瞬移而赢得胜利,因为如果外挂要作泡泡堂的瞬移,它需要考虑的因素和判断的逻辑太多了,由于比赛进程的瞬息万变,外挂的瞬移点判断不一定就比真正的玩家来得准确,所在,在玩家移动这个逻辑上使用外挂,在泡泡堂这样的游戏中通常是得不偿失的(当然,那种特别变态的高智能的外挂除外)。从目前我查到的消息来看,泡泡堂的外挂多数是一些按键精灵脚本,它的本质还不是完全的游戏机器人,并不是通过纯粹的协议接管实现的外挂功能。这也从反面验证了我以上的想法。

  说到这里,也许你已经明白了。是的!TCP通信负责“关键逻辑”,而UDP通信负责“非关键逻辑”,这里的“非关键逻辑”中就包含了玩家移动。在泡泡堂中,TCP通信用于本地玩家与服务器之间的通信,而UDP则用于本地玩家与同一地图中的其他各玩家的通信。当本地玩家要移动时,它会同时向同一地图内的所有玩家广播自己的移动消息,其他玩家收到这个消息后会更新自己的游戏画面以实现画面同步。而当本地玩家要在地图上放置一个炸弹时,本地玩家需要将此消息同时通知同一地图内的其他玩家以及服务器,甚至这里,可以不把放置炸弹的消息通知给服务器,而仅仅通知其他玩家。当炸弹爆炸后,要拾取物品时才向服务器提交拾取物品的消息。

  那么,你可能会问,“地图上某一点是否存在道具”这个消息,服务器是什么时候通知给客户端的呢?这个问题,可以有两种解决方案:
  1.客户端如果在放置炸弹时,将放置炸弹的消息通知给服务器,服务器可以在收到这个消息后,告诉客户端炸弹爆炸后会有哪些道具。但我觉得这种方案不好,因为这样作会增加游戏运行过程中的数据流量。
  2.而这第2种方案就是,客户端进入地图后,游戏刚开始时,就由服务器将本地图内的各道具所在点的信息传给各客户端,这样,可以省去两方面的开销:a.客户端放炸弹时,可以不通知服务器而只通知其它玩家;b.服务器也不用在游戏运行过程中再向客户端传递有关某点有道具的信息。
  
  但是,不管采用哪种方案,服务器上都应该保留一份本地图内道具所在点的信息。因为服务器要用它来验证一个关键逻辑:玩家拾取道具。当玩家要在某点拾取道具时,服务器必须要判定此点是否有道具,否则,外挂可以通过频繁地发拾取道具的包而不断取得道具。

  至于泡泡堂其它游戏逻辑的实现方法,我想,还是要依靠这个原则:首先判断这个逻辑是关键逻辑吗?如果不全是,那其中的哪部分是非关键逻辑呢?对于非关键逻辑,都可以交由客户端之间(UDP)去自行完成。而对于关键逻辑,则必须要有服务器(TCP)的校验和认证。这便是我要说的。

  以上仅仅是在理论上探讨关于泡泡堂类游戏在通信架构上的可能作法,这些想法是没有事实依据的,所有结论皆来源于对封包的分析以及个人经验,文章的内容和观点可能跟真实的泡泡堂通信架构实现有相当大的差异,但我想,这并不是主要的,因为我的目的是向大家介绍这样的TCP和UDP通信并存情况下,如何对游戏逻辑的进行取舍和划分。无论是“关键逻辑”的定性,还是“玩家移动”的具体实施,都需要开发者在具体的实践中进行总结和优化。此文全当是一个引子罢,如有疑问,请加Msn讨论。

]]>
http://blog.donews.com/sodme/archive/2005/08/31/535247.aspx/feed 1
CSDN旧文备份—逆向思维之魔兽世界封包分析(2) http://blog.donews.com/sodme/archive/2005/08/31/535241.aspx http://blog.donews.com/sodme/archive/2005/08/31/535241.aspx#comments Wed, 31 Aug 2005 15:37:00 +0000 大宝(sodme) http://blog.donews.com/sodme/archive/2005/08/31/535241.aspx 本文作者:sodme
本文出处:http://blog.csdn.net/sodme
声明:本文可以不经作者同意任意转载、复制、传播,但任何对本文的引用均须注明本文作者、出处及本行声明信息。谢谢!

  封包分析的手段,说简单也挺简单的,那就是:比较!要不断地从不同的思维角度对封包进行对比分析,要充分发挥你的想象力不断地截取自己需要的包进行比较。不仅要作横向(同类)的比较,还要作纵向(不同类)的比较。即时对于同一个包,也要不断地反复研究。

  初涉封包分析的新手,一般会不知道封包分析究竟该从何入手。基于经验,本文将告诉你一般会从哪些类型的包入手进行分析以及应该怎样对封包进行初步的分析。需要指出的是:封包分析是一件非常有趣但同时也非常考验耐心的事,通常,半天的封包分析下来,会让你眼前全是诸如“B0 EF 58 02 10 72….”之类的网络数据,而且附带有头疼、头晕症状,所以,没有充分的心理准备,还请不要轻易尝试。呵呵。

  从事封包分析的基本前提是:应该了解和熟悉TCP协议,并知道数据包“粘合”是怎么一回事。当然,我们平常截获到的包,从数量上来看,只有一小部分是属于“粘合”的情况。但如果不了解它,将可能会对你的分析思路产生误导和困惑。关于“粘包”的更详细解释,请参考我的另外一篇文章“拼包函数及网络封包的异常处理(含代码) (http://blog.csdn.net/sodme/archive/2005/07/10/419233.aspx)”。

  上一篇有关魔兽世界封包分析的文章(http://blog.csdn.net/sodme/archive/2005/06/18/397371.aspx)中,我根据客户端与服务器端连接及断开事件的处理流程以及登录过程中的一些数据包分析了魔兽的架构和登录逻辑。这篇文章中,我将结合聊天数据包的分析,来阐述魔兽世界封包的大体结构。  

  首先解释一下我们的目标:封包的大体结构。封包的大体结构包含哪些内容呢?一般情况下,封包的大体结构至少包括两方面的信息:
  1、一个封包是如何表示它的长度的?封包长度是由哪个字段表示的?(或者说:如何表示封包的开始和结束的)
  2、各种不同的封包类型是通过哪个字段表示的?

  是不是所有游戏的封包都必然会有表示“长度”信息的“字段”呢?答案是否定的。有的游戏确实没有采用这种方式,它们的作法设定特殊的包开始和包结束标志。但是,从应用的角度来看,偶推荐使用“长度”这样的方法,因为不管在网络底层的处理效率以及上层应用的处理便捷性来说,使用“长度”字段标识一个完整的逻辑包都是比较好的办法。在确定了封包的大体结构后,我们才方便分析具体类型包(比如聊天、行走等)的详细结构。

  作数据包分析,在单纯采用黑箱分析的阶段,我们选取的数据包,须要是具有这种性质的,即:在数据包发送前客户端未进行加密等处理时,这个数据包中的部分内容,我们是已经知道的。这样的包,就可以作为封包分析的突破口。这样看来,我们拿“聊天封包”作为第一个分析对象也就不难理解了,因为我们说的话,打上去的字,我们自己是知道的,但是我们说的话经过客户端的处理后,发到网络上的可能就是已经加了密的或者加了校验码的。站在黑箱分析的角度,我们能作的,就是不断截取各种“聊天包”进行对比、判断和总结。

  OK,打开你的commview。让我们从“聊天封包”开始。

  分析“聊天包”的前提,是我们能够正常判断哪种类型的数据包是属于聊天的,不要误把行走或其它的数据包当作了聊天数据包。为了减小分析难度,建议新手到游戏中人少或周围没有玩家的地方进行封包分析。这样一来没人打扰,二来你的网络通信量会相对小得多,比较容易进行一些封包判定。

  第一步,我们需要确定客户端与服务器通信所用的端口,然后在commview的rules->ports中设定服务器端口,截获与该端口通信的所有数据包。服务器端口的确定方法:不要使用其它网络通信工具,打开commview,进游戏,截包,观察其通信端口。进行封包分析时,特别是初期的封包分析时,你的网络通信应该尽可能是单一的,即:除了游戏,其它的通信软件尽可能不要开。但当你确定了服务器的IP和端口后,就可以照常使用其它网络软件了。

  第二步,如前面述,在游戏中找个人少或没人的地方,开始“自言自语”,呵呵。说话的内容,建议以字母和数字为宜,不要说中文。因为中文是双字节的,而字母和数字是单字节的,对于单字节的信息内容,截包软件会以单字节的文本信息显示,但对于双字节的汉字而言,截包软件在对其进行显示时由于换行等原因会造成部分中文显示有乱码,不容易直接看出中文内容。如果执意要说中文,偶也不拦你,给你推荐一个工具:String Demander(下载地址:http://www.cnxhacker.com/Download/show/395.html),这个软件,可以查询中文所对应的编码。

  第三步,设定好commview的rules并使之生效,开始截包。

  观察通过以上的过程所截的包,可以发现,魔兽世界的聊天封包的说话内容是明文的!这一点,用不着大惊小怪,呵呵。聊天封包本身并不会对游戏的关键逻辑造成损害,所以,即使让其明文显示也不足为奇。但是,我们还是不太相信自己的眼睛,于是再截若干个包,发现包中的说话内容确实是明文的!但是,包的其它字段却是我们一时看不懂的“密文”。

  看来,下面的事情就是对这些包里的“密文”进行研究了。一般情况下,这种“密文”的加密方法,通过封包分析是分析不出来的,但,我们仍然可以通过封包分析来推论一些与“密文”生成算法有关的问题。我们可以作以下的对比分析:
  1、连续三次输入“a”,并分别观察及保存封包数据;
  2、连续三次输入“aa”,并分别观察及保存封包数据;
  3、连续三次输入“aaa”,并分别观察及保存封包数据。

  输入的封包用例,我们选择了字母"a",它的ASCII码是61。输入的规律是:每种情况连续输入三次,然后逐次增加a字母的个数。于是,我们发现这样一个有趣的现象:
  1、包中有关说话的内容是明文的;
  2、即使针对于同样的说话内容,比如“a”,客户端所发出去的包也是不一样的;
  3、当一次说话的字母个数增加1时,封包的总体长度也随之增加1;
  4、除每个封包的前面6个字节以及说话的字节外,其余的封包内容每次都一样;
  5、每个聊天封包的结尾字节都是0。

  于是,我们可以试着得出如下结论:
  1、包是没有压缩的,它所使用的加密算法应该是按字节进行的,并没有改变封包的长度使之看上去使用统一的长度;
  2、包是以0结尾的(尽管我们不知道它是以什么表示开头的,呵呵);
  3、封包加密算法中所使用的密钥是可变的,即针对于相同的数据包内容由于加密的密钥不同,所以产生的密文也不同。由于客户端的数据传到服务器端后,服务器端还要对数据进行解密。所以,客户端的加密算法与服务器端的解密算法应该共用了前6字节中的某些内容,以此作为解密算法的密钥。如果这6字节中没有包含有关封包加、解密所需要的同步数据,那客户端和服务器之间应该会通过其它的方式同步这样的数据。不过,偶倾向于前者,即:这6字节中应该含有加、解密所需要的密钥信息。

  回头看我们上面观察到的有趣现象,针对于第2点,反过来想,这应该也是最起码的功能了。就是说,即使客户端作出的是同样的动作,在客户端发出的包中,包的内容也是不一样的。这样,外挂就不能靠单纯的重复发相同的包而达到其目的了。

  分析来分析去,我们还是没能确定魔兽封包的大体结构。其实,到现在,我觉得我此文的目的已经达到了,即向大家展示封包分析的思维角度和思维方式。至于具体结果,偶觉得倒真的不重要的了。可以肯定地告诉大家的是,魔兽的封包结构偶大致已经掌握了。偶仅在此公布我的分析结果:
  1、魔兽的封包长度字段是每个封包的前两字节,它的表示方式是:前两字节的数值+2。之所以加这个2,是因为封包长度字段本身占用了两个字节的长度。
  2、魔兽的封包类型偶推断是第三和第四字节,其中普通聊天的类型标识是“95 00”。

  请不要来信向我询问任何有关魔兽封包破解的内容,偶能说的都已经在文章里说了,偶之所以写这个系列的文章不是想破解魔兽,而是想以这样优秀的一款游戏作为案例来向大家展示它在封包设计方面值得我们学习和讨论的地方,同时向更多的朋友普及有关封包分析的常识、工具以及思维方式,仅此而已。

  ps:由于每次封包分析的内容都很多,所以,一有了点结论后,要及时记录和总结,并与之前取得的总结进行对比,及时更新相关的记录文档。

]]>
http://blog.donews.com/sodme/archive/2005/08/31/535241.aspx/feed 0
CSDN旧文备份—逆向思维之魔兽世界封包分析(1) http://blog.donews.com/sodme/archive/2005/08/31/535238.aspx http://blog.donews.com/sodme/archive/2005/08/31/535238.aspx#comments Wed, 31 Aug 2005 15:36:00 +0000 大宝(sodme) http://blog.donews.com/sodme/archive/2005/08/31/535238.aspx 本文作者:sodme
本文出处:http://blog.csdn.net/sodme
声明:本文可以不经作者同意任意转载,但任何对本文的引用都须注明作者、出处及此声明信息。谢谢!!

  特别声明:
  本人非常欣赏暴雪及他们的游戏,之所以写这个文章,是想让大家了解一些网络封包分析方面的常见方法以及学习暴雪游戏在网络处理方面的经验,偶认为作为一个网络编程者,熟练掌握封包分析的工具和方法应该是其基本功之一。本文所列的所有封包分析内容,全部是采用普通黑箱方式即可得来的,并未涉及对魔兽世界可执行程序的逆向工程。同时,除此文涉及的内容外,本人拒绝向任何人透露更详细的关于魔兽世界封包方面的更多内容,有兴趣者请自己进行相关的试验,本人在此文中也将尽量避免公开敏感的封包内容及相关加解密算法。谨以此文献给忠爱的暴雪!

  一、登录模块流程及封包分析

  我们先看登录流程。从封包流程来看,魔兽的登录流程是这样的:

  1.由Client向登录/账号服务器(Login Server)发送用户名及密码等信息。此数据包的最后部分是用户名(明文表示,未加密),在用户名的前一个字节表示的是用户名的长度。登录/账号服务器向Client返回登录成功及后续连接到游戏服务器服务器所必备的信息等。这中间的两个来往数据包,我还没有看出具体有什么作用。在这个交互过程中,由登录/账号服务器向Client发送所有的游戏服务器列表,服务器列表数据包的内容包括:ip, port, 服务器上所拥有的角色个数等信息,因服务器列表内容过多,被客户端分为两次接收完毕。

  2.Client收到Login Server的服务器列表后,根据最近访问的服务器标识(这个信息应该是包含在那个服务器列表数据包中),连接到最近游戏的那个游戏服务器(Game Server)。连接成功后,Game Server首先向Client发送一个8字节的数据包,据以往的常识判断,这个数据包的内容很可能是以后客户端与服务器通信的加密密钥。

  3.Client向Game Server再次发送自己的账号信息。Game Server与Client经过两个数据包的交互后,向Client发送角色数据包,此包中包括了玩家在该Game Server所创建的所有角色信息,当然这个信息只是部分的,并不是该角色的所有信息。

  4.在此后的通信过程中,Client每隔30秒向Game Server发送一个保持连接的包,该包长度为10字节,包的最后四字节是一个递增数字,前面6字节暂时未看出规律。

  5.只要Client没有点击某个角色进入最终的Game Server,则Client要始终与Login Server保持连接。当Client点击角色进入Game Server时,Client才与Login Server断开连接。在以后的游戏过程中,Client始终与且仅与该Game Server进行数据通信。

  通过对登录流程中的数据包初步分析,可以得出以下几个结论:
  1.Client向Login Server发的第一个数据包,用户名部分是采用明文的,且该数据包的内容,每次登录都一样,并没有因时间的不同而发生改变。由此可以推算:针对于此数据包中的密码加密算法是固定不变的,换句话说,密码的加密算法是比较容易通过逆向工程被找到的。偶认为,针对于此处,服务器也应该先向客户端发送一个加密密钥,以后的通信可以用该密钥作为安全验证的依据。但暴雪没有这样作,最大的可能是为了提高服务器的效率,在登录服务器上,如果每个客户端一旦连接成功,登录服务器都得向客户端广播一个数据包的话,可能这个量还是比较大的,这可能延长了玩家的登录等待时间,所以他们没有在这块作。

  2.Client在登录Login Server的地址,每次Login Server的登录地址都可能是不一样的。偶没有在客户端目录里找到这些地址,只在客户端目录里找到了四个大区的四个域名,我猜想,魔兽世界是用的DNS解析的简单方法来实现Login Server的简单动态均衡的。不知道这个猜想是否正确。

  3.“根据玩家最近在玩的哪个游戏,由客户端和服务器自动为玩家选择进入这个游戏服务器”,这一项设定充分体现了暴雪一贯的风格:为玩家着想,最大限度地提高游戏的舒适度。再次对暴雪的态度予以肯定!

  4.一旦玩家进入了游戏世界,客户端与服务器的通信端口会一直保持不变。即:魔兽世界的游戏世界服务器群设计结构采用的是带网关的服务器集群。

  5.偶觉得在整个的登录流程中,让我产生最大疑问的就是Login Server与Client的连接保持逻辑。当Client与Game Server连接了之后,Client并未与Login Server断开,是一直保持连接的。后来,经进一步的抓包分析,Client之所以要与Login Server保持这样的连接,是为了当Client重新选择服务器时,不至于重新连接Login Server。当Client点击了"选择服务器"按纽后,Login Server会每隔5秒向Client发一个当前所有的服务器列表数据包。要知道,这个服务器列表数据包的内容可是非常大的,如果有玩家就打开了这个窗口不关闭,Login Server向这种情况的所有玩家每5秒钟就发一个服务器列表数据包,这个广播量可是很大的哦(2k左右,这可是一个用户是2k哦)。偶认为这里的逻辑设计是相当不合理的。Login Server如果为了给客户端提供一个最新的全局服务器列表,可以保持连接,但也没必要每隔5秒就向客户端发一个服务器列表,最多只在客户端在某个服务器上创建了不同的角色后再更新这个列表也是可以的,但只用更新这个列表中的变化内容即可,不用发全部的完整包,这样,在通信量上就小了很多。据说,魔兽刚开始的时候,产生DOWN机的原因就是登录模块没有处理好,偶不知道现在的这个情况是不是已经经过改良的了。但偶还是认为每隔5秒就向客户端发送一个2K的包,这一点是不可以被接受的。

  以上只是针对于魔兽世界登录流程的简单分析,没有多少技术含量,拿出来跟大家相互讨论讨论,看看有没有可以借鉴的地方,后面还会有其它部分的封包分析。欢迎继续关注偶的Blog: http://blog.csdn.net/sodme

  偶在文章前面部分说过,作为一个网络编程人员,熟练使用截包软件和掌握基本的封包分析方法是其基本能力之一,发此文的目的一个原因也是希望向正在作网络编程的兄弟介绍一下相关工具的使用和常见的分析方法。下面补充一下关于封包分析的基本方法和相关工具:

  1.你需要一个截包工具,偶推荐:commview,小巧但功能强大,支持自定义的封包分析插件以DLL形式装载,也就是说只要你愿意,你可以写个DLL对某类特殊形式的包进行显示、记录、解密等特别处理。

  2.如何查看真正的封包数据。在commview里,会详细列出自网卡级别以上的各层封包数据,包括Ethernet层,IP层和TCP层。而我们作封包分析时,只需要关注TCP层。但TCP层里也有很多内容,对于我们的分析需求来说,我们需要关注的是其Data字段(在协议目录里可以看到"data length标识,点击即可查看data段")的内容。

  3.TCP的几个状态对于我们分析所起的作用。在TCP层,有个FLAGS字段,这个字段有以下几个标识:SYN, FIN, ACK, PSH, RST, URG.其中,对于我们日常的分析有用的就是前面的五个字段。它们的含义是:SYN表示建立连接,FIN表示关闭连接,ACK表示响应,PSH表示有DATA数据传输,RST表示连接重置。其中,ACK是可能与SYN,FIN等同时使用的,比如SYN和ACK可能同时为1,它表示的就是建立连接之后的响应,如果只是单个的一个SYN,它表示的只是建立连接。TCP的几次握手就是通过这样的ACK表现出来的。但SYN与FIN是不会同时为1的,因为前者表示的是建立连接,而后者表示的是断开连接。RST一般是在FIN之后才会出现为1的情况,表示的是连接重置。一般地,当出现FIN包或RST包时,我们便认为客户端与服务器端断开了连接;而当出现SYN和SYN+ACK包时,我们认为客户端与服务器建立了一个连接。PSH为1的情况,一般只出现在DATA内容不为0的包中,也就是说PSH为1表示的是有真正的TCP数据包内容被传递。TCP的连接建立和连接关闭,都是通过请求-响应的模式完成的。

  <未完待续>

]]>
http://blog.donews.com/sodme/archive/2005/08/31/535238.aspx/feed 1
CSDN旧文备份—完成端口中的单句柄数据结构与单IO数据结构的理解与设计 http://blog.donews.com/sodme/archive/2005/08/31/535235.aspx http://blog.donews.com/sodme/archive/2005/08/31/535235.aspx#comments Wed, 31 Aug 2005 15:34:00 +0000 大宝(sodme) http://blog.donews.com/sodme/archive/2005/08/31/535235.aspx 本文作者:sodme
本文出处:http://blog.csdn.net/sodme
声明:本文可以不经作者同意任意转载、复制、传播,但任何对本文的引用均须保留本文的作者、出处及本行声明信息!谢谢!

  完成端口模型,针对于WIN平台的其它异步网络模型而言,最大的好处,除了性能方面的卓越外,还在于完成端口在传递网络事件的通知时,可以一并传递与此事件相关的应用层数据。这个应用层数据,体现在两个方面:一是单句柄数据,二是单IO数据。

  GetQueuedCompletionStatus函数的原型如下:
  WINBASEAPI
  BOOL
  WINAPI
  GetQueuedCompletionStatus(
      IN  HANDLE CompletionPort,
      OUT LPDWORD lpNumberOfBytesTransferred,
      OUT PULONG_PTR lpCompletionKey,
      OUT LPOVERLAPPED *lpOverlapped,
      IN  DWORD dwMilliseconds
     );
  其中,我们把第三个参数lpCompletionKey称为完成键,由它传递的数据称为单句柄数据。我们把第四个参数lpOverlapped称为重叠结构体,由它传递的数据称为单IO数据。

  以字面的意思来理解,lpCompletionKey内包容的东西应该是与各个socket一一对应的,而lpOverlapped是与每一次的wsarecv或wsasend操作一一对应的。

  在网络模型的常见设计中,当一个客户端连接到服务器后,服务器会通过accept或AcceptEx创建一个socket,而应用层为了保存与此socket相关的其它信息(比如:该socket所对应的sockaddr_in结构体数据,该结构体内含客户端IP等信息,以及为便于客户端的逻辑包整理而准备的数据整理缓冲区等),往往需要创建一个与该socket一一对应的客户端底层通信对象,这个对象可以负责保存仅在网络层需要处理的数据成员和方法,然后我们需要将此客户端底层通信对象放入一个类似于list或map的容器中,待到需要使用的时候,使用容器的查找算法根据socket值找到它所对应的对象然后进行我们所需要的操作。

  让人非常高兴的是,完成端口“体贴入微”,它已经帮我们在每次的完成事件通知时,稍带着把该socket所对应的底层通信对象的指针送给了我们,这个指针就是lpCompletionKey。也就是说,当我们从GetQueuedCompletionStatus函数取得一个数据接收完成的通知,需要将此次收到的数据放到该socket所对应的通信对象整理缓冲区内对数据进行整理时,我们已经不需要去执行list或map等的查找算法,而是可以直接定位这个对象了,当客户端连接量很大时,频繁查表还是很影响效率的。哇哦,太帅了,不是吗?呵呵。

  基于以上的认识,我们的lpCompletionKey对象可以设计如下:
  typedef struct PER_HANDLE_DATA
  {
    SOCKET socket;             //本结构体对应的socket值
    sockaddr_in addr;          //用于存放客户端IP等信息
    char DataBuf[ 2*MAX_BUFFER_SIZE ];  //整理缓冲区,用于存放每次整理时的数据
  }

  PER_HANDLE_DATA与socket的绑定,通过CreateIOCompletionPort完成,将该结构体地址作为该函数的第三个参数传入即可。而PER_HANDLE_DATA结构体中addr成员,是在accept执行成功后进行赋值的。DataBuf则可以在每次WSARecv操作完成,需要整理缓冲区数据时使用。

  下面我们再来看看完成端口的收、发操作中所使用到的重叠结构体OVERLAPPED。

  关于重叠IO的知识,请自行GOOGLE相关资料。简单地说,OVERLAPPED是应用层与核心层交互共享的数据单元,如果要执行一个重叠IO操作,必须带有OVERLAPPED结构。在完成端口中,它允许应用层对OVERLAPPED结构进行扩展和自定义,允许应用层根据自己的需要在OVERLAPPED的基础上形成新的扩展OVERLAPPED结构。一般地,扩展的OVERLAPPED结构中,要求放在第一个的数据成员是原OVERLAPPED结构。我们可以形如以下方式定义自己的扩展OVERLAPPED结构:
  typedef struct PER_IO_DATA
  {
    OVERLAPPED ovl;
    WSABUF           buf;
    char                    RecvDataBuf[ MAX_BUFFER_SIZE ];   //接收缓冲区
    char                    SendDataBuf[ MAX_BUFFER_SIZE ];   //发送缓冲区
    OpType              opType;                                                       //操作类型:发送、接收或关闭等
  }
  
  在执行WSASend和WSARecv操作时,应用层会将扩展OVERLAPPED结构的地址传给核心,核心完成相应的操作后,仍然通过原有的这个结构传递操作结果,比如“接收”操作完成后,RecvDataBuf里存放便是此次接收下来的数据。

  根据各自应用的不同,不同的完成端口设计者可能会设计出不同的PER_HANDLE_DATA
和PER_IO_DATA,我这里给出的设计也只是针对自己的应用场合的,不一定就适合你。但我想,最主要的还是要搞明白PER_HANDLE_DATA和PER_IO_DATA两种结构体的含义、用途,以及调用流程。

]]>
http://blog.donews.com/sodme/archive/2005/08/31/535235.aspx/feed 0
CSDN旧文备份—拼包函数及网络封包的异常处理(含代码) http://blog.donews.com/sodme/archive/2005/08/31/535234.aspx http://blog.donews.com/sodme/archive/2005/08/31/535234.aspx#comments Wed, 31 Aug 2005 15:33:00 +0000 大宝(sodme) http://blog.donews.com/sodme/archive/2005/08/31/535234.aspx 本文作者:sodme
本文出处:http://blog.csdn.net/sodme
声明:本文可以不经作者同意任意转载、复制、传播,但任何对本文的引用都请保留作者、出处及本声明信息。谢谢!

  常见的网络服务器,基本上是7*24小时运转的,对于网游来说,至少要求服务器要能连续工作一周以上的时间并保证不出现服务器崩溃这样的灾难性事件。事实上,要求一个服务器在连续的满负荷运转下不出任何异常,要求它设计的近乎完美,这几乎是不太现实的。服务器本身可以出异常(但要尽可能少得出),但是,服务器本身应该被设计得足以健壮,“小病小灾”打不垮它,这就要求服务器在异常处理方面要下很多功夫。

  服务器的异常处理包括的内容非常广泛,本文仅就在网络封包方面出现的异常作一讨论,希望能对正从事相关工作的朋友有所帮助。

  关于网络封包方面的异常,总体来说,可以分为两大类:一是封包格式出现异常;二是封包内容(即封包数据)出现异常。在封包格式的异常处理方面,我们在最底端的网络数据包接收模块便可以加以处理。而对于封包数据内容出现的异常,只有依靠游戏本身的逻辑去加以判定和检验。游戏逻辑方面的异常处理,是随每个游戏的不同而不同的,所以,本文随后的内容将重点阐述在网络数据包接收模块中的异常处理。

  为方便以下的讨论,先明确两个概念(这两个概念是为了叙述方面,笔者自行取的,并无标准可言):
  1、逻辑包:指的是在应用层提交的数据包,一个完整的逻辑包可以表示一个确切的逻辑意义。比如登录包,它里面就可以含有用户名字段和密码字段。尽管它看上去也是一段缓冲区数据,但这个缓冲区里的各个区间是代表一定的逻辑意义的。
  2、物理包:指的是使用recv(recvfrom)或wsarecv(wsarecvfrom)从网络底层接收到的数据包,这样收到的一个数据包,能不能表示一个完整的逻辑意义,要取决于它是通过UDP类的“数据报协议”发的包还是通过TCP类的“流协议”发的包。

  我们知道,TCP是流协议,“流协议”与“数据报协议”的不同点在于:“数据报协议”中的一个网络包本身就是一个完整的逻辑包,也就是说,在应用层使用sendto发送了一个逻辑包之后,在接收端通过recvfrom接收到的就是刚才使用sendto发送的那个逻辑包,这个包不会被分开发送,也不会与其它的包放在一起发送。但对于TCP而言,TCP会根据网络状况和neagle算法,或者将一个逻辑包单独发送,或者将一个逻辑包分成若干次发送,或者会将若干个逻辑包合在一起发送出去。正因为TCP在逻辑包处理方面的这种粘合性,要求我们在作基于TCP的应用时,一般都要编写相应的拼包、解包代码。

  因此,基于TCP的上层应用,一般都要定义自己的包格式。TCP的封包定义中,除了具体的数据内容所代表的逻辑意义之外,第一步就是要确定以何种方式表示当前包的开始和结束。通常情况下,表示一个TCP逻辑包的开始和结束有两种方式:
  1、以特殊的开始和结束标志表示,比如FF00表示开始,00FF表示结束。
  2、直接以包长度来表示。比如可以用第一个字节表示包总长度,如果觉得这样的话包比较小,也可以用两个字节表示包长度。

  下面将要给出的代码是以第2种方式定义的数据包,包长度以每个封包的前两个字节表示。我将结合着代码给出相关的解释和说明。

  函数中用到的变量说明:

  CLIENT_BUFFER_SIZE:缓冲区的长度,定义为:Const int CLIENT_BUFFER_SIZE=4096。
  m_ClientDataBuf:数据整理缓冲区,每次收到的数据,都会先被复制到这个缓冲区的末尾,然后由下面的整理函数对这个缓冲区进行整理。它的定义是:char m_ClientDataBuf[2* CLIENT_BUFFER_SIZE]。
  m_DataBufByteCount:数据整理缓冲区中当前剩余的未整理字节数。
  GetPacketLen(const char*):函数,可以根据传入的缓冲区首址按照应用层协议取出当前逻辑包的长度。
  GetGamePacket(const char*, int):函数,可以根据传入的缓冲区生成相应的游戏逻辑数据包。
  AddToExeList(PBaseGamePacket):函数,将指定的游戏逻辑数据包加入待处理的游戏逻辑数据包队列中,等待逻辑处理线程对其进行处理。
  DATA_POS:指的是除了包长度、包类型等这些标志型字段之外,真正的数据包内容的起始位置。

Bool SplitFun(const char* pData,const int &len)
{
    PBaseGamePacket pGamePacket=NULL;
    __int64 startPos=0, prePos=0, i=0;
    int packetLen=0;

  //先将本次收到的数据复制到整理缓冲区尾部
    startPos = m_DataBufByteCount;  
    memcpy( m_ClientDataBuf+startPos, pData, len );
    m_DataBufByteCount += len;   

    //当整理缓冲区内的字节数少于DATA_POS字节时,取不到长度信息则退出
 //注意:退出时并不置m_DataBufByteCount为0
    if (m_DataBufByteCount < DATA_POS+1)
        return false; 

    //根据正常逻辑,下面的情况不可能出现,为稳妥起见,还是加上
    if (m_DataBufByteCount >  2*CLIENT_BUFFER_SIZE)
    {
        //设置m_DataBufByteCount为0,意味着丢弃缓冲区中的现有数据
        m_DataBufByteCount = 0;

  //可以考虑开放错误格式数据包的处理接口,处理逻辑交给上层
  //OnPacketError()
        return false;
    }

     //还原起始指针
     startPos = 0;

     //只有当m_ClientDataBuf中的字节个数大于最小包长度时才能执行此语句
    packetLen = GetPacketLen( pIOCPClient->m_ClientDataBuf );

    //当逻辑层的包长度不合法时,则直接丢弃该包
    if ((packetLen < DATA_POS+1) || (packetLen > 2*CLIENT_BUFFER_SIZE))
    {
        m_DataBufByteCount = 0;

  //OnPacketError()
        return false;
    }

    //保留整理缓冲区的末尾指针
    __int64 oldlen = m_DataBufByteCount; 

    while ((packetLen <= m_DataBufByteCount) && (m_DataBufByteCount>0))
    {
        //调用拼包逻辑,获取该缓冲区数据对应的数据包
        pGamePacket = GetGamePacket(m_ClientDataBuf+startPos, packetLen); 

        if (pGamePacket!=NULL)
        {
            //将数据包加入执行队列
            AddToExeList(pGamePacket);
        }

        pGamePacket = NULL;
 
  //整理缓冲区的剩余字节数和新逻辑包的起始位置进行调整
        m_DataBufByteCount -= packetLen;
        startPos += packetLen; 

        //残留缓冲区的字节数少于一个正常包大小时,只向前复制该包随后退出
        if (m_DataBufByteCount < DATA_POS+1)
        {
            for(i=startPos; i<startPos+m_DataBufByteCount; ++i)
                m_ClientDataBuf[i-startPos] = m_ClientDataBuf[i];

            return true;
        }

        packetLen = GetPacketLen(m_ClientDataBuf + startPos );

         //当逻辑层的包长度不合法时,丢弃该包及缓冲区以后的包
        if ((packetLen<DATA_POS+1) || (packetLen>2*CLIENT_BUFFER_SIZE))
        {
            m_DataBufByteCount = 0;

      //OnPacketError()
            return false;
        }

         if (startPos+packetLen>=oldlen)
        {
            for(i=startPos; i<startPos+m_DataBufByteCount; ++i)
                m_ClientDataBuf[i-startPos] = m_ClientDataBuf[i];          

            return true;
        }
     }//取所有完整的包

     return true;
}

  以上便是数据接收模块的处理函数,下面是几点简要说明:

  1、用于拼包整理的缓冲区(m_ClientDataBuf)应该比recv中指定的接收缓冲区(pData)长度(CLIENT_BUFFER_SIZE)要大,通常前者是后者的2倍(2*CLIENT_BUFFER_SIZE)或更大。

  2、为避免因为剩余数据前移而导致的额外开销,建议m_ClientDataBuf使用环形缓冲区实现。

  3、为了避免出现无法拼装的包,我们约定每次发送的逻辑包,其单个逻辑包最大长度不可以超过CLIENT_BUFFER_SIZE的2倍。因为我们的整理缓冲区只有2*CLIENT_BUFFER_SIZE这么长,更长的数据,我们将无法整理。这就要求在协议的设计上以及最终的发送函数的处理上要加上这样的异常处理机制。


  4、对于数据包过短或过长的包,我们通常的情况是置m_DataBufByteCount为0,即舍弃当前包的处理。如果此处不设置m_DataBufByteCount为0也可,但该客户端只要发了一次格式错误的包,则其后继发过来的包则也将连带着产生格式错误,如果设置m_DataBufByteCount为0,则可以比较好的避免后继的包受此包的格式错误影响。更好的作法是,在此处开放一个封包格式异常的处理接口(OnPacketError),由上层逻辑决定对这种异常如何处置。比如上层逻辑可以对封包格式方面出现的异常进行计数,如果错误的次数超过一定的值,则可以断开该客户端的连接。

  5、建议不要在recv或wsarecv的函数后,就紧接着作以上的处理。当recv收到一段数据后,生成一个结构体或对象(它主要含有data和len两个内容,前者是数据缓冲区,后者是数据长度),将这样的一个结构体或对象放到一个队列中由后面的线程对其使用SplitFun函数进行整理。这样,可以最大限度地提高网络数据的接收速度,不至因为数据整理的原因而在此处浪费时间。

  代码中,我已经作了比较详细的注释,可以作为拼包函数的参考,代码是从偶的应用中提取、修改而来,本身只为演示之用,所以未作调试,应用时需要你自己去完善。如有疑问,可以我的blog上留言提出。

]]>
http://blog.donews.com/sodme/archive/2005/08/31/535234.aspx/feed 0