import java.io.*; import com.fatdog.textEngine.XmlEngine; import com.fatdog.textEngine.exceptions.*; import com.fatdog.textEngine.query.XQLResultListener; public class Search implements XQLResultListener { public static void main( String[] args ) { XmlEngine engine = new XmlEngine(); String searchFile = args[0]; String searchType = args[1]; String query = args[2]; try { file://配置引擎 engine.setSaxParserName( "org.apache.xerces.parsers.SAXParser"); engine.setMinIndexableWordLength( 3 ); engine.setDoIndexNumbers( false ); engine.setDocument( searchFile ); if (searchType.equals("1")) { engine.setListenerType( XmlEngine.STANDARD_LISTENER); } else if (searchType.equals("2")) { engine.setListenerType( XmlEngine.SUMMARY_LISTENER); } else { engine.setListenerType( XmlEngine.CSV_LISTENER); } } catch( MissingOrInvalidSaxParserException e ){ System.out.println( "~少或不可用?SAX解析? ); return; } catch( FileNotFoundException e ) { System.out.println( "不能扑ֈ XML 文g: "); return; } catch( CantParseDocumentException e ) { System.out.println( "不能解析 XML 文g: "); return; } // engine.printSessionStats(); engine.addXQLResultListener( new Search() ); try { engine.setQuery( query ); } catch( InvalidQueryException e ) { System.out.println( "不可用的查询h: " + e.getMessage() ); return; } } public void results( String xqlResults ) { System.out.println( xqlResults ); } } |
Qorg.apache.xerces.parsers.SAXParserQ? installed successfully 1: indexing web.xml Query: ( // ( / welcome-file-list welcome-file ) ) 3 hit(s) for file://welcome-file-list/welcome-file Q?xml version="1.0"?Q? Qxql:result query="http://welcome-file-list/welcome-file" hitCount="3" elemCount="3" docCount="1" xmlns:xql="http://www.fatdog.com/ Standard_Listener.html"Q? Qwelcome-fileQ? index.jsp Q?welcome-fileQ? Qwelcome-fileQ? index.html Q?welcome-fileQ? Qwelcome-fileQ? index.htm Q?welcome-fileQ? Q?xql:resultQ?br> |
C:\xql\xql1Qjava Search web.xml 2 "http://welcome-file-list/welcome-file" Parser.installSaxParser: Qorg.apache.xerces.parsers.SAXParserQ?br>installed successfully 1: indexing web.xml Query: ( // ( / welcome-file-list welcome-file ) ) 3 hit(s) for file://welcome-file-list/welcome-file Q?xml version="1.0"?Q?br>Qxql:result query="http://welcome-file-list/welcome-file" hitCount="3" elemCount="3" docCount="1" xmlns:xql="http://www.fatdog.com/ Summary_Listener.html"Q?br>Qwelcome-file xql:docID="0" xql:elemIx="270"/Q?br>Qwelcome-file xql:docID="0" xql:elemIx="271"/Q?br>Qwelcome-file xql:docID="0" xql:elemIx="272"/Q?br>Q?xql:resultQ?br> |
C:\xql\xql1Qjava Search web.xml 3 "http://welcome-file-list/welcome-file" Parser.installSaxParser: Qorg.apache.xerces.parsers.SAXParserQ?br>installed successfully 1: indexing web.xml Query: ( // ( / welcome-file-list welcome-file ) ) 3 hit(s) for file://welcome-file-list/welcome-file 3,3,1,0 0,270,welcome-file 0,271,welcome-file 0,272,welcome-file |
同步在网l游戏中是非帔R要的Q它保证了每个玩家在屏幕上看到的?
西大体是一L(fng)。其实呢Q解军_步问题的最单的Ҏ(gu)是把每个玩家的动作都向其他玩家q播一遍,q里其实存在两个问题:1Q向哪些玩家q播Q广播哪?
消息?Q如果网lgq怎么办。事实上呢,W一个问题是个非常简单的问题Q不q之所以我提出q个问题来,是提醒大家在设计自己的消息结构的时候,需要把q?
个因素考虑q去。而对于第二个问题Q则是一个挺ȝ的问题,大家可以来看q么个例子:
比如有一个玩家A向服务器发了条指令,说我现在在P1点,要去 P2炏V指令发出的旉是T0Q服务器收到指o的时间是T1Q然后向周围的玩家广播这条消息,消息的内Ҏ(gu)“玩家A从P1到P2”有一个在A附近的玩? BQ收到服务器的这则广播的消息的时间是T2Q然后开始在客户端上dQA从P1到P2炏V这个时候就存在一个不同步的问题,玩家A和玩家B的屏q上昄 的画面相差了T2-T1的时间。这个时候怎么办呢Q? 有个解决Ҏ(gu)Q我l它取名? 预测拉扯Q虽然有些怪异了点Q不q基本上大家也能从字面上来理解它的意思。要解决q个问题Q首先要定义一个值叫Q预误差。然后需要在服务器端每个玩家q? 接的c里面加一属性,叫TimeModifiedQ然后在玩家登陆的时候,对客L(fng)的时间和服务器的旉q行比较Q得出来的差g存在 TimeModified里面。还是上面的那个例子Q服务器q播消息的时候,根据要q播对象的TimeModifiedQ计出一个客L(fng)? CurrentTimeQ然后在消息头里面包含这个CurrentTimeQ然后再q行q播。ƈ且同时在玩家A的客L(fng)本地建立一个队列,保存该条消息Q? 只到获得服务器验证就从未被验证的消息队列里面该消息删除Q如果验证失败,则会被拉扯回P1炏V然后当玩家B收到了服务器发过来的消息“玩家A从P1? P2”这个时候就查消息里面服务器发出的时间和本地旉做比较,如果大于定义的预误差,q出在T2q个旉Q玩家A的屏q上走到的地点P3Q然后把 玩家B屏幕上的玩家A直接拉扯到P3Q再l箋C去,q样p保证同步。更q一步,Z保证客户端运行v来更加smoothQ我q不推荐直接把玩家拉扯过 去,而是出P3偏后的一点P4Q然后用(P4-P1)/T(P4-P3)来算Z个很快的速度SQ然后让玩家A用速度S快速移动到P4Q这L(fng)处理Ҏ(gu) 是比较合理的Q这U解x案的原Ş在国际上被称为(Full plesiochronousQ,当然Q该原Ş被我改了很多来适应|络游戏的同步,所以而变成所谓的Q预拉扯?/font> 另外一个解x案,我给它取名叫 验证同步Q听名字也知道,大体的意思就是每条指令在l过服务器验证通过了以后再执行动作。具体的思\如下Q首先也需要在每个玩家q接cd里面定义一? TimeModifiedQ然后在客户端响应玩安标行走的同时Q客L(fng)q不会先行走动,而是发一条走路的指ol服务器Q然后等待服务器的验证。服务器? 受到q条消息以后Q进行逻辑层的验证Q然后计出需要广播的范围Q包括玩家A在内Q根据各个客L(fng)不同的TimeModified生成不同的消息头Q开? q播Q这个时候这个玩家的走\信息是完全同步的了。这个方法的优点是能保证各个客户端之间绝对的同步Q缺Ҏ(gu)当网lgq比较大的时候,玩家的客L(fng)的行 Z变得比较不流畅,l玩家带来很不爽的感觉。该U解x案的原Ş在国际上被称为(Hierarchical master-slave synchronizationQ,80q代以后被广泛应用于|络的各个领域? 最后一U解x案是一U理惛_的解x案,在国际上被称为Mutual synchronizationQ是一U对未来|络的前景的良好预测出来的解x案。这里之所以要提这个方案,q不是说我们已经完全的实Cq种Ҏ(gu)Q? 只是在网l游戏领域的某些斚w应用到这U方案的某些思想。我对该U方案取名ؓQ半服务器同步。大体的设计思\如下Q?/font> 首先客户端需要在登陆世界的时候徏立很多张q播列表Q这些列 表在客户端后台和服务器要q行不及时同步,之所以要建立多张列表Q是因ؓ要广播的cd是不止一U的Q比如说有local message,有remote message,q有global message {等Q这些列表都需要在客户端登陆的时候根据服务器发过来的消息建立好。在建立列表的同Ӟq需要获得每个列表中q播对象的TimeModifiedQƈ 且要l护一张完整的用户状态列表在后台Q也是不及时的和服务器进行同步,Ҏ(gu)本地的用L(fng)态表Q可以做C部分决策由客L(fng)自己来决定,当客L(fng)发送这? 分决{的时候,则直接将最l决{发送到各个q播列表里面的客L(fng)Qƈ对其旉q行校对Q保证每个客L(fng)在收到的消息的时间是和根据本地时间进行校对过的? 那么再采用预拉扯中提到q的计算提前量,提高速度行走q去的方法,会使同步变得非常的smooth。该Ҏ(gu)的优Ҏ(gu)不通过服务器,客户端自׃间进? 同步Q大大的降低了由于网lgq而带来的误差Qƈ且由于大部分决策都可以由客户端来做,也大大的降低了服务器的资源。由此带来的弊端是׃消息和决{权 都放在客L(fng)本地Q所以给外挂提供了很大的可乘之机?/font> l合以上三种关于|络同步z的优~点Q综合出一套关于网l游戏传输同步的较完整的解决Ҏ(gu)Q我U它为综合同步法Qcolligate synchronizationQ。大体设计思\如下Q?/font> 首先服务器需要同步的所有消息从划分一个优先等U,然后按照3/4的比例划分出重要消息和非重要消息Q对于非重要消息Q把决策权放在客L(fng)Q在客户端逻辑上徏立相关的决策机构和各U消息缓存区Q以及相关的消息~存区管理机构,如下图所C:
上图单说明了对于非重要消息,客户端的大体处理程Q其中有 一个客L(fng)被动行ؓ值得大家注意Q其中包括对服务器发q来的某些验证代码做q回Q来保消息~存中的消息和服务器端是一致的Q从而有效的防止外挂来篡Ҏ(gu) 地消息缓存。其中的消息来源是包括本地的客户端响应玩家的消息以及q程服务器传递过来的消息?/font> 对于重要消息Q比如说战斗或者是某些牉|到玩家一些比较敏? 数据的操作,则采用另外一套方案,该方案首先需要在服务器和客户端之间徏立一套Ping SystemQ然后服务器保存和用L(fng)及时的ping|当ping比较?yu)的时候,响应玩家消息的同时先不进行动作,而是先把该消息反馈给服务器,q且? 塞,服务器收到该消息Q进行逻辑验证之后向所有该详细q播的有效对象进行广播(包括消息发v者)Q然后客L(fng)收到该消息的验证Q才开始执行动作。而当 ping比较大的时候,客户端响应玩家消息的同时立刻q行动作Qƈ且同时把该消息反馈给服务器,值得注意的是q个时候还需要在本地建立一个无验证消息的队 列,把该消息入队Q执行动作的同时{待服务器的验证Q还需要保存当前状态。服务器收到客户端的h后,q行逻辑验证Qƈ把消息反馈到各个客户端,带上各个 客户端校对过的本地时间。如果验证通过不过Q则通知消息发v者,该消息验证失败,然后客户端自动把已经在进行中的动作取消,恢复原来状态。如果验证通过Q? 则广播到的各个客L(fng)Ҏ(gu)从服务器获得校对旉q行对其q行拉扯Q保证在该行为完成之前完成同步?
xQ一个比较成熟的|络游戏的同步机制已l初步徏立v来了Q接下来的逻辑代码根据各自不同的游戏风格以及侧重Ҏ(gu)写了?/font> 同步是网l游戏最重要的问题,如何同步也牵扯到各个斚w的问 题,比如说游戏的规模Q游戏的cd以及各种各样的方面,对于规模比较大的游戏Q在同步斚w可以下很多的工夫Q把消息分得十分的细腻,对于不同的消息采用不 同的同步机制Q而对于规模比较小的游戏,则可以采用大体上一L(fng)同步机制Q究竟怎么样同步,没有个定式,是需要根据自q不同情况来做Z同的同步决策? |游同步法之导航推(Dead ReckoningQ算法: 在了解该法前,我们先来谈谈该算法的一些背景资料。大安 知道Q在|络传输的时候,延迟现象是很普遍的,而在ZServer/Clientl构下的|络游戏的同步也成了很头疼的问题,在保证客L(fng)响应用户? 地指令流畅的情况下,没法有效的保证的同步的及时性。同P在军方也有类似的事情发生Q即使是同一LAN里面的机器,也会因ؓ传输的gq,D一些运的 pQ介于此Q美国国防部投入了大量的资金用于研究一U比较的好的Ҏ(gu)来解军_布式pȝ中的延迟问题Q特别是一个叫分布式模拟运? QDistributed Interactive SimulationQ的pȝQ这套系l呢Q其中就提出了一套号U是Latency Hiding & Bandwidth Reduction的方案,命名为Dead Reckoning。呵呵,来头很大吧,恩,那么我们下面来看看q套pȝ的一些观点,以及我们如何把它q用到我们的|络游戏的同步中?/font> 首先Q这套同步方案是Z我那《网l游戏的同步》一文中的Mutual Synchronization同步Ҏ(gu)的,也就是说Q它q不是Server/Clientl构的,而是Z客户端之间的同步的。下面我们先来说一些本文中用到的名词概念Q |状|络Q客L(fng)之间构成的网l 节点Q网状网l中的每个客L(fng) 极限误差Q进行同步的时候可能生的误差的极? 恩,在探讨其原理的之前,我们先来看看我们需要一个什么样的环境。首先,需要一个网状网l,|状|络如何构成呢?当有新节点进入的时候,通知该网l里面的 所有节点,各节点ؓ该客L(fng)在本地创Z个副本,d的时候,则通知所有节炚w毁本地关于该节点的副本。然后每个节点该保存一些什么数据呢Q首先有一个很 重要的包需要保存,叫做协议数据包(PDU Protocol Data UnitQ,PDU包含节点的一些相关的q动信息Q比如当前位|,速度Q运动方向,或者还有加速度{一些信息。除PDU之外Q还有其他信息需要保存,比如 说节点客L(fng)人物的HPQMP之类的。然后,保证每个节点在最?U之内要向其它节点广播一ơPDU信息。最后,讄一个极限误差倹{到此,其环境就搭 建完成了。下面,我们来看看相关的具体算法: 假设在节点A有一个小人(路hԌQ开始跑路了Q这个时候,像所有的节点q播一ơ他的PDU信息Q包括:速度QSQ,方向QOQ,加速度QAQ。那么所 有的节点开始模拟\人甲的运动轨q和路线Q包括节点A本nQ这点很重要Q,同时Q\人甲在某某玩家的控制下,会不时的改变一下方向,让其跑\的\U变? 不是那么正规。在跑\的过E中Q节点A有一个值在不停的记录着其真实坐标和在后台模拟运动的坐标的差|当差值大于极限误差的时候,则计出当前的速度 SQ方向O和速度AQ算法将在后面介l)Qƈq播l网l中其他所有节炏V其他节点在收到q条消息之后呢,可以用一些很qx的移动把路h甲拉扯过去,然后 重新调整模拟跑\的数据,让其l箋在后台模拟跑路?/font> 很显Ӟ如果极限误差定义得大了,其他节点看到的偏差就会过 大,如果极限偏差定义得小了,|络带宽׃增大。如果定义这个极限误差,pҎ(gu)各种数据的重要性来设计了。如果是回合制的|络游戏Q那么在走\上把极限 误差定义得大些无所谓,可以减少带宽。但是如果是及时打斗的网l游戏,那么得把极限误差定义得一些,否则会出现某人看到某远把自q砍死的情c?/font> Dead Reckoning的主要算法有9U,但是只有两种是解决主要问题的Q其他的基本上只是针对不同的坐标pȝ一些不同的法Q这里就不一一介绍了。好Q那么我们下面来看传说中的最主要的两U算法: W一Q目标点 = 原点 + 速度 * 旉?/font> W二Q目标点 = 原点 + 速度 * 旉?+ 1/2 * 加速度 * 旉差呵呵,传说中的法都是很经典的Q虽然我们早在初中物理的时候就学过?/font> 该算法的好处呢,正如它开始所说的QLatency Hiding & Bandwidth ReductionQ从原则上解决了|络延迟D的不同步的问题,q且有效的减了带宽Q不好的地方是该算法基本上只能使用于移动中的同步,当然Q移? 的同步是|络游戏中同步的最大的问题?/font> 该方法结合我在《网l游戏的同步》一文中提出的综合同步法的构架可以基本上解决掉网l游戏中走\同步的问题。相关问题欢q大家一赯论?有关D推测法QDead ReckoningQ中的^滑处理: Ҏ(gu)我上文章所介绍的,在节点A收到节点B新的PDU? Ӟ如果和A本地的关于B的模拟运动的坐标不一致时Q怎么样在A的屏q上把B拽到新的PDU包所描叙的点上面dQ上文中只提了用“很qx的移动”把B “拉扯”过去,那么实际中应该怎么操作呢?q里介绍四种Ҏ(gu)?/font> W一U方法,我取名叫直接拉扯法,大家听名字也知道Q就是直接把B生生的拽到新的PDU包所描叙的坐标上去,该方法的好处是:单。坏处是Q看了以下三U方法之后你׃会用q种Ҏ(gu)了?/font> W二U方法,叫直U行赎ͼLinearQ,卌B从它的当前坐标走直线到新的PDU包所描叙的坐标,行走速度用上文中所介绍的经典算法: 目标?= 原点 + 速度 * 旉?+ 1/2 * 加速度 * 旉差算出: 首先出从当前坐标到PDU包中描叙的坐标所需要的旉Q?/font> T = Dest( TargetB ?OriginB ) / Speed 然后Ҏ(gu)新PDU包中所描叙的坐标信息模拟计出在时间T之后Q按照新的PDU包中的运动信息所应该辑ֈ的位|: _TargetB = NewPDU.Speed * T 然后Ҏ(gu)当前模拟行动中的B和_TargetB的距配合时间T出一个修正过的速度_S Q?/font> _S = Dest( _TargetB ?OriginB ) / T 然后在画面上让B以速度_S走直U到Target_BQƈ且在走到之后调整光度Q方向,加速度{信息ؓ新的PDU包中所描叙的?/font> q种Ҏ(gu)呢,非常的土Q会让物体在画面上移动v来变得非常的不现实,l常会出现很生硬的拐角,而且对于l常要修改的速度_SQ在玩家A的画面上Q玩家B的行动会变得非常的诡异。其好处是:比第一U方法要好?/font> W三U方法,叫二ơ方E行赎ͼQuadraticQ,该方? 的原理呢Q就是在直线行走的过E中Q加入二ơ方E来计算一条曲U\径,让Dest( _TargetB ?OriginB )的过E是一条曲U,而不是一条直U,恩,具体的实现方法,是在LinearҎ(gu)的计中Q设定一个二ơ方E,在Dest函数计算距离的时候根据设定的 二次方程来计,q样一来,可以使B在玩家A屏幕上的Ud变得比较的有人性化一些。但是该Ҏ(gu)的考虑也是不周全的Q仅仅只考虑了TargetB? _TargetB的方向,而没有考虑新的PDU包中的方向描叙,那么从_TargetB开始模拟行走的时候,仍然是会出现比较生硬的拐角,那么下面提出? 最l解x案,彻底解册个问题?/font> 最后一U方法叫Q立方体抖动QCubic SplinesQ,q个东东比较复杂Q它需要四个坐标信息作为它的参数来q行q算Q第一个参数Pos1是OriginBQ第二个参数Pos2? OriginB在模拟运行一U以后的位置Q第三个参数Pos3是到达_TargetB前一U的位置Q第四个参数pos4是_TargetB的位|?/font> Struct pos { Coordinate X; Coordinate Y; } Pos1 = OriginB Pos2 = OriginB + V Pos3 = _TargetB ?V Pos4 = _TargetB q动轨迹?x, y)的坐标?/font> x = At^3 + Bt^2 + Ct + D y = Et^3 + Ft^2 + Gt + H Q其中时间t的取D围ؓ0-1Q在Pos1的时候ؓ0Q在Pos4的时候ؓ1Q?x(0-3)代表Pos1-Pos4中x的|y(0-3)代表Pos1-Pos4中y的?/font> A = x3 ?3 * x2 +3 * x1 ?x0 B = 3 * x2 ?6 * x1 + 3 * x0 C = 3 * x1 ?3 * x0 D = x0 E = y3 ?3 * y2 +3 * y1 ?y0 F = 3 * y2 ?6 * y1 + 3 * y0 G = 3 * y1 ?3 * y0 H = y0 上面是公式,那么下面我们来看看如何获得Pos1-Pos4Q? 首先QPos1? Pos2的取g比较Ҏ(gu)获得Q根据OriginB配合当前的速度和方向可以获得,然而Pos3和Pos4呢,怎么获得呢?如果在从Pos1到Pos4? q程中有新的PDU到达Q那么我们定义它为NewPackage?/font> Pos3 = NewPackage.X + NewPackage.Y * t + 1/2 * NewPackage.a * t^2 Pos4 = Pos3 ?(NewPackage.V + NewPackage.a * t) 如果没有NewPackage的情况下,则Pos3和Pos4按照开始所规定的方法获得?/font> xQ关于导航推的法大致介绍完毕?/font> Ƣ迎讨论Q联pM者:QQ 181194 MSN: xiataiyi@hotmail.com 参考文献《Defeating Lag with Cubic Splines?/font> |