??xml version="1.0" encoding="utf-8" standalone="yes"?>久久www免费人成看国产片,狼狼综合久久久久综合网,久久99亚洲综合精品首页 http://www.shnenglu.com/kevinlynx/category/6338.html低调做技术__ C/C++\MMORPG服务器\模块架构__ TODOQlinux env/read more books __Kevin Lynxzh-cnWed, 29 Aug 2012 07:02:34 GMTWed, 29 Aug 2012 07:02:34 GMT60MMO聊天服务器设?/title><link>http://www.shnenglu.com/kevinlynx/archive/2012/08/29/188604.html</link><dc:creator>Kevin Lynx</dc:creator><author>Kevin Lynx</author><pubDate>Wed, 29 Aug 2012 03:37:00 GMT</pubDate><guid>http://www.shnenglu.com/kevinlynx/archive/2012/08/29/188604.html</guid><wfw:comment>http://www.shnenglu.com/kevinlynx/comments/188604.html</wfw:comment><comments>http://www.shnenglu.com/kevinlynx/archive/2012/08/29/188604.html#Feedback</comments><slash:comments>1</slash:comments><wfw:commentRss>http://www.shnenglu.com/kevinlynx/comments/commentRss/188604.html</wfw:commentRss><trackback:ping>http://www.shnenglu.com/kevinlynx/services/trackbacks/188604.html</trackback:ping><description><![CDATA[<div> <div> <p>MMO中的聊天服务主要功能是做客L之间的聊天内容{发。但是聊天的形式有很多,例如U聊、同场景聊、队伍内聊、工会内聊、全服务器聊、甚至 时组建房间聊。这些逻辑功能其实都是可以做在逻辑服务器上的,最多改改世界服务器Q但是这样完成功能的话,不免聊天本w的逻辑与游戏逻辑兌h。我?希望做得更上一层,聊天服务本w脱d来。但是独立聊天服务还不够Q因为就独立出来了Q也有可能在实现上与具体的游戏逻辑相关联。所以,我们做了q一 步的抽象Q想实现一个更为通用的聊天服务器?/p> <h2>设计实现</h2> <h3>实体设计</h3> <p>聊天q个q程Q我们将其抽象ؓ实体(entity)与实体间的对话。这个实体概念其实很宽泛。Q何可接收聊天消息的都做实体Q例如单个玩家、一?场景、一个队伍、一个房间、一个工会、甚x个服务器。这个思想其实是支持整个聊天服务器设计的最Ҏ思想。最开始,我将聊天服务器分Z体和l两个概 念,其实q个抽象E度都太低,q且会导致实C的复杂。相反,整个系l完全用实体这个概忉|l装Q就单很多。当Ӟ实体是有很多U类的,在处理接?聊天消息q个动作Ӟ其处理方式就不同。例如单个玩家实体仅做消息的发送,场景实体则是消息发l场景内的所有玩Ӟ队伍实体是消息发l队伍内的所?玩家。从q一Ҏ看,我们的实体种cd实ƈ不多Q因为场景、队伍这些,都是l实?group entity)。用C++来描qͼ</p> <div> <div style="background-color: #eeeeee; font-size: 13px; border: 1px solid #cccccc; padding: 4px 5px 4px 4px; width: 98%;"><!--<br /><br />Code highlighting produced by Actipro CodeHighlighter (freeware)<br />http://www.CodeHighlighter.com/<br /><br />--><span style="color: #0000ff;">class</span><span style="color: #000000;"> Entity {<br /></span><span style="color: #0000ff;">public</span><span style="color: #000000;">:<br />    </span><span style="color: #008000;">//</span><span style="color: #008000;"> send text to this entity</span><span style="color: #008000;"><br /></span><span style="color: #000000;">    </span><span style="color: #0000ff;">virtual</span><span style="color: #000000;"> </span><span style="color: #0000ff;">bool</span><span style="color: #000000;"> Send(Entity </span><span style="color: #000000;">*</span><span style="color: #000000;">sender, </span><span style="color: #0000ff;">const</span><span style="color: #000000;"> std::</span><span style="color: #0000ff;">string</span><span style="color: #000000;"> </span><span style="color: #000000;">&</span><span style="color: #000000;">text) </span><span style="color: #000000;">=</span><span style="color: #000000;"> </span><span style="color: #000000;">0</span><span style="color: #000000;">;<br /><br /></span><span style="color: #0000ff;">protected</span><span style="color: #000000;">:<br />    GUID m_id;<br />    </span><span style="color: #0000ff;">int</span><span style="color: #000000;"> m_type;<br />};<br /><br /></span><span style="color: #0000ff;">class</span><span style="color: #000000;"> SockEntity : pubilc Entity {<br /></span><span style="color: #0000ff;">public</span><span style="color: #000000;">:<br />    </span><span style="color: #0000ff;">virtual</span><span style="color: #000000;"> </span><span style="color: #0000ff;">bool</span><span style="color: #000000;"> Send(Entity </span><span style="color: #000000;">*</span><span style="color: #000000;">sender, </span><span style="color: #0000ff;">const</span><span style="color: #000000;"> std::</span><span style="color: #0000ff;">string</span><span style="color: #000000;"> </span><span style="color: #000000;">&</span><span style="color: #000000;">text) {<br />        </span><span style="color: #008000;">//</span><span style="color: #008000;"> find the map socket and send text to the socket</span><span style="color: #008000;"><br /></span><span style="color: #000000;">        </span><span style="color: #0000ff;">long</span><span style="color: #000000;"> socket </span><span style="color: #000000;">=</span><span style="color: #000000;"> FindSocket(</span><span style="color: #0000ff;">this</span><span style="color: #000000;">);<br />        Message msg(MSG_CS2E_SENDTEXT);<br />        msg.Add(sender</span><span style="color: #000000;">-></span><span style="color: #000000;">ID());<br />        msg.Add(text);<br />        msg.SendToSocket(socket);<br />        </span><span style="color: #0000ff;">return</span><span style="color: #000000;"> </span><span style="color: #0000ff;">true</span><span style="color: #000000;">;<br />    }<br />};<br /><br /></span><span style="color: #0000ff;">class</span><span style="color: #000000;"> GroupEntity : </span><span style="color: #0000ff;">public</span><span style="color: #000000;"> Entity {<br /></span><span style="color: #0000ff;">public</span><span style="color: #000000;">:<br />    </span><span style="color: #0000ff;">virtual</span><span style="color: #000000;"> </span><span style="color: #0000ff;">bool</span><span style="color: #000000;"> Send(Entity </span><span style="color: #000000;">*</span><span style="color: #000000;">sender, </span><span style="color: #0000ff;">const</span><span style="color: #000000;"> std::</span><span style="color: #0000ff;">string</span><span style="color: #000000;"> </span><span style="color: #000000;">&</span><span style="color: #000000;">text) {<br />        </span><span style="color: #0000ff;">for</span><span style="color: #000000;"> (std::list</span><span style="color: #000000;"><</span><span style="color: #000000;">Entity</span><span style="color: #000000;">*></span><span style="color: #000000;">::const_iterator it </span><span style="color: #000000;">=</span><span style="color: #000000;"> m_mems.begin(); it </span><span style="color: #000000;">!=</span><span style="color: #000000;"> m_mems.end(); </span><span style="color: #000000;">++</span><span style="color: #000000;">it) {<br />            (</span><span style="color: #000000;">*</span><span style="color: #000000;">it)</span><span style="color: #000000;">-></span><span style="color: #000000;">Send(sender, text);<br />        }<br />        </span><span style="color: #0000ff;">return</span><span style="color: #000000;"> </span><span style="color: #0000ff;">true</span><span style="color: #000000;">;<br />    }<br /></span><span style="color: #0000ff;">private</span><span style="color: #000000;">:<br />    std::list</span><span style="color: #000000;"><</span><span style="color: #000000;">Entity</span><span style="color: #000000;">*></span><span style="color: #000000;"> m_mems;<br />};<br /><br /></span></div><pre><code> </code></pre> </div> <p><code>SockEntity</code>用于表示物理上聊天服务器的客LQ例如游戏客L?/p> <h3>|络拓扑</h3> <p>实际上,除了转发聊天内容?Entity::Send)Q实体还有很多其他行为,例如最L的,创徏l实体,往l实体里d成员{。在设计上,l?实体的创建由逻辑服务器或者其他服务器来完成,目前游戏客户端是没有创徏l实体的权限的(实现上我们还为实体添加了权限验证机制Q。在|络拓扑上,聊天?务器始终是作为服务器角色Q而它的客L则包括游戏客L、逻辑服务器、甚臛_他服务器Q这栯天服务器在提供了固定的协议后Q它是完全独立的,不依?M其他lgQ?/p> <div style="background-color: #eeeeee; font-size: 13px; border: 1px solid #cccccc; padding: 4px 5px 4px 4px; width: 98%;"><!--<br /><br />Code highlighting produced by Actipro CodeHighlighter (freeware)<br />http://www.CodeHighlighter.com/<br /><br />--><span style="color: #000000;">             CS<br />          </span><span style="color: #000000;">/</span><span style="color: #000000;">  </span><span style="color: #000000;">|</span><span style="color: #000000;">  \<br />         </span><span style="color: #000000;">/</span><span style="color: #000000;">   </span><span style="color: #000000;">|</span><span style="color: #000000;">   \<br />        </span><span style="color: #000000;">/</span><span style="color: #000000;">    </span><span style="color: #000000;">|</span><span style="color: #000000;">    \<br />       GC   GC   GS<br /></span></div><pre><code> </code></pre> <p>(CS: Chat Server, GC: Game Client, GS: Game Server)</p> <p>Z此,我们扩充了Entity的类体系Q?/p> <div> <div style="background-color: #eeeeee; font-size: 13px; border: 1px solid #cccccc; padding: 4px 5px 4px 4px; width: 98%;"><!--<br /><br />Code highlighting produced by Actipro CodeHighlighter (freeware)<br />http://www.CodeHighlighter.com/<br /><br />--><span style="color: #0000ff;">class</span><span style="color: #000000;"> ClientEntity : </span><span style="color: #0000ff;">public</span><span style="color: #000000;"> SockEntity {<br /><br /></span><span style="color: #0000ff;">private</span><span style="color: #000000;">:<br />    GUID m_gsEntity; </span><span style="color: #008000;">//</span><span style="color: #008000;"> 标示该客L实体位于哪个逻辑服务器实体上</span><span style="color: #008000;"><br /></span><span style="color: #000000;">};<br /><br /></span><span style="color: #0000ff;">class</span><span style="color: #000000;"> GSEntity : </span><span style="color: #0000ff;">public</span><span style="color: #000000;"> SockEntity {<br />};<br /></span></div> </div> <h3>消息协议</h3> <p>聊天服务器的核心实现Q其实就是针对以上实体做操作。因此,聊天服务器的消息协议斚wQ也主要是针对这些实体的操作Q包括:</p> <ul> <li> <p>创徏</p> <p> 实体的创建很单,不同的实体其创徏所需的参数都不一栗例如客L实体创徏旉要传入一个逻辑服务器实体的IDQ组实体的创建可以携带组成员实体列表?Z处理权限和安全问题,在具体实CQ逻辑服务器实体的创徏是由聊天服务器本地的配置军_Q即聊天服务器启动则Ҏ配置创徏好逻辑服务器实体;客户端实 体是当角色进入逻辑服务器后Q由服务器创建,客户端无法创建实体?/p> </li> <li> <p>删除</p> <p> 实体的删除ؓ了处理方便,U定删除h必须由实体的创徏者发赗因Z逻辑上将Q某个模块如果可以创Z个实体,那么其必然知道什么时候该删除q个实体?/p> </li> <li> <p>修改</p> <p> 修改指的是修改实体内部实现的一些属性,例如l实体修改其l成员。这个操作是非常重要的。对?code>SockEntity</code>?aQ修Ҏ味着修改其连接状态,例如当逻辑服务器在聊天服务器上创徏了客L实体后,实际上此时客Lq没有在|络斚wq接聊天服务器,此时q个<code>Entity</code>?际上是不可用的,因ؓ它无法用于发送消息。这个时候我们标志该实体的状态ؓ非连接状态。当客户端主动连接上聊天服务器后Q客L׃动发起修改自己对应的 客户端实体请求,该请求将自己的状态修改ؓq接状态。当客户端关闭时Q聊天服务器|络层接收到q接断开通知Q该通知肯定是早于逻辑服务器发来的删除实体?知的Q此时将该客L实体状态修改ؓ断开状态,q在接收到逻辑服务器删除实体通知时将其真正删除。这里展C的q种状态修改策略,实际上在整个pȝ中是非常 重要的。它用于指导|络q接和上层逻辑之间的关p,因ؓ整个聊天pȝ中,各个q程的状态是不可预料的(随时可能宕掉Q,当某个进E尤其是逻辑服务器宕?后,聊天服务器是得不CQ何正帔R辑通知的,它只能得到网l连接的通知?/p> </li> </ul> <h2>ȝ</h2> <p>整个pȝ实现下来Q实际上是非常简单的Q代码量也很。当然还有很多细节问题,例如聊天信息中携带物品信息,q涉及到异步预处理聊天内容,q里׃ 方便l说了?/p> <p> 原文地址Q?<a >http://codemacro.com/2012/08/29/mmo-chat-server/</a><br /> written by <a >Kevin Lynx</a>  posted at <a >http://codemacro.com</a> </p> </div> </div><img src ="http://www.shnenglu.com/kevinlynx/aggbug/188604.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.shnenglu.com/kevinlynx/" target="_blank">Kevin Lynx</a> 2012-08-29 11:37 <a href="http://www.shnenglu.com/kevinlynx/archive/2012/08/29/188604.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>谈谈我们的游戏逻辑服务器实玎ͼ二)http://www.shnenglu.com/kevinlynx/archive/2012/04/25/172741.htmlKevin LynxKevin LynxWed, 25 Apr 2012 08:55:00 GMThttp://www.shnenglu.com/kevinlynx/archive/2012/04/25/172741.htmlhttp://www.shnenglu.com/kevinlynx/comments/172741.htmlhttp://www.shnenglu.com/kevinlynx/archive/2012/04/25/172741.html#Feedback2http://www.shnenglu.com/kevinlynx/comments/commentRss/172741.htmlhttp://www.shnenglu.com/kevinlynx/services/trackbacks/172741.html原文链接Q?a >http://codemacro.com/2012/04/25/game-server-info-2/

上一谈了一些关键技术的实现Ҏ。本描qC些遇到的问题?/p>

在策划制作完了几个职业后Q主要是技能制作)Q大概去q年底公司内部进行了一ơ؜战测试?0个角色在一个场景进行؜战,试l果从技术上来说非常不理惟뀂首先是客户端和服务器都巨卡无比。服务器CPU一直是满负载状态。而客L又频J宕机?/p>

我们x的主要问题,是服务器CPU满负载问题。最开始,我通过日志初步定位为网l模块问题,因ؓ逻辑U程表现不是那么差。然后考虑到技能过E中的特效、动作都是通过服务器消息驱动,q且本nҎ和动作就比一般网游复杂,通过逐一屏蔽q一部分功能Q最l确认确为网l模块导致。然后团队决定从两方面努力:重写|络模块Q改善性能Q改善技能实现机Ӟ表现类逻辑Ud客户端?/p>

至于|络模块Q在后来才发玎ͼ虽然|络量q高Q但D|络U程CPU满的原因竟然是网l模块自w的量限制D。而技能实现机制的改善Q考虑到改动的成本Q最l用了一URPC机制Q让服务器脚本可以调用客L脚本Qƈ且支持传入复杂参数。然后策划通过一些关键数据在客户端计出Ҏ、动作之cR?/p>

此外Q程序将更多的技能属性广播给客户端,一个客L上保存了周围角色的技能数据,从而可以进行更多的客户端逻辑。这一块具体的修改当然q是{划在做Q我们的脚本{划基本是半个E序员)。后l测试,效果改善显著?/p>

?/h2>

在策划制作了一个PVP竞技副本后,服务器在10V10试q程中又表现出CPU负蝲较高的情c这个问题到目前为止依然存在Q只不过情况略微不同?/p>

首先是触发器生命周期的问题。触发器自n包含最大触发次数、存留时间等需求,卛_触发一定次敎ͼ或超q存留时间后Q需要由E序自动删除Q另一斚wQ触发器可以是定时器cdQ而定时器也决定了触发器的生命周期。这一块代码写的非常糟p,大概是理职责划分不清Q导致出现对象自己删除自己,而删除后q在依赖自己做逻辑?/p>

但这L逻辑Q最多就是导致野指针的出现。不q,q种混ؕ的代码,也更ҎDBUG。例如,在某U情况下触发器得不到自动删除了。但q个BUGq不是直接暴露的Q直接暴露的Q是CPU满了。我们的怪物AI在脚本中是通过定时器类触发器驱动的Q每ơAI帧完了后注册一个触发器Q以驱动下一ơAI帧。由于这个BUGD触发器没有被删除Q从而导致服务器上触发器的数量急剧增加。但Q这也就D内存增长吧?

另一个y合的原因在于Q在当时的版本中Q触发器是保存一个表里的Q即定时器类触发器、属性类触发器、移动类触发器等都在一个表里。每ơQ意触发器事g发生Ӟ例如属性改变,都会遍历q个表,查其是否触发?/p>

Z以上原因Q悲剧就发生了。在q个怪物的AI脚本里,有行代码讄了怪物的属性。这会导致程序遍历该怪物的所有触发器。而这个怪物的触发器数量一直在增长。然后就出现了在很多游戏帧里出现q长的遍历操作,CPU׃M?/p>

扑ֈq个问题了几乎花了我一天的旉。因本代码不是我写的Q触发器的最初版本也不是我写的。通过逐一排除可能的代码,最l竟然发现是一行毫不v眼的属性改变导致。这个问题的查找程Q反映了大量逻辑攑֜脚本中的不便之处Q查起问题来实在吃力不讨好?/p>

修复了这个BUG后,我又对触发器理做了单的优化。将触发器列表改成二U表Q将触发器按照类型保存成几个列表。每ơ触发事件时Q找出对应类型的表遍历?/p>

改进

除了修改触发器的l护数据l构外,E序q实C一套性能l计机制Q大概就是统计某个函数在一D|间内的执行时间情c最初这套机制仅用于E序Q但考虑到脚本代码在整个目中的比例Q又军_其应用到脚本中?/p>

q个l计需要在函数q入退出时做一些事情,C++中可以通过cd象的构徏和析构完成,但lua中没有类似机制。最初,我用了lua的调试库来捕获函数进?退ZӞ但后来又x这U方式本w存在效率消耗,取消了。我们用lua的方式,不仅仅是全局函数Q还包括函数对象。而函数对象是没有名字标示的,q对于日志记录不是什么好事。ؓ了解册个问题,我只好对部分功能做了装Qƈ让策划显C填入函数对于的字符串标C?/p>

除此之外Q因发器是一U重要的敏感资源Q我又加入了一个专门的触发器统计模块,分别l计触发器的cd数量、游戏对象拥有的触发器数量等?/p>

END

到目前ؓ止,D服务器CPU负蝲q高Q一般都是由BUGD。这些BUG通常会造成一个过长的列表Q然后有针对q个列表的遍历操作,从而导致CPU负蝲q高。更重要的,我们使用了这么多的脚本去开发这个游戏,如何扑ֈ一个更有效合理的监方法,如何让程序框架更E_Q则是接下来更困难而又必须去面对的事情?/p>

Kevin Lynx 2012-04-25 16:55 发表评论
]]>
谈谈我们的游戏逻辑服务器实玎ͼ一Q?/title><link>http://www.shnenglu.com/kevinlynx/archive/2012/04/25/172739.html</link><dc:creator>Kevin Lynx</dc:creator><author>Kevin Lynx</author><pubDate>Wed, 25 Apr 2012 08:54:00 GMT</pubDate><guid>http://www.shnenglu.com/kevinlynx/archive/2012/04/25/172739.html</guid><wfw:comment>http://www.shnenglu.com/kevinlynx/comments/172739.html</wfw:comment><comments>http://www.shnenglu.com/kevinlynx/archive/2012/04/25/172739.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.shnenglu.com/kevinlynx/comments/commentRss/172739.html</wfw:commentRss><trackback:ping>http://www.shnenglu.com/kevinlynx/services/trackbacks/172739.html</trackback:ping><description><![CDATA[<p>原文链接Q?a >http://codemacro.com/2012/04/23/game-server-info-1/</a></p> <p>我们的逻辑服务?Game ServerQ以下简UGS)主要逻辑大概是从d夏天开始写的。因为很多基模块Q包括整体结构沿用了上个目的代码,所以算不上从头开始做。{眼又快一q_我觉得回头ȝ下对于经验的U篏太有必要?/p> <h2>整体架构</h2> <p>GS的架构很大程度取决于游戏的功能需求,当然更受限于上个目的基架构。基架构包括场景、对象的关系理Q消息广播等?/p> <h3>需?/h3> <p>q一回,E序员其实已l不需要太q关心需求。因为我们决定大量用脚本。到目前为止整个目主要q是集中在技能开发上。而这个用脚本的度,是技能全部由{划使用脚本制作Q程序员不会ȝ写某个具体技能,也不会提供某U配|方式去让策划通过配置来开发技能。这真是一个好消息Q不对于程序员而言Q还是对于策划而言。但后来Q我觉得对于q一点还是带来了很多问题?/p> <h3>实现</h3> <p>Z以上需求,E序员所做的是开发框Ӟ制定功能实现Ҏ。脚本ؓ了与整个游戏框架交互Q我们制定了“触发?#8220;q个概念Q大概就是一U事件系l?/p> <p>q个触发器系l,单来_是提供一U?#8220;兛_“?#8221;通知“的交互方式,也就是一般意义上的事件机制。例如,脚本中告诉程序它兛_某个对象的移动,那么当程序中该对象生移动时Q就通知脚本。脚本中可以兛_很多东西Q包括对象属性,其关心的方式包括属性值改变、变大、变,各种变化形式Q对象开始移动,Ud停止Q对象碰撞,q个会单独谈谈;定时器等?/p> <p>除了触发器系l外Q还有个较大的系l是游戏对象的属性系l。对象的属性必然是游戏逻辑中策划最兛_最Ҏ改动的模块。既然我们程序的大方向是可能地不关心策划需求,所以对象属性在设计上就不可能去~写某个具体属性,更不会编写这个属性相关的逻辑功能。简单来_E序为每个对象维护一个key-value表,也就是属性名、属性D。该表的内容p本填入,脚本享有存取权限。然后脚本中可以围l某个属性来~写功能Q而程序仅起存储作用?/p> <p>W三Q怪物AI模块。AI模块的设计在开发周期上靠后。同PE序不会ȝ写某cAI的实现。程序提供了另一U简单的事gpȝQ这个系l其实就是一个调用脚本的Ҏ。当关于某个怪物发生了某个事件时Q程序调用脚本,传入事gcd和事件参数。这个事件分Zc:E序cd脚本cR脚本类E序不需兛_Q仅提供事g触发API。程序类事g非常有限Q怪物创徏、出生、删除?/p> <p>除了以上三块之外Q还有很多零散的脚本交互。例如游戏对象属性初始化Q角色进入游戏,角色q入场景{。这些都无关痛痒?/p> <p>接下来谈一些关键模块的实现?/p> <p><strong>定时?/strong></p> <p>整个GS的很多逻辑模块都基于这个定时器来实现。这个定时器接收逻辑模块的注册,在主循环中传入系l时_定时器模块检查哪些定时器实例时Q然后触发调用之。这个主循环以每?ms的速率q行Q也卛_?000/5?/p> <p>q个定时器是Z操作pȝ的时间。随着帧率的不同,它在触发逻辑功能Ӟ必然不_。游戏客LQ包括单机游戏)在率这块的实现上,一般逻辑功能的计都会考虑C个dtQ也是一帧的旉差)Q例如移动更斎ͼ一般都是x = last_x + speed * dt。但Q我们这里ƈ没有q样做。我们的几乎所有逻辑功能Q都没有考虑q个旉差?/p> <p>例如Q我们的Ud模块注册了一个固定时间值的定时器,假设?00ms。理x况下Q定时器模块?00ms回调Ud模块更新坐标。但现实情况肯定是大?00ms的更新频率,悲剧的是Q移动模块每ơ更新坐标都更新一个固定偏UR这昄是不够精的?/p> <p>更悲剧的是,定时器的实现中,q可能出现蟩q一些更新。例如,理论情况下,定时器会在系l时间点t1/t2/t3/t4分别回调某个逻辑模块。某一帧里Q定时器大概在t1回调了某逻辑模块Q而当该耗时严重Ӟ下一帧定时器模块在计时Q其旉gؓtQ而t大于t4Q此时定时器模块跌t2/t3。相当于该逻辑模块了2ơ更新。这对于Ud模块而言Q相当于某个对象本来?U的旉里该?|但实际情况却C1根{?/p> <p>当然Q当游戏帧率无法保证Ӟ逻辑模块q行不理想也是情有可原的。但Q不理想q不包含BUG。而我觉得Q这里面是可能出现BUG的。如何改善这块,目前为止我也没什么方案?/p> <p><strong>Ud</strong></p> <p>有很多更上层的模块依赖移动。我们的Ud采用了一U分别模拟的实现。客L复杂的Ud路径拆分Z条一条的U段Q然后每个线D请求服务器Ud。然后服务器上用定时器来模拟在该线D上的移动。因为服务器上的L是二l格子,q样服务器的模拟也很单。当Ӟq个模块在具体实C复杂很多Q这里不l谈?/p> <p><strong>撞?/strong></p> <p>我们的技能要求有撞,q主要包括对象与对象之间的碰撞。在最早的实现中,当脚本关心某个对象的撞情况ӞE序׃ؓ该对象注册定时器Q然后周期触发检与周围对象的距dp,q个周期低于100ms。这个实现很单,l护h也就很简单。但它是有问题的。因为它Z了一个不_的定时器Q和一个不_的移动模块?/p> <p>首先Q这个检是Z对象的当前坐标。前面分析过在率掉到移动更新都掉帧的情况下,服务器的对象坐标和理论情况差距会很大Q而客L基本上是接近正确情况的,q个时候做的距L,׃可能正确。另一斚wQ就移动精了Q这个碰撞检还是会带来BUG。例如现在检到了碰撞,触发了脚本,脚本中注册了兛_d的事件。但不幸的是Q在q个定时器开始检前Q这两个对象已经l历了碰撞、离开、再撞的过E,而定时器开始检的时候,因ؓ它基于了当前的对象坐标,它依然看到的是两个对象处于碰撞状态?/p> <p>最开始,我们直觉q样的实现是Ҏ的,是不_的。然后有了第二种实现。这个实现基于了Ud的实现。因为对象的Ud是基于直U的Q服务器上)。我们就在对象开始移动时Q根据移动方向、速度预测两个对象会在未来的某个时间点发生撞。当Ӟ对于频繁的小距离Ud而言Q这个预从直觉上来说也是费时的。然后实C码写了出来,一看,挺复杂,l护隑ֺ不小。如果效果好q个l护成本也就了Q但是,它依然是不精的。因为,它也依赖了这个定时器?/p> <p>例如Q在某个对象开始移动时Q我们预到?00ms会与对象B发生撞。然后注册了一?00ms的定时器。但定时器不会精地在未?00ms触发Q随着帧率的下降,400ms触发都有可能。即便不考虑帧率下降的情况,它还是有问题。前面说q,我们游戏帧保证每帧至?msQ本来这是一个限帧手D,目的当然是避免busy-loop。这D定时器最多出?ms的gq。如果策划用这个碰撞检去做飞行道L实现Q例如一个快速飞出去的火球,当这个飞行速度很快的时候,q?ms相对于这个预碰撞时间就不再是个数目。真悲剧?/p> <p><strong>技?/strong></p> <p>虽然具体的技能不是程序写的,但正如把几乎所有具体逻辑交给{划写带来的悲剧一Pq事不是你干的,但你得负责它的性能。所以有必要谈谈技能的实现?/p> <p>技能的实现里,只有一个技能用入口,E序只需要在客户端发Z用技能的消息Ӟ调用q个入口脚本函数。然后脚本中会通过注册一些触发器来驱动整个技能运作。程序员一直希望策划能把技能用一个统一的、具体的框架l一hQ所谓的变动都是Zq个框架来变的。但{划也一直坚持,他们心目中的技能是无法l一的?/p> <p>我们的技能确实很复杂。一个技能的整个q程中,服务器可能会和客L发生多次消息交互。在最初的实现中,服务器甚至会控制客户端的技能特效、释攑֊作等各种l节Q甚至于服务器会在这个过E中依赖客户端的若干ơ输入?/p> <hr /> <p>下一我谈谈一些遇到的问题?/p><img src ="http://www.shnenglu.com/kevinlynx/aggbug/172739.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.shnenglu.com/kevinlynx/" target="_blank">Kevin Lynx</a> 2012-04-25 16:54 <a href="http://www.shnenglu.com/kevinlynx/archive/2012/04/25/172739.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>MMO游戏对象属性设?/title><link>http://www.shnenglu.com/kevinlynx/archive/2011/05/02/145504.html</link><dc:creator>Kevin Lynx</dc:creator><author>Kevin Lynx</author><pubDate>Mon, 02 May 2011 11:19:00 GMT</pubDate><guid>http://www.shnenglu.com/kevinlynx/archive/2011/05/02/145504.html</guid><wfw:comment>http://www.shnenglu.com/kevinlynx/comments/145504.html</wfw:comment><comments>http://www.shnenglu.com/kevinlynx/archive/2011/05/02/145504.html#Feedback</comments><slash:comments>18</slash:comments><wfw:commentRss>http://www.shnenglu.com/kevinlynx/comments/commentRss/145504.html</wfw:commentRss><trackback:ping>http://www.shnenglu.com/kevinlynx/services/trackbacks/145504.html</trackback:ping><description><![CDATA[<div id="xivlcjc" class="document" id="mmo"> <h1 class="title">MMO游戏对象属性设?/h1> <table class="docinfo" frame="void" rules="none"> <col class="docinfo-name" /> <col class="docinfo-content" /> <tbody valign="top"> <tr><th class="docinfo-name">Author:</th> <td>Kevin Lynx</td></tr> <tr><th class="docinfo-name">Date:</th> <td>5.2.2011</td></tr> </tbody> </table> <p>一般的MMORPG中,游戏对象主要包括怪物和玩家。这两类对象在经q游戏性方面的不断“q化”后,其属性数量及与之相关的逻辑往往会变得很巨大。如何将q一块做得既不损失效率,又能保证l构的灵zR清晰、可l护Q本文将提供一U简单的l构?/p> <div id="ymdabnn" class="section" id="id1"> <h1>原始l构</h1> <p>最原始的结构,极有可能?</p> <pre class="literal-block"> Player: +---------------+ | property-1 | +---------------+ | property-2 | +---------------+ | ... | +---------------+ | operator-1 | +---------------+ | operator-2 | +---------------+ | ... | +---------------+ </pre> <p>也就是,一个对象ؓ一个C++c,然后里面直接塞满了各U属性名Q然后是针对q个属性的逻辑操作Q函敎ͼ。其l果是Player成ؓ巨类。针对这个情况,一直以来我觉得可以使用一U简单的Ҏ来拆分这个类。冠以官腔,UC为Entity-Component-based Desgin。生这U想法和我的个h技术积累有一定关p,见下文?/p> </div> <div id="qaeisfd" class="section" id="policy-based-design"> <h1>Policy-based Design</h1> <p>Policy-based DesignQ基于决{的设计。这个概忉|源于<Modern C++ Design>。虽然这本书讲述的是针对C++模板的用及设计技巧。但q种思想依然被我潜意识般地用在其他地斏VPolicy大致来说是一个小的组?Component)。它努力不依赖于其他东西Q它可能是个简单的c,它拥有极的数据l构Q及针对q些数据的极操作接口。D例而言Q玩家MP的自动回复功能,可装Z个Policy。将许多Policyl合hQ就可完成一个复杂的功能?/p> <p>q种思想q可指导很多E序l构斚w的设计。例如在做功能的接口拆分Ӟ将每个函数设计得够小Q小到单U地完成一个功能。一个功能的入口函数Q就之前实现的函数全部组合v来,然后共同完成功能炏V?/p> <p>当然Q?lt;Modern C++ Design>里的Policy在表现Ş式上有所不同。但其核心思想相同Q主要体现在 <strong>l合</strong> 特点上?/p> </div> <div id="bfaguxa" class="section" id="entity-component-based-design"> <h1>Entity-Component-based Design</h1> <p>Entity-Component-based Design按照google到的文章Q严格来说算是与OOP完全不同的Y件设计方法。不q在q里它将按照我的意思重新被解释?/p> <p>如果说Policy-based Design极大可能地媄响着我们qx的细节编码,那么Entity-Component则是直接Ҏ戏对象的l构设计做直接的说明?<strong>一个游戏对象就是一个Entity?/strong> Entity拥有很少的属性,也许仅包含一个全局标示的ID?<strong>一个Component则是Entity的某个行为、或者说某个l成部分?/strong> 其实说白了,以玩家ؓ例,一个玩家对象就是一个EntityQ而一个MP的自动回复功能就可被包装Z个Component。这个Component可能包含若干与该功能相关的数据,例如回复旉间隔Q每ơ的回复量等。我们往玩家对象q个Entityd各种ComponentQ也是l玩家添加各U逻辑功能?/p> <p>但是QComponent之间可能会涉及到交互Q玩家对象之外的模块可能也会与玩家内的某个Component交互。子功能点的拆分Q不得不涉及到更多的胶水代码Q这也算一U代仗?/p> </div> <div id="skxdyqi" class="section" id="id2"> <h1>游戏对象属性设?/h1> <p>q䆾属性结构设计,基本是参考了上面提到的设计思想。整个系l有如下lg:</p> <pre class="literal-block"> Entity: +-------------------+ | property-table | +-------------------+ | component-table | +-------------------+ Property: +-------------------+ | observer-list | +-------------------+ Component: +--------------------+ | logic-related data | +--------------------+ | logic-related func | +--------------------+ </pre> <p>意即Q所有Entity都包含一个属性表和组件表。这里的属性表q编码的属性数据成员集合,而是一个key-value形式的表。Property包含一个观察者列表,其实是一pd回调函数Q但是这些观察者本质上也是lgQ后面会提到。Component正如上文描述Q仅包含Component本n实现的功能所需要的数据和函数。整个结构大致的代码如下:</p> <pre class="literal-block"> class Entity { private: GUID id; std::map<std::string, IComponent*> components; std::map<std::string, Property*> properties; }; class Property { private: std::string name; Value val; std::vector<IComponent*> observers; }; class IComponent { public: virtual bool Operate (const Args &args) { return false; } virtual void OnNotify (const Property &property, const Args &args) {} protected: std::string name; Entity *entity; }; </pre> <p>属性本w是抽象的,q完全是因ؓ我们属性统一地放在了一个表里。从而又D属性的g需?a >l箋阅读</a></p> </div> </div><img src ="http://www.shnenglu.com/kevinlynx/aggbug/145504.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.shnenglu.com/kevinlynx/" target="_blank">Kevin Lynx</a> 2011-05-02 19:19 <a href="http://www.shnenglu.com/kevinlynx/archive/2011/05/02/145504.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>|游中的玩家Udhttp://www.shnenglu.com/kevinlynx/archive/2010/06/22/118497.htmlKevin LynxKevin LynxTue, 22 Jun 2010 13:27:00 GMThttp://www.shnenglu.com/kevinlynx/archive/2010/06/22/118497.htmlhttp://www.shnenglu.com/kevinlynx/comments/118497.htmlhttp://www.shnenglu.com/kevinlynx/archive/2010/06/22/118497.html#Feedback8http://www.shnenglu.com/kevinlynx/comments/commentRss/118497.htmlhttp://www.shnenglu.com/kevinlynx/services/trackbacks/118497.html器在q里担当一个被动角艌Ӏ但是服务器端的玩家数据却是真正的被其他逻辑模块参考的?
据?nbsp; 阅读全文

Kevin Lynx 2010-06-22 21:27 发表评论
]]>
游戏资源包简单设?/title><link>http://www.shnenglu.com/kevinlynx/archive/2010/06/19/118242.html</link><dc:creator>Kevin Lynx</dc:creator><author>Kevin Lynx</author><pubDate>Sat, 19 Jun 2010 06:59:00 GMT</pubDate><guid>http://www.shnenglu.com/kevinlynx/archive/2010/06/19/118242.html</guid><wfw:comment>http://www.shnenglu.com/kevinlynx/comments/118242.html</wfw:comment><comments>http://www.shnenglu.com/kevinlynx/archive/2010/06/19/118242.html#Feedback</comments><slash:comments>5</slash:comments><wfw:commentRss>http://www.shnenglu.com/kevinlynx/comments/commentRss/118242.html</wfw:commentRss><trackback:ping>http://www.shnenglu.com/kevinlynx/services/trackbacks/118242.html</trackback:ping><description><![CDATA[     摘要: 一般的资源包文件格式基本上是由包文件头和包内容l成。文件头描述资源包内打包的文?<br>信息Q例如文件名、在资源包里的偏UR大、压~加密相关信息等Q包内容则是实际文g <br>打包在一起后的内容,可能直接是未打包前文件连l存攑֜一L内容Q也可能是相同类?<br>文g除掉文g头的内容Q例如某个资源包里打包的全部是相同格式的囄文gQ那么这些图 <br>片文件被打包后包内只需要保存一个图片文件头Q包内容全部是直接的囄数据Q?  <a href='http://www.shnenglu.com/kevinlynx/archive/2010/06/19/118242.html'>阅读全文</a><img src ="http://www.shnenglu.com/kevinlynx/aggbug/118242.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.shnenglu.com/kevinlynx/" target="_blank">Kevin Lynx</a> 2010-06-19 14:59 <a href="http://www.shnenglu.com/kevinlynx/archive/2010/06/19/118242.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>SGI STL的内存池http://www.shnenglu.com/kevinlynx/archive/2008/06/12/53054.htmlKevin LynxKevin LynxThu, 12 Jun 2008 13:26:00 GMThttp://www.shnenglu.com/kevinlynx/archive/2008/06/12/53054.htmlhttp://www.shnenglu.com/kevinlynx/comments/53054.htmlhttp://www.shnenglu.com/kevinlynx/archive/2008/06/12/53054.html#Feedback10http://www.shnenglu.com/kevinlynx/comments/commentRss/53054.htmlhttp://www.shnenglu.com/kevinlynx/services/trackbacks/53054.htmlstl中各U容器都有一个可选的模板参数QallocatorQ也是一个负责内存分配的lg。STL标准规定的allcator
被定义在memory文g中。STL标准规定的allocator只是单纯地封装operator newQ效率上有点q意不去?

SGI实现的STL里,所有的容器都用SGI自己定义的allocator。这个allocator实现了一个small object的内存池?br>Loki里ؓ了处理小对象的内存分配,也实CcM的内存管理机制?

该内存池大致上,是一大块一大块Cpȝ获取内存Q然后将其分成很多小块以链表的Ş式链接v来。其内部
有很多不同类型的链表Q不同的链表l护不同大小的内存块。每一ơ客L要求分配内存Ӟallcator根据请?br>的大找到相应的链表(最接近的尺?Q然后从链表里取出内存。当客户端归q内存时Qallocator将q块内存
攑֛到对应的链表里?

我简单地Mq图表示整个l构Q?/font>

allocator

allocator内部l护一个链表数l,数组元素全部是链表头指针。链表A每一个节点维护一?bytes的内存块Q链?br>B每一个节点维护一?6bytes的内存块?

当客Lh分配10bytes的内存时Qallocator?0调整为最接近?6bytes(只能大于10bytes)Q然后发?6bytes
q个链表(链表B)里有可用内存块,于是从B里取Z块内存返回。当客户端归q时Qallocator扑ֈ对应的链表,?br>内存重新攑֛链表B卛_?

大致q程p么简单,也许有h要说用链表维护一块内存,链表本n׃费一些内?在我很早前接触内存池Ӟ
M看到cM的论? =|)Q其实通过一些简单的技巧是完全可以避免的。例如,q里allocatorl护了很多内存块Q?br>反正q些内存本n是闲置的,因此我们可以直接在q些内存里记录链表的信息(下一个元??

q是写点代码详细说下q个技巧:

   

struct Obj
    
{
        Obj 
*next;
    }


    
void *mem = malloc( 100 );
    Obj 
*header = (Obj*) mem;
    Obj 
*cur_obj = header;
    Obj 
*next_obj = cur_obj;
    
forint i = 0; ; ++ i )
    
{
        cur_obj 
= next_obj;
        next_obj 
= (Obj*)((char*)next_obj + 10 );
        
if( i == 9 )
        
{
            cur_obj
->next = 0;
            
break;
        }

        
else
        
{
            cur_obj
->next = next_obj;
        }

    }

    free( mem );

 

q样Q通过header指针和next域,可以逐块(q里?0byts)地访问mem所指向的内存,而这些链表的节点Q都
是直接保存在q块内存里的Q所以完全没有额外消耗?

我用C模仿着SGI的这个allocator写了个可配置的内存池Q在其上按照STL的标准包装了一个allocatorQ可以直?br>用于VC自带的STL里?/font>试代码E微试了下Q发现在不同的机器上有明昄差距?



Kevin Lynx 2008-06-12 21:26 发表评论
]]>
H破select的FD_SETSIZE限制http://www.shnenglu.com/kevinlynx/archive/2008/05/20/50500.htmlKevin LynxKevin LynxTue, 20 May 2008 03:20:00 GMThttp://www.shnenglu.com/kevinlynx/archive/2008/05/20/50500.htmlhttp://www.shnenglu.com/kevinlynx/comments/50500.htmlhttp://www.shnenglu.com/kevinlynx/archive/2008/05/20/50500.html#Feedback11http://www.shnenglu.com/kevinlynx/comments/commentRss/50500.htmlhttp://www.shnenglu.com/kevinlynx/services/trackbacks/50500.htmlAuthor : Kevin Lynx 

前言Q?/strong>

在很多比较各U网l模型的文章中,但凡提到select模型Ӟ都会说select受限于轮询的套接字数量,q个
数量也就是系l头文g中定义的FD_SETSIZE?例如64)。但事实上这个算不上真的限制?

C语言的偏方:

在C语言的世界里存在一个关于结构体的偏门技巧,例如Q?

 

typedef struct _str_type
{
   
int _len;
   
char _s[1];
}
str_type;

 

str_type用于保存字符?我只是D例,事实上这个结构体没什么用?Q乍看上去str_type只能保存长度?br>1的字W串('\0')。但是,通过写下如下的代码,你将H破q个限制Q?

int str_len = 5;
str_type
*s = (str_type*) malloc( sizeof( str_type ) + str_len - 1 );
//
free( s );


q个技巧原理很单,因ؓ_s恰好在结构体NQ所以可以ؓ其分配一D连l的I间Q只要注意指针的使用Q?br>q个q不上代码上的|恶。但是这个技巧有个限Ӟstr_type定义的变量必L被分配在堆上Q否则会?br>坏堆栈。另外,需要动态增长的成员需要位于结构体的末。最后,一个忠告就是,q个是C语言里的技巧,
如果你的l构体包含了C++的东西,q个技巧将不再安全(<Inside the C++ object model>)?

其实select也可以这样做Q?/strong>

事实上,因ؓselect涉及到的fd_set是一个完全满上q要求的l构体:

winsock2.h :

typedef
struct fd_set {
        u_int fd_count;              
/* how many are SET? */
        SOCKET  fd_array[FD_SETSIZE];  
/* an array of SOCKETs */
}
fd_set;


但是Q如果用了以上技巧来增加fd_array的数?也就是保存的套接字数?Q那么关于fd_set的那些宏?br>能就无法使用了,例如FD_SET?

winsock2.h :

#define FD_SET(fd, set) do { \
    u_int __i; \
   
for (__i = 0; __i < ((fd_set FAR *)(set))->fd_count; __i++) { \
       
if (((fd_set FAR *)(set))->fd_array[__i] == (fd)) { \
           
break; \
        }
\
    }
\
   
if (__i == ((fd_set FAR *)(set))->fd_count) { \
       
if (((fd_set FAR *)(set))->fd_count < FD_SETSIZE) { \
            ((fd_set FAR
*)(set))->fd_array[__i] = (fd); \
            ((fd_set FAR
*)(set))->fd_count++; \
        }
\
    }
\
}
while(0)


有点让hD~ؕQ我鼓励你仔l看Q其实很单。这里有个小技巧,是他把q些代码攑ֈ一个do...while(0)
里,Z么要q样做,我觉得应该是防止名字污染Q也是防止那个__i变量与你的代码相冲突。可以看出,
FD_SET会将fd_count与FD_SETSIZE相比较,q里主要是防止往fd_array的非法位|写数据?

因ؓq个宏原理不q如此,所以我们完全可以自己写一个新的版本。例如:

#define MY_FD_SET( fd, set, size ) do { \
    unsigned
int i = 0; \
   
for( i = 0; i < ((fd_set*) set)->fd_count; ++ i ) { \
       
if( ((fd_set*)set)->fd_array[i] == (fd) ) { \
           
break; \
        }
\
    }
\
   
if( i == ((fd_set*)set)->fd_count ) { \
       
if( ((fd_set*)set)->fd_count < (size) ) { \
            ((fd_set
*)set)->fd_array[i] = (fd); \
            ((fd_set
*)set)->fd_count ++; \
        }
\
    }
\
}
while( 0 )


没什么变化,只是为FD_SET加入一个fd_array的长度参敎ͼ宏体也只是将FD_SETSIZE换成q个长度参数?br>于是Q现在你可以写下q样的代码:

unsigned int count = 100;
fd_set
*read_set = (fd_set*) malloc( sizeof( fd_set ) + sizeof(SOCKET) * (count - FD_SETSIZE ) );
SOCKET s
= socket( AF_INET, SOCK_STREAM, 0 );
//
MY_FD_SET( s, read_set, count );
//
free( read_set );
closesocket( s );


提下select模型Q?/strong>

q里我不会具体讲select模型Q我只稍微提一下。一个典型的select轮询模型为:

int r = select( 0, &read_set, 0, 0, &timeout );
if( r < 0 )
{
   
// select error
}
 

if( r > 0 )
{
   
for( each sockets )
   
{
       
if( FD_ISSET( now_socket, &read_set ) )
       
{
           
// this socket can read data
        }

    }

}
 


轮询write时也差不多。在Etwork(一个超型的基本用于练习网l编E的|络?google yourself)中,作?br>的轮询方式则有所不同Q?

// read_set, write_set为采用了上文所q技巧的fd_setcd的指?/span>
int r = select( 0, read_set, write_set, 0, &timeout );
//  error handling
for( int i = 0; i < read_set->fd_count; ++ i )
{
   
// 轮询所有socketQ这里直接采用read_set->fd_array[i] == now_socket判断Q而不是FD_ISSET
}
 

for( int i = 0; i < write_set->fd_count; ++ i )
{
   
// 轮询所有socketQ检查其whether can writeQ判断方式同?/span>
}
 


两种方式的效率从代码上看M乎都差不多,关键在于QFD_ISSETq了什么?q个宏实际上使用了__WSAFDIsSet
函数Q而__WSAFDIsSet做了什么则不知道。也许它会依赖于FD_SETSIZE宏,那么q在我们q里是不安全的Q?br>所以相比之下,如果我们使用了这个突破FD_SETSIZE的偏ҎD,那么也许W二U方式要好些?

相关下蝲(5.21.2008)

随便写了一个改q的select模型的echo服务器,放上源码?/span>

Kevin Lynx 2008-05-20 11:20 发表评论
]]>
tcp要点学习-断开q接http://www.shnenglu.com/kevinlynx/archive/2008/05/14/49825.htmlKevin LynxKevin LynxWed, 14 May 2008 07:46:00 GMThttp://www.shnenglu.com/kevinlynx/archive/2008/05/14/49825.htmlhttp://www.shnenglu.com/kevinlynx/comments/49825.htmlhttp://www.shnenglu.com/kevinlynx/archive/2008/05/14/49825.html#Feedback2http://www.shnenglu.com/kevinlynx/comments/commentRss/49825.htmlhttp://www.shnenglu.com/kevinlynx/services/trackbacks/49825.htmlAuthor : Kevin Lynx

主要部分Q四ơ握手:

断开q接其实从我的角度看不区分客L和服务器端,M一斚w可以调用close(or closesocket)之类
的函数开始主动终止一个连接。这里先暂时说正常情c当调用close函数断开一个连接时Q主动断开?br>一方发送FIN(finish报文l对斏V有了之前的l验Q我想你应该明白我说的FIN报文时什么东ѝ也是
一个设|了FIN标志位的报文DcFIN报文也可能附加用h据,如果q一方还有数据要发送时Q将数据?br>加到q个FIN报文时完全正常的。之后你会看刎ͼq种附加报文q会有很多,例如ACK报文。我们所要把?br>的原则是QTCP肯定会力所能及地达到最大效率,所以你能够惛_的优化方法,我想TCP都会惛_?

当被动关闭的一Ҏ到FIN报文Ӟ它会发送ACK认报文(对于ACKq个东西你应该很熟悉?。这里有?br>东西要注意,因ؓTCP是双工的Q也是_你可以想象一对TCPq接上有两条数据通\。当发送FIN报文
Ӟ意思是_发送FIN的一端就不能发送数据,也就是关闭了其中一条数据通\。被动关闭的一端发?br>了ACK后,应用层通常׃到q个q接卛_断开Q然后被动断开的应用层调用close关闭q接?

我可以告诉你Q一旦当你调用close(or closesocket)Q这一端就会发送FIN报文。也是_现在被动
关闭的一端也发送FINl主动关闭端。有时候,被动关闭端会ACK和FIN两个报文合在一起发送。主?br>关闭端收到FIN后也发送ACKQ然后整个连接关?事实上还没完全关闭,只是关闭需要交换的报文发?br>完毕)Q四ơ握手完成。如你所见,因ؓ被动关闭端可能会ACK和FIN合到一起发送,所以这也算不上
严格的四ơ握?--四个报文Dc?

在前面的文章中,我一直没提TCP的状态{换。在q里我还是在犹U是不是该那张四处通用的图拿出来,
不过Q这里我只给出断开q接时的状态{换图Q摘?lt;The TCP/IP Guide>Q?

tcpclose

l出一个正常关闭时的windump信息Q?br>

14:00:38.819856 IP cd-zhangmin.1748 > 220.181.37.55.80: F 1:1(0) ack 1 win 65535
14:00:38.863989 IP 220.181.37.55.80 > cd-zhangmin.1748: F 1:1(0) ack 2 win 2920
14:00:38.864412 IP cd-zhangmin.1748 > 220.181.37.55.80: . ack 2 win 65535 

 

补充l节Q?/strong>

关于以上的四ơ握手,我补充下l节Q?br>1. 默认情况?不改变socket选项)Q当你调用close( or closesocketQ以下说close不再重复)Ӟ如果
发送缓冲中q有数据QTCP会l把数据发送完?br>2. 发送了FIN只是表示q端不能l箋发送数?应用层不能再调用send发?Q但是还可以接收数据?br>3. 应用层如何知道对端关闭?通常Q在最单的d模型中,当你调用recvӞ如果q回0Q则表示对端
关闭。在q个时候通常的做法就是也调用closeQ那么TCP层就发送FINQl完成四ơ握手。如果你不调?br>closeQ那么对端就会处于FIN_WAIT_2状态,而本端则会处于CLOSE_WAIT状态。这个可以写代码试试?br>4. 在很多时候,TCPq接的断开都会由TCP层自动进行,例如你CTRL+Cl止你的E序QTCPq接依然会正常关
闭,你可以写代码试试?

特别的TIME_WAIT状态:

从以上TCPq接关闭的状态{换图可以看出Q主动关闭的一方在发送完对对方FIN报文的确?ACK)报文后,
会进入TIME_WAIT状态。TIME_WAIT状态也UCؓ2MSL状态?

什么是2MSLQMSL即Maximum Segment LifetimeQ也是报文最大生存时_引用<TCP/IP详解>中的话:?br>?MSL)是Q何报文段被丢弃前在网l内的最长时间。”那么,2MSL也就是这个时间的2倍。其实我觉得?br>必要把这个MSL的确切含义搞明白Q你所需要明白的是,当TCPq接完成四个报文D늚交换Ӟd关闭?br>一方将l箋{待一定时?2-4分钟)Q即使两端的应用E序l束。你可以写代码试试,然后用netstat查看下?

Z么需?MSLQ根?lt;TCP/IP详解>?lt;The TCP/IP Guide>中的说法Q有两个原因Q?br>其一Q保证发送的ACK会成功发送到ҎQ如何保证?我觉得可能是通过时计时器发送。这个就很难?br>代码演示了?br>其二Q报文可能会被؜淆,意思是_其他时候的q接可能会被当作本次的连接。直接引?lt;The TCP/IP Guide>
的说法:The second is to provide a “buffering period?between the end of this connection
and any subsequent ones. If not for this period, it is possible that packets from different
connections could be mixed, creating confusion.

TIME_WAIT状态所带来的媄响:

当某个连接的一端处于TIME_WAIT状态时Q该q接不能再被用。事实上Q对于我们比较有现实意义?br>是,q个端口不能再被用。某个端口处于TIME_WAIT状?其实应该是这个连?Ӟq意味着q个TCP
q接q没有断开(完全断开)Q那么,如果你bindq个端口Q就会失败?

对于服务器而言Q如果服务器H然crash掉了Q那么它无法再2MSL内重新启动,因ؓbind会失败。解册
个问题的一个方法就是设|socket的SO_REUSEADDR选项。这个选项意味着你可以重用一个地址?

对于TIME_WAIT的插Ԍ

当徏立一个TCPq接Ӟ服务器端会l用原有端口监听Q同时用q个端口与客L通信。而客L默认情况
下会使用一个随机端口与服务器端的监听端口通信。有时候,Z服务器端的安全性,我们需要对客户端进?br>验证Q即限定某个IP某个特定端口的客L。客L可以使用bind来用特定的端口?

对于服务器端Q当讄了SO_REUSEADDR选项Ӟ它可以在2MSL内启动ƈlisten成功。但是对于客LQ当?br>用bindq设|SO_REUSEADDRӞ如果?MSL内启动,虽然bind会成功,但是在windowsq_上connect会失败?br>而在linux上则不存在这个问题?我的实验q_Qwinxp, ubuntu7.10)

要解决windowsq_的这个问题,可以讄SO_LINGER选项。SO_LINGER选项军_调用closeӞTCP的行为?br>SO_LINGER涉及到lingerl构体,如果讄l构体中l_onoff为非0Ql_linger?Q那么调用close时TCPq接
会立L开QTCP不会发送缓冲中未发送的数据发送,而是立即发送一个RST报文l对方,q个时候TCPq?br>接就不会q入TIME_WAIT状态?

如你所见,q样做虽然解决了问题Q但是ƈ不安全。通过以上方式讄SO_LINGER状态,{同于设|SO_DONTLINGER
状态?

断开q接时的意外Q?/strong>
q个不上断开q接时的意外Q当TCPq接发生一些物理上的意外情冉|Q例如网U断开Qlinux上的TCP实现
会依然认q接有效Q而windows则会在一定时间后q回错误信息?

q似乎可以通过讄SO_KEEPALIVE选项来解冻I不过不知道这个选项是否对于所有^台都有效?

ȝQ?/strong>

个h感觉Q越写越烂。接下来会讲到TCP的数据发送,q会涉及到滑动窗口各U定时器之类的东ѝ我真诚
希望各位能够多提意见。对于TCPq接的断开Q我们只要清楚:
1. 在默认情况下Q调用close时TCP会l将数据发送完毕;
2. TIME_WAIT状态会D的问题;
3. q接意外断开时可能会出现的问题?br>4. maybe more...



Kevin Lynx 2008-05-14 15:46 发表评论
]]>
学生时代做的东西-留个U念http://www.shnenglu.com/kevinlynx/archive/2008/05/14/49783.htmlKevin LynxKevin LynxWed, 14 May 2008 01:23:00 GMThttp://www.shnenglu.com/kevinlynx/archive/2008/05/14/49783.htmlhttp://www.shnenglu.com/kevinlynx/comments/49783.htmlhttp://www.shnenglu.com/kevinlynx/archive/2008/05/14/49783.html#Feedback15http://www.shnenglu.com/kevinlynx/comments/commentRss/49783.htmlhttp://www.shnenglu.com/kevinlynx/services/trackbacks/49783.html可能我这个h比较怀旧,对什么东襉K惛_个记录,方便日后回忆。可能很多认识我的朋友都是通过GameRes那个作品?/p>

区。我对于当年那种疯狂~程的干劲很是自豪,现在差了很多Q以前帮别h做小学生pd游戏外包的时候,可以12时?/p>

个弱智的游戏,那些日子一度被我称为?2时~程挑战赛‘,只是自己跟自己比赛?/p>

 

每一ơ发布在GameRes(排除早期的那些垃圄意)Q在写简介时我都要把自己开发用的时间写上,可是脾气好的sea_bug

每次都给我删掉了。我自己汇M下:

 

1. 最让我自豪的一个游戏引擎,耗尽了我当时所有的设计能力。我努力把它做得很具扩展性,可是忽略了功能性。现在基本不l护了,可能是用L太少了。我xq是没做好吧Q?/p>

edge2d google code page

托sea_bug的忙搞了个论坛,h得让我心?http://bbs.gameres.com/showforum.asp?forumid=91

 

2. PacShooter3d:

http://data.gameres.com/showmessage.asp?TopicID=90655

不知道怎么的被人放C个网站上了:http://noyes.cn/Software.Asp?id=9667

源代码下载?/a>

 

3. Space Demon demo

当初看到dophi写的俄罗斯方块营造的那种感觉觉得很不错,于是军_认真地做个游戏出来。结果后来做的东西让我很失望。这是一个在代码上过度设计的东西。我虽然对这个游戏不满意Q但是我对代码还基本满意。后来这个游戏的代码被我游戏学院的一个朋友拿l金q一个主E(在他们学校教书?Q看Q还得到了表扬?D

q个游戏我是直接开源了的:http://www.gameres.com/showmessage.asp?TopicID=73123

 

4. Crazy Eggs Clone

<Crazy Eggs>是小林子他们工作室做的东西,属于casual gamesQ拿到国外去卖的。我当时也觉得casual games市场不错Q还找了个美工,大谈特谈Q吹嘘了很多Q最l在写策划案的时候失败了。我当时心也懒了Q最l失败?/p>

同样是在GameRes上:http://www.gameres.com/showmessage.asp?TopicID=72351

源代码下载?/a>

后来我ؓ了宣传edge2dQ特地把q个游戏UL到我的引擎上。我从来很自豪自׃码的模块性,所以移植v来很Ҏ。除了edge2d版本Q我q做了HGE版本Q不qHGE版本是做l别人的外包Q?/p>

edge2d版本下蝲

 

5. Brick Shooter Jr

q个游戏也是我翻版别人的Q用的别人的术+音乐资源Q自己重写代码。后来网上有个h又用我的资源M了个Q做的比我好?/p>

http://data.gameres.com/showmessage.asp?TopicID=65654

源代码下?/a>

6. Feeding Frenzy

Popcap的经典游戏,我做的垃圾东西,不提其他的了Q?/p>

http://data.gameres.com/showmessage.asp?TopicID=62796

源代码下?/a>

 

7.是男人就下一癑ֱ

古老的东西Q这个东西当初还和上一家广告公司合作过。我{v了长q么大的W一份合同,l果后来一分钱没捞到。他们公司现在也不做q个了。和我合作的产品l理现在貌似在搞牌?/p>

http://data.gameres.com/showmessage.asp?TopicID=54475

源代码下?/a>

8. 所谓的LQ一个我最早做的东西,现在你开baidu搜烦 kevin lynxQ出来最多的链接是<Lkevinlynx?gt;Q别信那

些,全是氓软g?/p>

http://data.gameres.com/showmessage.asp?TopicID=54474

源代码下?/a>

 

其他q给别h做了一些外包,在此特别感谢哆啦G梦老大Q给我找了很多工作。他q个人四处蟩巢,q给我说了几ơ工作?/p>

只是我还x时留在成都,所以都拒绝了。那些外包做的都比较垃圾Q做到后来基本有个小游戏框架了。版权问题可能不

能发布出来吧?/p>

Kevin Lynx 2008-05-14 09:23 发表评论
]]>
tcp要点学习-建立q接http://www.shnenglu.com/kevinlynx/archive/2008/05/11/49482.htmlKevin LynxKevin LynxSat, 10 May 2008 17:03:00 GMThttp://www.shnenglu.com/kevinlynx/archive/2008/05/11/49482.htmlhttp://www.shnenglu.com/kevinlynx/comments/49482.htmlhttp://www.shnenglu.com/kevinlynx/archive/2008/05/11/49482.html#Feedback10http://www.shnenglu.com/kevinlynx/comments/commentRss/49482.htmlhttp://www.shnenglu.com/kevinlynx/services/trackbacks/49482.htmlAuthor : Kevin Lynx

准备Q?/strong>

在这里本文将遵@上一文章的风格Q只提TCP协议中的要点Q这h觉得可以更容易地掌握TCP。或?br>Ҏ谈不上掌握,对于q种U理论的东西Q即使你现在掌握了再多的l节Q一D|间后也会淡忘?

在以后各U细节中Q因为我们会涉及到分析一些TCP中的数据报,因此一个协议包截获工具必不可少。在
<TCP/IP详解>中一直用tcpdump。这里因为我的系l是windowsQ所以只好用windowsq_的tcpdumpQ?br>也就?a target="_blank">WinDump。在使用WinDump之前Q你需要安装该E序使用的库WinpCap?

关于WinDump的具体用法你可以从网上其他地方获取,q里我只E微提一下。要让WinDump开始监听数据,
首先需要确定让其监听哪一个网l设?或者说是网l接?。你可以:

 

windump -D

 

获取当前机器上的|络接口。然后用:

 

windump -i 2 

 

开始对|络接口2的数据监听。windump如同tcpdump(其实是tcpdump)一h持过滤表辑ּQwindump
会Ҏ你提供的qo表达式过滤不需要的|络数据包,例如Q?

 

windump -i 2 port 4000 

 

那么windump只会昄端口号ؓ4000的网l数据?

序号和确认号Q?/strong>

要讲解TCP的徏立过E,也就是那个所谓的三次握手Q就会涉及到序号和确认号q两个东ѝ翻书到TCP
的报文头Q有两个很重要的?都是32?是序号域和认号域。可能有些同学会对TCP那个报文头有所
疑惑(能看懂我在讲什么的会生这L疑惑么?)Q这里我可以告诉你,你可以假想TCP的报文头是?br>C语言l构?假想而已Q去ȝbsd对TCP的实玎ͼ肯定没这么简?Q那么大致上Q所谓的TCP报文头就是:

typedef struct _tcp_header
{
   
/// 16位源端口?/span>
    unsigned short src_port;
   
/// 16位目的端口号
    unsigned short dst_port;
   
/// 32位序?/span>
    unsigned long seq_num;
   
/// 32位确认号
    unsigned long ack_num;
   
/// 16位标志位[4位首部长度,保留6位,ACK、SYN之类的标志位]
    unsigned short flag;
   
/// 16位窗口大?/span>
    unsigned short win_size;
   
/// 16位校验和
    short crc_sum;
   
/// 16位紧急指?/span>
    short ptr;
   
/// 可选选项
   
/// how to implement this ?   

}
tcp_header;


那么Q这个序号和认h什么?TCP报文为每一个字节都讄一个序P觉得很奇怪?q里q不是ؓ每一
字节附加一个序?那会是多么可W的~程手法?)Q而是Z个TCP报文附加一个序Pq个序号表示报文
中数据的W一个字节的序号Q而其他数据则是根据离W一个数据的偏移来决定序LQ例如,现在有数据:
abcd。如果这D|据的序号?200Q那么a的序号就?200Qb的序号就?201。而TCP发送的下一个数据包
的序号就会是上一个数据包最后一个字节的序号加一。例如efghi是abcd的下一个数据包Q那么它的序号就
?204。通过q种看似单的ҎQTCP实C为每一个字节设|序L功能(l于明白Z么书上要告诉
我们‘ؓ每一个字节设|一个序号’了?)。注意,讄序号是一U可以让TCP成ؓ’可靠协议‘的手段?br>TCP中各Uؕ七八p的东西都是有目的的Q大部分目的q是Z’可靠‘两个字。别把TCP看高׃Q如?br>让你来设计一个网l协议,目的需要告诉你是’可靠的‘,你就会明白ؓ什么会产生那些׃八糟的东西了?

接着看,认h什么?因ؓTCP会对接收到的数据包进行确认,发送确认数据包Ӟ׃讄q个认P
认号通常表示接收方希望接收到的下一D|文的序号。例如某一ơ接收方收到序号?200?字节CD报,
那么它发送确认报文给发送方Ӟ׃讄认号ؓ1204?

大部分书上在讲确认号和序hQ都会说认h序号加一。这其实有点误解人,所以我才在q里废话?br>半天(高手宽容?D)?

开始三ơ握手:

如果你还不会单的tcp socket~程Q我你先d学,q就好比你不会C++基本语法Q就别去研究vtable
之类?

三次握手开始于客户端试图连接服务器端。当你调用诸如connect的函数时Q正常情况下׃开始三ơ握手?br>随便在网上找张三ơ握手的图:

connection

如前文所qͼ三次握手也就是生了三个数据包。客Ldq接Q发送SYN被设|了的报?注意序号?br>认P因ؓq里不包含用h据,所以序号和认号就是加一减一的关p?。服务器端收到该报文Ӟ?br>常情况下发送SYN和ACK被设|了的报文作为确认,以及告诉客户端:我想打开我这边的q接(双工)。客?br>端于是再Ҏ务器端的SYNq行认Q于是再发送ACK报文。然后连接徏立完毕。对于阻塞式socket而言Q你
的connect可能p回成功给你?

在进行了铺天盖地的罗利巴索的基础概念的讲解后Q看看这个连接徏立的q程Q是不是单得几近无聊Q?

我们来实际点Q写个最单的客户端代码:

   sockaddr_in addr;
    memset(
&addr, 0, sizeof( addr ) );
    addr.sin_family
= AF_INET;
    addr.sin_port
= htons( 80 );
   
/// 220.181.37.55
    addr.sin_addr.s_addr = inet_addr( "220.181.37.55" );
    printf(
"%s : connecting to server.\n", _str_time() );
   
int err = connect( s, (sockaddr*) &addr, sizeof( addr ) );

 
主要是connect。运行程序前我们q行windumpQ?

 

windump -i 2 host 220.181.37.55 

 

00:38:22.979229 IP noname.domain.4397 > 220.181.37.55.80: S 2523219966:2523219966(0) win 65535 <mss 1460,nop,nop,sackOK>
00:38:23.024254 IP 220.181.37.55.80 > noname.domain.4397: S 1277008647:1277008647(0) ack 2523219967 win 2920 <mss 1440,nop,nop,sackOK>
00:38:23.024338 IP noname.domain.4397 > 220.181.37.55.80: . ack 1 win 65535 

 

如何分析windump的结果,参看<tcp/ip详解>中对于tcpdump的描q?

建立q接的附加信息:

虽然SYN、ACK之类的报文没有用h据,但是TCPq是附加了其他信息。最为重要的是附加的MSS倹{这?br>可以被协商的MSS值基本上只在徏立连接时协商。如以上数据表示QMSS?460字节?

q接的意外:

q接的意外我大致分ؓ两种情况(也许q有更多情况)Q目的主Z可达、目的主机ƈ没有在指定端口监听?br>当目的主Z可达Ӟ也就是说QSYN报文D|本无法到辑֯?如果你的机器Ҏ没插|线Q你׃可达)Q?br>那么TCP收不CQ何回复报文。这个时候,你会看到TCP中的定时器机制出C。TCP对发出的SYN报文q行
计时Q当在指定时间内没有得到回复报文ӞTCP׃重传刚才的SYN报文。通常Q各U不同的TCP实现对于
q个时值都不同Q但是据我观察,重传ơ数基本上都?ơ。例如,我连接一个不可达的主机:

 

12:39:50.560690 IP cd-zhangmin.1573 > 220.181.37.55.1024: S 3117975575:3117975575(0) win 65535 <mss 1460,nop,nop,sackOK>
12:39:53.538734 IP cd-zhangmin.1573 > 220.181.37.55.1024: S 3117975575:3117975575(0) win 65535 <mss 1460,nop,nop,sackOK>
12:39:59.663726 IP cd-zhangmin.1573 > 220.181.37.55.1024: S 3117975575:3117975575(0) win 65535 <mss 1460,nop,nop,sackOK>

 

发出了三个序号一LSYN报文Q但是没有得C个回复报?废话)。每一个SYN报文之间的间隔时间都?br>有规律的Q在windows上是3U?U?U?2U。上面的数据你看不到12U这个数据,因ؓq是W三个报文发出的
旉和connectq回错误信息时的旉之差。另一斚wQ如果连接同一个网l,q个间隔旉又不同。例?br>直接q局域网Q间隔时间就差不多ؓ500ms?

(我强烈徏议你能运行windump去试验这里提到的每一个现象,如果你在ubuntu下用tcpdumpQ记住sudo :D)

出现意外的第二种情况是如果主机数据包可达Q但是试图连接的端口Ҏ没有监听Q那么发送SYN报文的这
方会收到RST被设|的报文(connect也会q回相应的信息给?Q例如:

 

13:37:22.202532 IP cd-zhangmin.1658 > 7AURORA-CCTEST.7100: S 2417354281:2417354281(0) win 65535 <mss 1460,nop,nop,sackOK>
13:37:22.202627 IP 7AURORA-CCTEST.7100 > cd-zhangmin.1658: R 0:0(0) ack 2417354282 win 0
13:37:22.711415 IP cd-zhangmin.1658 > 7AURORA-CCTEST.7100: S 2417354281:2417354281(0) win 65535 <mss 1460,nop,nop,sackOK>
13:37:22.711498 IP 7AURORA-CCTEST.7100 > cd-zhangmin.1658: R 0:0(0) ack 1 win 0
13:37:23.367733 IP cd-zhangmin.1658 > 7AURORA-CCTEST.7100: S 2417354281:2417354281(0) win 65535 <mss 1460,nop,nop,sackOK>
13:37:23.367826 IP 7AURORA-CCTEST.7100 > cd-zhangmin.1658: R 0:0(0) ack 1 win 0 

 

可以看出Q?AURORA-CCTEST.7100q回了RST报文l我Q但是我q边Ҏ不在乎这个报文,l箋发送SYN报文?br>三次q后connectp回了?数据反映的事实是q样)

关于listen:

TCP服务器端会维护一个新q接的队列。当新连接上的客L三次握手完成Ӟ׃其攑օq个队列。这个队

列的大小是通过listen讄的。当q个队列满时Q如果有新的客户端试图连接(发送SYNQ,服务器端丢弃报文Q?

同时不做M回复?

ȝQ?/strong>
TCPq接的徏立的相关要点是q些(or more?)。正常情况下是三次握手Q非正常情况下就是SYN三次时Q?br>以及收到RST报文却被忽略?



Kevin Lynx 2008-05-11 01:03 发表评论
]]>
lua和python谁更适用于嵌入MMORPGQ?/title><link>http://www.shnenglu.com/kevinlynx/archive/2008/05/06/49023.html</link><dc:creator>Kevin Lynx</dc:creator><author>Kevin Lynx</author><pubDate>Tue, 06 May 2008 09:37:00 GMT</pubDate><guid>http://www.shnenglu.com/kevinlynx/archive/2008/05/06/49023.html</guid><wfw:comment>http://www.shnenglu.com/kevinlynx/comments/49023.html</wfw:comment><comments>http://www.shnenglu.com/kevinlynx/archive/2008/05/06/49023.html#Feedback</comments><slash:comments>14</slash:comments><wfw:commentRss>http://www.shnenglu.com/kevinlynx/comments/commentRss/49023.html</wfw:commentRss><trackback:ping>http://www.shnenglu.com/kevinlynx/services/trackbacks/49023.html</trackback:ping><description><![CDATA[<p> </p> <p>预计新项目会选择lua或python之一作ؓ游戏的脚本语a。以前草草地接触q这两门语言Q对于语法,以及嵌入qC/C++E序都有Ҏ性上的认识。可能是受《UNIX~程艺术》中KISS原则的媄响,现在dƢ简z的东西。所以我个h比较偏向于用lua?/p> <p> </p> <p>q两天翻了下|络上的资料Q在lua的wiki上看C比较lua和python?a >文章</a>Q草草地译点:</p> <p>Python:<br>1. 扩展库很多,资料很多<br>2. 数D比较强大,支持多维数组Q而lua没有数组cd<br>3. 本n带的ccd(?)支持处理动态链接库Q不需要进行C装(C扩展)<br>4. q程调试器,glua扩展工具支持<br>5. 自然语言似的语法<br>6. 对于string和list的支持,lua可以通过扩展库实?br>7. 对unicode的支?br>8. I格敏感(代码不忽略空?Q这其实可以使python的代码风格看h更好一?br>9. 内徏位操作,lua可以通过扩展库支?br>10.语言本n寚w误的处理要好些,可以有效减少E序错误<br>11.初文档比lua?br>12.寚w向对象支持更? <p>Lua:<br>1. 比pythony很多(包括~译出来的运行时?<br>2. 占用更小的内?br>3. 解释器速度更快<br>4. 比python更容易集成到C语言?br>5. 对于对象不用引用计?引用计数会导致更多的问题Q?<br>6. lua早期定位于一U配|语a(作ؓ配置文g)Q因此比起python来更Ҏ配置数据<br>7. 语言更漂?nice)、简?simple)、强?powerful)?br>8. lua支持多线E,每个U程可以配置独立的解释器Q因此lua更适合于集成进多线E程?br>9. 对空g敏感Q不用担心编辑器会将tab替换成空? <p>Useful Comments:<br>1. Everything is an object allocated on the heap in Python, including numbers. (So 123+456 creates a new heap object).<br>2. lua对于coroutine的支持更适用于嵌入进游戏Q虽然python也有Q但是ƈ没有包含q核心模? <p>3.Python was a language better suited to Game AI</p> <p> </p> <p>本来惛_扄对于python的正面资?嵌入q游戏这斚wQ,但是居然没找到。客观地说如果单独用python做应用,pythonq是很有优势。现在心意已冻I应该向leader推荐lua?/p> <p> </p> <p>psQ希望能补充以上两种语言的特炏V?/p><img src ="http://www.shnenglu.com/kevinlynx/aggbug/49023.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.shnenglu.com/kevinlynx/" target="_blank">Kevin Lynx</a> 2008-05-06 17:37 <a href="http://www.shnenglu.com/kevinlynx/archive/2008/05/06/49023.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>TCP/IP Concepts 1http://www.shnenglu.com/kevinlynx/archive/2008/04/18/47471.htmlKevin LynxKevin LynxFri, 18 Apr 2008 02:02:00 GMThttp://www.shnenglu.com/kevinlynx/archive/2008/04/18/47471.htmlhttp://www.shnenglu.com/kevinlynx/comments/47471.htmlhttp://www.shnenglu.com/kevinlynx/archive/2008/04/18/47471.html#Feedback1http://www.shnenglu.com/kevinlynx/comments/commentRss/47471.htmlhttp://www.shnenglu.com/kevinlynx/services/trackbacks/47471.html
最q在草草地看<TCP/IP详解>TCP那一部分Q之所以草草地看是因ؓ觉得早晚一天会回过头去l看。手头上
有工作要做,所以先草草地把之前随便摘抄的TCP/IP相关概念贴出来:

l箋草草地脓:

--------------------------------------------------------------------------------------------------------------------------------------------------------
TCP segment:
Thus, we have simply “passed the buck” to TCP, which must take the stream from the application
and divide it into discrete messages for IP. These messages are called TCP segments.

On regular intervals, it forms segments to be transmitted using IP. The size of the segment is
controlled by two primary factors. The first issue is that there is an overall limit to the size
of a segment, chosen to prevent unnecessary fragmentation at the IP layer. This is governed by a
parameter called the maximum segment size (MSS), which is determined during connection establishment.
The second is that TCP is designed so that once a connection is set up, each of the devices tells the
other how much data it is ready to accept at any given time. If this is lower than the MSS value, a
smaller segment must be sent. This is part of the sliding window system described in the next topic.

Since TCP works with individual bytes of data rather than discrete messages, it must use an
identification scheme that works at the byte level to implement its data transmission and tracking
system. This is accomplished by assigning each byte TCP processes a sequence number.


Since applications send data to TCP as a stream of bytes and not prepackaged messages, each
application must use its own scheme to determine where one application data element ends and the
next begins.

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

TCP MSS:
http://www.tcpipguide.com/free/t_TCPMaximumSegmentSizeMSSandRelationshiptoIPDatagra.htm

In addition to the dictates of the current window size, each TCP device also has associated
with it a ceiling on TCP size—a segment size that will never be exceeded regardless of how
 large the current window is. This is called the maximum segment size (MSS). When deciding
how much data to put into a segment, each device in the TCP connection will choose the amount
 based on the current window size, in conjunction with the various algorithms described in
the reliability section, but it will never be so large that the amount of data exceeds the
 MSS of the device to which it is sending.


Note: I need to point out that the name “maximum segment size” is in fact misleading. The
 value actually refers to the maximum amount of data that a segment can hold—it does not
include the TCP headers. So if the MSS is 100, the actual maximum segment size could be 120
(for a regular TCP header) or larger (if the segment includes TCP options).

This was computed by starting with the minimum MTU for IP networks of 576.

Devices can indicate that they wish to use a different MSS value from the default by including
a Maximum Segment Size option in the SYN message they use to establish a connection. Each
device in the connection may use a different MSS value.

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

delayed ACK algorithm

http://tangentsoft.net/wskfaq/intermediate.html#delayed-ack

In a simpleminded implementation of TCP, every data packet that comes in is immediately acknowledged
 with an ACK packet. (ACKs help to provide the reliability TCP promises.)

In modern stacks, ACKs are delayed for a short time (up to 200ms, typically) for three reasons: a)
to avoid the silly window syndrome; b) to allow ACKs to piggyback on a reply frame if one is ready
to go when the stack decides to do the ACK; and c) to allow the stack to send one ACK for several
frames, if those frames arrive within the delay period.

The stack is only allowed to delay ACKs for up to 2 frames of data.


 

--------------------------------------------------------------------------------------------------------------------------------------------------------
Nagle algorithm:

Nagle's algorithm, named after John Nagle, is a means of improving the efficiency of TCP/IP networks by reducing the number of packets that need to be sent over the network.

Nagle's document, Congestion Control in IP/TCP Internetworks (RFC896) describes what he called the 'small packet problem', where an application repeatedly emits data in small chunks, frequently only 1 byte in size. Since TCP packets have a 40 byte header (20 bytes for TCP, 20 bytes for IPv4), this results in a 41 byte packet for 1 byte of useful information, a huge overhead. This situation often occurs in Telnet sessions, where most keypresses generate a single byte of data which is transmitted immediately. Worse, over slow links, many such packets can be in transit at the same time, potentially leading to congestion collapse.

Nagle's algorithm works by coalescing a number of small outgoing messages, and sending them all at once. Specifically, as long as there is a sent packet for which the sender has received no acknowledgment, the sender should keep buffering its output until it has a full packet's worth of output, so that output can be sent all at once.


[edit] Algorithm
if there is new data to send
  if the window size >= MSS and available data is >= MSS
    send complete MSS segment now
  else
    if there is unconfirmed data still in the pipe
      enqueue data in the buffer until an acknowledge is received
    else
      send data immediately
    end if
  end if
end if
where MSS = Maximum segment size

This algorithm interacts badly with TCP delayed acknowledgments, a feature introduced into TCP at roughly the same time in the early 1980s, but by a different group. With both algorithms enabled, applications which do two successive writes to a TCP connection, followed by a read, experience a constant delay of up to 500 milliseconds, the "ACK delay". For this reason, TCP implementations usually provide applications with an interface to disable the Nagle algorithm. This is typically called the TCP_NODELAY option. The first major application to run into this problem was the X Window System.

The tinygram problem and silly window syndrome are sometimes confused. The tinygram problem occurs when the window is almost empty. Silly window syndrome occurs when the window is almost full

===================================================================================================================================
3.17 - What is the Nagle algorithm?
The Nagle algorithm is an optimization to TCP that makes the stack wait until all data is acknowledged on the connection before it sends more data. The exception is that Nagle will not cause the stack to wait for an ACK if it has enough enqueued data that it can fill a network frame. (Without this exception, the Nagle algorithm would effectively disable TCP's sliding window algorithm.) For a full description of the Nagle algorithm, see RFC 896.

So, you ask, what's the purpose of the Nagle algorithm?

The ideal case in networking is that each program always sends a full frame of data with each call to send(). That maximizes the percentage of useful program data in a packet.

The basic TCP and IPv4 headers are 20 bytes each. The worst case protocol overhead percentage, therefore, is 40/41, or 98%. Since the maximum amount of data in an Ethernet frame is 1500 bytes, the best case protocol overhead percentage is 40/1500, less than 3%.

While the Nagle algorithm is causing the stack to wait for data to be ACKed by the remote peer, the local program can make more calls to send(). Because TCP is a stream protocol, it can coalesce the data in those send() calls into a single TCP packet, increasing the percentage of useful data.

Imagine a simple Telnet program: the bulk of a Telnet conversation consists of sending one character, and receiving an echo of that character back from the remote host. Without the Nagle algorithm, this results in TCP's worst case: one byte of user data wrapped in dozens of bytes of protocol overhead. With the Nagle algorithm enabled, the TCP stack won't send that one Telnet character out until the previous characters have all been acknowledged. By then, the user may well have typed another character or two, reducing the relative protocol overhead.

This simple optimization interacts with other features of the TCP protocol suite, too:

Most stacks implement the delayed ACK algorithm: this causes the remote stack to delay ACKs under certain circumstances, which allows the local stack a bit of time to "Nagle" some more bytes into a single packet.

The Nagle algorithm tends to improve the percentage of useful data in packets more on slow networks than on fast networks, because ACKs take longer to come back.

TCP allows an ACK packet to also contain data. If the local stack decides it needs to send out an ACK packet and the Nagle algorithm has caused data to build up in the output buffer, the enqueued data will go out along with the ACK packet.
The Nagle algorithm is on by default in Winsock, but it can be turned off on a per-socket basis with the TCP_NODELAY option of setsockopt(). This option should not be turned off except in a very few situations.

Beware of depending on the Nagle algorithm too heavily. send() is a kernel function, so every call to send() takes much more time than for a regular function call. Your application should coalesce its own data as much as is practical to minimize the number of calls to send().


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

Sliding Window Acknowledgment System :
http://www.tcpipguide.com/free/t_TCPSlidingWindowAcknowledgmentSystemForDataTranspo.htm
--------------------------------------------------------------------------------------------
A basic technique for ensuring reliability in communications uses a rule that requires a
device to send back an acknowledgment each time it successfully receives a transmission.
If a transmission is not acknowledged after a period of time, it is retransmitted by its
sender. This system is called positive acknowledgment with retransmission (PAR). One
 drawback with this basic scheme is that the transmitter cannot send a second message
until the first has been acknowledged.
--------------------------------------------------------------------------------------------

http://www.ssfnet.org/Exchange/tcp/tcpTutorialNotes.html

The sliding window serves several purposes:
(1) it guarantees the reliable delivery of data
(2) it ensures that the data is delivered in order,
(3) it enforces flow control between the sender and the receiver.




------------------to be continued



Kevin Lynx 2008-04-18 10:02 发表评论
]]>
WoW服务器模拟器Ascent|络模块分析http://www.shnenglu.com/kevinlynx/archive/2008/04/02/46097.htmlKevin LynxKevin LynxWed, 02 Apr 2008 13:22:00 GMThttp://www.shnenglu.com/kevinlynx/archive/2008/04/02/46097.htmlhttp://www.shnenglu.com/kevinlynx/comments/46097.htmlhttp://www.shnenglu.com/kevinlynx/archive/2008/04/02/46097.html#Feedback4http://www.shnenglu.com/kevinlynx/comments/commentRss/46097.htmlhttp://www.shnenglu.com/kevinlynx/services/trackbacks/46097.htmlAscent|络模块

Author: Kevin Lynx

 

Ascent是WoW的服务器模拟器,你可以从它的SVN上获取它的全部代码,q从它的WIKI面获取架构h个服务器的相x骤?/p>

基本架构Q?o:p>

Ascent|络模块核心的几个类关系如下图所C:


ThreadBase属于AscentU程池模块中的类Q它实现了一个jobc,当其被加入到U程池中开始执行时Q线E池理器会为其分配一个线E(如果有线E资源)q多态调用到ThreadBasezcȝrun函数?/p>

SocketWorkerThread用以代表IOCP|络模型中的一个工作者线E,它会从IOCPl果队列里取出异步IO的操作结果。这里的IOCP使用的完成键是Socket对象指针。SocketWorkerThread获取到IO操作l果后,Ҏ获得的完成键结果通知l具体的Socket对象。(Socket的说明见后面Q?/p>

ListenSocket代表一个监听套接字。该|络模块其实只是单地socket中的概念加以装。也pQ它依然把一个套接字分ؓ两种cdQ监听套接字和数据套接字Q代表一个网l连接)。所谓的监听套接字,是指只可以在该套接字上进行监听操作;而数据套接字则只可以在此套接字上q行发送、接收数据的操作?/p>

Socket代表我上面说的数据套接字。ListenSocket是一个类模板Qؓq个模板指定的模板参数通常是派生于Socket的类。其实这里用了q个技巧隐藏了工厂模式的细节。因为ListenSocket被放在一个单独的U程里运作,当其接受C个新的网l连接时Q就创徏一个Socketzcd象。(ListenSocketcd何知道这个派生类的类名?q就是通过cL板的那个模板参数Q?/p>

上层模块通常会派生Socketc,实现一些IO操作的回调。也pQ当某个IO操作完成后,会通过Socket基类让上层模块获取通知?/p>

SocketMgr是一个全局单gcR它主要负责一些网l库的全局操作Q例如winsock库的初始化)Q它q维护了一个容器,保存所有的Socket对象。这其实是它的主要作用?/p>

q作之一Q接收新的连?/strong>Q?o:p>

接收新的|络q接是通过ListenSocket实现的。在创徏一个ListenSocket对象Ӟ你需要指定它的模板参数。这个参数通常是一个派生于Socket的类。如下:

ascent-logonserver/Main.cpp

    

ListenSocket<AuthSocket> * cl = new ListenSocket<AuthSocket>(host.c_str(), cport);

 

AuthSocketz于Socket。创建ListenSocket时构造函数指定监听IP和监听端口?/p>

 

因ؓListenSocketz于ThreadBaseQ属于线E池jobQ因此要让ListenSocket工作hQ只需要将其加入到U程池管理器Q?/p>

ascent-logonserver/Main.cpp

 

ThreadPool.ExecuteTask(cl);

 

ListenSocket开始运作v来后Q会d式地WSAAccept。如果WSAAcceptq回一个有效的套接字,ListenSocket创Z个Socketzcd象(cd由模板参数指定)Q在上面丄例子中,也就是AuthSocketQ?/p>

ascent-logonserver/ ListenSocketWin32.h

        

  socket = new T(aSocket); //创徏AuthSocketq保存网l套接字aSocket

         socket
->SetCompletionPort(m_cp);//保存完成端口对象

         socket
->Accept(&m_tempAddress); //兌到完成端口等

Accept函数最l会新创徏的Socket对象保存到SocketMgr对象内部l护的容器里。在q里Q还会回调到上层模块的OnConnect函数Q从而实C息捕莗?/p>

q作之二Q接收数?o:p>

在windowsq_下,该网l模块用的是IOCP模型Q属于异步IO。当接收新的q接Ӟ卛_出WSARecv的IO操作。在工作者线E中Q也是SocketWorkerThread中,会根据IOCP完成键得到Socket对象指针Q然后根据不同的IO操作l果多态回调到Socketzcd应的函数。例如如果是WSARecv完成Q则调用到AuthSocket::OnRead函数Q上qC子)。OnRead函数直接可以获取C存数据的~冲区指针。事实上Q每一个Socket对象在被创徏Ӟ׃自动创徏接收~冲Z及发送缓冲区?/p>

q作之三Q发送数?o:p>

分析到这里,我们可以看出Q该|络模块实现得很一般。在接受数据部分Q网l工作者线E回调到对应的Socket对象QSocket直接Ҏ据进行上层逻辑处理。更好的做法是当工作者线E回调到上层SocketQSocket的派生类Q时Q这里应该简单地数据组l成上层数据包ƈ攑օ上层数据包队列,让上层逻辑E后处理Q而不是让|络模块自己d理。这样做主要是考虑到多U程模型?/p>

同样Q该|络模块的发送模块也是一P没有~冲机制。当要发送数据时Q直接调用到Socket的Send函数。该函数拯用户数据到自q护的发送缓冲区Q然后将自己的缓冲区指针直接提交lIOCPQWSASend发送?/p>

 

l束

该网l模块实现的g有点陋,在该模块之上也没有数据校验、数据加密的模块Q这些动作散乱地分布在最上层逻辑Q。在架构上也没能很好地将概念区分开来,Socket套用了原始socket中的数据套接字,而不是我所希望的NetSession。可以圈点的地方在于该模块很多地方用了回调函数表,从而方便地实现事g传送?/p>

 



Kevin Lynx 2008-04-02 21:22 发表评论
]]>
探究CRC32法实现原理-why table-driven implementionhttp://www.shnenglu.com/kevinlynx/archive/2008/04/01/45952.htmlKevin LynxKevin LynxTue, 01 Apr 2008 13:22:00 GMThttp://www.shnenglu.com/kevinlynx/archive/2008/04/01/45952.htmlhttp://www.shnenglu.com/kevinlynx/comments/45952.htmlhttp://www.shnenglu.com/kevinlynx/archive/2008/04/01/45952.html#Feedback13http://www.shnenglu.com/kevinlynx/comments/commentRss/45952.htmlhttp://www.shnenglu.com/kevinlynx/services/trackbacks/45952.html阅读全文

Kevin Lynx 2008-04-01 21:22 发表评论
]]>
޾ƷۺϾþ| þۺϹ׾Ʒ| վþþ| Ʒþۺ | þ99Ʒ鶹լլ| ھƷ˾þþþ777| ˳˳ۺþþ| AVݺɫۺϾþ| þ97þ97Ʒӿϼ| һɫþ88ۺպƷ | ѹ99þþ㽶| þùƷ͵99| ͵þþþƷר | ӰһѾþþþþþþ | ľƷ99þù| þAvԴվ| þþþҹҹҹƷ| ?VþþƷ| þŷƷ| þü¶| þþþAVר| ƷëٸAVѾþ| ˾þĻ| Ʒþþþþþþþ| ɫۺϾþĻ| þþƷһ| þþƷAV㽶| þþƷƷëƬ| ƷŮٸaѾþ| þ޾Ʒ벥| ƷۺϾþþþþ97| þþƷа| þþþùһëƬ| þòþüƵ7| ɫݺݾþۺ| Ӱһþҹײ| ŷþþþþ| þѹƷ| 99þۺϾƷ| þҹӰԺѹۿ| þ޾ƷƷ|