﻿<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:trackback="http://madskills.com/public/xml/rss/module/trackback/" xmlns:wfw="http://wellformedweb.org/CommentAPI/" xmlns:slash="http://purl.org/rss/1.0/modules/slash/"><channel><title>C++博客-&lt;font color="#ff8000"&gt;&amp;nbsp&amp;nbsp&amp;nbspC++&amp;nbsp技术中心&lt;/font&gt;-随笔分类-游戏开发</title><link>http://www.cppblog.com/API/category/19780.html</link><description /><language>zh-cn</language><lastBuildDate>Thu, 07 May 2020 14:46:01 GMT</lastBuildDate><pubDate>Thu, 07 May 2020 14:46:01 GMT</pubDate><ttl>60</ttl><item><title>origin游戏服务器引擎介绍</title><link>http://www.cppblog.com/API/archive/2020/05/07/217287.html</link><dc:creator>C++技术中心</dc:creator><author>C++技术中心</author><pubDate>Thu, 07 May 2020 08:06:00 GMT</pubDate><guid>http://www.cppblog.com/API/archive/2020/05/07/217287.html</guid><wfw:comment>http://www.cppblog.com/API/comments/217287.html</wfw:comment><comments>http://www.cppblog.com/API/archive/2020/05/07/217287.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cppblog.com/API/comments/commentRss/217287.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/API/services/trackbacks/217287.html</trackback:ping><description><![CDATA[<div>origin 游戏服务器引擎简介</div><div>==================</div><div></div><div></div><div>origin 是一个由 Go 语言（golang）编写的分布式开源游戏服务器引擎。origin适用于各类游戏服务器的开发，包括 H5（HTML5）游戏服务器。</div><div></div><div>origin 解决的问题：</div><div>* origin总体设计如go语言设计一样，总是尽可能的提供简洁和易用的模式，快速开发。</div><div>* 能够根据业务需求快速并灵活的制定服务器架构。</div><div>* 利用多核优势，将不同的service配置到不同的node，并能高效的协同工作。</div><div>* 将整个引擎抽象三大对象，node,service,module。通过统一的组合模型管理游戏中各功能模块的关系。</div><div>* 有丰富并健壮的工具库。</div><div></div><div>Hello world!</div><div>---------------</div><div>下面我们来一步步的建立origin服务器,先下载[origin引擎](https://github.com/duanhf2012/origin "origin引擎"),或者使用如下命令：</div><div>```go</div><div>go get -v -u&nbsp; github.com/duanhf2012/origin</div><div>```</div><div>于是下载到GOPATH环境目录中,在src中加入main.go,内容如下：</div><div>```go</div><div>package main</div><div></div><div>import (</div><div><span style="white-space:pre">	</span>"github.com/duanhf2012/origin/node"</div><div>)</div><div></div><div>func main() {</div><div><span style="white-space:pre">	</span>node.Start()</div><div>}</div><div>```</div><div>一个origin进程需要创建一个node对象,Start开始运行。您也可以直接下载origin引擎示例:</div><div>```</div><div>go get -v -u github.com/duanhf2012/originserver</div><div>```</div><div>本文所有的说明都是基于该示例为主。</div><div></div><div>origin引擎三大对象关系</div><div>---------------</div><div>* Node:&nbsp; &nbsp;可以认为每一个Node代表着一个origin进程</div><div>* Service:一个独立的服务可以认为是一个大的功能模块，他是Node的子集，创建完成并安装Node对象中。服务可以支持对外部RPC等功能。</div><div>* Module: 这是origin最小对象单元，强烈建议所有的业务模块都划分成各个小的Module组合，origin引擎将监控所有服务与Module运行状态，例如可以监控它们的慢处理和死循环函数。Module可以建立树状关系。Service本身也是Module的类型。</div><div></div><div>origin集群核心配置文件在config的cluster目录下，在cluster下有子网目录，如github.com/duanhf2012/originserver的config/cluster目录下有subnet目录，表示子网名为subnet，可以新加多个子网的目录配置。子网与子网间是隔离的，后续将支持子网间通信规则，origin集群配置以子网的模式配置，在每个子网下配置多个Node服务器,子网在应对复杂的系统时可以应用到各个子系统，方便每个子系统的隔离。在示例的subnet目录中有cluster.json与service.json配置：</div><div></div><div>cluster.json如下：</div><div>---------------</div><div>```</div><div>{</div><div>&nbsp; &nbsp; "NodeList":[</div><div>&nbsp; &nbsp; &nbsp; &nbsp; {</div><div>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; "NodeId": 1,</div><div>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; "ListenAddr":"127.0.0.1:8001",</div><div>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; "NodeName": "Node_Test1",</div><div><span style="white-space:pre">		</span>&nbsp; "remark":"//以_打头的，表示只在本机进程，不对整个子网开发",</div><div>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; "ServiceList": ["TestService1","TestService2","TestServiceCall","GateService","_TcpService","HttpService","WSService"]</div><div>&nbsp; &nbsp; &nbsp; &nbsp; },</div><div><span style="white-space:pre">		</span> {</div><div>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; "NodeId": 2,</div><div>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; "ListenAddr":"127.0.0.1:8002",</div><div>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; "NodeName": "Node_Test1",</div><div><span style="white-space:pre">		</span>&nbsp; "remark":"//以_打头的，表示只在本机进程，不对整个子网开发",</div><div>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; "ServiceList": ["TestService1","TestService2","TestServiceCall","GateService","TcpService","HttpService","WSService"]</div><div>&nbsp; &nbsp; &nbsp; &nbsp; }</div><div>&nbsp; &nbsp; ]</div><div>```</div><div>---------------</div><div>以上配置了两个结点服务器程序:</div><div>* NodeId: 表示origin程序的结点Id标识，不允许重复。</div><div>* ListenAddr:Rpc通信服务的监听地址</div><div>* NodeName:结点名称</div><div>* remark:备注，可选项</div><div>* ServiceList:该Node将安装的服务列表</div><div>---------------</div><div></div><div>在启动程序命令program start nodeid=1中nodeid就是根据该配置装载服务。</div><div>service.json如下：</div><div>---------------</div><div>```</div><div>{</div><div>&nbsp; "Service":{</div><div><span style="white-space:pre">	</span>&nbsp; "HttpService":{</div><div><span style="white-space:pre">		</span>"ListenAddr":"0.0.0.0:9402",</div><div><span style="white-space:pre">		</span>"ReadTimeout":10000,</div><div><span style="white-space:pre">		</span>"WriteTimeout":10000,</div><div><span style="white-space:pre">		</span>"ProcessTimeout":10000,</div><div><span style="white-space:pre">		</span>"CAFile":[</div><div><span style="white-space:pre">		</span>{</div><div><span style="white-space:pre">			</span>"Certfile":"",</div><div><span style="white-space:pre">			</span>"Keyfile":""</div><div><span style="white-space:pre">		</span>}</div><div><span style="white-space:pre">		</span>]</div><div><span style="white-space:pre">		</span></div><div><span style="white-space:pre">	</span>&nbsp; },</div><div><span style="white-space:pre">	</span>&nbsp; "TcpService":{</div><div><span style="white-space:pre">		</span>"ListenAddr":"0.0.0.0:9030",</div><div><span style="white-space:pre">		</span>"MaxConnNum":3000,</div><div><span style="white-space:pre">		</span>"PendingWriteNum":10000,</div><div><span style="white-space:pre">		</span>"LittleEndian":false,</div><div><span style="white-space:pre">		</span>"MinMsgLen":4,</div><div><span style="white-space:pre">		</span>"MaxMsgLen":65535</div><div><span style="white-space:pre">	</span>&nbsp; },</div><div><span style="white-space:pre">	</span>&nbsp; "WSService":{</div><div><span style="white-space:pre">		</span>"ListenAddr":"0.0.0.0:9031",</div><div><span style="white-space:pre">		</span>"MaxConnNum":3000,</div><div><span style="white-space:pre">		</span>"PendingWriteNum":10000,</div><div><span style="white-space:pre">		</span>"MaxMsgLen":65535</div><div><span style="white-space:pre">	</span>&nbsp; }&nbsp;&nbsp;</div><div>&nbsp; },</div><div>&nbsp; "NodeService":[</div><div>&nbsp; &nbsp;{</div><div>&nbsp; &nbsp; &nbsp; "NodeId":1,</div><div><span style="white-space:pre">	</span>&nbsp; "TcpService":{</div><div><span style="white-space:pre">		</span>"ListenAddr":"0.0.0.0:9830",</div><div><span style="white-space:pre">		</span>"MaxConnNum":3000,</div><div><span style="white-space:pre">		</span>"PendingWriteNum":10000,</div><div><span style="white-space:pre">		</span>"LittleEndian":false,</div><div><span style="white-space:pre">		</span>"MinMsgLen":4,</div><div><span style="white-space:pre">		</span>"MaxMsgLen":65535</div><div><span style="white-space:pre">	</span>&nbsp; },</div><div><span style="white-space:pre">	</span>&nbsp; "WSService":{</div><div><span style="white-space:pre">		</span>"ListenAddr":"0.0.0.0:9031",</div><div><span style="white-space:pre">		</span>"MaxConnNum":3000,</div><div><span style="white-space:pre">		</span>"PendingWriteNum":10000,</div><div><span style="white-space:pre">		</span>"MaxMsgLen":65535</div><div><span style="white-space:pre">	</span>&nbsp; }&nbsp;&nbsp;</div><div>&nbsp; &nbsp;},</div><div>&nbsp; &nbsp;{</div><div>&nbsp; &nbsp; &nbsp; "NodeId":2,</div><div><span style="white-space:pre">	</span>&nbsp; "TcpService":{</div><div><span style="white-space:pre">		</span>"ListenAddr":"0.0.0.0:9030",</div><div><span style="white-space:pre">		</span>"MaxConnNum":3000,</div><div><span style="white-space:pre">		</span>"PendingWriteNum":10000,</div><div><span style="white-space:pre">		</span>"LittleEndian":false,</div><div><span style="white-space:pre">		</span>"MinMsgLen":4,</div><div><span style="white-space:pre">		</span>"MaxMsgLen":65535</div><div><span style="white-space:pre">	</span>&nbsp; },</div><div><span style="white-space:pre">	</span>&nbsp; "WSService":{</div><div><span style="white-space:pre">		</span>"ListenAddr":"0.0.0.0:9031",</div><div><span style="white-space:pre">		</span>"MaxConnNum":3000,</div><div><span style="white-space:pre">		</span>"PendingWriteNum":10000,</div><div><span style="white-space:pre">		</span>"MaxMsgLen":65535</div><div><span style="white-space:pre">	</span>&nbsp; }&nbsp;&nbsp;</div><div>&nbsp; &nbsp;}</div><div>&nbsp; ]</div><div>&nbsp;</div><div>}</div><div>```</div><div></div><div>---------------</div><div>以上配置分为两个部分：Service与NodeService，NodeService中配置的对应结点中服务的配置，如果启动程序中根据nodeid查找该域的对应的服务，如果找不到时，从Service公共部分查找。</div><div></div><div>**HttpService配置**</div><div>* ListenAddr:Http监听地址</div><div>* ReadTimeout:读网络超时毫秒</div><div>* WriteTimeout:写网络超时毫秒</div><div>* ProcessTimeout: 处理超时毫秒</div><div>* CAFile: 证书文件，如果您的服务器通过web服务器代理配置https可以忽略该配置</div><div></div><div>**TcpService配置**</div><div>* ListenAddr: 监听地址</div><div>* MaxConnNum: 允许最大连接数</div><div>* PendingWriteNum：发送网络队列最大数量</div><div>* LittleEndian:是否小端</div><div>* MinMsgLen:包最小长度</div><div>* MaxMsgLen:包最大长度</div><div></div><div>**WSService配置**</div><div>* ListenAddr: 监听地址</div><div>* MaxConnNum: 允许最大连接数</div><div>* PendingWriteNum：发送网络队列最大数量</div><div>* MaxMsgLen:包最大长度</div><div>---------------</div><div></div><div></div><div></div><div></div><div>第一章：origin基础:</div><div>---------------</div><div>查看github.com/duanhf2012/originserver中的simple_service中新建两个服务，分别是TestService1.go与CTestService2.go。</div><div></div><div>simple_service/TestService1.go如下：</div><div>```</div><div>package simple_service</div><div></div><div>import (</div><div><span style="white-space:pre">	</span>"github.com/duanhf2012/origin/node"</div><div><span style="white-space:pre">	</span>"github.com/duanhf2012/origin/service"</div><div>)</div><div></div><div>//模块加载时自动安装TestService1服务</div><div>func init(){</div><div><span style="white-space:pre">	</span>node.Setup(&amp;TestService1{})</div><div>}</div><div></div><div>//新建自定义服务TestService1</div><div>type TestService1 struct {</div><div></div><div><span style="white-space:pre">	</span>//所有的自定义服务必需加入service.Service基服务</div><div><span style="white-space:pre">	</span>//那么该自定义服务将有各种功能特性</div><div><span style="white-space:pre">	</span>//例如: Rpc,事件驱动,定时器等</div><div><span style="white-space:pre">	</span>service.Service</div><div>}</div><div></div><div>//服务初始化函数，在安装服务时，服务将自动调用OnInit函数</div><div>func (slf *TestService1) OnInit() error {</div><div><span style="white-space:pre">	</span>return nil</div><div>}</div><div></div><div></div><div>```</div><div>simple_service/TestService2.go如下：</div><div>```</div><div>import (</div><div><span style="white-space:pre">	</span>"github.com/duanhf2012/origin/node"</div><div><span style="white-space:pre">	</span>"github.com/duanhf2012/origin/service"</div><div>)</div><div></div><div>func init(){</div><div><span style="white-space:pre">	</span>node.Setup(&amp;TestService2{})</div><div>}</div><div></div><div>type TestService2 struct {</div><div><span style="white-space:pre">	</span>service.Service</div><div>}</div><div></div><div>func (slf *TestService2) OnInit() error {</div><div><span style="white-space:pre">	</span>return nil</div><div>}</div><div></div><div></div><div>```</div><div></div><div>* main.go运行代码</div><div></div><div>```go</div><div>package main</div><div></div><div>import (</div><div><span style="white-space:pre">	</span>"github.com/duanhf2012/origin/node"</div><div><span style="white-space:pre">	</span>//导入simple_service模块</div><div><span style="white-space:pre">	</span>_"orginserver/simple_service"</div><div>)</div><div></div><div>func main(){</div><div><span style="white-space:pre">	</span>node.Start()</div><div>}</div><div></div><div>```</div><div></div><div>* config/cluster/subnet/cluster.json如下：</div><div>```</div><div>{</div><div>&nbsp; &nbsp; "NodeList":[</div><div>&nbsp; &nbsp; &nbsp; &nbsp; {</div><div>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; "NodeId": 1,</div><div>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; "ListenAddr":"127.0.0.1:8001",</div><div>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; "NodeName": "Node_Test1",</div><div><span style="white-space:pre">		</span>&nbsp; "remark":"//以_打头的，表示只在本机进程，不对整个子网开发",</div><div>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; "ServiceList": ["TestService1","TestService2"]</div><div>&nbsp; &nbsp; &nbsp; &nbsp; }</div><div>&nbsp; &nbsp; ]</div><div>}</div><div>```</div><div></div><div>编译后运行结果如下：</div><div>```</div><div>#originserver start nodeid=1</div><div>TestService1 OnInit.</div><div>TestService2 OnInit.</div><div>```</div><div></div><div>第二章：Service中常用功能:</div><div>---------------</div><div></div><div>定时器:</div><div>---------------</div><div>在开发中最常用的功能有定时任务，origin提供两种定时方式：</div><div></div><div>一种AfterFunc函数，可以间隔一定时间触发回调，参照simple_service/TestService2.go,实现如下：</div><div>```</div><div>func (slf *TestService2) OnInit() error {</div><div><span style="white-space:pre">	</span>fmt.Printf("TestService2 OnInit.\n")</div><div><span style="white-space:pre">	</span>slf.AfterFunc(time.Second*1,slf.OnSecondTick)</div><div><span style="white-space:pre">	</span>return nil</div><div>}</div><div></div><div>func (slf *TestService2) OnSecondTick(){</div><div><span style="white-space:pre">	</span>fmt.Printf("tick.\n")</div><div><span style="white-space:pre">	</span>slf.AfterFunc(time.Second*1,slf.OnSecondTick)</div><div>}</div><div>```</div><div>此时日志可以看到每隔1秒钟会print一次"tick."，如果下次还需要触发，需要重新设置定时器</div><div></div><div></div><div>另一种方式是类似Linux系统的crontab命令，使用如下：</div><div>```</div><div></div><div>func (slf *TestService2) OnInit() error {</div><div><span style="white-space:pre">	</span>fmt.Printf("TestService2 OnInit.\n")</div><div></div><div><span style="white-space:pre">	</span>//crontab模式定时触发</div><div><span style="white-space:pre">	</span>//NewCronExpr的参数分别代表:Seconds Minutes Hours DayOfMonth Month DayOfWeek</div><div><span style="white-space:pre">	</span>//以下为每换分钟时触发</div><div><span style="white-space:pre">	</span>cron,_:=timer.NewCronExpr("0 * * * * *")</div><div><span style="white-space:pre">	</span>slf.CronFunc(cron,slf.OnCron)</div><div><span style="white-space:pre">	</span>return nil</div><div>}</div><div></div><div></div><div>func (slf *TestService2) OnCron(){</div><div><span style="white-space:pre">	</span>fmt.Printf(":A minute passed!\n")</div><div>}</div><div>```</div><div>以上运行结果每换分钟时打印:A minute passed!</div><div></div><div></div><div>打开多协程模式:</div><div>---------------</div><div>在origin引擎设计中，所有的服务是单协程模式，这样在编写逻辑代码时，不用考虑线程安全问题。极大的减少开发难度，但某些开发场景下不用考虑这个问题，而且需要并发执行的情况，比如，某服务只处理数据库操作控制，而数据库处理中发生阻塞等待的问题，因为一个协程，该服务接受的数据库操作只能是一个</div><div>一个的排队处理，效率过低。于是可以打开此模式指定处理协程数，代码如下：</div><div>```</div><div>func (slf *TestService1) OnInit() error {</div><div><span style="white-space:pre">	</span>fmt.Printf("TestService1 OnInit.\n")</div><div><span style="white-space:pre">	</span></div><div><span style="white-space:pre">	</span>//打开多线程处理模式，10个协程并发处理</div><div><span style="white-space:pre">	</span>slf.SetGoRouterNum(10)</div><div><span style="white-space:pre">	</span>return nil</div><div>}</div><div>```</div><div>为了</div><div></div><div></div><div>性能监控功能:</div><div>---------------</div><div>我们在开发一个大型的系统时，经常由于一些代码质量的原因，产生处理过慢或者死循环的产生，该功能可以被监测到。使用方法如下：</div><div></div><div>```</div><div>func (slf *TestService1) OnInit() error {</div><div><span style="white-space:pre">	</span>fmt.Printf("TestService1 OnInit.\n")</div><div><span style="white-space:pre">	</span>//打开性能分析工具</div><div><span style="white-space:pre">	</span>slf.OpenProfiler()</div><div><span style="white-space:pre">	</span>//监控超过1秒的慢处理</div><div><span style="white-space:pre">	</span>slf.GetProfiler().SetOverTime(time.Second*1)</div><div><span style="white-space:pre">	</span>//监控超过10秒的超慢处理，您可以用它来定位是否存在死循环</div><div><span style="white-space:pre">	</span>//比如以下设置10秒，我的应用中是不会发生超过10秒的一次函数调用</div><div><span style="white-space:pre">	</span>//所以设置为10秒。</div><div><span style="white-space:pre">	</span>slf.GetProfiler().SetMaxOverTime(time.Second*10)</div><div></div><div><span style="white-space:pre">	</span>slf.AfterFunc(time.Second*2,slf.Loop)</div><div><span style="white-space:pre">	</span>//打开多线程处理模式，10个协程并发处理</div><div><span style="white-space:pre">	</span>//slf.SetGoRouterNum(10)</div><div><span style="white-space:pre">	</span>return nil</div><div>}</div><div></div><div>func (slf *TestService1) Loop(){</div><div><span style="white-space:pre">	</span>for {</div><div><span style="white-space:pre">		</span>time.Sleep(time.Second*1)</div><div><span style="white-space:pre">	</span>}</div><div>}</div><div></div><div></div><div>func main(){</div><div><span style="white-space:pre">	</span>//打开性能分析报告功能，并设置10秒汇报一次</div><div><span style="white-space:pre">	</span>node.OpenProfilerReport(time.Second*10)</div><div><span style="white-space:pre">	</span>node.Start()</div><div>}</div><div></div><div>```</div><div>上面通过GetProfiler().SetOverTime与slf.GetProfiler().SetMaxOverTimer设置监控时间</div><div>并在main.go中，打开了性能报告器，以每10秒汇报一次，因为上面的例子中，定时器是有死循环，所以可以得到以下报告：</div><div></div><div>2020/04/22 17:53:30 profiler.go:179: [release] Profiler report tag TestService1:</div><div>process count 0,take time 0 Milliseconds,average 0 Milliseconds/per.</div><div>too slow process:Timer_orginserver/simple_service.(*TestService1).Loop-fm is take 38003 Milliseconds</div><div>直接帮助找到TestService1服务中的Loop函数</div><div></div><div></div><div></div><div>第三章：Module使用:</div><div>---------------</div><div></div><div>Module创建与销毁:</div><div>---------------</div><div>可以认为Service就是一种Module，它有Module所有的功能。在示例代码中可以参考originserver/simple_module/TestService3.go。</div><div>```</div><div>package simple_module</div><div></div><div>import (</div><div><span style="white-space:pre">	</span>"fmt"</div><div><span style="white-space:pre">	</span>"github.com/duanhf2012/origin/node"</div><div><span style="white-space:pre">	</span>"github.com/duanhf2012/origin/service"</div><div>)</div><div></div><div>func init(){</div><div><span style="white-space:pre">	</span>node.Setup(&amp;TestService3{})</div><div>}</div><div></div><div>type TestService3 struct {</div><div><span style="white-space:pre">	</span>service.Service</div><div>}</div><div></div><div>type Module1 struct {</div><div><span style="white-space:pre">	</span>service.Module</div><div>}</div><div></div><div>type Module2 struct {</div><div><span style="white-space:pre">	</span>service.Module</div><div>}</div><div></div><div>func (slf *Module1) OnInit()error{</div><div><span style="white-space:pre">	</span>fmt.Printf("Module1 OnInit.\n")</div><div><span style="white-space:pre">	</span>return nil</div><div>}</div><div></div><div>func (slf *Module1) OnRelease(){</div><div><span style="white-space:pre">	</span>fmt.Printf("Module1 Release.\n")</div><div>}</div><div></div><div>func (slf *Module2) OnInit()error{</div><div><span style="white-space:pre">	</span>fmt.Printf("Module2 OnInit.\n")</div><div><span style="white-space:pre">	</span>return nil</div><div>}</div><div></div><div>func (slf *Module2) OnRelease(){</div><div><span style="white-space:pre">	</span>fmt.Printf("Module2 Release.\n")</div><div>}</div><div></div><div></div><div>func (slf *TestService3) OnInit() error {</div><div><span style="white-space:pre">	</span>//新建两个Module对象</div><div><span style="white-space:pre">	</span>module1 := &amp;Module1{}</div><div><span style="white-space:pre">	</span>module2 := &amp;Module2{}</div><div><span style="white-space:pre">	</span>//将module1添加到服务中</div><div><span style="white-space:pre">	</span>module1Id,_ := slf.AddModule(module1)</div><div><span style="white-space:pre">	</span>//在module1中添加module2模块</div><div><span style="white-space:pre">	</span>module1.AddModule(module2)</div><div><span style="white-space:pre">	</span>fmt.Printf("module1 id is %d, module2 id is %d",module1Id,module2.GetModuleId())</div><div></div><div><span style="white-space:pre">	</span>//释放模块module1</div><div><span style="white-space:pre">	</span>slf.ReleaseModule(module1Id)</div><div><span style="white-space:pre">	</span>fmt.Printf("xxxxxxxxxxx")</div><div><span style="white-space:pre">	</span>return nil</div><div>}</div><div></div><div>```</div><div>在OnInit中创建了一条线型的模块关系TestService3-&gt;module1-&gt;module2，调用AddModule后会返回Module的Id，自动生成的Id从10e17开始,内部的id，您可以自己设置Id。当调用ReleaseModule释放时module1时，同样会将module2释放。会自动调用OnRelease函数，日志顺序如下：</div><div>```</div><div>Module1 OnInit.</div><div>Module2 OnInit.</div><div>module1 id is 100000000000000001, module2 id is 100000000000000002</div><div>Module2 Release.</div><div>Module1 Release.</div><div>```</div><div>在Module中同样可以使用定时器功能，请参照第二章节的定时器部分。</div><div></div><div></div><div>第四章：事件使用</div><div>---------------</div><div>事件是origin中一个重要的组成部分，可以在同一个node中的service与service或者与module之间进行事件通知。系统内置的几个服务，如：TcpService/HttpService等都是通过事件功能实现。他也是一个典型的观察者设计模型。在event中有两个类型的interface，一个是event.IEventProcessor它提供注册与卸载功能，另一个是event.IEventHandler提供消息广播等功能。</div><div></div><div>在目录simple_event/TestService4.go中</div><div>```</div><div>package simple_event</div><div></div><div>import (</div><div><span style="white-space:pre">	</span>"github.com/duanhf2012/origin/event"</div><div><span style="white-space:pre">	</span>"github.com/duanhf2012/origin/node"</div><div><span style="white-space:pre">	</span>"github.com/duanhf2012/origin/service"</div><div><span style="white-space:pre">	</span>"time"</div><div>)</div><div></div><div>const (</div><div><span style="white-space:pre">	</span>//自定义事件类型，必需从event.Sys_Event_User_Define开始</div><div><span style="white-space:pre">	</span>//event.Sys_Event_User_Define以内给系统预留</div><div><span style="white-space:pre">	</span>EVENT1 event.EventType =event.Sys_Event_User_Define+1</div><div>)</div><div></div><div>func init(){</div><div><span style="white-space:pre">	</span>node.Setup(&amp;TestService4{})</div><div>}</div><div></div><div>type TestService4 struct {</div><div><span style="white-space:pre">	</span>service.Service</div><div>}</div><div></div><div>func (slf *TestService4) OnInit() error {</div><div><span style="white-space:pre">	</span>//10秒后触发广播事件</div><div><span style="white-space:pre">	</span>slf.AfterFunc(time.Second*10,slf.TriggerEvent)</div><div><span style="white-space:pre">	</span>return nil</div><div>}</div><div></div><div>func (slf *TestService4) TriggerEvent(){</div><div><span style="white-space:pre">	</span>//广播事件，传入event.Event对象，类型为EVENT1,Data可以自定义任何数据</div><div><span style="white-space:pre">	</span>//这样，所有监听者都可以收到该事件</div><div><span style="white-space:pre">	</span>slf.GetEventHandler().NotifyEvent(&amp;event.Event{</div><div><span style="white-space:pre">		</span>Type: EVENT1,</div><div><span style="white-space:pre">		</span>Data: "event data.",</div><div><span style="white-space:pre">	</span>})</div><div>}</div><div></div><div></div><div>```</div><div></div><div>在目录simple_event/TestService5.go中</div><div>```</div><div>package simple_event</div><div></div><div>import (</div><div><span style="white-space:pre">	</span>"fmt"</div><div><span style="white-space:pre">	</span>"github.com/duanhf2012/origin/event"</div><div><span style="white-space:pre">	</span>"github.com/duanhf2012/origin/node"</div><div><span style="white-space:pre">	</span>"github.com/duanhf2012/origin/service"</div><div>)</div><div></div><div>func init(){</div><div><span style="white-space:pre">	</span>node.Setup(&amp;TestService5{})</div><div>}</div><div></div><div>type TestService5 struct {</div><div><span style="white-space:pre">	</span>service.Service</div><div>}</div><div></div><div>type TestModule struct {</div><div><span style="white-space:pre">	</span>service.Module</div><div>}</div><div></div><div>func (slf *TestModule) OnInit() error{</div><div><span style="white-space:pre">	</span>//在当前node中查找TestService4</div><div><span style="white-space:pre">	</span>pService := node.GetService("TestService4")</div><div></div><div><span style="white-space:pre">	</span>//在TestModule中，往TestService4中注册EVENT1类型事件监听</div><div><span style="white-space:pre">	</span>pService.(*TestService4).GetEventProcessor().RegEventReciverFunc(EVENT1,slf.GetEventHandler(),slf.OnModuleEvent)</div><div><span style="white-space:pre">	</span>return nil</div><div>}</div><div></div><div>func (slf *TestModule) OnModuleEvent(ev *event.Event){</div><div><span style="white-space:pre">	</span>fmt.Printf("OnModuleEvent type :%d data:%+v\n",ev.Type,ev.Data)</div><div>}</div><div></div><div></div><div>//服务初始化函数，在安装服务时，服务将自动调用OnInit函数</div><div>func (slf *TestService5) OnInit() error {</div><div><span style="white-space:pre">	</span>//通过服务名获取服务对象</div><div><span style="white-space:pre">	</span>pService := node.GetService("TestService4")</div><div></div><div><span style="white-space:pre">	</span>////在TestModule中，往TestService4中注册EVENT1类型事件监听</div><div><span style="white-space:pre">	</span>pService.(*TestService4).GetEventProcessor().RegEventReciverFunc(EVENT1,slf.GetEventHandler(),slf.OnServiceEvent)</div><div><span style="white-space:pre">	</span>slf.AddModule(&amp;TestModule{})</div><div><span style="white-space:pre">	</span>return nil</div><div>}</div><div></div><div>func (slf *TestService5) OnServiceEvent(ev *event.Event){</div><div><span style="white-space:pre">	</span>fmt.Printf("OnServiceEvent type :%d data:%+v\n",ev.Type,ev.Data)</div><div>}</div><div></div><div></div><div>```</div><div>程序运行10秒后，调用slf.TriggerEvent函数广播事件，于是在TestService5中会收到</div><div>```</div><div>OnServiceEvent type :1001 data:event data.</div><div>OnModuleEvent type :1001 data:event data.</div><div>```</div><div>在上面的TestModule中监听的事情，当这个Module被Release时监听会自动卸载。</div><div></div><div>第五章：RPC使用</div><div>---------------</div><div>RPC是service与service间通信的重要方式，它允许跨进程node互相访问，当然也可以指定nodeid进行调用。如下示例：</div><div></div><div>simple_rpc/TestService6.go文件如下：</div><div>```</div><div>package simple_rpc</div><div></div><div>import (</div><div><span style="white-space:pre">	</span>"github.com/duanhf2012/origin/node"</div><div><span style="white-space:pre">	</span>"github.com/duanhf2012/origin/service"</div><div>)</div><div></div><div>func init(){</div><div><span style="white-space:pre">	</span>node.Setup(&amp;TestService6{})</div><div>}</div><div></div><div>type TestService6 struct {</div><div><span style="white-space:pre">	</span>service.Service</div><div>}</div><div></div><div>func (slf *TestService6) OnInit() error {</div><div><span style="white-space:pre">	</span>return nil</div><div>}</div><div></div><div>type InputData struct {</div><div><span style="white-space:pre">	</span>A int</div><div><span style="white-space:pre">	</span>B int</div><div>}</div><div></div><div>func (slf *TestService6) RPC_Sum(input *InputData,output *int) error{</div><div><span style="white-space:pre">	</span>*output = input.A+input.B</div><div><span style="white-space:pre">	</span>return nil</div><div>}</div><div></div><div>```</div><div></div><div>simple_rpc/TestService7.go文件如下：</div><div>```</div><div>package simple_rpc</div><div></div><div>import (</div><div><span style="white-space:pre">	</span>"fmt"</div><div><span style="white-space:pre">	</span>"github.com/duanhf2012/origin/node"</div><div><span style="white-space:pre">	</span>"github.com/duanhf2012/origin/service"</div><div><span style="white-space:pre">	</span>"time"</div><div>)</div><div></div><div>func init(){</div><div><span style="white-space:pre">	</span>node.Setup(&amp;TestService7{})</div><div>}</div><div></div><div>type TestService7 struct {</div><div><span style="white-space:pre">	</span>service.Service</div><div>}</div><div></div><div>func (slf *TestService7) OnInit() error {</div><div><span style="white-space:pre">	</span>slf.AfterFunc(time.Second*2,slf.CallTest)</div><div><span style="white-space:pre">	</span>slf.AfterFunc(time.Second*2,slf.AsyncCallTest)</div><div><span style="white-space:pre">	</span>slf.AfterFunc(time.Second*2,slf.GoTest)</div><div><span style="white-space:pre">	</span>return nil</div><div>}</div><div></div><div>func (slf *TestService7) CallTest(){</div><div><span style="white-space:pre">	</span>var input InputData</div><div><span style="white-space:pre">	</span>input.A = 300</div><div><span style="white-space:pre">	</span>input.B = 600</div><div><span style="white-space:pre">	</span>var output int</div><div></div><div><span style="white-space:pre">	</span>//同步调用其他服务的rpc,input为传入的rpc,output为输出参数</div><div><span style="white-space:pre">	</span>err := slf.Call("TestService6.RPC_Sum",&amp;input,&amp;output)</div><div><span style="white-space:pre">	</span>if err != nil {</div><div><span style="white-space:pre">		</span>fmt.Printf("Call error :%+v\n",err)</div><div><span style="white-space:pre">	</span>}else{</div><div><span style="white-space:pre">		</span>fmt.Printf("Call output %d\n",output)</div><div><span style="white-space:pre">	</span>}</div><div>}</div><div></div><div></div><div>func (slf *TestService7) AsyncCallTest(){</div><div><span style="white-space:pre">	</span>var input InputData</div><div><span style="white-space:pre">	</span>input.A = 300</div><div><span style="white-space:pre">	</span>input.B = 600</div><div><span style="white-space:pre">	</span>/*slf.AsyncCallNode(1,"TestService6.RPC_Sum",&amp;input,func(output *int,err error){</div><div><span style="white-space:pre">	</span>})*/</div><div><span style="white-space:pre">	</span>//异步调用，在数据返回时，会回调传入函数</div><div><span style="white-space:pre">	</span>//注意函数的第一个参数一定是RPC_Sum函数的第二个参数，err error为RPC_Sum返回值</div><div><span style="white-space:pre">	</span>slf.AsyncCall("TestService6.RPC_Sum",&amp;input,func(output *int,err error){</div><div><span style="white-space:pre">		</span>if err != nil {</div><div><span style="white-space:pre">			</span>fmt.Printf("AsyncCall error :%+v\n",err)</div><div><span style="white-space:pre">		</span>}else{</div><div><span style="white-space:pre">			</span>fmt.Printf("AsyncCall output %d\n",*output)</div><div><span style="white-space:pre">		</span>}</div><div><span style="white-space:pre">	</span>})</div><div>}</div><div></div><div>func (slf *TestService7) GoTest(){</div><div><span style="white-space:pre">	</span>var input InputData</div><div><span style="white-space:pre">	</span>input.A = 300</div><div><span style="white-space:pre">	</span>input.B = 600</div><div></div><div><span style="white-space:pre">	</span>//在某些应用场景下不需要数据返回可以使用Go，它是不阻塞的,只需要填入输入参数</div><div><span style="white-space:pre">	</span>err := slf.Go("TestService6.RPC_Sum",&amp;input)</div><div><span style="white-space:pre">	</span>if err != nil {</div><div><span style="white-space:pre">		</span>fmt.Printf("Go error :%+v\n",err)</div><div><span style="white-space:pre">	</span>}</div><div></div><div><span style="white-space:pre">	</span>//以下是广播方式，如果在同一个子网中有多个同名的服务名，CastGo将会广播给所有的node</div><div><span style="white-space:pre">	</span>//slf.CastGo("TestService6.RPC_Sum",&amp;input)</div><div>}</div><div></div><div>```</div><div>您可以把TestService6配置到其他的Node中，比如NodeId为2中。只要在一个子网，origin引擎可以无差别调用。开发者只需要关注Service关系。同样它也是您服务器架构设计的核心需要思考的部分。</div><div></div><div></div><div>第六章：HttpService使用</div><div>---------------</div><div>HttpService是origin引擎中系统实现的http服务，http接口中常用的GET,POST以及url路由处理。</div><div></div><div>simple_http/TestHttpService.go文件如下：</div><div>```</div><div>package simple_http</div><div></div><div>import (</div><div><span style="white-space:pre">	</span>"fmt"</div><div><span style="white-space:pre">	</span>"github.com/duanhf2012/origin/node"</div><div><span style="white-space:pre">	</span>"github.com/duanhf2012/origin/service"</div><div><span style="white-space:pre">	</span>"github.com/duanhf2012/origin/sysservice"</div><div><span style="white-space:pre">	</span>"net/http"</div><div>)</div><div></div><div>func init(){</div><div><span style="white-space:pre">	</span>node.Setup(&amp;sysservice.HttpService{})</div><div><span style="white-space:pre">	</span>node.Setup(&amp;TestHttpService{})</div><div>}</div><div></div><div>//新建自定义服务TestService1</div><div>type TestHttpService struct {</div><div><span style="white-space:pre">	</span>service.Service</div><div>}</div><div></div><div>func (slf *TestHttpService) OnInit() error {</div><div><span style="white-space:pre">	</span>//获取系统httpservice服务</div><div><span style="white-space:pre">	</span>httpervice := node.GetService("HttpService").(*sysservice.HttpService)</div><div></div><div><span style="white-space:pre">	</span>//新建并设置路由对象</div><div><span style="white-space:pre">	</span>httpRouter := sysservice.NewHttpHttpRouter()</div><div><span style="white-space:pre">	</span>httpervice.SetHttpRouter(httpRouter,slf.GetEventHandler())</div><div></div><div><span style="white-space:pre">	</span>//GET方法，请求url:http://127.0.0.1:9402/get/query?nickname=boyce</div><div><span style="white-space:pre">	</span>//并header中新增key为uid,value为1000的头,则用postman测试返回结果为：</div><div><span style="white-space:pre">	</span>//head uid:1000, nickname:boyce</div><div><span style="white-space:pre">	</span>httpRouter.GET("/get/query", slf.HttpGet)</div><div></div><div><span style="white-space:pre">	</span>//POST方法 请求url:http://127.0.0.1:9402/post/query</div><div><span style="white-space:pre">	</span>//返回结果为：{"msg":"hello world"}</div><div><span style="white-space:pre">	</span>httpRouter.POST("/post/query", slf.HttpPost)</div><div></div><div><span style="white-space:pre">	</span>//GET方式获取目录下的资源，http://127.0.0.1:port/img/head/a.jpg</div><div><span style="white-space:pre">	</span>httpRouter.SetServeFile(sysservice.METHOD_GET,"/img/head/","d:/img")</div><div></div><div><span style="white-space:pre">	</span>return nil</div><div>}</div><div></div><div>func (slf *TestHttpService) HttpGet(session *sysservice.HttpSession){</div><div><span style="white-space:pre">	</span>//从头中获取key为uid对应的值</div><div><span style="white-space:pre">	</span>uid := session.GetHeader("uid")</div><div><span style="white-space:pre">	</span>//从url参数中获取key为nickname对应的值</div><div><span style="white-space:pre">	</span>nickname,_ := session.Query("nickname")</div><div><span style="white-space:pre">	</span>//向body部分写入数据</div><div><span style="white-space:pre">	</span>session.Write([]byte(fmt.Sprintf("head uid:%s, nickname:%s",uid,nickname)))</div><div><span style="white-space:pre">	</span>//写入http状态</div><div><span style="white-space:pre">	</span>session.WriteStatusCode(http.StatusOK)</div><div><span style="white-space:pre">	</span>//完成返回</div><div><span style="white-space:pre">	</span>session.Done()</div><div>}</div><div></div><div>type HttpRespone struct {</div><div><span style="white-space:pre">	</span>Msg string `json:"msg"`</div><div>}</div><div></div><div>func (slf *TestHttpService) HttpPost(session *sysservice.HttpSession){</div><div><span style="white-space:pre">	</span>//也可以采用直接返回数据对象方式，如下：</div><div><span style="white-space:pre">	</span>session.WriteJsonDone(http.StatusOK,&amp;HttpRespone{Msg: "hello world"})</div><div>}</div><div></div><div>```</div><div>注意，要在main.go中加入import _ "orginserver/simple_service"，并且在config/cluster/subnet/cluster.json中的ServiceList加入服务。</div><div></div><div></div><div></div><div>第七章：TcpService服务使用</div><div>---------------</div><div>TcpService是origin引擎中系统实现的Tcp服务，可以支持自定义消息格式处理器。只要重新实现network.Processor接口。目前内置已经实现最常用的protobuf处理器。</div><div></div><div>simple_tcp/TestTcpService.go文件如下：</div><div>```</div><div>package simple_tcp</div><div></div><div>import (</div><div><span style="white-space:pre">	</span>"fmt"</div><div><span style="white-space:pre">	</span>"github.com/duanhf2012/origin/network/processor"</div><div><span style="white-space:pre">	</span>"github.com/duanhf2012/origin/node"</div><div><span style="white-space:pre">	</span>"github.com/duanhf2012/origin/service"</div><div><span style="white-space:pre">	</span>"github.com/duanhf2012/origin/sysservice"</div><div><span style="white-space:pre">	</span>"github.com/golang/protobuf/proto"</div><div><span style="white-space:pre">	</span>"orginserver/simple_tcp/msgpb"</div><div>)</div><div></div><div>func init(){</div><div><span style="white-space:pre">	</span>node.Setup(&amp;sysservice.TcpService{})</div><div><span style="white-space:pre">	</span>node.Setup(&amp;TestTcpService{})</div><div>}</div><div></div><div>//新建自定义服务TestService1</div><div>type TestTcpService struct {</div><div><span style="white-space:pre">	</span>service.Service</div><div><span style="white-space:pre">	</span>processor *processor.PBProcessor</div><div><span style="white-space:pre">	</span>tcpService *sysservice.TcpService</div><div>}</div><div></div><div>func (slf *TestTcpService) OnInit() error {</div><div><span style="white-space:pre">	</span>//获取安装好了的TcpService对象</div><div><span style="white-space:pre">	</span>slf.tcpService =&nbsp; node.GetService("TcpService").(*sysservice.TcpService)</div><div></div><div><span style="white-space:pre">	</span>//新建内置的protobuf处理器，您也可以自定义路由器，比如json，后续会补充</div><div><span style="white-space:pre">	</span>slf.processor = processor.NewPBProcessor()</div><div></div><div><span style="white-space:pre">	</span>//注册监听客户连接断开事件</div><div><span style="white-space:pre">	</span>slf.processor.RegisterDisConnected(slf.OnDisconnected)</div><div><span style="white-space:pre">	</span>//注册监听客户连接事件</div><div><span style="white-space:pre">	</span>slf.processor.RegisterConnected(slf.OnConnected)</div><div><span style="white-space:pre">	</span>//注册监听消息类型MsgType_MsgReq，并注册回调</div><div><span style="white-space:pre">	</span>slf.processor.Register(uint16(msgpb.MsgType_MsgReq),&amp;msgpb.Req{},slf.OnRequest)</div><div><span style="white-space:pre">	</span>//将protobuf消息处理器设置到TcpService服务中</div><div><span style="white-space:pre">	</span>slf.tcpService.SetProcessor(slf.processor,slf.GetEventHandler())</div><div></div><div><span style="white-space:pre">	</span>return nil</div><div>}</div><div></div><div></div><div>func (slf *TestTcpService) OnConnected(clientid uint64){</div><div><span style="white-space:pre">	</span>fmt.Printf("client id %d connected\n",clientid)</div><div>}</div><div></div><div></div><div>func (slf *TestTcpService) OnDisconnected(clientid uint64){</div><div><span style="white-space:pre">	</span>fmt.Printf("client id %d disconnected\n",clientid)</div><div>}</div><div></div><div>func (slf *TestTcpService) OnRequest (clientid uint64,msg proto.Message){</div><div><span style="white-space:pre">	</span>//解析客户端发过来的数据</div><div><span style="white-space:pre">	</span>pReq := msg.(*msgpb.Req)</div><div><span style="white-space:pre">	</span>//发送数据给客户端</div><div><span style="white-space:pre">	</span>err := slf.tcpService.SendMsg(clientid,&amp;msgpb.Req{</div><div><span style="white-space:pre">		</span>Msg: proto.String(pReq.GetMsg()),</div><div><span style="white-space:pre">	</span>})</div><div><span style="white-space:pre">	</span>if err != nil {</div><div><span style="white-space:pre">		</span>fmt.Printf("send msg is fail %+v!",err)</div><div><span style="white-space:pre">	</span>}</div><div>}</div><div>```</div><div></div><div></div><div>第八章：其他系统模块介绍</div><div>---------------</div><div>* sysservice/wsservice.go:支持了WebSocket协议，使用方法与TcpService类似</div><div>* sysmodule/DBModule.go:对mysql数据库操作</div><div>* sysmodule/RedisModule.go:对Redis数据进行操作</div><div>* sysmodule/HttpClientPoolModule.go:Http客户端请求封装</div><div>* log/log.go:日志的封装，可以使用它构建对象记录业务文件日志</div><div>* util:在该目录下，有常用的uuid,hash,md5,协程封装等工具库</div><div>* https://github.com/duanhf2012/originservice: 其他扩展支持的服务可以在该工程上看到，目前支持firebase推送的封装。</div><div></div><div></div><div>**欢迎加入origin服务器开发QQ交流群:168306674，有任何疑问我都会及时解答**</div><div></div><div>提交bug及特性: https://github.com/duanhf2012/origin/issues</div><div></div><div></div><div></div><img src ="http://www.cppblog.com/API/aggbug/217287.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/API/" target="_blank">C++技术中心</a> 2020-05-07 16:06 <a href="http://www.cppblog.com/API/archive/2020/05/07/217287.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>golang游戏服务器引擎</title><link>http://www.cppblog.com/API/archive/2020/05/07/217286.html</link><dc:creator>C++技术中心</dc:creator><author>C++技术中心</author><pubDate>Thu, 07 May 2020 08:04:00 GMT</pubDate><guid>http://www.cppblog.com/API/archive/2020/05/07/217286.html</guid><wfw:comment>http://www.cppblog.com/API/comments/217286.html</wfw:comment><comments>http://www.cppblog.com/API/archive/2020/05/07/217286.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cppblog.com/API/comments/commentRss/217286.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/API/services/trackbacks/217286.html</trackback:ping><description><![CDATA[<span data-offset-key="51d80-0-0" style="color: #1a1a1a; font-family: -apple-system, BlinkMacSystemFont, &quot;Helvetica Neue&quot;, &quot;PingFang SC&quot;, &quot;Microsoft YaHei&quot;, &quot;Source Han Sans SC&quot;, &quot;Noto Sans CJK SC&quot;, &quot;WenQuanYi Micro Hei&quot;, sans-serif; font-size: 15px; white-space: pre-wrap; background-color: #ffffff;"><span data-text="true">现在go语言比较流行的有leaf,gowold,origin。前两个比较基础，实现集群还需要进行二次的编码设计。origin不一样，只需要通过配置方便快速的集群。</span></span>origin总体设计如go语言设计一样，总是尽可能的提供简洁和易用的模式，快速开发。 能够根据业务需求快速并灵活的制定服务器架构。 利用多核优势，将不同的service配置到不同的node，并能高效的协同工作。 将整个引擎抽象三大对象，node,service,module。通过统一的组合模型管理游戏中各功能模块的关系。&nbsp;<br /><br /><br /><div>origin引擎三大对象关系</div><div>---------------</div><div>* Node:&nbsp; &nbsp;可以认为每一个Node代表着一个origin进程</div><div>* Service:一个独立的服务可以认为是一个大的功能模块，他是Node的子集，创建完成并安装Node对象中。服务可以支持对外部RPC等功能。</div><div>* Module: 这是origin最小对象单元，强烈建议所有的业务模块都划分成各个小的Module组合，origin引擎将监控所有服务与Module运行状态，例如可以监控它们的慢处理和死循环函数。Module可以建立树状关系。Service本身也是Module的类型。<br /><br />更加详细的参照项目地址：<a href="https://github.com/duanhf2012/origin">https://github.com/duanhf2012/origin</a></div><img src ="http://www.cppblog.com/API/aggbug/217286.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/API/" target="_blank">C++技术中心</a> 2020-05-07 16:04 <a href="http://www.cppblog.com/API/archive/2020/05/07/217286.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>A*算法实现</title><link>http://www.cppblog.com/API/archive/2017/08/17/215164.html</link><dc:creator>C++技术中心</dc:creator><author>C++技术中心</author><pubDate>Thu, 17 Aug 2017 06:43:00 GMT</pubDate><guid>http://www.cppblog.com/API/archive/2017/08/17/215164.html</guid><wfw:comment>http://www.cppblog.com/API/comments/215164.html</wfw:comment><comments>http://www.cppblog.com/API/archive/2017/08/17/215164.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cppblog.com/API/comments/commentRss/215164.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/API/services/trackbacks/215164.html</trackback:ping><description><![CDATA[<span style="background-color: #eeeeee; font-size: 13px;">#ifndef&nbsp;_ASTAR_FLY_H__</span>
<div style="background-color:#eeeeee;font-size:13px;border:1px solid #CCCCCC;padding-right: 5px;padding-bottom: 4px;padding-left: 4px;padding-top: 4px;width: 98%;word-break:break-all"><span style="color: #0000FF; ">#define</span>&nbsp;_ASTAR_FLY_H__<br />
#include&nbsp;"Coordinate.h"<br />
#include&nbsp;&lt;map&gt;<br />
#include&nbsp;&lt;<span style="color: #0000FF; ">set</span>&gt;<br />
<br />
<span style="color: #0000FF; ">using</span>&nbsp;<span style="color: #0000FF; ">namespace</span>&nbsp;std;<br />
<br />
<br />
<br />
typedef&nbsp;<span style="color: #0000FF; ">struct</span>&nbsp;<br />
{<br />
&nbsp;&nbsp;&nbsp;&nbsp;<span style="color: #0000FF; ">int</span>&nbsp;x;<br />
&nbsp;&nbsp;&nbsp;&nbsp;<span style="color: #0000FF; ">int</span>&nbsp;y;<br />
&nbsp;&nbsp;&nbsp;&nbsp;<span style="color: #0000FF; ">int</span>&nbsp;f;&nbsp;<span style="color: #008000; ">//</span><span style="color: #008000; ">&nbsp;f&nbsp;=&nbsp;g&nbsp;+&nbsp;h</span><span style="color: #008000; "><br />
</span>&nbsp;&nbsp;&nbsp;&nbsp;<span style="color: #0000FF; ">int</span>&nbsp;g;<br />
&nbsp;&nbsp;&nbsp;&nbsp;<span style="color: #0000FF; ">int</span>&nbsp;h;<br />
<br />
&nbsp;&nbsp;&nbsp;&nbsp;<span style="color: #0000FF; ">void</span>&nbsp;Init(<span style="color: #0000FF; ">int</span>&nbsp;_x,<span style="color: #0000FF; ">int</span>&nbsp;_y)<br />
&nbsp;&nbsp;&nbsp;&nbsp;{<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;f&nbsp;=&nbsp;0;<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;g&nbsp;=&nbsp;0;<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;h&nbsp;=&nbsp;0;<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;x&nbsp;=&nbsp;_x;<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;y&nbsp;=&nbsp;_y;<br />
&nbsp;&nbsp;&nbsp;&nbsp;}<br />
}&nbsp;APoint,&nbsp;*PAPoint;<br />
<br />
<br />
<br />
<span style="color: #0000FF; ">class</span>&nbsp;CAStarFly<br />
{<br />
<span style="color: #0000FF; ">public</span>:<br />
&nbsp;&nbsp;&nbsp;&nbsp;typedef&nbsp;std::list&lt;CIntPoint&gt;&nbsp;PosList;<br />
<br />
<br />
&nbsp;&nbsp;&nbsp;&nbsp;CAStarFly();<br />
&nbsp;&nbsp;&nbsp;&nbsp;~CAStarFly();<br />
<br />
&nbsp;&nbsp;&nbsp;&nbsp;<span style="color: #0000FF; ">bool</span>&nbsp;CalcPath(PosList&amp;&nbsp;pathList,&nbsp;<span style="color: #0000FF; ">const</span>&nbsp;CIntPoint&amp;&nbsp;from,&nbsp;<span style="color: #0000FF; ">const</span>&nbsp;CIntPoint&amp;&nbsp;to);<br />
<span style="color: #0000FF; ">protected</span>:<br />
&nbsp;&nbsp;&nbsp;&nbsp;<span style="color: #0000FF; ">virtual</span>&nbsp;<span style="color: #0000FF; ">int</span>&nbsp;GetMapWidth();<br />
&nbsp;&nbsp;&nbsp;&nbsp;<span style="color: #0000FF; ">virtual</span>&nbsp;<span style="color: #0000FF; ">int</span>&nbsp;GetMapHeight();<br />
&nbsp;&nbsp;&nbsp;&nbsp;<span style="color: #0000FF; ">virtual</span>&nbsp;CIntSize&nbsp;GetRoleSize();<br />
&nbsp;&nbsp;&nbsp;&nbsp;<span style="color: #0000FF; ">virtual</span>&nbsp;<span style="color: #0000FF; ">bool</span>&nbsp;GetNearPos(<span style="color: #0000FF; ">int</span>&nbsp;curX,<span style="color: #0000FF; ">int</span>&nbsp;curY,PosList&nbsp;&amp;nearPos);<br />
&nbsp;&nbsp;&nbsp;&nbsp;<span style="color: #0000FF; ">virtual</span>&nbsp;<span style="color: #0000FF; ">bool</span>&nbsp;IsCanFly(<span style="color: #0000FF; ">int</span>&nbsp;x,<span style="color: #0000FF; ">int</span>&nbsp;y);<br />
&nbsp;&nbsp;&nbsp;&nbsp;<span style="color: #0000FF; ">virtual</span>&nbsp;<span style="color: #0000FF; ">void</span>&nbsp;OnAddBestPoint(<span style="color: #0000FF; ">int</span>&nbsp;x,&nbsp;<span style="color: #0000FF; ">int</span>&nbsp;y);<br />
&nbsp;&nbsp;&nbsp;&nbsp;<span style="color: #0000FF; ">virtual</span>&nbsp;<span style="color: #0000FF; ">void</span>&nbsp;OnUnAddBestPoint(<span style="color: #0000FF; ">int</span>&nbsp;x,&nbsp;<span style="color: #0000FF; ">int</span>&nbsp;y);<br />
<span style="color: #0000FF; ">private</span>:<br />
&nbsp;&nbsp;&nbsp;&nbsp;PAPoint&nbsp;CalcNextPoint(PosList&amp;&nbsp;pathList,&nbsp;PAPoint&nbsp;ptCalc);&nbsp;<span style="color: #008000; ">//</span><span style="color: #008000; ">&nbsp;应用递归的办法进行查询</span><span style="color: #008000; "><br />
</span><br />
&nbsp;&nbsp;&nbsp;&nbsp;<span style="color: #0000FF; ">void</span>&nbsp;SetStartPoint(<span style="color: #0000FF; ">int</span>&nbsp;x,&nbsp;<span style="color: #0000FF; ">int</span>&nbsp;y);<br />
&nbsp;&nbsp;&nbsp;&nbsp;<span style="color: #0000FF; ">void</span>&nbsp;SetEndPoint(<span style="color: #0000FF; ">int</span>&nbsp;x,&nbsp;<span style="color: #0000FF; ">int</span>&nbsp;y);<br />
&nbsp;&nbsp;&nbsp;&nbsp;<span style="color: #0000FF; ">void</span>&nbsp;SetOpened(<span style="color: #0000FF; ">int</span>&nbsp;x,&nbsp;<span style="color: #0000FF; ">int</span>&nbsp;y);<br />
&nbsp;&nbsp;&nbsp;&nbsp;<span style="color: #0000FF; ">void</span>&nbsp;SetClosed(<span style="color: #0000FF; ">int</span>&nbsp;x,&nbsp;<span style="color: #0000FF; ">int</span>&nbsp;y);<br />
&nbsp;&nbsp;&nbsp;&nbsp;<span style="color: #0000FF; ">void</span>&nbsp;SetCurrent(<span style="color: #0000FF; ">int</span>&nbsp;x,&nbsp;<span style="color: #0000FF; ">int</span>&nbsp;y);<br />
<br />
&nbsp;&nbsp;&nbsp;&nbsp;int32&nbsp;GetPosIndex(<span style="color: #0000FF; ">int</span>&nbsp;x,&nbsp;<span style="color: #0000FF; ">int</span>&nbsp;y);<br />
&nbsp;&nbsp;&nbsp;&nbsp;int32&nbsp;GetFValue(<span style="color: #0000FF; ">const</span>&nbsp;CIntPoint&nbsp;&amp;pos);<br />
&nbsp;&nbsp;&nbsp;&nbsp;<span style="color: #0000FF; ">bool</span>&nbsp;IsNotClosePos(<span style="color: #0000FF; ">int</span>&nbsp;x,&nbsp;<span style="color: #0000FF; ">int</span>&nbsp;y);<br />
<span style="color: #0000FF; ">private</span>:<br />
&nbsp;&nbsp;&nbsp;&nbsp;APoint&nbsp;m_startPoint;&nbsp;<span style="color: #008000; ">//</span><span style="color: #008000; ">起始点</span><span style="color: #008000; "><br />
</span>&nbsp;&nbsp;&nbsp;&nbsp;APoint&nbsp;m_endPoint;&nbsp;&nbsp;&nbsp;<span style="color: #008000; ">//</span><span style="color: #008000; ">结束点</span><span style="color: #008000; "><br />
</span>&nbsp;&nbsp;&nbsp;&nbsp;APoint&nbsp;m_curPoint;&nbsp;&nbsp;&nbsp;<span style="color: #008000; ">//</span><span style="color: #008000; ">当前移到点</span><span style="color: #008000; "><br />
</span>&nbsp;&nbsp;&nbsp;&nbsp;<span style="color: #0000FF; ">set</span>&lt;int32&gt;&nbsp;m_setClosePos;<br />
&nbsp;&nbsp;&nbsp;&nbsp;<span style="color: #0000FF; ">int</span>&nbsp;m_curGValue;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span style="color: #008000; ">//</span><span style="color: #008000; ">当前G值</span><span style="color: #008000; "><br />
</span>};<br />
<br />
<span style="color: #0000FF; ">#endif</span>/*_ASTAR_FLY_H__*/</div>
<br />
<br />
<br />
<div style="background-color:#eeeeee;font-size:13px;border:1px solid #CCCCCC;padding-right: 5px;padding-bottom: 4px;padding-left: 4px;padding-top: 4px;width: 98%;word-break:break-all"><!--<br />
<br />
Code highlighting produced by Actipro CodeHighlighter (freeware)<br />
http://www.CodeHighlighter.com/<br />
<br />
-->#include&nbsp;"stdafx.h"<br />
#include&nbsp;"AStarFly.h"<br />
<br />
<br />
CAStarFly::CAStarFly()<br />
{<br />
}<br />
<br />
CAStarFly::~CAStarFly()<br />
{<br />
}<br />
<br />
<span style="color: #0000FF; ">bool</span>&nbsp;CAStarFly::CalcPath(PosList&amp;&nbsp;pathList,&nbsp;<span style="color: #0000FF; ">const</span>&nbsp;CIntPoint&amp;&nbsp;from,&nbsp;<span style="color: #0000FF; ">const</span>&nbsp;CIntPoint&amp;&nbsp;to)<br />
{<br />
&nbsp;&nbsp;&nbsp;&nbsp;<span style="color: #008000; ">//</span><span style="color: #008000; ">1.先设置开始与目标</span><span style="color: #008000; "><br />
</span>&nbsp;&nbsp;&nbsp;&nbsp;SetStartPoint(from.x,&nbsp;from.y);<br />
&nbsp;&nbsp;&nbsp;&nbsp;SetEndPoint(to.x,&nbsp;to.y);<br />
&nbsp;&nbsp;&nbsp;&nbsp;m_curPoint&nbsp;=&nbsp;m_startPoint;<br />
&nbsp;&nbsp;&nbsp;&nbsp;SetClosed(m_startPoint.x,&nbsp;m_startPoint.y);&nbsp;<span style="color: #008000; ">//</span><span style="color: #008000; ">路径搜索不再经过</span><span style="color: #008000; "><br />
</span>&nbsp;&nbsp;&nbsp;&nbsp;m_curGValue&nbsp;=&nbsp;0;<br />
<br />
&nbsp;&nbsp;&nbsp;&nbsp;<span style="color: #008000; ">//</span><span style="color: #008000; ">从起点开始计算路径点</span><span style="color: #008000; "><br />
</span>&nbsp;&nbsp;&nbsp;&nbsp;<span style="color: #0000FF; ">return</span>&nbsp;CalcNextPoint(pathList,&nbsp;nullptr)!=nullptr;<br />
}<br />
<br />
PAPoint&nbsp;CAStarFly::CalcNextPoint(PosList&amp;&nbsp;pathList,&nbsp;PAPoint&nbsp;ptCalc)<br />
{<br />
&nbsp;&nbsp;&nbsp;&nbsp;<span style="color: #0000FF; ">if</span>&nbsp;(nullptr&nbsp;==&nbsp;ptCalc)<br />
&nbsp;&nbsp;&nbsp;&nbsp;{<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;ptCalc&nbsp;=&nbsp;&amp;m_startPoint;<br />
&nbsp;&nbsp;&nbsp;&nbsp;}<br />
&nbsp;&nbsp;&nbsp;&nbsp;<span style="color: #0000FF; ">int</span>&nbsp;curX&nbsp;=&nbsp;ptCalc-&gt;x;<br />
&nbsp;&nbsp;&nbsp;&nbsp;<span style="color: #0000FF; ">int</span>&nbsp;curY&nbsp;=&nbsp;ptCalc-&gt;y;<br />
&nbsp;&nbsp;&nbsp;&nbsp;<span style="color: #0000FF; ">int</span>&nbsp;destX&nbsp;=&nbsp;m_endPoint.x;<br />
&nbsp;&nbsp;&nbsp;&nbsp;<span style="color: #0000FF; ">int</span>&nbsp;destY&nbsp;=&nbsp;m_endPoint.y;<br />
<br />
&nbsp;&nbsp;&nbsp;&nbsp;<span style="color: #008000; ">//</span><span style="color: #008000; ">判断是否已经到了最终位置</span><span style="color: #008000; "><br />
</span>&nbsp;&nbsp;&nbsp;&nbsp;<span style="color: #0000FF; ">if</span>&nbsp;((curX&nbsp;==&nbsp;destX&nbsp;&amp;&amp;&nbsp;abs(curY&nbsp;-&nbsp;destY)&nbsp;==&nbsp;1)&nbsp;||&nbsp;(curY&nbsp;==&nbsp;destY&nbsp;&amp;&amp;&nbsp;abs(curX&nbsp;-&nbsp;destX)&nbsp;==&nbsp;1))<br />
&nbsp;&nbsp;&nbsp;&nbsp;{<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;pathList.push_back(CIntPoint(m_endPoint.x,&nbsp;m_endPoint.y));<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;OnAddBestPoint(m_endPoint.x,&nbsp;m_endPoint.y);<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span style="color: #0000FF; ">return</span>&nbsp;&amp;m_endPoint;<br />
&nbsp;&nbsp;&nbsp;&nbsp;}<br />
<br />
&nbsp;&nbsp;&nbsp;&nbsp;<span style="color: #008000; ">//</span><span style="color: #008000; ">&nbsp;最优步骤的坐标和值</span><span style="color: #008000; "><br />
</span>&nbsp;&nbsp;&nbsp;&nbsp;<span style="color: #0000FF; ">int</span>&nbsp;xmin&nbsp;=&nbsp;curX;<br />
&nbsp;&nbsp;&nbsp;&nbsp;<span style="color: #0000FF; ">int</span>&nbsp;ymin&nbsp;=&nbsp;curY;<br />
&nbsp;&nbsp;&nbsp;&nbsp;<span style="color: #0000FF; ">int</span>&nbsp;fmin&nbsp;=&nbsp;0;<br />
<br />
&nbsp;&nbsp;&nbsp;&nbsp;<span style="color: #008000; ">//</span><span style="color: #008000; ">获得当前周边点的的坐标</span><span style="color: #008000; "><br />
</span>&nbsp;&nbsp;&nbsp;&nbsp;PosList&nbsp;nearPos;<br />
&nbsp;&nbsp;&nbsp;&nbsp;<span style="color: #0000FF; ">if</span>&nbsp;(GetNearPos(curX,curY,nearPos)&nbsp;==&nbsp;<span style="color: #0000FF; ">false</span>)<br />
&nbsp;&nbsp;&nbsp;&nbsp;{<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span style="color: #0000FF; ">return</span>&nbsp;nullptr;<br />
&nbsp;&nbsp;&nbsp;&nbsp;}<br />
<br />
&nbsp;&nbsp;&nbsp;&nbsp;<span style="color: #008000; ">//</span><span style="color: #008000; ">删除不能飞的区块<br />
&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="color: #008000; ">//</span><span style="color: #008000; ">找出最优f值</span><span style="color: #008000; "><br />
</span>&nbsp;&nbsp;&nbsp;&nbsp;<span style="color: #0000FF; ">for</span>&nbsp;(auto&nbsp;itPos&nbsp;:&nbsp;nearPos)<br />
&nbsp;&nbsp;&nbsp;&nbsp;{<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span style="color: #0000FF; ">int</span>&nbsp;fValue&nbsp;=&nbsp;GetFValue(itPos);<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span style="color: #0000FF; ">if</span>&nbsp;(fmin&nbsp;==&nbsp;0&nbsp;||&nbsp;fValue&lt;fmin)<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;{<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;fmin&nbsp;=&nbsp;fValue;<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;xmin&nbsp;=&nbsp;itPos.x;<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;ymin&nbsp;=&nbsp;itPos.y;<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;}<br />
&nbsp;&nbsp;&nbsp;&nbsp;}<br />
<br />
&nbsp;&nbsp;&nbsp;&nbsp;<span style="color: #0000FF; ">if</span>&nbsp;(fmin&nbsp;&gt;&nbsp;0)<br />
&nbsp;&nbsp;&nbsp;&nbsp;{<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;SetCurrent(xmin,&nbsp;ymin);<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;SetClosed(xmin,&nbsp;ymin);&nbsp;<span style="color: #008000; ">//</span><span style="color: #008000; ">路径搜索不再经过</span><span style="color: #008000; "><br />
</span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;pathList.push_back(CIntPoint(xmin,ymin));<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;OnAddBestPoint(xmin,&nbsp;ymin);<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;PAPoint&nbsp;pAPoint=&nbsp;CalcNextPoint(pathList,&amp;m_curPoint);<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span style="color: #0000FF; ">if</span>&nbsp;(nullptr&nbsp;==&nbsp;pAPoint)<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;{<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;SetCurrent(curX,&nbsp;curY);<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;SetClosed(xmin,&nbsp;ymin);&nbsp;<span style="color: #008000; ">//</span><span style="color: #008000; ">路径搜索不再经过<br />
<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="color: #008000; ">//</span><span style="color: #008000; ">将最后一次入的队删除</span><span style="color: #008000; "><br />
</span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;pathList.pop_back();<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;OnUnAddBestPoint(xmin,&nbsp;ymin);<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span style="color: #0000FF; ">return</span>&nbsp;CalcNextPoint(pathList,&nbsp;&amp;m_curPoint);<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;}<br />
<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span style="color: #0000FF; ">return</span>&nbsp;pAPoint;<br />
&nbsp;&nbsp;&nbsp;&nbsp;}<br />
&nbsp;&nbsp;&nbsp;&nbsp;<br />
<br />
&nbsp;&nbsp;&nbsp;&nbsp;<span style="color: #0000FF; ">return</span>&nbsp;nullptr;<br />
}<br />
<br />
CIntSize&nbsp;CAStarFly::GetRoleSize()<br />
{<br />
&nbsp;&nbsp;&nbsp;&nbsp;<span style="color: #0000FF; ">return</span>&nbsp;CIntSize(1,1);<br />
}<br />
<br />
<span style="color: #0000FF; ">int</span>&nbsp;CAStarFly::GetMapWidth()<br />
{<br />
&nbsp;&nbsp;&nbsp;&nbsp;<span style="color: #0000FF; ">return</span>&nbsp;-1;<br />
}<br />
<br />
<span style="color: #0000FF; ">int</span>&nbsp;CAStarFly::GetMapHeight()<br />
{<br />
&nbsp;&nbsp;&nbsp;&nbsp;<span style="color: #0000FF; ">return</span>&nbsp;-1;<br />
}<br />
<br />
<span style="color: #0000FF; ">void</span>&nbsp;CAStarFly::SetStartPoint(<span style="color: #0000FF; ">int</span>&nbsp;x,&nbsp;<span style="color: #0000FF; ">int</span>&nbsp;y)<br />
{<br />
&nbsp;&nbsp;&nbsp;&nbsp;m_startPoint.Init(&nbsp;x,y);<br />
}<br />
<br />
<span style="color: #0000FF; ">void</span>&nbsp;CAStarFly::SetEndPoint(<span style="color: #0000FF; ">int</span>&nbsp;x,&nbsp;<span style="color: #0000FF; ">int</span>&nbsp;y)<br />
{<br />
&nbsp;&nbsp;&nbsp;&nbsp;m_endPoint.Init(x,y);<br />
}<br />
<br />
<br />
<span style="color: #0000FF; ">void</span>&nbsp;CAStarFly::SetClosed(<span style="color: #0000FF; ">int</span>&nbsp;x,&nbsp;<span style="color: #0000FF; ">int</span>&nbsp;y)<br />
{<br />
&nbsp;&nbsp;&nbsp;&nbsp;m_setClosePos.insert(GetPosIndex(x,y));<br />
}<br />
<br />
<span style="color: #0000FF; ">void</span>&nbsp;CAStarFly::SetCurrent(<span style="color: #0000FF; ">int</span>&nbsp;x,&nbsp;<span style="color: #0000FF; ">int</span>&nbsp;y)<br />
{<br />
&nbsp;&nbsp;&nbsp;&nbsp;m_curPoint.Init(&nbsp;x,&nbsp;y);<br />
}<br />
<br />
int32&nbsp;CAStarFly::GetPosIndex(<span style="color: #0000FF; ">int</span>&nbsp;x,<span style="color: #0000FF; ">int</span>&nbsp;y)<br />
{<br />
&nbsp;&nbsp;&nbsp;&nbsp;<span style="color: #0000FF; ">return</span>&nbsp;&nbsp;y&nbsp;*&nbsp;GetMapWidth()&nbsp;+&nbsp;x;<br />
}<br />
<br />
<br />
<span style="color: #0000FF; ">bool</span>&nbsp;CAStarFly::GetNearPos(<span style="color: #0000FF; ">int</span>&nbsp;curX,&nbsp;<span style="color: #0000FF; ">int</span>&nbsp;curY,&nbsp;PosList&nbsp;&amp;nearPos)<br />
{<br />
&nbsp;&nbsp;&nbsp;&nbsp;<span style="color: #0000FF; ">int</span>&nbsp;newX;<br />
&nbsp;&nbsp;&nbsp;&nbsp;<span style="color: #0000FF; ">int</span>&nbsp;newY;<br />
&nbsp;&nbsp;&nbsp;&nbsp;<span style="color: #008000; ">//</span><span style="color: #008000; ">1.上</span><span style="color: #008000; "><br />
</span>&nbsp;&nbsp;&nbsp;&nbsp;<span style="color: #0000FF; ">if</span>&nbsp;(curY&nbsp;&gt;&nbsp;0)<br />
&nbsp;&nbsp;&nbsp;&nbsp;{<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;newX&nbsp;=&nbsp;curX;<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;newY&nbsp;=&nbsp;curY&nbsp;-&nbsp;1;<br />
<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span style="color: #0000FF; ">if</span>&nbsp;(IsNotClosePos(newX,&nbsp;newY)&nbsp;&amp;&amp;&nbsp;IsCanFly(newX,&nbsp;newY))<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;{<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;nearPos.push_back(CIntPoint(newX,&nbsp;newY));<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;}<br />
&nbsp;&nbsp;&nbsp;&nbsp;}<br />
&nbsp;&nbsp;&nbsp;&nbsp;<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<br />
&nbsp;&nbsp;&nbsp;&nbsp;<br />
<br />
<br />
&nbsp;&nbsp;&nbsp;&nbsp;<span style="color: #008000; ">//</span><span style="color: #008000; ">3.左</span><span style="color: #008000; "><br />
</span>&nbsp;&nbsp;&nbsp;&nbsp;<span style="color: #0000FF; ">if</span>&nbsp;(curX&nbsp;&gt;&nbsp;0)<br />
&nbsp;&nbsp;&nbsp;&nbsp;{<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;newX&nbsp;=&nbsp;curX&nbsp;-&nbsp;1;<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;newY&nbsp;=&nbsp;curY;<br />
<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span style="color: #0000FF; ">if</span>&nbsp;(IsNotClosePos(newX,&nbsp;newY)&nbsp;&amp;&amp;&nbsp;IsCanFly(newX,&nbsp;newY))<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;{<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;nearPos.push_back(CIntPoint(newX,&nbsp;newY));<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;}<br />
&nbsp;&nbsp;&nbsp;&nbsp;}<br />
&nbsp;&nbsp;&nbsp;&nbsp;<br />
<br />
&nbsp;&nbsp;&nbsp;&nbsp;<span style="color: #008000; ">//</span><span style="color: #008000; ">4.右</span><span style="color: #008000; "><br />
</span>&nbsp;&nbsp;&nbsp;&nbsp;<span style="color: #0000FF; ">if</span>&nbsp;(curX&nbsp;&lt;&nbsp;GetMapWidth())<br />
&nbsp;&nbsp;&nbsp;&nbsp;{<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;newX&nbsp;=&nbsp;curX&nbsp;+&nbsp;1;<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;newY&nbsp;=&nbsp;curY;<br />
<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span style="color: #0000FF; ">if</span>&nbsp;(IsNotClosePos(newX,&nbsp;newY)&nbsp;&amp;&amp;&nbsp;IsCanFly(newX,&nbsp;newY))<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;{<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;nearPos.push_back(CIntPoint(newX,&nbsp;newY));<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;}<br />
&nbsp;&nbsp;&nbsp;&nbsp;}<br />
&nbsp;&nbsp;&nbsp;&nbsp;<br />
&nbsp;&nbsp;&nbsp;&nbsp;<span style="color: #008000; ">//</span><span style="color: #008000; ">2.下</span><span style="color: #008000; "><br />
</span>&nbsp;&nbsp;&nbsp;&nbsp;<span style="color: #0000FF; ">if</span>&nbsp;(curY&nbsp;&lt;&nbsp;GetMapHeight())<br />
&nbsp;&nbsp;&nbsp;&nbsp;{<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;newX&nbsp;=&nbsp;curX;<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;newY&nbsp;=&nbsp;curY&nbsp;+&nbsp;1;<br />
<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span style="color: #0000FF; ">if</span>&nbsp;(IsNotClosePos(newX,&nbsp;newY)&nbsp;&amp;&amp;&nbsp;IsCanFly(newX,&nbsp;newY))<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;{<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;nearPos.push_back(CIntPoint(newX,&nbsp;newY));<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;}<br />
&nbsp;&nbsp;&nbsp;&nbsp;}<br />
&nbsp;&nbsp;&nbsp;&nbsp;<span style="color: #0000FF; ">return</span>&nbsp;nearPos.size()&gt;0;<br />
}<br />
<br />
<span style="color: #0000FF; ">bool</span>&nbsp;CAStarFly::IsCanFly(<span style="color: #0000FF; ">int</span>&nbsp;x,&nbsp;<span style="color: #0000FF; ">int</span>&nbsp;y)<br />
{<br />
&nbsp;&nbsp;&nbsp;&nbsp;<span style="color: #0000FF; ">return</span>&nbsp;<span style="color: #0000FF; ">true</span>;<br />
}<br />
<br />
int32&nbsp;CAStarFly::GetFValue(<span style="color: #0000FF; ">const</span>&nbsp;CIntPoint&nbsp;&amp;pos)<br />
{<br />
&nbsp;&nbsp;&nbsp;&nbsp;<span style="color: #0000FF; ">int</span>&nbsp;xDis&nbsp;=&nbsp;abs(pos.x&nbsp;-&nbsp;m_endPoint.x)*100;<br />
&nbsp;&nbsp;&nbsp;&nbsp;<span style="color: #0000FF; ">int</span>&nbsp;yDis&nbsp;=&nbsp;abs(pos.y&nbsp;-&nbsp;m_endPoint.y)*100;<br />
&nbsp;&nbsp;&nbsp;&nbsp;<span style="color: #0000FF; ">return</span>&nbsp;m_curGValue&nbsp;+&nbsp;&nbsp;(int32)sqrt(xDis*xDis&nbsp;+&nbsp;yDis*yDis);<br />
}<br />
<br />
<br />
<br />
<span style="color: #0000FF; ">bool</span>&nbsp;CAStarFly::IsNotClosePos(<span style="color: #0000FF; ">int</span>&nbsp;x,&nbsp;<span style="color: #0000FF; ">int</span>&nbsp;y)<br />
{<br />
&nbsp;&nbsp;&nbsp;&nbsp;<span style="color: #0000FF; ">return</span>&nbsp;m_setClosePos.find(GetPosIndex(x,&nbsp;y))&nbsp;==&nbsp;m_setClosePos.end();<br />
}<br />
<br />
<span style="color: #0000FF; ">void</span>&nbsp;CAStarFly::OnAddBestPoint(<span style="color: #0000FF; ">int</span>&nbsp;x,&nbsp;<span style="color: #0000FF; ">int</span>&nbsp;y)<br />
{<br />
&nbsp;&nbsp;&nbsp;&nbsp;m_curGValue&nbsp;+=&nbsp;100;<br />
}<br />
<br />
<span style="color: #0000FF; ">void</span>&nbsp;CAStarFly::OnUnAddBestPoint(<span style="color: #0000FF; ">int</span>&nbsp;x,&nbsp;<span style="color: #0000FF; ">int</span>&nbsp;y)<br />
{<br />
&nbsp;&nbsp;&nbsp;&nbsp;m_curGValue&nbsp;-=&nbsp;100;<br />
}</div>
<br /><br /><div style="background-color:#eeeeee;font-size:13px;border:1px solid #CCCCCC;padding-right: 5px;padding-bottom: 4px;padding-left: 4px;padding-top: 4px;width: 98%;word-break:break-all"><!--<br /><br />Code highlighting produced by Actipro CodeHighlighter (freeware)<br />http://www.CodeHighlighter.com/<br /><br />-->#include&nbsp;"AStarFly.h"<br />#include&nbsp;&lt;memory&gt;<br /><br /><span style="color: #0000FF; ">class</span>&nbsp;CNewMap;<br /><br /><span style="color: #0000FF; ">class</span>&nbsp;CMobAStarFly&nbsp;:&nbsp;<span style="color: #0000FF; ">public</span>&nbsp;CAStarFly<br />{<br /><span style="color: #0000FF; ">public</span>:<br />&nbsp;&nbsp;&nbsp;&nbsp;<span style="color: #0000FF; ">static</span>&nbsp;CMobAStarFly&amp;&nbsp;GetInstance();<br />&nbsp;&nbsp;&nbsp;&nbsp;<span style="color: #0000FF; ">bool</span>&nbsp;FindPath(<span style="color: #0000FF; ">const</span>&nbsp;std::shared_ptr&lt;CNewMap&gt;&amp;&nbsp;pMap,&nbsp;<span style="color: #0000FF; ">const</span>&nbsp;CIntPoint&amp;&nbsp;from,&nbsp;<span style="color: #0000FF; ">const</span>&nbsp;CIntPoint&amp;&nbsp;to,&nbsp;CAStarFly::PosList&amp;&nbsp;pathList,&nbsp;<span style="color: #0000FF; ">const</span>&nbsp;CIntSize&amp;&nbsp;roleSize&nbsp;=&nbsp;CIntSize(1,&nbsp;1));<br />&nbsp;&nbsp;&nbsp;&nbsp;<span style="color: #0000FF; ">void</span>&nbsp;setpath(<span style="color: #0000FF; ">int</span>&nbsp;x,&nbsp;<span style="color: #0000FF; ">int</span>&nbsp;y);<br />&nbsp;&nbsp;&nbsp;&nbsp;<span style="color: #0000FF; ">void</span>&nbsp;show();<br /><br />&nbsp;&nbsp;&nbsp;&nbsp;<span style="color: #0000FF; ">virtual</span>&nbsp;<span style="color: #0000FF; ">int</span>&nbsp;GetMapWidth()&nbsp;<span style="color: #0000FF; ">override</span>;<br />&nbsp;&nbsp;&nbsp;&nbsp;<span style="color: #0000FF; ">virtual</span>&nbsp;<span style="color: #0000FF; ">int</span>&nbsp;GetMapHeight()&nbsp;<span style="color: #0000FF; ">override</span>;<br />&nbsp;&nbsp;&nbsp;&nbsp;<span style="color: #0000FF; ">virtual</span>&nbsp;CIntSize&nbsp;GetRoleSize()&nbsp;<span style="color: #0000FF; ">override</span>;<br />&nbsp;&nbsp;&nbsp;&nbsp;<span style="color: #0000FF; ">virtual</span>&nbsp;<span style="color: #0000FF; ">bool</span>&nbsp;IsCanFly(<span style="color: #0000FF; ">int</span>&nbsp;x,&nbsp;<span style="color: #0000FF; ">int</span>&nbsp;y)&nbsp;<span style="color: #0000FF; ">override</span>;<br />&nbsp;&nbsp;&nbsp;&nbsp;<span style="color: #0000FF; ">void</span>&nbsp;OnAddBestPoint(<span style="color: #0000FF; ">int</span>&nbsp;x,&nbsp;<span style="color: #0000FF; ">int</span>&nbsp;y)&nbsp;<span style="color: #0000FF; ">override</span>;<br />&nbsp;&nbsp;&nbsp;&nbsp;<span style="color: #0000FF; ">void</span>&nbsp;OnUnAddBestPoint(<span style="color: #0000FF; ">int</span>&nbsp;x,&nbsp;<span style="color: #0000FF; ">int</span>&nbsp;y)&nbsp;<span style="color: #0000FF; ">override</span>;<br /><span style="color: #0000FF; ">private</span>:<br />&nbsp;&nbsp;&nbsp;&nbsp;weak_ptr&lt;CNewMap&gt;&nbsp;m_pNewMap;<br />&nbsp;&nbsp;&nbsp;&nbsp;CIntSize&nbsp;m_roleSize;<br />};</div><br /><br /><br /><div style="background-color:#eeeeee;font-size:13px;border:1px solid #CCCCCC;padding-right: 5px;padding-bottom: 4px;padding-left: 4px;padding-top: 4px;width: 98%;word-break:break-all"><!--<br /><br />Code highlighting produced by Actipro CodeHighlighter (freeware)<br />http://www.CodeHighlighter.com/<br /><br />-->#include&nbsp;"stdafx.h"<br />#include&nbsp;"MobFlyAStar.h"<br />#include&nbsp;"NewMap.h"<br /><br />CMobAStarFly&amp;&nbsp;CMobAStarFly::GetInstance()<br />{<br />&nbsp;&nbsp;&nbsp;&nbsp;<span style="color: #0000FF; ">static</span>&nbsp;CMobAStarFly&nbsp;s_Instance;<br />&nbsp;&nbsp;&nbsp;&nbsp;<span style="color: #0000FF; ">return</span>&nbsp;s_Instance;<br />}<br /><br /><span style="color: #0000FF; ">bool</span>&nbsp;CMobAStarFly::FindPath(<span style="color: #0000FF; ">const</span>&nbsp;std::shared_ptr&lt;CNewMap&gt;&amp;&nbsp;pMap,&nbsp;<span style="color: #0000FF; ">const</span>&nbsp;CIntPoint&amp;&nbsp;from,&nbsp;<span style="color: #0000FF; ">const</span>&nbsp;CIntPoint&amp;&nbsp;to,&nbsp;CAStarFly::PosList&amp;&nbsp;pathList,&nbsp;<span style="color: #0000FF; ">const</span>&nbsp;CIntSize&amp;&nbsp;roleSize)<br />{<br />&nbsp;&nbsp;&nbsp;&nbsp;m_pNewMap&nbsp;=&nbsp;pMap;<br />&nbsp;&nbsp;&nbsp;&nbsp;m_roleSize&nbsp;=&nbsp;roleSize;<br /><br />&nbsp;&nbsp;&nbsp;&nbsp;<span style="color: #0000FF; ">return</span>&nbsp;CalcPath(pathList,&nbsp;from,&nbsp;to);<br />}<br /><br /><span style="color: #0000FF; ">int</span>&nbsp;CMobAStarFly::GetMapWidth()<br />{<br />&nbsp;&nbsp;&nbsp;&nbsp;<span style="color: #0000FF; ">if</span>&nbsp;(m_pNewMap.<span style="color: #0000FF; ">lock</span>()&nbsp;==&nbsp;nullptr)<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span style="color: #0000FF; ">return</span>&nbsp;0;<br /><br />&nbsp;&nbsp;&nbsp;&nbsp;<span style="color: #0000FF; ">return</span>&nbsp;m_pNewMap.<span style="color: #0000FF; ">lock</span>()-&gt;GetWidth();<br />}<br /><br /><span style="color: #0000FF; ">int</span>&nbsp;CMobAStarFly::GetMapHeight()<br />{<br />&nbsp;&nbsp;&nbsp;&nbsp;<span style="color: #0000FF; ">if</span>&nbsp;(m_pNewMap.<span style="color: #0000FF; ">lock</span>()&nbsp;==&nbsp;nullptr)<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span style="color: #0000FF; ">return</span>&nbsp;0;<br /><br />&nbsp;&nbsp;&nbsp;&nbsp;<span style="color: #0000FF; ">return</span>&nbsp;m_pNewMap.<span style="color: #0000FF; ">lock</span>()-&gt;GetHeight();<br />}<br /><br />CIntSize&nbsp;CMobAStarFly::GetRoleSize()<br />{<br />&nbsp;&nbsp;&nbsp;&nbsp;<span style="color: #0000FF; ">return</span>&nbsp;m_roleSize;<br />}<br /><br /><span style="color: #0000FF; ">bool</span>&nbsp;CMobAStarFly::IsCanFly(<span style="color: #0000FF; ">int</span>&nbsp;x,&nbsp;<span style="color: #0000FF; ">int</span>&nbsp;y)<br />{<br />&nbsp;&nbsp;&nbsp;&nbsp;<span style="color: #0000FF; ">for</span>&nbsp;(<span style="color: #0000FF; ">int</span>&nbsp;w&nbsp;=&nbsp;0;&nbsp;w&nbsp;&lt;&nbsp;m_roleSize.width;&nbsp;++w)<br />&nbsp;&nbsp;&nbsp;&nbsp;{<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span style="color: #0000FF; ">for</span>&nbsp;(<span style="color: #0000FF; ">int</span>&nbsp;h&nbsp;=&nbsp;0;&nbsp;h&nbsp;&lt;&nbsp;m_roleSize.height;&nbsp;++h)<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;{<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span style="color: #0000FF; ">if</span>&nbsp;(m_pNewMap.<span style="color: #0000FF; ">lock</span>()-&gt;IsPosBlock(m_pNewMap.<span style="color: #0000FF; ">lock</span>()-&gt;PosToWorldX(x&nbsp;+&nbsp;w),&nbsp;m_pNewMap.<span style="color: #0000FF; ">lock</span>()-&gt;PosToWorldY(y&nbsp;-&nbsp;h)))&nbsp;&nbsp;&nbsp;&nbsp;{<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span style="color: #0000FF; ">return</span>&nbsp;<span style="color: #0000FF; ">false</span>;<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;}<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;}<br />&nbsp;&nbsp;&nbsp;&nbsp;}<br /><br />&nbsp;&nbsp;&nbsp;&nbsp;<span style="color: #0000FF; ">return</span>&nbsp;<span style="color: #0000FF; ">true</span>;<br />}<br /><br /><span style="color: #0000FF; ">void</span>&nbsp;CMobAStarFly::setpath(<span style="color: #0000FF; ">int</span>&nbsp;x,&nbsp;<span style="color: #0000FF; ">int</span>&nbsp;y)<br />{<br />&nbsp;&nbsp;&nbsp;&nbsp;<br />}<br /><br /><span style="color: #0000FF; ">void</span>&nbsp;CMobAStarFly::show()<br />{<br /><br />}<br /><br /><span style="color: #0000FF; ">void</span>&nbsp;CMobAStarFly::OnAddBestPoint(<span style="color: #0000FF; ">int</span>&nbsp;x,&nbsp;<span style="color: #0000FF; ">int</span>&nbsp;y)<br />{<br /><br />}<br /><br /><span style="color: #0000FF; ">void</span>&nbsp;CMobAStarFly::OnUnAddBestPoint(<span style="color: #0000FF; ">int</span>&nbsp;x,&nbsp;<span style="color: #0000FF; ">int</span>&nbsp;y)<br />{<br /><br />}</div><img src ="http://www.cppblog.com/API/aggbug/215164.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/API/" target="_blank">C++技术中心</a> 2017-08-17 14:43 <a href="http://www.cppblog.com/API/archive/2017/08/17/215164.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>游戏服务器相关讨论(转)</title><link>http://www.cppblog.com/API/archive/2017/06/02/214972.html</link><dc:creator>C++技术中心</dc:creator><author>C++技术中心</author><pubDate>Fri, 02 Jun 2017 06:08:00 GMT</pubDate><guid>http://www.cppblog.com/API/archive/2017/06/02/214972.html</guid><wfw:comment>http://www.cppblog.com/API/comments/214972.html</wfw:comment><comments>http://www.cppblog.com/API/archive/2017/06/02/214972.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cppblog.com/API/comments/commentRss/214972.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/API/services/trackbacks/214972.html</trackback:ping><description><![CDATA[<p style="color: #333333; font-family: Arial; background-color: #ffffff;"><span style="color: #000000; font-stretch: normal; font-size: 12px; line-height: 18px; border-collapse: separate;"><strong>服务器结构探讨 -- 最简单的结构</strong>&nbsp;<br /><br />　　所谓服务器结构，也就是如何将服务器各部分合理地安排，以实现最初的功能需求。所以，结构本无所谓正确与错误；当然，优秀的结构更有助于系统的搭建，对系统的可扩展性及可维护性也有更大的帮助。&nbsp;<br /><br />　　好的结构不是一蹴而就的，而且每个设计者心中的那把尺都不相同，所以这个优秀结构的定义也就没有定论。在这里，我们不打算对现有游戏结构做评价，而是试着从头开始搭建一个我们需要的MMOG结构。&nbsp;<br /><br />　　对于一个最简单的游戏服务器来说，它只需要能够接受来自客户端的连接请求，然后处理客户端在游戏世界中的移动及交互，也即游戏逻辑处理即可。如果我们把这两项功能集成到一个服务进程中，则最终的结构很简单：&nbsp;<br /><br />　　client ----- server&nbsp;<br /><br />　　嗯，太简单了点，这样也敢叫服务器结构？好吧，现在我们来往里面稍稍加点东西，让它看起来更像是服务器结构一些。&nbsp;<br /><br />　　一般来说，我们在接入游戏服务器的时候都会要提供一个帐号和密码，验证通过后才能进入。关于为什么要提供用户名和密码才能进入的问题我们这里不打算做过多讨论，云风曾对此也提出过类似的疑问，并给出了只用一个标识串就能进入的设想，有兴趣的可以去看看他们的讨论。但不管是采用何种方式进入，照目前看来我们的服务器起码得提供一个帐号验证的功能。&nbsp;<br /><br />　　我们把观察点先集中在一个大区内。在大多数情况下，一个大区内都会有多组游戏服，也就是多个游戏世界可供选择。简单点来实现，我们完全可以抛弃这个大区的概念，认为一个大区也就是放在同一个机房的多台服务器组，各服务器组间没有什么关系。这样，我们可为每组服务器单独配备一台登录服。最后的结构图应该像这样：&nbsp;<br /><br />　　loginServer&nbsp;&nbsp; gameServer&nbsp;<br />　　　　　|&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; /&nbsp;<br />　　　　　|&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; /&nbsp;<br />　　　　　client&nbsp;<br /><br />　　该结构下的玩家操作流程为，先选择大区，再选择大区下的某台服务器，即某个游戏世界，点击进入后开始帐号验证过程，验证成功则进入了该游戏世界。但是，如果玩家想要切换游戏世界，他只能先退出当前游戏世界，然后进入新的游戏世界重新进行帐号验证。&nbsp;<br /><br />　　早期的游戏大都采用的是这种结构，有些游戏在实现时采用了一些技术手段使得在切换游戏服时不需要再次验证帐号，但整体结构还是未做改变。&nbsp;<br /><br />　　该结构存在一个服务器资源配置的问题。因为登录服处理的逻辑相对来说比较简单，就是将玩家提交的帐号和密码送到<a href="http://lib.csdn.net/base/mysql" title="MySQL知识库" target="_blank" style="color: #df3434; text-decoration: none; font-weight: bold;">数据库</a>进行验证，和生成会话密钥发送给游戏服和客户端，操作完成后连接就会立即断开，而且玩家在以后的游戏过程中不会再与登录服打任何交道。这样处理短连接的过程使得系统在大多数情况下都是比较空闲的，但是在某些时候，由于请求比较密集，比如开新服的时候，登录服的负载又会比较大，甚至会处理不过来。&nbsp;<br /><br />　　另外在实际的游戏运营中，有些游戏世界很火爆，而有些游戏世界却非常冷清，甚至没有多少人玩的情况也是很常见的。所以，我们能否更合理地配置登录服资源，使得整个大区内的登录服可以共享就成了下一步改进的目标。&nbsp;<br /><br /><strong>服务器结构探讨 -- 登录服的负载均衡</strong>&nbsp;<br /><br />　　回想一下我们在玩wow时的操作流程：运行wow.exe进入游戏后，首先就会要求我们输入用户名和密码进行验证，验证成功后才会出来游戏世界列表，之后是排队进入游戏世界，开始游戏...&nbsp;<br /><br />　　可以看到跟前面的描述有个很明显的不同，那就是要先验证帐号再选择游戏世界。这种结构也就使得登录服不是固定配备给个游戏世界，而是全区共有的。&nbsp;<br /><br />　　我们可以试着从实际需求的角度来考虑一下这个问题。正如我们之前所描述过的那样，登录服在大多数情况下都是比较空闲的，也许我们的一个拥有20个游戏世界的大区仅仅使用10台或更少的登录服即可满足需求。而当在开新区的时候，或许要配备40台登录服才能应付那如潮水般涌入的玩家登录请求。所以，登录服在设计上应该能满足这种动态增删的需求，我们可以在任何时候为大区增加或减少登录服的部署。&nbsp;<br /><br />　　当然，在这里也不会存在要求添加太多登录服的情况。还是拿开新区的情况来说，即使新增加登录服满足了玩家登录的请求，游戏世界服的承载能力依然有限，玩家一样只能在排队系统中等待，或者是进入到游戏世界中导致大家都卡。&nbsp;<br /><br />　　另外，当我们在增加或移除登录服的时候不应该需要对游戏世界服有所改动，也不会要求重启世界服，当然也不应该要求客户端有什么更新或者修改，一切都是在背后自动完成。&nbsp;<br /><br />　　最后，有关数据持久化的问题也在这里考虑一下。一般来说，使用现有的商业数据库系统比自己手工技术先进要明智得多。我们需要持久化的数据有玩家的帐号及密码，玩家创建的角色相关信息，另外还有一些游戏世界全局共有数据也需要持久化。&nbsp;<br /><br />　　好了，需求已经提出来了，现在来考虑如何将其实现。&nbsp;<br /><br />　　对于负载均衡来说，已有了成熟的解决方案。一般最常用，也最简单部署的应该是基于DNS的负载均衡系统了，其通过在DNS中为一个域名配置多个IP地址来实现。最新的DNS服务已实现了根据服务器系统状态来实现的动态负载均衡，也就是实现了真正意义上的负载均衡，这样也就有效地解决了当某台登录服当机后，DNS服务器不能立即做出反应的问题。当然，如果找不到这样的解决方案，自己从头打造一个也并不难。而且，通过DNS来实现的负载均衡已经包含了所做的修改对登录服及客户端的透明。&nbsp;<br /><br />　　而对于数据库的应用，在这种结构下，登录服及游戏世界服都会需要连接数据库。从数据库服务器的部署上来说，可以将帐号和角色数据都放在一个中心数据库中，也可分为两个不同的库分别来处理，基到从物理上分到两台不同的服务器上去也行。&nbsp;<br /><br />　　但是对于不同的游戏世界来说，其角色及游戏内数据都是互相独立的，所以一般情况下也就为每个游戏世界单独配备一台数据库服务器，以减轻数据库的压力。所以，整体的服务器结构应该是一个大区有一台帐号数据库服务器，所有的登录服都连接到这里。而每个游戏世界都有自己的游戏数据库服务器，只允许本游戏世界内的服务器连接。&nbsp;<br /><br />　　最后，我们的服务器结构就像这样：&nbsp;<br /><br />　　　　　　　　　　&nbsp;&nbsp; 　　大区服务器&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<br />　　　　　　　　　　/&nbsp;&nbsp; 　 |&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; \&nbsp;<br />&nbsp;&nbsp;&nbsp;&nbsp; 　　　　　　　/&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; |&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 　\&nbsp;<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 　　　　　登录服1&nbsp;&nbsp; 登录服2&nbsp;&nbsp; 世界服1&nbsp;&nbsp; 世界服2&nbsp;<br />　　　　　　　　　\&nbsp;&nbsp;&nbsp;&nbsp; 　&nbsp;&nbsp; |&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 　 |&nbsp;&nbsp;&nbsp;&nbsp; 　 |&nbsp;&nbsp;&nbsp;<br />　　　　　　　　　 \&nbsp;&nbsp; 　&nbsp;&nbsp; |&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 　 |&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; |&nbsp;<br />　　　　　　　　　　帐号数据库&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; DBS&nbsp;&nbsp;&nbsp;&nbsp; DBS&nbsp;<br /><br />　　这里既然讨论到了大区及帐号数据库，所以顺带也说一下关于激活大区的概念。wow中一共有八个大区，我们想要进入某个大区游戏之前，必须到官网上激活这个区，这是为什么呢？&nbsp;<br /><br />　　一般来说，在各个大区帐号数据库之上还有一个总的帐号数据库，我们可以称它为中心数据库。比如我们在官网上注册了一个帐号，这时帐号数据是只保存在中心数据库上的。而当我们要到一区去创建角色开始游戏的时候，在一区的帐号数据库中并没有我们的帐号数据，所以，我们必须先到官网上做一次激活操作。这个激活的过程也就是从中心库上把我们的帐号数据拷贝到所要到的大区帐号数据库中。&nbsp;<br /><br /><strong>服务器结构探讨 -- 简单的世界服实现</strong>&nbsp;<br /><br />　　讨论了这么久我们一直都还没有进入游戏世界服务器内部，现在就让我们来窥探一下里面的结构吧。&nbsp;<br /><br />　　对于现在大多数MMORPG来说，游戏服务器要处理的基本逻辑有移动、聊天、技能、物品、任务和生物等，另外还有地图管理与消息广播来对其他高级功能做支撑。如纵队、好友、公会、战场和副本等，这些都是通过基本逻辑功能组合或扩展而成。&nbsp;<br /><br />　　在所有这些基础逻辑中，与我们要讨论的服务器结构关系最紧密的当属地图管理方式。决定了地图的管理方式也就决定了我们的服务器结构，我们仍然先从最简单的实现方式开始说起。&nbsp;<br /><br />　　回想一下我们曾战斗过无数个夜晚的暗黑破坏神，整个暗黑的世界被分为了若干个独立的小地图，当我们在地图间穿越时，一般都要经过一个叫做传送门的装置。世界中有些地图间虽然在地理上是直接相连的，但我们发现其游戏内部的逻辑却是完全隔离的。可以这样认为，一块地图就是一个独立的数据处理单元。&nbsp;<br /><br />　　既然如此，我们就把每块地图都当作是一台独立的服务器，他提供了在这块地图上游戏时的所有逻辑功能，至于内部结构如何划分我们暂不理会，先把他当作一个黑盒子吧。&nbsp;<br /><br />　　当两个人合作做一件事时，我们可以以对等的关系相互协商着来做，而且一般也都不会有什么问题。当人数增加到三个时，我们对等的合作关系可能会有些复杂，因为我们每个人都同时要与另两个人合作协商。正如俗语所说的那样，三个和尚可能会碰到没水喝的情况。当人数继续增加，情况就变得不那么简单了，我们得需要一个管理者来对我们的工作进行分工、协调。游戏的地图服务器之间也是这么回事。&nbsp;<br /><br />　　一般来说，我们的游戏世界不可能会只有一块或者两块小地图，那顺理成章的，也就需要一个地图管理者。先称它为游戏世界的中心服务器吧，毕竟是管理者嘛，大家都以它为中心。&nbsp;<br /><br />　　中心服务器主要维护一张地图ID到地图服务器地址的映射表。当我们要进入某张地图时，会从中心服上取得该地图的IP和port告诉客户端，客户端主动去连接，这样进入他想要去的游戏地图。在整个游戏过程中，客户端始终只会与一台地图服务器保持连接，当要切换地图的时候，在获取到新地图的地址后，会先与当前地图断开连接，再进入新的地图，这样保证玩家数据在服务器上只有一份。&nbsp;<br /><br />　　我们来看看结构图是怎样的：&nbsp;<br /><br />　　　&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 中心服务器&nbsp;<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; /&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; \&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; \&nbsp;<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; /&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; \&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; \&nbsp;<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 登录服&nbsp;&nbsp;&nbsp;&nbsp; 地图1&nbsp;&nbsp;&nbsp;&nbsp; 地图2&nbsp;&nbsp; 地图n&nbsp;<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; \&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; |&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; /&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; /&nbsp;<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; \&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; |&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; /&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; /&nbsp;<br />　　　&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 客户端&nbsp;<br /><br />　　很简单，不是吗。但是简单并不表示功能上会有什么损失，简单也更不能表示游戏不能赚钱。早期不少游戏也确实采用的就是这种简单结构。&nbsp;<br /><br /><strong>服务器结构探讨 -- 继续世界服</strong>&nbsp;<br /><br />　　都已经看出来了，这种每切换一次地图就要重新连接服务器的方式实在是不够优雅，而且在实际游戏运营中也发现，地图切换导致的卡号，复制装备等问题非常多，这里完全就是一个事故多发地段，如何避免这种频繁的连接操作呢？&nbsp;<br /><br />　　最直接的方法就是把那个图倒转过来就行了。客户端只需要连接到中心服上，所有到地图服务器的数据都由中心服来转发。很完美的解决方案，不是吗？&nbsp;<br /><br />　　这种结构在实际的部署中也遇到了一些挑战。对于一般的MMORPG服务器来说，单台服务器的承载量平均在2000左右，如果你的服务器很不幸地只能带1000人，没关系，不少游戏都是如此；如果你的服务器上跑了3000多玩家依然比较流畅，那你可以自豪地告诉你的策划，多设计些大量消耗服务器资源的玩法吧，比如大型国战、公会战争等。&nbsp;<br /><br />　　2000人，似乎我们的策划朋友们不大愿意接受这个数字。我们将地图服务器分开来原来也是想将负载分开，以多带些客户端，现在要所有的连接都从中心服上转发，那连接数又遇到单台服务器的可最大承载量的瓶颈了。&nbsp;<br /><br />　　这里有必要再解释下这个数字。我知道，有人一定会说，才带2000人，那是你水平不行，我随便写个TCP服务器都可带个五六千连接。问题恰恰在于你是随便写的，而MMORPG的服务器是复杂设计的。如果一个演示socket API用的echo服务器就能满足MMOG服务器的需求，那写服务器该是件多么惬意的事啊。&nbsp;<br /><br />　　但我们所遇到的事实是，服务器收到一个移动包后，要向周围所有人广播，而不是echo服务器那样简单的回应；服务器在收到一个连接断开通知时要向很多人通知玩家退出事件，并将该玩家的资料写入数据库，而不是echo服务器那样什么都不需要做；服务器在收到一个物品使用请求包后要做一系列的逻辑判断以检查玩家有没有作弊；服务器上还启动着很多定时器用来更新游戏世界的各种状态......&nbsp;<br /><br />　　其实这么一比较，我们也看出资源消耗的所在了：服务器上大量的复杂的逻辑处理。再回过头来看看我们想要实现的结构，我们既想要有一个唯一的入口，使得客户端不用频繁改变连接，又希望这个唯一入口的负载不会太大，以致于接受不了多少连接。&nbsp;<br /><br />　　仔细看一看这个需求，我们想要的仅仅只是一台管理连接的服务器，并不打算让他承担太多的游戏逻辑。既然如此，那五六千个连接也还有满足我们的要求。至少在现在来说，一个游戏世界内，也就是一组服务器内同时有五六千个在线的玩家还是件让人很兴奋的事。事实上，在大多数游戏的大部分时间里，这个数字也是很让人眼红的。&nbsp;<br /><br />　　什么？你说梦幻、魔兽还有史先生的那个什么征途远不止这么点人了！噢，我说的是大多数，是大多数，不包括那些明星。你知道大陆现在有多少游戏在运营吗？或许你又该说，我们不该在一开始就把自己的目标定的太低！好吧，我们还是先不谈这个。&nbsp;<br /><br />　　继续我们的结构讨论。一般来说，我们把这台负责连接管理的服务器称为网关服务器，因为内部的数据都要通过这个网关才能出去，不过从这台服务器提供的功能来看，称其为反向代理服务器可能更合适。我们也不在这个名字上纠缠了，就按大家通用的叫法，还是称他为网关服务器吧。&nbsp;<br /><br />　　网关之后的结构我们依然可以采用之前描述的方案，只是，似乎并没有必要为每一个地图都开一个独立的监听端口了。我们可以试着对地图进行一些划分，由一个Master Server来管理一些更小的Zone Server，玩家通过网关连接到Master Server上，而实际与地图有关的逻辑是分派给更小的Zone Server去处理。&nbsp;<br /><br />　　最后的结构看起来大概是这样的：&nbsp;<br /><br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; Zone Server&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; Zone Server&nbsp;<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; \&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; /&nbsp;<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; \&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; /&nbsp;<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; Master Server&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; Master Server&nbsp;<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; /&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; \&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; /&nbsp;<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; /&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; \&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; /&nbsp;<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; Gateway Server&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; \&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; /&nbsp;<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; |&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; \&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; \&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; /&nbsp;<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; |&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; \&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; \&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; /&nbsp;<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; |&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; Center Server&nbsp;<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; |&nbsp;<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; |&nbsp;<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; Client&nbsp;<br /><br /><strong>服务器结构探讨 -- 最终的结构</strong>&nbsp;<br /><br />　　如果我们就此打住，可能马上就会有人要嗤之以鼻了，就这点古董级的技术也敢出来现。好吧，我们还是把之前留下的问题拿出来解决掉吧。&nbsp;<br /><br />　　一般来说，当某一部分能力达不到我们的要求时，最简单的解决方法就是在此多投入一点资源。既然想要更多的连接数，那就再加一台网关服务器吧。新增加了网关服后需要在大区服上做相应的支持，或者再简单点，有一台主要的网关服，当其负载较高时，主动将新到达的连接重定向到其他网关服上。&nbsp;<br /><br />　　而对于游戏服来说，有一台还是多台网关服是没有什么区别的。每个代表客户端玩家的对象内部都保留一个代表其连接的对象，消息广播时要求每个玩家对象使用自己的连接对象发送数据即可，至于连接是在什么地方，那是完全透明的。当然，这只是一种简单的实现，也是普通使用的一种方案，如果后期想对消息广播做一些优化的话，那可能才需要多考虑一下。&nbsp;<br /><br />　　既然说到了优化，我们也稍稍考虑一下现在结构下可能采用的优化方案。&nbsp;<br /><br />　　首先是当前的Zone Server要做的事情太多了，以至于他都处理不了多少连接。这其中最消耗系统资源的当属生物的AI处理了，尤其是那些复杂的寻路<a href="http://lib.csdn.net/base/datastructure" title="算法与数据结构知识库" target="_blank" style="color: #df3434; text-decoration: none; font-weight: bold;">算法</a>，所以我们可以考虑把这部分AI逻辑独立出来，由一台单独的AI服务器来承担。&nbsp;<br /><br />　　然后，我们可以试着把一些与地图数据无关的公共逻辑放到Master Server上去实现，这样Zone Server上只保留了与地图数据紧密相关的逻辑，如生物管理，玩家移动和状态更新等。&nbsp;<br /><br />　　还有聊天处理逻辑，这部分与游戏逻辑没有任何关联，我们也完全可以将其独立出来，放到一台单独的聊天服务器上去实现。&nbsp;<br /><br />　　最后是数据库了，为了减轻数据库的压力，提高数据请求的响应速度，我们可以在数据库之前建立一个数据库缓存服务器，将一些常用数据缓存在此，服务器与数据库的通信都要通过这台服务器进行代理。缓存的数据会定时的写入到后台数据库中。&nbsp;<br /><br />　　好了，做完这些优化我们的服务器结构大体也就定的差不多了，暂且也不再继续深入，更细化的内容等到各个部分实现的时候再探讨。&nbsp;<br /><br />　　好比我们去看一场晚会，舞台上演员们按着预定的节目单有序地上演着，但这就是整场晚会的全部吗？显然不止，在幕后还有太多太多的人在忙碌着，甚至在晚会前和晚会后都有。我们的游戏服务器也如此。&nbsp;<br /><br />　　在之前描述的部分就如同舞台上的演员，是我们能直接看到的，幕后的工作人员我们也来认识一下。&nbsp;<br /><br />　　现实中有警察来维护秩序，游戏中也如此，这就是我们常说的GM。GM可以采用跟普通玩家一样的拉入方式来进入游戏，当然权限会比普通玩家高一些，也可以提供一台GM服务器专门用来处理GM命令，这样可以有更高的安全性，GM服一般接在中心服务器上。&nbsp;<br /><br />　　在以时间收费的游戏中，我们还需要一台计费的服务器，这台服务器一般接在网关服务器上，注册玩家登录和退出事件以记录玩家的游戏时间。&nbsp;<br /><br />　　任何为用户提供服务的地方都会有日志记录，游戏服务器当然也不例外。从记录玩家登录的时间，地址，机器信息到游戏过程中的每一项操作都可以作为日志记录下来，以备查错及数据挖掘用。至于搜集玩家机器资料所涉及到的法律问题不是我们该考虑的。&nbsp;<br /><br />　　差不多就这么多了吧，接下来我们会按照这个大致的结构来详细讨论各部分的实现。&nbsp;<br /><br /><strong>服务器结构探讨 -- 一点杂谈</strong>&nbsp;<br /><br />　　再强调一下，服务器结构本无所谓好坏，只有是否适合自己。我们在前面探讨了一些在现在的游戏中见到过的结构，并尽我所知地分析了各自存在的一些问题和可以做的一些改进，希望其中没有谬误，如果能给大家也带来些启发那自然更好。&nbsp;<br /><br />　　突然发现自己一旦罗嗦起来还真是没完没了。接下来先说说我在开发中遇到过的一些困惑和一基础问题探讨吧，这些问题可能有人与我一样，也曾遇到过，或者正在被困扰中，而所要探讨的这些基础问题向来也是争论比较多的，我们也不评价其中的好与坏，只做简单的描述。&nbsp;<br /><br />　　首先是服务器<a href="http://lib.csdn.net/base/operatingsystem" title="操作系统知识库" target="_blank" style="color: #df3434; text-decoration: none; font-weight: bold;">操作系统</a>，<a href="http://lib.csdn.net/base/linux" title="Linux知识库" target="_blank" style="color: #df3434; text-decoration: none; font-weight: bold;">Linux</a>与windows之争随处可见，其实在大多数情况下这不是我们所能决定的，似乎各大公司也基本都有了自己的传统，如网易的freebsd，腾讯的linux等。如果真有权利去选择的话，选自己最熟悉的吧。&nbsp;<br /><br />　　决定了OS也就基本上确定了网络IO模型，windows上的IOCP和linux下的epool，或者直接使用现有的网络框架，如ACE和asio等，其他还有些商业的网络库在国内的使用好像没有见到，不符合中国国情嘛。:)&nbsp;<br /><br />　　然后是网络协议的选择，以前的选择大多倾向于UDP，为了可靠传输一般自己都会在上面实现一层封装，而现在更普通的是直接采用本身就很可靠的TCP，或者TCP与UDP的混用。早期选择UDP的主要原因还是带宽限制，现在宽带普通的情况下TCP比UDP多出来的一点点开销与开发的便利性相比已经不算什么了。当然，如果已有了成熟的可靠UDP库，那也可以继续使用着。&nbsp;<br /><br />　　还有消息包格式的定义，这个曾在云风的blog上展开过激烈的争论。消息包格式定义包括三段，包长、消息码和包体，争论的焦点在于应该是消息码在前还是包长在前，我们也把这个当作是信仰问题吧，有兴趣的去云风的blog上看看，论论。&nbsp;<br /><br />　　另外早期有些游戏的包格式定义是以特殊字符作分隔的，这样一个好处是其中某个包出现错误后我们的游戏还能继续。但实际上，我觉得这是完全没有必要的，真要出现这样的错误，直接断开这个客户端的连接可能更安全。而且，以特殊字符做分隔的消息包定义还加大了一点点网络数据量。&nbsp;<br /><br />　　最后是一个纯技术问题，有关socket连接数的最大限制。开始学习网络编程的时候我犯过这样的错误，以为port的定义为unsigned short，所以想当然的认为服务器的最大连接数为65535，这会是一个硬性的限制。而实际上，一个socket描述符在windows上的定义是unsigned int，因此要有限制那也是四十多亿，放心好了。&nbsp;<br /><br />　　在服务器上port是监听用的，想象这样一种情况，web server在80端口上监听，当一个连接到来时，系统会为这个连接分配一个socket句柄，同时与其在80端口上进行通讯；当另一个连接到来时，服务器仍然在80端口与之通信，只是分配的socket句柄不一样。这个socket句柄才是描述每个连接的唯一标识。按windows网络编程第二版上的说法，这个上限值配置影响。&nbsp;<br /><br />　　好了，废话说完了，下一篇，我们开始进入登录服的设计吧。&nbsp;<br /><br /><strong>登录服的设计 -- 功能需求</strong>&nbsp;<br /><br />　　正如我们在前面曾讨论过的，登录服要实现的功能相当简单，就是帐号验证。为了便于描述，我们暂不引入那些讨论过的优化手段，先以最简单的方式实现，另外也将基本以mangos的代码作为参考来进行描述。&nbsp;<br /><br />　　想象一下帐号验证的实现方法，最容易的那就是把用户输入的明文用帐号和密码直接发给登录服，服务器根据帐号从数据库中取出密码，与用户输入的密码相比较。&nbsp;<br /><br />　　这个方法存在的安全隐患实在太大，明文的密码传输太容易被截获了。那我们试着在传输之前先加一下密，为了服务器能进行密码比较，我们应该采用一个可逆的加密算法，在服务器端把这个加密后的字串还原为原始的明文密码，然后与数据库密码进行比较。既然是一个可逆的过程，那外挂制作者总有办法知道我们的加密过程，所以，这个方法仍不够安全。&nbsp;<br /><br />　　哦，如果我们只是希望密码不可能被还原出来，那还不容易吗，使用一个不可逆的散列算法就行了。用户在登录时发送给服务器的是明文的帐号和经散列后的不可逆密码串，服务器取出密码后也用同样的算法进行散列后再进行比较。比如，我们就用使用最广泛的md5算法吧。噢，不要管那个王小云的什么论文，如果我真有那么好的运气，早中500w了，还用在这考虑该死的服务器设计吗？&nbsp;<br /><br />　　似乎是一个很完美的方案，外挂制作者再也偷不到我们的密码了。慢着，外挂偷密码的目的是什么？是为了能用我们的帐号进游戏！如果我们总是用一种固定的算法来对密码做散列，那外挂只需要记住这个散列后的字串就行了，用这个做密码就可以成功登录。&nbsp;<br /><br />　　嗯，这个问题好解决，我们不要用固定的算法进行散列就是了。只是，问题在于服务器与客户端采用的散列算法得出的字串必须是相同的，或者是可验证其是否匹配的。很幸运的是，伟大的数学字们早就为我们准备好了很多优秀的这类算法，而且经理论和实践都证明他们也确实是足够安全的。&nbsp;<br /><br />　　这其中之一是一个叫做SRP的算法，全称叫做Secure Remote Password，即安全远程密码。wow使用的是第6版，也就是SRP6算法。有关其中的数学证明，如果有人能向我解释清楚，并能让我真正弄明白的话，我将非常感激。不过其代码实现步骤倒是并不复杂，mangos中的代码也还算清晰，我们也不再赘述。&nbsp;<br /><br />　　登录服除了帐号验证外还得提供另一项功能，就是在玩家的帐号验证成功后返回给他一个服务器列表让他去选择。这个列表的状态要定时刷新，可能有新的游戏世界开放了，也可能有些游戏世界非常不幸地停止运转了，这些状态的变化都要尽可能及时地让玩家知道。不管发生了什么事，用户都有权利知道，特别是对于付过费的用户来说，我们不该藏着掖着，不是吗？&nbsp;<br /><br />　　这个游戏世界列表的功能将由大区服来提供，具体的结构我们在之前也描述过，这里暂不做讨论。登录服将从大区服上获取到的游戏世界列表发给已验证通过的客户端即可。好了，登录服要实现的功能就这些，很简单，是吧。&nbsp;<br /><br />　　确实是太简单了，不过简单的结构正好更适合我们来看一看游戏服务器内部的模块结构，以及一些服务器共有组件的实现方法。这就留作下一篇吧。&nbsp;<br /><br /><strong>服务器公共组件实现 -- mangos的游戏主循环&nbsp;<br /></strong><br />　　当阅读一项工程的源码时，我们大概会选择从main函数开始，而当开始一项新的工程时，第一个写下的函数大多也是main。那我们就先来看看，游戏服务器代码实现中，main函数都做了些什么。&nbsp;<br /><br />　　由于我在读技术文章时最不喜看到的就是大段大段的代码，特别是那些直接Ctrl+C再Ctrl+V后未做任何修改的代码，用句时髦的话说，一点技术含量都没有！所以在我们今后所要讨论的内容中，尽量会避免出现直接的代码，在有些地方确实需要代码来表述时，也将会选择使用伪码。&nbsp;<br /><br />　　先从mangos的登录服代码开始。mangos的登录服是一个单线程的结构，虽然在数据库连接中可以开启一个独立的线程，但这个线程也只是对无返回结果的执行类SQL做缓冲，而对需要有返回结果的查询类SQL还是在主逻辑线程中阻塞调用的。&nbsp;<br /><br />　　登录服中唯一的这一个线程，也就是主循环线程对监听的socket做select操作，为每个连接进来的客户端读取其上的数据并立即进行处理，直到服务器收到SIGABRT或SIGBREAK信号时结束。&nbsp;<br /><br />　　所以，mangos登录服主循环的逻辑，也包括后面游戏服的逻辑，主循环的关键代码其实是在SocketHandler中，也就是那个Select函数中。检查所有的连接，对新到来的连接调用OnAccept方法，有数据到来的连接则调用OnRead方法，然后socket处理器自己定义对接收到的数据如何处理。&nbsp;<br /><br />　　很简单的结构，也比较容易理解。&nbsp;<br /><br /><br />　　只是，在对性能要求比较高的服务器上，select一般不会是最好的选择。如果我们使用windows平台，那IOCP将是首选；如果是linux，epool将是不二选择。我们也不打算讨论基于IOCP或是基于epool的服务器实现，如果仅仅只是要实现服务器功能，很简单的几个API调用即可，而且网上已有很多好的教程；如果是要做一个成熟的网络服务器产品，不是我几篇简单的技术介绍文章所能达到。&nbsp;<br /><br />　　另外，在服务器实现上，网络IO与逻辑处理一般会放在不同的线程中，以免耗时较长的IO过程阻塞住了需要立即反应的游戏逻辑。&nbsp;<br /><br />　　数据库的处理也类似，会使用异步的方式，也是避免耗时的查询过程将游戏服务器主循环阻塞住。想象一下，因某个玩家上线而发起的一次数据库查询操作导致服务器内所有在线玩家都卡住不动将是多么恐怖的一件事！&nbsp;<br /><br />　　另外还有一些如事件、脚本、消息队列、状态机、日志和异常处理等公共组件，我们也会在接下来的时间里进行探讨。&nbsp;<br /><br /><strong>服务器公共组件实现 -- 继续来说主循环&nbsp;<br /></strong><br />　　前面我们只简单了解了下mangos登录服的程序结构，也发现了一些不足之处，现在我们就来看看如何提供一个更好的方案。&nbsp;<br /><br />　　正如我们曾讨论过的，为了游戏主逻辑循环的流畅运行，所有比较耗时的IO操作都会分享到单独的线程中去做，如网络IO，数据库IO和日志IO等。当然，也有把这些分享到单独的进程中去做的。&nbsp;<br /><br />　　另外对于大多数服务器程序来说，在运行时都是作为精灵进程或服务进程的，所以我们并不需要服务器能够处理控制台用户输入，我们所要处理的数据来源都来自网络。&nbsp;<br /><br />　　这样，主逻辑循环所要做的就是不停要取消息包来处理，当然这些消息包不仅有来自客户端的玩家操作数据包，也有来自GM服务器的管理命令，还包括来自数据库查询线程的返回结果消息包。这个循环将一直持续，直到收到一个通知服务器关闭的消息包。&nbsp;<br /><br />　　主逻辑循环的结构还是很简单的，复杂的部分都在如何处理这些消息包的逻辑上。我们可以用一段简单的伪码来描述这个循环过程：&nbsp;<br /><br />　　　　while (Message* msg = getMessage())&nbsp;<br />　　　　{&nbsp;<br />　　　　　　if (msg为服务器关闭消息)&nbsp;<br />　　　　　　　　break;&nbsp;<br />　　　　　　处理msg消息;&nbsp;<br />　　　　}&nbsp;<br /><br />　　这里就有一个问题需要探讨了，在getMessage()的时候，我们应该去哪里取消息？前面我们考虑过，至少会有三个消息来源，而我们还讨论过，这些消息源的IO操作都是在独立的线程中进行的，我们这里的主线程不应该直接去那几处消息源进行阻塞式的IO操作。&nbsp;<br /><br />　　很简单，让那些独立的IO线程在接收完数据后自己送过来就是了。好比是，我这里提供了一个仓库，有很多的供货商，他们有货要给我的时候只需要交到仓库，然后我再到仓库去取就是了，这个仓库也就是消息队列。消息队列是一个普通的队列实现，当然必须要提供多线程互斥访问的安全性支持，其基本的接口定义大概类似这样：&nbsp;<br /><br />　　　　IMessageQueue&nbsp;<br />　　　　{&nbsp;<br />　　　　　　void putMessage(Message*);&nbsp;<br />　　　　　　Message* getMessage();&nbsp;<br />　　　　}&nbsp;<br /><br />　　网络IO，数据库IO线程把整理好的消息包都加入到主逻辑循环线程的这个消息队列中便返回。有关消息队列的实现和线程间消息的传递在ACE中有比较完全的代码实现及描述，还有一些使用示例，是个很好的参考。&nbsp;<br /><br />　　这样的话，我们的主循环就很清晰了，从主线程的消息队列中取消息，处理消息，再取下一条消息......&nbsp;<br /><br /><strong>服务器公共组件实现 -- 消息队列</strong>&nbsp;<br /><br />　　既然说到了消息队列，那我们继续来稍微多聊一点吧。&nbsp;<br /><br />　　我们所能想到的最简单的消息队列可能就是使用stl的list来实现了，即消息队列内部维护一个list和一个互斥锁，putMessage时将message加入到队列尾，getMessage时从队列头取一个message返回，同时在getMessage和putMessage之前都要求先获取锁资源。&nbsp;<br /><br />　　实现虽然简单，但功能是绝对满足需求的，只是性能上可能稍稍有些不尽如人意。其最大的问题在频繁的锁竞争上。&nbsp;<br /><br />　　对于如何减少锁竞争次数的优化方案，Ghost Cheng提出了一种。提供一个队列容器，里面有多个队列，每个队列都可固定存放一定数量的消息。网络IO线程要给逻辑线程投递消息时，会从队列容器中取一个空队列来使用，直到将该队列填满后再放回容器中换另一个空队列。而逻辑线程取消息时是从队列容器中取一个有消息的队列来读取，处理完后清空队列再放回到容器中。&nbsp;<br /><br />　　这样便使得只有在对队列容器进行操作时才需要加锁，而IO线程和逻辑线程在操作自己当前使用的队列时都不需要加锁，所以锁竞争的机会大大减少了。&nbsp;<br /><br />　　这里为每个队列设了个最大消息数，看来好像是打算只有当IO线程写满队列时才会将其放回到容器中换另一个队列。那这样有时也会出现IO线程未写满一个队列，而逻辑线程又没有数据可处理的情况，特别是当数据量很少时可能会很容易出现。Ghost Cheng在他的描述中没有讲到如何解决这种问题，但我们可以先来看看另一个方案。&nbsp;<br /><br />　　这个方案与上一个方案基本类似，只是不再提供队列容器，因为在这个方案中只使用了两个队列，arthur在他的一封邮件中描述了这个方案的实现及部分代码。两个队列，一个给逻辑线程读，一个给IO线程用来写，当逻辑线程读完队列后会将自己的队列与IO线程的队列相调换。所以，这种方案下加锁的次数会比较多一些，IO线程每次写队列时都要加锁，逻辑线程在调换队列时也需要加锁，但逻辑线程在读队列时是不需要加锁的。&nbsp;<br /><br />　　虽然看起来锁的调用次数是比前一种方案要多很多，但实际上大部分锁调用都是不会引起阻塞的，只有在逻辑线程调换队列的那一瞬间可能会使得某个线程阻塞一下。另外对于锁调用过程本身来说，其开销是完全可以忽略的，我们所不能忍受的仅仅是因为锁调用而引起的阻塞而已。&nbsp;<br /><br />　　两种方案都是很优秀的优化方案，但也都是有其适用范围的。Ghost Cheng的方案因为提供了多个队列，可以使得多个IO线程可以总工程师的，互不干扰的使用自己的队列，只是还有一个遗留问题我们还不了解其解决方法。arthur的方案很好的解决了上一个方案遗留的问题，但因为只有一个写队列，所以当想要提供多个IO线程时，线程间互斥地写入数据可能会增大竞争的机会，当然，如果只有一个IO线程那将是非常完美的。&nbsp;</span></p><span style="font-family: Arial; background-color: #ffffff; font-stretch: normal; font-size: 12px; line-height: 18px; border-collapse: separate;"><span style="font-stretch: normal; line-height: 18px; border-collapse: separate;"><strong>服务器公共组件实现 -- 环形缓冲区</strong>&nbsp;<br /><br />　　消息队列锁调用太频繁的问题算是解决了，另一个让人有些苦恼的大概是这太多的内存分配和释放操作了。频繁的内存分配不但增加了系统开销，更使得内存碎片不断增多，非常不利于我们的服务器长期稳定运行。也许我们可以使用内存池，比如SGI STL中附带的小内存分配器。但是对于这种按照严格的先进先出顺序处理的，块大小并不算小的，而且块大小也并不统一的内存分配情况来说，更多使用的是一种叫做环形缓冲区的方案，mangos的网络代码中也有这么一个东西，其原理也是比较简单的。&nbsp;<br /><br />　　就好比两个人围着一张圆形的桌子在追逐，跑的人被网络IO线程所控制，当写入数据时，这个人就往前跑；追的人就是逻辑线程，会一直往前追直到追上跑的人。如果追上了怎么办？那就是没有数据可读了，先等会儿呗，等跑的人向前跑几步了再追，总不能让游戏没得玩了吧。那要是追的人跑的太慢，跑的人转了一圈过来反追上追的人了呢？那您也先歇会儿吧。要是一直这么反着追，估计您就只能换一个跑的更快的追逐者了，要不这游戏还真没法玩下去。&nbsp;<br /><br />　　前面我们特别强调了，按照严格的先进先出顺序进行处理，这是环形缓冲区的使用必须遵守的一项要求。也就是，大家都得遵守规定，追的人不能从桌子上跨过去，跑的人当然也不允许反过来跑。至于为什么，不需要多做解释了吧。&nbsp;<br /><br />　　环形缓冲区是一项很好的技术，不用频繁的分配内存，而且在大多数情况下，内存的反复使用也使得我们能用更少的内存块做更多的事。&nbsp;<br /><br />　　在网络IO线程中，我们会为每一个连接都准备一个环形缓冲区，用于临时存放接收到的数据，以应付半包及粘包的情况。在解包及解密完成后，我们会将这个数据包复制到逻辑线程消息队列中，如果我们只使用一个队列，那这里也将会是个环形缓冲区，IO线程往里写，逻辑线程在后面读，互相追逐。可要是我们使用了前面介绍的优化方案后，可能这里便不再需要环形缓冲区了，至少我们并不再需要他们是环形的了。因为我们对同一个队列不再会出现同时读和写的情况，每个队列在写满后交给逻辑线程去读，逻辑线程读完后清空队列再交给IO线程去写，一段固定大小的缓冲区即可。没关系，这么好的技术，在别的地方一定也会用到的。&nbsp;<br /><br /><strong>服务器公共组件实现 -- 发包的方式</strong>&nbsp;<br /><br />　　前面一直都在说接收数据时的处理方法，我们应该用专门的IO线程，接收到完整的消息包后加入到主线程的消息队列，但是主线程如何发送数据还没有探讨过。&nbsp;<br /><br />　　一般来说最直接的方法就是逻辑线程什么时候想发数据了就直接调用相关的socket API发送，这要求服务器的玩家对象中保存其连接的socket句柄。但是直接send调用有时候有会存在一些问题，比如遇到系统的发送缓冲区满而阻塞住的情况，或者只发送了一部分数据的情况也时有发生。我们可以将要发送的数据先缓存一下，这样遇到未发送完的，在逻辑线程的下一次处理时可以接着再发送。&nbsp;<br /><br />　　考虑数据缓存的话，那这里这可以有两种实现方式了，一是为每个玩家准备一个缓冲区，另外就是只有一个全局的缓冲区，要发送的数据加入到全局缓冲区的时候同时要指明这个数据是发到哪个socket的。如果使用全局缓冲区的话，那我们可以再进一步，使用一个独立的线程来处理数据发送，类似于逻辑线程对数据的处理方式，这个独立发送线程也维护一个消息队列，逻辑线程要发数据时也只是把数据加入到这个队列中，发送线程循环取包来执行send调用，这时的阻塞也就不会对逻辑线程有任何影响了。&nbsp;<br /><br />　　采用第二种方式还可以附带一个优化方案。一般对于广播消息而言，发送给周围玩家的数据都是完全相同的，我们如果采用给每个玩家一个缓冲队列的方式，这个数据包将需要拷贝多份，而采用一个全局发送队列时，我们只需要把这个消息入队一次，同时指明该消息包是要发送给哪些socket的即可。有关该优化的说明在云风描述其连接服务器实现的blog文章中也有讲到，有兴趣的可以去阅读一下。&nbsp;<br /><br /><strong>服务器公共组件实现 -- 状态机</strong>&nbsp;<br /><br />　　有关State模式的设计意图及实现就不从设计模式中摘抄了，我们只来看看游戏服务器编程中如何使用State设计模式。&nbsp;<br /><br />　　首先还是从mangos的代码开始看起，我们注意到登录服在处理客户端发来的消息时用到了这样一个结构体：&nbsp;<br /><br />　　struct AuthHandler&nbsp;<br />　　{&nbsp;<br />　　　　eAuthCmd cmd;&nbsp;<br />　　　　uint32 status;&nbsp;<br />　　　　bool (AuthSocket::*handler)(void);&nbsp;<br />　　};&nbsp;<br /><br />　　该结构体定义了每个消息码的处理函数及需要的状态标识，只有当前状态满足要求时才会调用指定的处理函数，否则这个消息码的出现是不合法的。这个status状态标识的定义是一个宏，有两种有效的标识，STATUS_CONNECTED和STATUS_AUTHED，也就是未认证通过和已认证通过。而这个状态标识的改变是在运行时进行的，确切的说是在收到某个消息并正确处理完后改变的。&nbsp;<br /><br />　　我们再来看看设计模式中对State模式的说明，其中关于State模式适用情况里有一条，当操作中含有庞大的多分支的条件语句，且这些分支依赖于该对象的状态，这个状态通常用一个或多个枚举变量表示。&nbsp;<br /><br />　　描述的情况与我们这里所要处理的情况是如此的相似，也许我们可以试一试。那再看看State模式提供的解决方案是怎样的，State模式将每一个条件分支放入一个独立的类中。&nbsp;<br /><br />　　由于这里的两个状态标识只区分出了两种状态，所以，我们仅需要两个独立的类，用以表示两种状态即可。然后，按照State模式的描述，我们还需要一个Context类，也就是状态机管理类，用以管理当前的状态类。稍作整理，大概的代码会类似这样：&nbsp;<br /><br />　　状态基类接口：&nbsp;<br />　　StateBase&nbsp;<br />　　{&nbsp;<br />　　　　void Enter() = 0;&nbsp;<br />　　　　void Leave() = 0;&nbsp;<br />　　　　void Process(Message* msg) = 0;&nbsp;<br />　　};&nbsp;<br /><br />　　状态机基类接口：&nbsp;<br />　　MachineBase&nbsp;<br />　　{&nbsp;<br />　　　　void ChangeState(StateBase* state) = 0;&nbsp;<br /><br />　　　　StateBase* m_curState;&nbsp;<br />　　};&nbsp;<br /><br />　　我们的逻辑处理类会从MachineBase派生，当取出数据包后交给当前状态处理，前面描述的两个状态类从StateBase派生，每个状态类只处理该状态标识下需要处理的消息。当要进行状态转换时，调用MachineBase的ChangeState()方法，显示地告诉状态机管理类自己要转到哪一个状态。所以，状态类内部需要保存状态机管理类的指针，这个可以在状态类初始化时传入。具体的实现细节就不做过多描述了。&nbsp;<br /><br />　　使用状态机虽然避免了复杂的判断语句，但也引入了新的麻烦。当我们在进行状态转换时，可能会需要将一些现场数据从老状态对象转移到新状态对象，这需要在定义接口时做一下考虑。如果不希望执行拷贝，那么这里公有的现场数据也可放到状态机类中，只是这样在使用时可能就不那么优雅了。&nbsp;<br /><br />　　正如同在设计模式中所描述的，所有的模式都是已有问题的另一种解决方案，也就是说这并不是唯一的解决方案。放到我们今天讨论的State模式中，就拿登录服所处理的两个状态来说，也许用mangos所采用的遍历处理函数的方法可能更简单，但当系统中的状态数量增多，状态标识也变多的时候，State模式就显得尤其重要了。&nbsp;<br /><br />　　比如在游戏服务器上玩家的状态管理，还有在实现NPC人工智能时的各种状态管理，这些就留作以后的专题吧。&nbsp;<br /><br /><strong>服务器公共组件 -- 事件与信号</strong>&nbsp;<br /><br />关于这一节，这几天已经打了好几遍草稿，总觉得说不清楚，也不好组织这些内容，但是打铁要趁热，为避免热情消退，先整理一点东西放这，好继续下面的主题，以后如果有机会再回来完善吧。本节内容欠考虑，希望大家多给点意见。&nbsp;<br /><br />有些类似于QT中的event与signal，我将一些动作请求消息定义为事件，而将状态改变消息定义为信号。比如在QT应用程序中，用户的一次鼠标点击会产生一个鼠标点击事件加入到事件队列中，当处理此事件时可能会导致某个按钮控件产生一个clicked()信号。&nbsp;<br /><br />对应到我们的服务器上的一个例子，玩家登录时会发给服务器一个请求登录的数据包，服务器可将其当作一个用户登录事件，该事件处理完后可能会产生一个用户已登录信号。&nbsp;<br /><br />这样，与QT类似，对于事件我们可以重定义其处理方法，甚至过滤掉某些事件使其不被处理，但对于信号我们只是收到了一个通知，有些类似于Observe模式中的观察者，当收到更新通知时，我们只能更新自己的状态，对刚刚发生的事件我不已不能做任何影响。&nbsp;<br /><br />仔细来看，事件与信号其实并无多大差别，从我们对其需求上来说，都只要能注册事件或信号响应函数，在事件或信号产生时能够被通知到即可。但有一项区别在于，事件处理函数的返回值是有意义的，我们要根据这个返回值来确定是否还要继续事件的处理，比如在QT中，事件处理函数如果返回true，则这个事件处理已完成，QApplication会接着处理下一个事件，而如果返回false，那么事件分派函数会继续向上寻找下一个可以处理该事件的注册方法。信号处理函数的返回值对信号分派器来说是无意义的。&nbsp;<br /><br />简单点说，就是我们可以为事件定义过滤器，使得事件可以被过滤。这一功能需求在游戏服务器上是到处存在的。&nbsp;<br /><br />关于事件和信号机制的实现，网络上的开源训也比较多，比如FastDelegate，sigslot，boost::signal等，其中sigslot还被Google采用，在libjingle的代码中我们可以看到他是如何被使用的。&nbsp;<br /><br />在实现事件和信号机制时或许可以考虑用同一套实现，在前面我们就分析过，两者唯一的区别仅在于返回值的处理上。&nbsp;<br /><br />另外还有一个需要我们关注的问题是事件和信号处理时的优先级问题。在QT中，事件因为都是与窗口相关的，所以事件回调时都是从当前窗口开始，一级一级向上派发，直到有一个窗口返回true，截断了事件的处理为止。对于信号的处理则比较简单，默认是没有顺序的，如果需要明确的顺序，可以在信号注册时显示地指明槽的位置。&nbsp;<br /><br />在我们的需求中，因为没有窗口的概念，事件的处理也与信号类似，对注册过的处理器要按某个顺序依次回调，所以优先级的设置功能是需要的。&nbsp;<br /><br />最后需要我们考虑的是事件和信号的处理方式。在QT中，事件使用了一个事件队列来维护，如果事件的处理中又产生了新的事件，那么新的事件会加入到队列尾，直到当前事件处理完毕后，QApplication再去队列头取下一个事件来处理。而信号的处理方式有些不同，信号处理是立即回调的，也就是一个信号产生后，他上面所注册的所有槽都会立即被回调。这样就会产生一个递归调用的问题，比如某个信号处理器中又产生了一个信号，会使得信号的处理像一棵树一样的展开。我们需要注意的一个很重要的问题是会不会引起循环调用。&nbsp;<br /><br />关于事件机制的考虑其实还很多，但都是一些不成熟的想法。在上面的文字中就同时出现了消息、事件和信号三个相近的概念，而在实际处理中，经常发现三者不知道如何界定的情况，实际的情况比我在这里描述的要混乱的多。&nbsp;<br /><br />这里也就当是挖下一个坑，希望能够有所交流。&nbsp;<br /><br /><strong>再谈登录服的实现</strong>&nbsp;<br /><br />&nbsp;&nbsp;&nbsp; 离我们的登录服实现已经太远了，先拉回来一下。&nbsp;<br />&nbsp;&nbsp;&nbsp;&nbsp;<br />&nbsp;&nbsp;&nbsp; 关于登录服、大区服及游戏世界服的结构之前已做过探讨，这里再把各自的职责和关系列一下。&nbsp;<br /><br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; GateWay/WorldServer&nbsp;&nbsp; GateWay/WodlServer LoginServer LoginServer DNSServer WorldServerMgr&nbsp;<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; |&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; |&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; |&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; |&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; |&nbsp;<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; ---------------------------------------------------------------------------------------------&nbsp;<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; | | |&nbsp;<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; internet&nbsp;<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; |&nbsp;<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; clients&nbsp;<br /><br />&nbsp;&nbsp;&nbsp; 其中DNSServer负责带负载均衡的域名解析服务，返回LoginServer的IP地址给客户端。WorldServerMgr维护当前大区内的世界服列表，LoginServer会从这里取世界列表发给客户端。LoginServer处理玩家的登录及世界服选择请求。GateWay/WorldServer为各个独立的世界服或者通过网关连接到后面的世界服。&nbsp;<br /><br />&nbsp;&nbsp;&nbsp; 在mangos的代码中，我们注意到登录服是从数据库中取的世界列表，而在wow官方服务器中，我们却会注意到，这个世界服列表并不是一开始就固定，而是动态生成的。当每周一次的维护完成之后，我们可以很明显的看到这个列表生成的过程。刚开始时，世界列表是空的，慢慢的，世界服会一个个加入进来，而这里如果有世界服当机，他会显示为离线，不会从列表中删除。但是当下一次服务器再维护后，所有的世界服都不存在了，全部重新开始添加。&nbsp;<br /><br />&nbsp;&nbsp;&nbsp; 从上面的过程描述中，我们很容易想到利用一个临时的列表来保存世界服信息，这也是我们增加WorldServerMgr服务器的目的所在。GateWay/WorldServer在启动时会自动向WorldServerMgr注册自己，这样就把自己所代表的游戏世界添加到世界列表中了。类似的，如果DNSServer也可以让LoginServer自己去注册，这样在临时LoginServer时就不需要去改动DNSServer的配置文件了。&nbsp;<br /><br />&nbsp;&nbsp;&nbsp; WorldServerMgr内部的实现很简单，监听一个固定的端口，接受来自WorldServer的主动连接，并检测其状态。这里可以用一个心跳包来实现其状态的检测，如果WorldServer的连接断开或者在规定时间内未收到心跳包，则将其状态更新为离线。另外WorldServerMgr还处理来自LoginServer的列表请求。由于世界列表并不常变化，所以LoginServer没有必要每次发送世界列表时都到WorldServerMgr上去取，LoginServer完全可以自己维护一个列表，当WorldServerMgr上的列表发生变化时，WorldServerMgr会主动通知所有的LoginServer也更新一下自己的列表。这个或许就可以用前面描述过的事件方式，或者就是观察者模式了。&nbsp;<br /><br />&nbsp;&nbsp;&nbsp; WorldServerMgr实现所要考虑的内容就这些，我们再来看看LoginServer，这才是我们今天要重点讨论的对象。&nbsp;<br /><br />&nbsp;&nbsp;&nbsp; 前面探讨一些服务器公共组件，那我们这里也应该试用一下，不能只是停留在理论上。先从状态机开始，前面也说过了，登录服上的连接会有两种状态，一是帐号密码验证状态，一是服务器列表选择状态，其实还有另外一个状态我们未曾讨论过，因为它与我们的登录过程并无多大关系，这就是升级包发送状态。三个状态的转换流程大致为：&nbsp;<br /><br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; LogonState -- 验证成功 -- 版本检查 -- 版本低于最新值 -- 转到UpdateState&nbsp;<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; |&nbsp;<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; -- 版本等于最新值 -- 转到WorldState&nbsp;<br /><br />&nbsp;&nbsp;&nbsp; 这个版本检查的和决定下一个状态的过程是在LogonState中进行的，下一个状态的选择是由当前状态来决定。密码验证的过程使用了SRP6协议，具体过程就不多做描述，每个游戏使用的方式也都不大一样。而版本检查的过程就更无值得探讨的东西，一个if-else即可。<br /><br />&nbsp;&nbsp;&nbsp; 升级状态其实就是文件传输过程，文件发送完毕后通知客户端开始执行升级文件并关闭连接。世界选择状态则提供了一个列表给客户端，其中包括了所有游戏世界网关服务器的IP、PORT和当前负载情况。如果客户端一直连接着，则该状态会以每5秒一次的频率不停刷新列表给客户端，当然是否值得这样做还是有待商榷。&nbsp;<br /><br />&nbsp;&nbsp;&nbsp; 整个过程似乎都没有值得探讨的内容，但是，还没有完。当客户端选择了一个世界之后该怎么办？wow的做法是，当客户端选择一个游戏世界时，客户端会主动去连接该世界服的IP和PORT，然后进入这个游戏世界。与此同时，与登录服的连接还没有断开，直到客户端确实连接上了选定的世界服并且走完了排队过程为止。这是一个很必要的设计，保证了我们在因意外情况连接不上世界服或者发现世界服正在排队而想换另外一个试试时不会需要重新进行密码验证。&nbsp;<br /><br />&nbsp;&nbsp;&nbsp; 但是我们所要关注的还不是这些，而是客户端去连接游戏世界的网关服时服务器该如何识别我们。打个比方，有个不自觉的玩家不遵守游戏规则，没有去验证帐号密码就直接跑去连接世界服了，就如同一个不自觉的乘客没有换登机牌就直接跑到登机口一样。这时，乘务员会客气地告诉你要先换登机牌，那登机牌又从哪来？检票口换的，人家会先验明你的身份，确认后才会发给你登机牌。一样的处理过程，我们的登录服在验明客户端身份后，也会发给客户端一个登机牌，这个登机牌还有一个学名，叫做session key。&nbsp;<br /><br />&nbsp;&nbsp;&nbsp; 客户端拿着这个session key去世界服网关处就可正确登录了吗？似乎还是有个疑问，他怎么知道我这个key是不是造假的？没办法，中国的假货太多，我们不得不到处都考虑假货的问题。方法很简单，去找给他登机牌的那个检票员问一下，这张牌是不是他发的不就得了。可是，那么多的LoginServer，要一个个问下来，这效率也太低了，后面排的长队一定会开始叫唤了。那么，LoginServer将这个key存到数据库中，让网关服自己去数据库验证？似乎也是个可行的方案。&nbsp;<br /><br />&nbsp;&nbsp;&nbsp; 如果觉得这样给数据库带来了太大的压力的话，也可以考虑类似WorldServerMgr的做法，用一个临时的列表来保存，甚至可以将这个列表就保存到WorldServerMgr上，他正好是全区唯一的。这两种方案的本质并无差别，只是看你愿意将负载放在哪里。而不管在哪里，这个查询的压力都是有点大的，想想，全区所有玩家呢。所以，我们也可以试着考虑一种新的方案，一种不需要去全区唯一一个入口查询的方案。&nbsp;<br /><br />&nbsp;&nbsp;&nbsp; 那我们将这些session key分开存储不就得了。一个可行的方案是，让任意时刻只有一个地方保存一个客户端的session key，这个地方可能是客户端当前正连接着的服务器，也可以是它正要去连接的服务器。让我们来详细描述一下这个过程，客户端在LoginServer上验证通过时，LoginServer为其生成了本次会话的session key，但只是保存在当前的LoginServer上，不会存数据库，也不会发送给WorldServerMgr。如果客户端这时想要去某个游戏世界，那么他必须先通知当前连接的LoginServer要去的服务器地址，LoginServer将session key安全转移给目标服务器，转移的意思是要确保目标服务器收到了session key，本地保存的要删除掉。转移成功后LoginServer通知客户端再去连接目标服务器，这时目标服务器在验证session key合法性的时候就不需要去别处查询了，只在本地保存的session key列表中查询即可。&nbsp;<br /><br />&nbsp;&nbsp;&nbsp; 当然了，为了session key的安全，所有的服务器在收到一个新的session key后都会为其设一个有效期，在有效期过后还没来认证的，则该session key会被自动删除。同时，所有服务器上的session key在连接关闭后一定会被删除，保证一个session key真正只为一次连接会话服务。&nbsp;<br /><br />&nbsp;&nbsp;&nbsp; 但是，很显然的，wow并没有采用这种方案，因为客户端在选择世界服时并没有向服务器发送要求确认的消息。wow中的session key应该是保存在一个类似于WorldServerMgr的地方，或者如mangos一样，就是保存在了数据库中。不管是怎样一种方式，了解了其过程，代码实现都是比较简单的，我们就不再赘述了。</span></span><img src ="http://www.cppblog.com/API/aggbug/214972.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/API/" target="_blank">C++技术中心</a> 2017-06-02 14:08 <a href="http://www.cppblog.com/API/archive/2017/06/02/214972.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>aoi--地图视野处理</title><link>http://www.cppblog.com/API/archive/2017/03/07/214733.html</link><dc:creator>C++技术中心</dc:creator><author>C++技术中心</author><pubDate>Tue, 07 Mar 2017 07:09:00 GMT</pubDate><guid>http://www.cppblog.com/API/archive/2017/03/07/214733.html</guid><wfw:comment>http://www.cppblog.com/API/comments/214733.html</wfw:comment><comments>http://www.cppblog.com/API/archive/2017/03/07/214733.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cppblog.com/API/comments/commentRss/214733.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/API/services/trackbacks/214733.html</trackback:ping><description><![CDATA[<h2>
<div style="display: inline-block;">
<p style="margin-top: 10px; margin-bottom: 10px; font-family: Verdana, Geneva, Arial, Helvetica, sans-serif; font-size: 13.3333px; font-weight: normal; background-color: #ffffff;">AOI主要有九宫格、灯塔和十字链表的算法实现。本文阐述十字链表的实现和尝试。</p>
</div>
</h2>
<h3 id="基本原理" style="font-size: 16px; border-bottom: 1px dotted #d6d6d6; background-color: #ffffff; line-height: 1.5; margin: 10px 0px; font-family: Verdana, Geneva, Arial, Helvetica, sans-serif;">1. 基本原理</h3>
<h2>
<div style="display: inline-block;">
<p style="margin-top: 10px; margin-bottom: 10px; font-family: Verdana, Geneva, Arial, Helvetica, sans-serif; font-size: 13.3333px; font-weight: normal; background-color: #ffffff;">根据二维地图，将其分成x轴和y轴两个链表。如果是三维地图，则还需要维护多一个z轴的链表。将对象的坐标值按照大小相应的排列在相应的坐标轴上面。</p>
</div>
</h2>
<h3 id="基本接口" style="font-size: 16px; border-bottom: 1px dotted #d6d6d6; background-color: #ffffff; line-height: 1.5; margin: 10px 0px; font-family: Verdana, Geneva, Arial, Helvetica, sans-serif;">2. 基本接口</h3>
<h2>
<div style="display: inline-block;">
<p style="margin-top: 10px; margin-bottom: 10px; font-family: Verdana, Geneva, Arial, Helvetica, sans-serif; font-size: 13.3333px; font-weight: normal; background-color: #ffffff;">对对象的操作主要有以下三个接口：</p>
<ul style="margin-left: 30px; padding-left: 0px; font-family: Verdana, Geneva, Arial, Helvetica, sans-serif; font-size: 13.3333px; font-weight: normal; background-color: #ffffff;">
     <li style="list-style-type: disc;">add：对象进入地图；</li>
     <li style="list-style-type: disc;">leave：对象离开地图；</li>
     <li style="list-style-type: disc;">move：对象在地图内移动。</li>
</ul>
</div>
</h2>
<h3 id="算法实现" style="font-size: 16px; border-bottom: 1px dotted #d6d6d6; background-color: #ffffff; line-height: 1.5; margin: 10px 0px; font-family: Verdana, Geneva, Arial, Helvetica, sans-serif;">2. 算法实现</h3>
<h2>
<p style="margin-top: 10px; margin-bottom: 10px; font-family: Verdana, Geneva, Arial, Helvetica, sans-serif; font-size: 13.3333px; font-weight: normal; background-color: #ffffff;">既然是链表，很自然地想到用线性表来实现。因为存在向前和向后找的情况，所以使用双链表实现。其实实现也是非常简单，就是两个双链表（这里以二维地图举例）。那么我们的节点需要四个指针，分布为x轴的前后指针，y轴的前后指针。<br />
</p>
<div style="background-color: #eeeeee; font-size: 13px; border: 1px solid #cccccc; padding: 4px 5px 4px 4px; width: 98%; word-break: break-all;"><!--<br />
<br />
Code highlighting produced by Actipro CodeHighlighter (freeware)<br />
http://www.CodeHighlighter.com/<br />
<br />
--><span style="color: #008000; ">//</span><span style="color: #008000; ">&nbsp;双链表（对象）</span><span style="color: #008000; "><br />
</span><span style="color: #0000FF; ">class</span>&nbsp;DoubleNode<br />
{<br />
<span style="color: #0000FF; ">public</span>:<br />
&nbsp;&nbsp;&nbsp;&nbsp;DoubleNode(<span style="color: #0000FF; ">string</span>&nbsp;key,&nbsp;<span style="color: #0000FF; ">int</span>&nbsp;x,&nbsp;<span style="color: #0000FF; ">int</span>&nbsp;y)<br />
&nbsp;&nbsp;&nbsp;&nbsp;{<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span style="color: #0000FF; ">this</span>-&gt;key&nbsp;=&nbsp;key;<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span style="color: #0000FF; ">this</span>-&gt;x&nbsp;=&nbsp;x;<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span style="color: #0000FF; ">this</span>-&gt;y&nbsp;=&nbsp;y;<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;xPrev&nbsp;=&nbsp;xNext&nbsp;=&nbsp;NULL;<br />
&nbsp;&nbsp;&nbsp;&nbsp;};<br />
&nbsp;&nbsp;&nbsp;&nbsp;<br />
&nbsp;&nbsp;&nbsp;&nbsp;DoubleNode&nbsp;*&nbsp;xPrev;<br />
&nbsp;&nbsp;&nbsp;&nbsp;DoubleNode&nbsp;*&nbsp;xNext;<br />
&nbsp;&nbsp;&nbsp;&nbsp;<br />
&nbsp;&nbsp;&nbsp;&nbsp;DoubleNode&nbsp;*&nbsp;yPrev;<br />
&nbsp;&nbsp;&nbsp;&nbsp;DoubleNode&nbsp;*&nbsp;yNext;<br />
&nbsp;&nbsp;&nbsp;&nbsp;<br />
&nbsp;&nbsp;&nbsp;&nbsp;<span style="color: #0000FF; ">string</span>&nbsp;key;&nbsp;&nbsp;<span style="color: #008000; ">//</span><span style="color: #008000; ">&nbsp;只是一个关键字</span><span style="color: #008000; "><br />
</span>&nbsp;&nbsp;&nbsp;&nbsp;<span style="color: #0000FF; ">int</span>&nbsp;x;&nbsp;<span style="color: #008000; ">//</span><span style="color: #008000; ">&nbsp;位置（x坐标）</span><span style="color: #008000; "><br />
</span>&nbsp;&nbsp;&nbsp;&nbsp;<span style="color: #0000FF; ">int</span>&nbsp;y;&nbsp;<span style="color: #008000; ">//</span><span style="color: #008000; ">&nbsp;位置（y坐标）</span><span style="color: #008000; "><br />
</span><br />
<span style="color: #0000FF; ">private</span>:<br />
<br />
};</div>
</h2>
<span style="font-family: Verdana, Geneva, Arial, Helvetica, sans-serif; font-size: 13.3333px; background-color: #ffffff;">下面是地图场景信息和接口。这里的实现比较粗略，是带头尾的的双链表，暂时不考虑空间占用的问题。类</span><code style="line-height: 1.8; margin: 1px 5px; vertical-align: middle; display: inline-block; font-family: &quot;Courier New&quot;, sans-serif !important; font-size: 12px !important; background-color: #f5f5f5 !important; border: 1px solid #cccccc !important; padding: 0px 5px !important; border-radius: 3px !important;">Scene</code><span style="font-family: Verdana, Geneva, Arial, Helvetica, sans-serif; font-size: 13.3333px; background-color: #ffffff;">有分别有一个头尾指针，初始化的时候会为其赋值，主要用</span><code style="line-height: 1.8; margin: 1px 5px; vertical-align: middle; display: inline-block; font-family: &quot;Courier New&quot;, sans-serif !important; font-size: 12px !important; background-color: #f5f5f5 !important; border: 1px solid #cccccc !important; padding: 0px 5px !important; border-radius: 3px !important;">DoubleNode</code><span style="font-family: Verdana, Geneva, Arial, Helvetica, sans-serif; font-size: 13.3333px; background-color: #ffffff;">类的指针来存储x轴和y轴的头尾。初始化的时候，将</span><code style="line-height: 1.8; margin: 1px 5px; vertical-align: middle; display: inline-block; font-family: &quot;Courier New&quot;, sans-serif !important; font-size: 12px !important; background-color: #f5f5f5 !important; border: 1px solid #cccccc !important; padding: 0px 5px !important; border-radius: 3px !important;">_head</code><span style="font-family: Verdana, Geneva, Arial, Helvetica, sans-serif; font-size: 13.3333px; background-color: #ffffff;">的next指针指向尾</span><code style="line-height: 1.8; margin: 1px 5px; vertical-align: middle; display: inline-block; font-family: &quot;Courier New&quot;, sans-serif !important; font-size: 12px !important; background-color: #f5f5f5 !important; border: 1px solid #cccccc !important; padding: 0px 5px !important; border-radius: 3px !important;">_tail</code><span style="font-family: Verdana, Geneva, Arial, Helvetica, sans-serif; font-size: 13.3333px; background-color: #ffffff;">；将</span><code style="line-height: 1.8; margin: 1px 5px; vertical-align: middle; display: inline-block; font-family: &quot;Courier New&quot;, sans-serif !important; font-size: 12px !important; background-color: #f5f5f5 !important; border: 1px solid #cccccc !important; padding: 0px 5px !important; border-radius: 3px !important;">_tail</code><span style="font-family: Verdana, Geneva, Arial, Helvetica, sans-serif; font-size: 13.3333px; background-color: #ffffff;">的prev指针指向</span><code style="line-height: 1.8; margin: 1px 5px; vertical-align: middle; display: inline-block; font-family: &quot;Courier New&quot;, sans-serif !important; font-size: 12px !important; background-color: #f5f5f5 !important; border: 1px solid #cccccc !important; padding: 0px 5px !important; border-radius: 3px !important;">_head</code><span style="font-family: Verdana, Geneva, Arial, Helvetica, sans-serif; font-size: 13.3333px; background-color: #ffffff;">。<br />
</span>
<div style="background-color:#eeeeee;font-size:13px;border:1px solid #CCCCCC;padding-right: 5px;padding-bottom: 4px;padding-left: 4px;padding-top: 4px;width: 98%;word-break:break-all"><!--<br />
<br />
Code highlighting produced by Actipro CodeHighlighter (freeware)<br />
http://www.CodeHighlighter.com/<br />
<br />
--><span style="color: #008000; ">//</span><span style="color: #008000; ">&nbsp;地图/场景</span><span style="color: #008000; "><br />
</span><span style="color: #0000FF; ">class</span>&nbsp;Scene<br />
{<br />
<span style="color: #0000FF; ">public</span>:<br />
&nbsp;&nbsp;&nbsp;&nbsp;Scene()<br />
&nbsp;&nbsp;&nbsp;&nbsp;{<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span style="color: #0000FF; ">this</span>-&gt;_head&nbsp;=&nbsp;<span style="color: #0000FF; ">new</span>&nbsp;DoubleNode("[head]",&nbsp;0,&nbsp;0);&nbsp;<span style="color: #008000; ">//</span><span style="color: #008000; ">&nbsp;带头尾的双链表(可优化去掉头尾)</span><span style="color: #008000; "><br />
</span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span style="color: #0000FF; ">this</span>-&gt;_tail&nbsp;=&nbsp;<span style="color: #0000FF; ">new</span>&nbsp;DoubleNode("[tail]",&nbsp;0,&nbsp;0);<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;_head-&gt;xNext&nbsp;=&nbsp;_tail;<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;_head-&gt;yNext&nbsp;=&nbsp;_tail;<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;_tail-&gt;xPrev&nbsp;=&nbsp;_head;<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;_tail-&gt;yPrev&nbsp;=&nbsp;_head;<br />
&nbsp;&nbsp;&nbsp;&nbsp;};<br />
<br />
&nbsp;&nbsp;&nbsp;&nbsp;<span style="color: #008000; ">//</span><span style="color: #008000; ">&nbsp;对象加入(新增)</span><span style="color: #008000; "><br />
</span>&nbsp;&nbsp;&nbsp;&nbsp;DoubleNode&nbsp;*&nbsp;Add(<span style="color: #0000FF; ">string</span>&nbsp;name,&nbsp;<span style="color: #0000FF; ">int</span>&nbsp;x,&nbsp;<span style="color: #0000FF; ">int</span>&nbsp;y);<br />
<br />
&nbsp;&nbsp;&nbsp;&nbsp;<span style="color: #008000; ">//</span><span style="color: #008000; ">&nbsp;对象离开(删除)</span><span style="color: #008000; "><br />
</span>&nbsp;&nbsp;&nbsp;&nbsp;<span style="color: #0000FF; ">void</span>&nbsp;Leave(DoubleNode&nbsp;*&nbsp;node);<br />
<br />
&nbsp;&nbsp;&nbsp;&nbsp;<span style="color: #008000; ">//</span><span style="color: #008000; ">&nbsp;对象移动</span><span style="color: #008000; "><br />
</span>&nbsp;&nbsp;&nbsp;&nbsp;<span style="color: #0000FF; ">void</span>&nbsp;Move(DoubleNode&nbsp;*&nbsp;node,&nbsp;<span style="color: #0000FF; ">int</span>&nbsp;x,&nbsp;<span style="color: #0000FF; ">int</span>&nbsp;y);<br />
<br />
&nbsp;&nbsp;&nbsp;&nbsp;<span style="color: #008000; ">//</span><span style="color: #008000; ">&nbsp;获取范围内的AOI&nbsp;(参数为查找范围)</span><span style="color: #008000; "><br />
</span>&nbsp;&nbsp;&nbsp;&nbsp;<span style="color: #0000FF; ">void</span>&nbsp;PrintAOI(DoubleNode&nbsp;*&nbsp;node,&nbsp;<span style="color: #0000FF; ">int</span>&nbsp;xAreaLen,&nbsp;<span style="color: #0000FF; ">int</span>&nbsp;yAreaLen);<br />
<br />
<span style="color: #0000FF; ">private</span>:<br />
&nbsp;&nbsp;&nbsp;&nbsp;DoubleNode&nbsp;*&nbsp;_head;<br />
&nbsp;&nbsp;&nbsp;&nbsp;DoubleNode&nbsp;*&nbsp;_tail;<br />
};</div>
<h4>2.1. add(进入地图)</h4>
<p style="margin-top: 10px; margin-bottom: 10px; font-family: Verdana, Geneva, Arial, Helvetica, sans-serif; font-size: 13.3333px; background-color: #ffffff;">将<code style="line-height: 1.8; margin: 1px 5px; vertical-align: middle; display: inline-block; font-family: &quot;Courier New&quot;, sans-serif !important; font-size: 12px !important; background-color: #f5f5f5 !important; border: 1px solid #cccccc !important; padding: 0px 5px !important; border-radius: 3px !important;">DoubleNode</code>对象插入到十字链表中。x轴和y轴分别处理，处理方法基本一致。其实就是双链表的数据插入操作，需要从头开始遍历线性表，对比相应轴上的值的大小，插入到合适的位置。<br />
</p>
<div style="background-color:#eeeeee;font-size:13px;border:1px solid #CCCCCC;padding-right: 5px;padding-bottom: 4px;padding-left: 4px;padding-top: 4px;width: 98%;word-break:break-all"><!--<br />
<br />
Code highlighting produced by Actipro CodeHighlighter (freeware)<br />
http://www.CodeHighlighter.com/<br />
<br />
--><span style="color: #0000FF; ">void</span>&nbsp;_add(DoubleNode&nbsp;*&nbsp;node)<br />
{<br />
&nbsp;&nbsp;&nbsp;&nbsp;<span style="color: #008000; ">//</span><span style="color: #008000; ">&nbsp;x轴处理</span><span style="color: #008000; "><br />
</span>&nbsp;&nbsp;&nbsp;&nbsp;DoubleNode&nbsp;*&nbsp;cur&nbsp;=&nbsp;_head-&gt;xNext;<br />
&nbsp;&nbsp;&nbsp;&nbsp;<span style="color: #0000FF; ">while</span>(cur&nbsp;!=&nbsp;NULL)<br />
&nbsp;&nbsp;&nbsp;&nbsp;{<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span style="color: #0000FF; ">if</span>((cur-&gt;x&nbsp;&gt;&nbsp;node-&gt;x)&nbsp;||&nbsp;cur==_tail)&nbsp;<span style="color: #008000; ">//</span><span style="color: #008000; ">&nbsp;插入数据</span><span style="color: #008000; "><br />
</span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;{<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;node-&gt;xNext&nbsp;=&nbsp;cur;<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;node-&gt;xPrev&nbsp;=&nbsp;cur-&gt;xPrev;<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;cur-&gt;xPrev-&gt;xNext&nbsp;=&nbsp;node;<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;cur-&gt;xPrev&nbsp;=&nbsp;node;<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span style="color: #0000FF; ">break</span>;<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;}<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;cur&nbsp;=&nbsp;cur-&gt;xNext;<br />
&nbsp;&nbsp;&nbsp;&nbsp;}<br />
<br />
&nbsp;&nbsp;&nbsp;&nbsp;<span style="color: #008000; ">//</span><span style="color: #008000; ">&nbsp;y轴处理</span><span style="color: #008000; "><br />
</span>&nbsp;&nbsp;&nbsp;&nbsp;cur&nbsp;=&nbsp;_head-&gt;yNext;<br />
&nbsp;&nbsp;&nbsp;&nbsp;<span style="color: #0000FF; ">while</span>(cur&nbsp;!=&nbsp;NULL)<br />
&nbsp;&nbsp;&nbsp;&nbsp;{<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span style="color: #0000FF; ">if</span>((cur-&gt;y&nbsp;&gt;&nbsp;node-&gt;y)&nbsp;||&nbsp;cur==_tail)&nbsp;<span style="color: #008000; ">//</span><span style="color: #008000; ">&nbsp;插入数据</span><span style="color: #008000; "><br />
</span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;{<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;node-&gt;yNext&nbsp;=&nbsp;cur;<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;node-&gt;yPrev&nbsp;=&nbsp;cur-&gt;yPrev;<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;cur-&gt;yPrev-&gt;yNext&nbsp;=&nbsp;node;<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;cur-&gt;yPrev&nbsp;=&nbsp;node;<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span style="color: #0000FF; ">break</span>;<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;}<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;cur&nbsp;=&nbsp;cur-&gt;yNext;<br />
&nbsp;&nbsp;&nbsp;&nbsp;}<br />
}</div>
<p style="margin-top: 10px; margin-bottom: 10px; font-family: Verdana, Geneva, Arial, Helvetica, sans-serif; font-size: 13.3333px; background-color: #ffffff;">假设可视范围为x轴2以内，y轴2以内，则运行：</p>
<ol style="padding-left: 40px; font-family: Verdana, Geneva, Arial, Helvetica, sans-serif; font-size: 13.3333px; background-color: #ffffff;">
     <li style="list-style-type: decimal;">分别插入以下数据a(1,5)、f(6,6)、c(3,1)、b(2,2)、e(5,3)，然后插入d(3,3)，按照x轴和y轴打印其双链表结果；</li>
     <li style="list-style-type: decimal;">插入d(3,3)数据，求其可视AOI范围（如图，除了f(6,6)，其它对象都在d的可视范围内）。</li>
</ol>
<p style="margin-top: 10px; margin-bottom: 10px; font-family: Verdana, Geneva, Arial, Helvetica, sans-serif; font-size: 13.3333px; background-color: #ffffff;">控制台结果（前8行）：</p>
<p style="margin-top: 10px; margin-bottom: 10px; font-family: Verdana, Geneva, Arial, Helvetica, sans-serif; font-size: 13.3333px; background-color: #ffffff;"><img src="http://images2015.cnblogs.com/blog/211061/201611/211061-20161129190004427-2249593.png" style="border: 0px; max-width: 900px;" alt="" /></p>
<p style="margin-top: 10px; margin-bottom: 10px; font-family: Verdana, Geneva, Arial, Helvetica, sans-serif; font-size: 13.3333px; background-color: #ffffff;">步骤1结果图示：</p>
<p style="margin-top: 10px; margin-bottom: 10px; font-family: Verdana, Geneva, Arial, Helvetica, sans-serif; font-size: 13.3333px; background-color: #ffffff;"><img src="http://images2015.cnblogs.com/blog/211061/201611/211061-20161129190012771-350959957.png" style="border: 0px; max-width: 900px;" alt="" /></p>
<p style="margin-top: 10px; margin-bottom: 10px; font-family: Verdana, Geneva, Arial, Helvetica, sans-serif; font-size: 13.3333px; background-color: #ffffff;">步骤2结果图示：</p>
<p style="margin-top: 10px; margin-bottom: 10px; font-family: Verdana, Geneva, Arial, Helvetica, sans-serif; font-size: 13.3333px; background-color: #ffffff;"><img src="http://images2015.cnblogs.com/blog/211061/201611/211061-20161129190019474-1993383424.png" style="border: 0px; max-width: 900px;" alt="" /></p>
<h4>2.2. leave(离开地图)和move(移动)</h4>
<p style="margin-top: 10px; margin-bottom: 10px; font-family: Verdana, Geneva, Arial, Helvetica, sans-serif; font-size: 13.3333px; background-color: #ffffff;">其实都是双链表的基本操作，断掉其相应的指针就好了。按理，是需要</p>
<p style="margin-top: 10px; margin-bottom: 10px; font-family: Verdana, Geneva, Arial, Helvetica, sans-serif; font-size: 13.3333px; background-color: #ffffff;">move和leave操作如图，move是将d(3,3)移动到(4,4)，然后再打印其AOI范围。</p>
<p style="margin-top: 10px; margin-bottom: 10px; font-family: Verdana, Geneva, Arial, Helvetica, sans-serif; font-size: 13.3333px; background-color: #ffffff;">控制台结果：</p>
<p style="margin-top: 10px; margin-bottom: 10px; font-family: Verdana, Geneva, Arial, Helvetica, sans-serif; font-size: 13.3333px; background-color: #ffffff;"><img src="http://images2015.cnblogs.com/blog/211061/201611/211061-20161129190030412-109242816.png" style="border: 0px; max-width: 900px;" alt="" /></p>
<p style="margin-top: 10px; margin-bottom: 10px; font-family: Verdana, Geneva, Arial, Helvetica, sans-serif; font-size: 13.3333px; background-color: #ffffff;">移动后AOI范围图示：</p>
<p style="margin-top: 10px; margin-bottom: 10px; font-family: Verdana, Geneva, Arial, Helvetica, sans-serif; font-size: 13.3333px; background-color: #ffffff;"><img src="http://images2015.cnblogs.com/blog/211061/201611/211061-20161129190035881-592880827.png" style="border: 0px; max-width: 900px;" alt="" /><br />
</p>
<h4>3. 完整代码实例<br />
<div style="background-color: #eeeeee; font-size: 13px; border: 1px solid #cccccc; padding: 4px 5px 4px 4px; width: 98%; word-break: break-all;"><!--<br />
<br />
Code highlighting produced by Actipro CodeHighlighter (freeware)<br />
http://www.CodeHighlighter.com/<br />
<br />
--><span style="color: #008000; ">//</span><span style="color: #008000; ">&nbsp;ConsoleApplication2.cpp&nbsp;:&nbsp;定义控制台应用程序的入口点。<br />
</span><span style="color: #008000; ">//<br />
</span><br />
#include&nbsp;"stdafx.h"<br />
<br />
<br />
#include&nbsp;"stdafx.h"<br />
#include&nbsp;"stdio.h"<br />
#include&nbsp;&lt;iostream&gt;<br />
#include&nbsp;&lt;<span style="color: #0000FF; ">string</span>&gt;<br />
<br />
<span style="color: #0000FF; ">using</span>&nbsp;<span style="color: #0000FF; ">namespace</span>&nbsp;std;<br />
<br />
<span style="color: #008000; ">//</span><span style="color: #008000; ">&nbsp;双链表（对象）</span><span style="color: #008000; "><br />
</span><span style="color: #0000FF; ">class</span>&nbsp;DoubleNode<br />
{<br />
<span style="color: #0000FF; ">public</span>:<br />
&nbsp;&nbsp;&nbsp;&nbsp;DoubleNode(<span style="color: #0000FF; ">string</span>&nbsp;key,&nbsp;<span style="color: #0000FF; ">int</span>&nbsp;x,&nbsp;<span style="color: #0000FF; ">int</span>&nbsp;y)<br />
&nbsp;&nbsp;&nbsp;&nbsp;{<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span style="color: #0000FF; ">this</span>-&gt;key&nbsp;=&nbsp;key;<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span style="color: #0000FF; ">this</span>-&gt;x&nbsp;=&nbsp;x;<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span style="color: #0000FF; ">this</span>-&gt;y&nbsp;=&nbsp;y;<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;xPrev&nbsp;=&nbsp;xNext&nbsp;=&nbsp;NULL;<br />
&nbsp;&nbsp;&nbsp;&nbsp;};<br />
<br />
&nbsp;&nbsp;&nbsp;&nbsp;DoubleNode&nbsp;*&nbsp;xPrev;<br />
&nbsp;&nbsp;&nbsp;&nbsp;DoubleNode&nbsp;*&nbsp;xNext;<br />
<br />
&nbsp;&nbsp;&nbsp;&nbsp;DoubleNode&nbsp;*&nbsp;yPrev;<br />
&nbsp;&nbsp;&nbsp;&nbsp;DoubleNode&nbsp;*&nbsp;yNext;<br />
<br />
&nbsp;&nbsp;&nbsp;&nbsp;<span style="color: #0000FF; ">string</span>&nbsp;key;<br />
&nbsp;&nbsp;&nbsp;&nbsp;<span style="color: #0000FF; ">int</span>&nbsp;x;&nbsp;<span style="color: #008000; ">//</span><span style="color: #008000; ">&nbsp;位置（x坐标）</span><span style="color: #008000; "><br />
</span>&nbsp;&nbsp;&nbsp;&nbsp;<span style="color: #0000FF; ">int</span>&nbsp;y;&nbsp;<span style="color: #008000; ">//</span><span style="color: #008000; ">&nbsp;位置（y坐标）</span><span style="color: #008000; "><br />
</span><br />
<span style="color: #0000FF; ">private</span>:<br />
<br />
};<br />
<br />
<br />
<br />
<br />
<span style="color: #008000; ">//</span><span style="color: #008000; ">&nbsp;地图/场景</span><span style="color: #008000; "><br />
</span><span style="color: #0000FF; ">class</span>&nbsp;Scene<br />
{<br />
<span style="color: #0000FF; ">public</span>:<br />
<br />
&nbsp;&nbsp;&nbsp;&nbsp;Scene()<br />
&nbsp;&nbsp;&nbsp;&nbsp;{<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span style="color: #0000FF; ">this</span>-&gt;_head&nbsp;=&nbsp;<span style="color: #0000FF; ">new</span>&nbsp;DoubleNode("[head]",&nbsp;0,&nbsp;0);&nbsp;<span style="color: #008000; ">//</span><span style="color: #008000; ">&nbsp;带头尾的双链表(可优化去掉头尾)</span><span style="color: #008000; "><br />
</span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span style="color: #0000FF; ">this</span>-&gt;_tail&nbsp;=&nbsp;<span style="color: #0000FF; ">new</span>&nbsp;DoubleNode("[tail]",&nbsp;0,&nbsp;0);<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;_head-&gt;xNext&nbsp;=&nbsp;_tail;<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;_head-&gt;yNext&nbsp;=&nbsp;_tail;<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;_tail-&gt;xPrev&nbsp;=&nbsp;_head;<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;_tail-&gt;yPrev&nbsp;=&nbsp;_head;<br />
&nbsp;&nbsp;&nbsp;&nbsp;};<br />
<br />
&nbsp;&nbsp;&nbsp;&nbsp;<span style="color: #008000; ">//</span><span style="color: #008000; ">&nbsp;对象加入(新增)</span><span style="color: #008000; "><br />
</span>&nbsp;&nbsp;&nbsp;&nbsp;DoubleNode&nbsp;*&nbsp;Add(<span style="color: #0000FF; ">string</span>&nbsp;name,&nbsp;<span style="color: #0000FF; ">int</span>&nbsp;x,&nbsp;<span style="color: #0000FF; ">int</span>&nbsp;y)<br />
&nbsp;&nbsp;&nbsp;&nbsp;{<br />
<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;DoubleNode&nbsp;*&nbsp;node&nbsp;=&nbsp;<span style="color: #0000FF; ">new</span>&nbsp;DoubleNode(name,&nbsp;x,&nbsp;y);<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;_add(node);<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span style="color: #0000FF; ">return</span>&nbsp;node;<br />
&nbsp;&nbsp;&nbsp;&nbsp;};<br />
<br />
&nbsp;&nbsp;&nbsp;&nbsp;<span style="color: #008000; ">//</span><span style="color: #008000; ">&nbsp;对象离开(删除)</span><span style="color: #008000; "><br />
</span>&nbsp;&nbsp;&nbsp;&nbsp;<span style="color: #0000FF; ">void</span>&nbsp;Leave(DoubleNode&nbsp;*&nbsp;node)<br />
&nbsp;&nbsp;&nbsp;&nbsp;{<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;node-&gt;xPrev-&gt;xNext&nbsp;=&nbsp;node-&gt;xNext;<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;node-&gt;xNext-&gt;xPrev&nbsp;=&nbsp;node-&gt;xPrev;<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;node-&gt;yPrev-&gt;yNext&nbsp;=&nbsp;node-&gt;yNext;<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;node-&gt;yNext-&gt;yPrev&nbsp;=&nbsp;node-&gt;yPrev;<br />
<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;node-&gt;xPrev&nbsp;=&nbsp;NULL;<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;node-&gt;xNext&nbsp;=&nbsp;NULL;<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;node-&gt;yPrev&nbsp;=&nbsp;NULL;<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;node-&gt;yNext&nbsp;=&nbsp;NULL;<br />
&nbsp;&nbsp;&nbsp;&nbsp;};<br />
<br />
&nbsp;&nbsp;&nbsp;&nbsp;<span style="color: #008000; ">//</span><span style="color: #008000; ">&nbsp;对象移动</span><span style="color: #008000; "><br />
</span>&nbsp;&nbsp;&nbsp;&nbsp;<span style="color: #0000FF; ">void</span>&nbsp;Move(DoubleNode&nbsp;*&nbsp;node,&nbsp;<span style="color: #0000FF; ">int</span>&nbsp;x,&nbsp;<span style="color: #0000FF; ">int</span>&nbsp;y)<br />
&nbsp;&nbsp;&nbsp;&nbsp;{<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;Leave(node);<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;node-&gt;x&nbsp;=&nbsp;x;<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;node-&gt;y&nbsp;=&nbsp;y;<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;_add(node);<br />
&nbsp;&nbsp;&nbsp;&nbsp;};<br />
<br />
&nbsp;&nbsp;&nbsp;&nbsp;<span style="color: #008000; ">//</span><span style="color: #008000; ">&nbsp;获取范围内的AOI&nbsp;(参数为查找范围)</span><span style="color: #008000; "><br />
</span>&nbsp;&nbsp;&nbsp;&nbsp;<span style="color: #0000FF; ">void</span>&nbsp;PrintAOI(DoubleNode&nbsp;*&nbsp;node,&nbsp;<span style="color: #0000FF; ">int</span>&nbsp;xAreaLen,&nbsp;<span style="color: #0000FF; ">int</span>&nbsp;yAreaLen)<br />
&nbsp;&nbsp;&nbsp;&nbsp;{<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;cout&nbsp;&lt;&lt;&nbsp;"Cur&nbsp;is:&nbsp;"&nbsp;&lt;&lt;&nbsp;node-&gt;key&nbsp;&lt;&lt;&nbsp;"（"&nbsp;&lt;&lt;&nbsp;node-&gt;x&nbsp;&lt;&lt;&nbsp;","&nbsp;&lt;&lt;&nbsp;node-&gt;y&nbsp;&lt;&lt;&nbsp;")"&nbsp;&lt;&lt;&nbsp;endl;<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;cout&nbsp;&lt;&lt;&nbsp;"Print&nbsp;AOI:"&nbsp;&lt;&lt;&nbsp;endl;<br />
<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span style="color: #008000; ">//</span><span style="color: #008000; ">&nbsp;往后找</span><span style="color: #008000; "><br />
</span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;DoubleNode&nbsp;*&nbsp;cur&nbsp;=&nbsp;node-&gt;xNext;<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span style="color: #0000FF; ">while</span>&nbsp;(cur&nbsp;!=&nbsp;_tail)<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;{<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span style="color: #0000FF; ">if</span>&nbsp;((cur-&gt;x&nbsp;-&nbsp;node-&gt;x)&nbsp;&gt;&nbsp;xAreaLen)<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;{<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span style="color: #0000FF; ">break</span>;<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;}<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span style="color: #0000FF; ">else</span><br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;{<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span style="color: #0000FF; ">int</span>&nbsp;inteval&nbsp;=&nbsp;0;<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;inteval&nbsp;=&nbsp;node-&gt;y&nbsp;-&nbsp;cur-&gt;y;<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span style="color: #0000FF; ">if</span>&nbsp;(inteval&nbsp;&gt;=&nbsp;-yAreaLen&nbsp;&amp;&amp;&nbsp;inteval&nbsp;&lt;=&nbsp;yAreaLen)<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;{<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;cout&nbsp;&lt;&lt;&nbsp;"\t"&nbsp;&lt;&lt;&nbsp;cur-&gt;key&nbsp;&lt;&lt;&nbsp;"("&nbsp;&lt;&lt;&nbsp;cur-&gt;x&nbsp;&lt;&lt;&nbsp;","&nbsp;&lt;&lt;&nbsp;cur-&gt;y&nbsp;&lt;&lt;&nbsp;")"&nbsp;&lt;&lt;&nbsp;endl;<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;}<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;}<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;cur&nbsp;=&nbsp;cur-&gt;xNext;<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;}<br />
<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span style="color: #008000; ">//</span><span style="color: #008000; ">&nbsp;往前找</span><span style="color: #008000; "><br />
</span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;cur&nbsp;=&nbsp;node-&gt;xPrev;<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span style="color: #0000FF; ">while</span>&nbsp;(cur&nbsp;!=&nbsp;_head)<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;{<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span style="color: #0000FF; ">if</span>&nbsp;((node-&gt;x&nbsp;-&nbsp;cur-&gt;x)&nbsp;&gt;&nbsp;xAreaLen)<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;{<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span style="color: #0000FF; ">break</span>;<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;}<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span style="color: #0000FF; ">else</span><br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;{<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span style="color: #0000FF; ">int</span>&nbsp;inteval&nbsp;=&nbsp;0;<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;inteval&nbsp;=&nbsp;node-&gt;y&nbsp;-&nbsp;cur-&gt;y;<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span style="color: #0000FF; ">if</span>&nbsp;(inteval&nbsp;&gt;=&nbsp;-yAreaLen&nbsp;&amp;&amp;&nbsp;inteval&nbsp;&lt;=&nbsp;yAreaLen)<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;{<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;cout&nbsp;&lt;&lt;&nbsp;"\t"&nbsp;&lt;&lt;&nbsp;cur-&gt;key&nbsp;&lt;&lt;&nbsp;"("&nbsp;&lt;&lt;&nbsp;cur-&gt;x&nbsp;&lt;&lt;&nbsp;","&nbsp;&lt;&lt;&nbsp;cur-&gt;y&nbsp;&lt;&lt;&nbsp;")"&nbsp;&lt;&lt;&nbsp;endl;<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;}<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;}<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;cur&nbsp;=&nbsp;cur-&gt;xPrev;<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;}<br />
&nbsp;&nbsp;&nbsp;&nbsp;};<br />
<br />
&nbsp;&nbsp;&nbsp;&nbsp;<span style="color: #008000; ">//</span><span style="color: #008000; ">&nbsp;调试代码</span><span style="color: #008000; "><br />
</span>&nbsp;&nbsp;&nbsp;&nbsp;<span style="color: #0000FF; ">void</span>&nbsp;PrintLink()&nbsp;&nbsp;<span style="color: #008000; ">//</span><span style="color: #008000; ">&nbsp;打印链表(从头开始)</span><span style="color: #008000; "><br />
</span>&nbsp;&nbsp;&nbsp;&nbsp;{<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span style="color: #008000; ">//</span><span style="color: #008000; ">&nbsp;打印x轴链表</span><span style="color: #008000; "><br />
</span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;DoubleNode&nbsp;*&nbsp;cur&nbsp;=&nbsp;_head-&gt;xNext;<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span style="color: #0000FF; ">while</span>&nbsp;(cur&nbsp;!=&nbsp;_tail)<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;{<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;cout&nbsp;&lt;&lt;&nbsp;(cur-&gt;key)&nbsp;&lt;&lt;&nbsp;"("&nbsp;&lt;&lt;&nbsp;(cur-&gt;x)&nbsp;&lt;&lt;&nbsp;","&nbsp;&lt;&lt;&nbsp;(cur-&gt;y)&nbsp;&lt;&lt;&nbsp;")&nbsp;-&gt;&nbsp;";<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;cur&nbsp;=&nbsp;cur-&gt;xNext;<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;}<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;cout&nbsp;&lt;&lt;&nbsp;"end"&nbsp;&lt;&lt;&nbsp;endl;<br />
<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span style="color: #008000; ">//</span><span style="color: #008000; ">&nbsp;打印y轴链表</span><span style="color: #008000; "><br />
</span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;cur&nbsp;=&nbsp;_head-&gt;yNext;<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span style="color: #0000FF; ">while</span>&nbsp;(cur&nbsp;!=&nbsp;_tail)<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;{<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;cout&nbsp;&lt;&lt;&nbsp;(cur-&gt;key)&nbsp;&lt;&lt;&nbsp;"("&nbsp;&lt;&lt;&nbsp;(cur-&gt;x)&nbsp;&lt;&lt;&nbsp;","&nbsp;&lt;&lt;&nbsp;(cur-&gt;y)&nbsp;&lt;&lt;&nbsp;")&nbsp;-&gt;&nbsp;";<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;cur&nbsp;=&nbsp;cur-&gt;yNext;<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;}<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;cout&nbsp;&lt;&lt;&nbsp;"end"&nbsp;&lt;&lt;&nbsp;endl;<br />
&nbsp;&nbsp;&nbsp;&nbsp;};<br />
<br />
<span style="color: #0000FF; ">private</span>:<br />
&nbsp;&nbsp;&nbsp;&nbsp;DoubleNode&nbsp;*&nbsp;_head;<br />
&nbsp;&nbsp;&nbsp;&nbsp;DoubleNode&nbsp;*&nbsp;_tail;<br />
<br />
&nbsp;&nbsp;&nbsp;&nbsp;<span style="color: #0000FF; ">void</span>&nbsp;_add(DoubleNode&nbsp;*&nbsp;node)<br />
&nbsp;&nbsp;&nbsp;&nbsp;{<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span style="color: #008000; ">//</span><span style="color: #008000; ">&nbsp;x轴处理</span><span style="color: #008000; "><br />
</span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;DoubleNode&nbsp;*&nbsp;cur&nbsp;=&nbsp;_head-&gt;xNext;<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span style="color: #0000FF; ">while</span>&nbsp;(cur&nbsp;!=&nbsp;NULL)<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;{<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span style="color: #0000FF; ">if</span>&nbsp;((cur-&gt;x&nbsp;&gt;&nbsp;node-&gt;x)&nbsp;||&nbsp;cur&nbsp;==&nbsp;_tail)&nbsp;<span style="color: #008000; ">//</span><span style="color: #008000; ">&nbsp;插入数据</span><span style="color: #008000; "><br />
</span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;{<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;node-&gt;xNext&nbsp;=&nbsp;cur;<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;node-&gt;xPrev&nbsp;=&nbsp;cur-&gt;xPrev;<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;cur-&gt;xPrev-&gt;xNext&nbsp;=&nbsp;node;<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;cur-&gt;xPrev&nbsp;=&nbsp;node;<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span style="color: #0000FF; ">break</span>;<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;}<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;cur&nbsp;=&nbsp;cur-&gt;xNext;<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;}<br />
<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span style="color: #008000; ">//</span><span style="color: #008000; ">&nbsp;y轴处理</span><span style="color: #008000; "><br />
</span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;cur&nbsp;=&nbsp;_head-&gt;yNext;<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span style="color: #0000FF; ">while</span>&nbsp;(cur&nbsp;!=&nbsp;NULL)<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;{<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span style="color: #0000FF; ">if</span>&nbsp;((cur-&gt;y&nbsp;&gt;&nbsp;node-&gt;y)&nbsp;||&nbsp;cur&nbsp;==&nbsp;_tail)&nbsp;<span style="color: #008000; ">//</span><span style="color: #008000; ">&nbsp;插入数据</span><span style="color: #008000; "><br />
</span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;{<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;node-&gt;yNext&nbsp;=&nbsp;cur;<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;node-&gt;yPrev&nbsp;=&nbsp;cur-&gt;yPrev;<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;cur-&gt;yPrev-&gt;yNext&nbsp;=&nbsp;node;<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;cur-&gt;yPrev&nbsp;=&nbsp;node;<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span style="color: #0000FF; ">break</span>;<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;}<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;cur&nbsp;=&nbsp;cur-&gt;yNext;<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;}<br />
&nbsp;&nbsp;&nbsp;&nbsp;}<br />
};<br />
<br />
<span style="color: #008000; ">//</span><span style="color: #008000; ">&nbsp;--------------------------------------------</span><span style="color: #008000; "><br />
</span><br />
<span style="color: #0000FF; ">int</span>&nbsp;_tmain(<span style="color: #0000FF; ">int</span>&nbsp;argc,&nbsp;_TCHAR*&nbsp;argv[])<br />
{<br />
&nbsp;&nbsp;&nbsp;&nbsp;Scene&nbsp;scene&nbsp;=&nbsp;Scene();<br />
&nbsp;&nbsp;&nbsp;&nbsp;<span style="color: #008000; ">//</span><span style="color: #008000; ">&nbsp;增加</span><span style="color: #008000; "><br />
</span>&nbsp;&nbsp;&nbsp;&nbsp;scene.Add("a",&nbsp;1,&nbsp;5);<br />
&nbsp;&nbsp;&nbsp;&nbsp;scene.Add("f",&nbsp;6,&nbsp;6);<br />
&nbsp;&nbsp;&nbsp;&nbsp;scene.Add("c",&nbsp;3,&nbsp;1);<br />
&nbsp;&nbsp;&nbsp;&nbsp;scene.Add("b",&nbsp;2,&nbsp;2);<br />
&nbsp;&nbsp;&nbsp;&nbsp;scene.Add("e",&nbsp;5,&nbsp;3);<br />
&nbsp;&nbsp;&nbsp;&nbsp;DoubleNode&nbsp;*&nbsp;node&nbsp;=&nbsp;scene.Add("d",&nbsp;3,&nbsp;3);<br />
<br />
&nbsp;&nbsp;&nbsp;&nbsp;scene.PrintLink();<br />
&nbsp;&nbsp;&nbsp;&nbsp;scene.PrintAOI(node,&nbsp;2,&nbsp;2);<br />
<br />
&nbsp;&nbsp;&nbsp;&nbsp;<span style="color: #008000; ">//</span><span style="color: #008000; ">&nbsp;移动</span><span style="color: #008000; "><br />
</span>&nbsp;&nbsp;&nbsp;&nbsp;cout&nbsp;&lt;&lt;&nbsp;endl&nbsp;&lt;&lt;&nbsp;"[MOVE]"&nbsp;&lt;&lt;&nbsp;endl;<br />
&nbsp;&nbsp;&nbsp;&nbsp;scene.Move(node,&nbsp;4,&nbsp;4);<br />
&nbsp;&nbsp;&nbsp;&nbsp;scene.PrintLink();<br />
<br />
&nbsp;&nbsp;&nbsp;&nbsp;scene.PrintAOI(node,&nbsp;2,&nbsp;2);<br />
<br />
&nbsp;&nbsp;&nbsp;&nbsp;<span style="color: #008000; ">//</span><span style="color: #008000; ">&nbsp;删除</span><span style="color: #008000; "><br />
</span>&nbsp;&nbsp;&nbsp;&nbsp;cout&nbsp;&lt;&lt;&nbsp;endl&nbsp;&lt;&lt;&nbsp;"[LEAVE]"&nbsp;&lt;&lt;&nbsp;endl;<br />
&nbsp;&nbsp;&nbsp;&nbsp;scene.Leave(node);<br />
&nbsp;&nbsp;&nbsp;&nbsp;scene.PrintLink();<br />
<br />
}<br />
</div>
<p style="color: #555555; font-family: &quot;Lucida Grande&quot;, sans-serif; font-size: 17.5px; font-weight: normal;">算法的大概思想如下.每个场景维护两个链表,分别为X轴和Y轴的坐标按序排列好的链表,也就是比如在X轴链表上,越在前的对象,X坐标越小,Y轴链表同理.这样,每次需要更新状态的时候,只需要在这个链表上向前或者向后遍历结点就知道该通知谁了.</p><p style="color: #555555; font-family: &quot;Lucida Grande&quot;, sans-serif; font-size: 17.5px; font-weight: normal;">这里假设有三个API:Add(向场景中添加一个对象),Leave(某对象离开场景),Move(某对象在场景中移动).</p><p style="color: #555555; font-family: &quot;Lucida Grande&quot;, sans-serif; font-size: 17.5px; font-weight: normal;">来一个一个看.</p><p style="color: #555555; font-family: &quot;Lucida Grande&quot;, sans-serif; font-size: 17.5px; font-weight: normal;">Add:根据新增对象的X,Y坐标,依次遍历X,Y轴坐标链表,这里有两个目的,一个是获得这个新增对象的坐标在X,Y轴坐标的位置,另一方面获得该通知哪些结点.通知的范围,每个对象可以自己定制自己的通知范围.但是为了简单起见,在这里我们假设每个结点X,Y坐标相差1,而通知的范围是2.比如原有的X轴坐标为:<br />a-&gt;b-&gt;c-&gt;d-&gt;e-&gt;f-&gt;g-&gt;h<br />假设新增一个对象z,它最终所在的位置是c和d之间,根据前面的假设,它只需要通知b,c,e,f四个结点就好了.所以,新增一个结点的时候,并不需要遍历完链表的.<br />但是这里还需要注意的是,一个结点,必须X,Y坐标同时都在通知范围内才可以进入通知集合.</p><p style="color: #555555; font-family: &quot;Lucida Grande&quot;, sans-serif; font-size: 17.5px; font-weight: normal;">Leave:在添加了对象之后,对象身上挂了两个结点,分别存放在X,Y轴坐标链表上的位置,那么在这个对象要离开场景时,也只需要向前向后探索一定的范围,就可以得到需要通知该对象要离开的集合了.</p><p style="color: #555555; font-family: &quot;Lucida Grande&quot;, sans-serif; font-size: 17.5px; font-weight: normal;">Move:移动操作比较麻烦,但是也可以比较漂亮的解决.在更新位置之前,同样根据前面的算法得到要通知的结点集合,称为old_set;更新位置之后,再获取另一个通知集合,称为new_set;然后,old_set和new_set的交集,称为move_set,在此集合中的结点,在移动前后都在通知范围,因此要向这个集合的结点发送该对象移动的消息;而old_set-move_set的集合,在移动之后已经离开了视野,因此要向它们发送该对象离开的消息;最后,new_set-move_set是移动之后才看见该结点的结点集合,这个集合的结点,要发送该结点进入场景的消息.</p>
</h4><img src ="http://www.cppblog.com/API/aggbug/214733.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/API/" target="_blank">C++技术中心</a> 2017-03-07 15:09 <a href="http://www.cppblog.com/API/archive/2017/03/07/214733.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>MMORPG服务器架构（转）</title><link>http://www.cppblog.com/API/archive/2017/03/06/214728.html</link><dc:creator>C++技术中心</dc:creator><author>C++技术中心</author><pubDate>Mon, 06 Mar 2017 08:01:00 GMT</pubDate><guid>http://www.cppblog.com/API/archive/2017/03/06/214728.html</guid><wfw:comment>http://www.cppblog.com/API/comments/214728.html</wfw:comment><comments>http://www.cppblog.com/API/archive/2017/03/06/214728.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cppblog.com/API/comments/commentRss/214728.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/API/services/trackbacks/214728.html</trackback:ping><description><![CDATA[<div><div style="font-size: 14.7px; font-weight: bold; margin-bottom: 10px; color: #4b4b4b; font-family: georgia, verdana, Arial, helvetica, sans-seriff;">转自<span style="font-size: 14.7px;">http://www.blogjava.net/landon/archive/2012/07/14/383092.html</span><br /><a id="viewpost1_TitleUrl" href="http://www.blogjava.net/landon/archive/2012/07/14/383092.html" style="text-decoration: none; color: #4371a6;">MMORPG服务器架构</a></div><span style="font-family: georgia, verdana, Arial, helvetica, sans-seriff; font-size: 12pt; color: #0000ff;">一.摘要</span><br style="color: #4b4b4b; font-family: georgia, verdana, Arial, helvetica, sans-seriff; font-size: 13px;" /><br style="color: #4b4b4b; font-family: georgia, verdana, Arial, helvetica, sans-seriff; font-size: 13px;" /><div style="color: #4b4b4b; font-family: georgia, verdana, Arial, helvetica, sans-seriff; font-size: 13px;"><span style="font-size: 12pt;">1.网络游戏</span><span style="font-size: 12pt; color: #0000ff;">MMORPG</span><span style="font-size: 12pt;">整体服务器框架，包括早期,中期，当前的一些主流架构</span><br /><span style="font-size: 12pt;">2.网络游戏网络层,包括网络</span><span style="font-size: 12pt; color: #0000ff;">协议</span><span style="font-size: 12pt;">,</span><span style="font-size: 12pt; color: #0000ff;">IO</span><span style="font-size: 12pt;">模型，网络框架，消息编码等。</span><br /><span style="font-size: 12pt;">3.网络游戏的</span><span style="font-size: 12pt; color: #0000ff;">场景</span><span style="font-size: 12pt;">管理,</span><span style="font-size: 12pt; color: #0000ff;">AI</span><span style="font-size: 12pt;">，</span><span style="font-size: 12pt; color: #0000ff;">脚本</span><span style="font-size: 12pt;">的应用等。</span><br /><span style="font-size: 12pt;">4.</span><span style="font-size: 12pt; color: #0000ff;">开源</span><span style="font-size: 12pt;">的网络服务器引擎</span><br /><span style="font-size: 12pt;">5.参考书籍，博客<br /><br /></span></div><span style="font-family: georgia, verdana, Arial, helvetica, sans-seriff; font-size: 12pt; color: #0000ff;">二.关键词</span><br style="color: #4b4b4b; font-family: georgia, verdana, Arial, helvetica, sans-seriff; font-size: 13px;" /><br style="color: #4b4b4b; font-family: georgia, verdana, Arial, helvetica, sans-seriff; font-size: 13px;" /><div style="color: #4b4b4b; font-family: georgia, verdana, Arial, helvetica, sans-seriff; font-size: 13px;"><span style="font-size: 12pt; color: #0000ff;">网络协议 网络IO 消息 广播 同步 CS TCP/UDP IP 集群 负载均衡 分布式&nbsp;<br />网关服务器 GateServer 心跳 多线程/线程池 开源网络通讯框架/模型</span><br /><span style="font-size: 12pt; color: #0000ff;">阻塞/非阻塞/同步/异步&nbsp;&nbsp; &nbsp;Proactor/Reactor/Actor Select/Poll/Epoll/Iocp/Kqueue&nbsp;<br />游戏开发中的设计模式/数据结构</span><br /><span style="font-size: 12pt; color: #0000ff;">短连接和长连接 游戏安全 缓存 消息编码协议 脚本语言&nbsp;<br />Socket Nagle/粘包/截断/TCP_NODELAY AI/场景 分线/分地图 开源MMORPG服务器</span><br /><br /><span style="font-size: 12pt; color: #0000ff;">三.正文</span><span style="font-size: 12pt; color: #0000ff;">框架结构</span><br /><br /><div><span style="font-size: 12pt; color: #0000ff;">1.&nbsp;&nbsp; &nbsp;早期的MMORPG服务器结构</span><br /><br /><span style="font-size: 12pt;">Client&lt;-&gt;GameServer&lt;-&gt;DB&nbsp;&nbsp; &nbsp;所有业务,数据集中处理</span><br /><div><img height="110" alt="" src="http://www.blogjava.net/images/blogjava_net/landon/ar1.jpg" width="630" /><br /><div><span style="font-size: 12pt; color: #0000ff;">优点</span><span style="font-size: 12pt;">:简单,快速开发</span><br /><span style="font-size: 12pt; color: #0000ff;">缺点:</span><br />&nbsp;&nbsp;&nbsp;&nbsp;<span style="font-size: 12pt;">1.所有业务放在一起,系统负担大大增加.一个bug可能导致整个服务器崩溃,造成所有玩家掉线甚至丢失等严重后果。</span><br />&nbsp;&nbsp;&nbsp;&nbsp;<span style="font-size: 12pt;">2.开服一刹那,所有玩家全部堆积在同一个新手村.-&gt;&gt;&gt;&gt;卡，客户端卡（同屏人数过多渲染/广播风暴） 服务器卡(处理大量同场景消息/广播风暴)</span></div><div><span style="font-size: 12pt; color: #0000ff;">2.&nbsp;&nbsp; &nbsp;中期-用户分离集群式</span><br /><br /><span style="font-size: 12pt;">&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; GameServe1</span><br /><span style="font-size: 12pt;">Client&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;|&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; DB</span><br /><span style="font-size: 12pt;">&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;GameServer2</span><br /><br /><span style="font-size: 12pt;">玩家不断增多-&gt;</span><span style="font-size: 12pt; color: #0000ff;">分线</span><span style="font-size: 12pt;">-&gt;程序自动或玩家手动选择进入</span><br /><span style="font-size: 12pt; color: #0000ff;">缺点</span><span style="font-size: 12pt;">:运营到后期,随着每条线玩家的减少, 互动大大减少。</span><br /><br /><div><span style="font-size: 12pt; color: #0000ff;">3.&nbsp;&nbsp; &nbsp;中后期 数据分离集群式</span></div><img height="204" alt="" src="http://www.blogjava.net/images/blogjava_net/landon/ar2.jpg" width="785" /></div></div><div><span style="font-size: 12pt; color: #0000ff;">按地图划分服务器,当前主流</span><br /><span style="font-size: 12pt;">&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="font-size: 12pt; color: #0000ff;">新手村问题</span><span style="font-size: 12pt;">：《</span><span style="font-size: 12pt; color: #0000ff;">天龙八部</span><span style="font-size: 12pt;">》提出了较好的解决方案，建立多个平行的新手村地图，一主多副，开服时尽可能多的同时容纳新用户的涌入，高等级玩家从其它地图回新手村只能到达主新手村。</span><br /><br /><div><span style="font-size: 12pt; color: #0000ff;">4.&nbsp;&nbsp; &nbsp;当前主流的网络游戏架构</span><br /><div><img height="371" alt="" src="http://www.blogjava.net/images/blogjava_net/landon/ar5.jpg" width="685" border="0" longdesc="http://www.blogjava.net/landon/archive/2012/07/14/383092.html" /><br /><br />&nbsp;&nbsp;&nbsp;&nbsp;<span style="font-size: 12pt; color: #0000ff;">&nbsp;&nbsp;&nbsp;&nbsp;注：在GateServer和CenterServer之间是有一条TCP连接的。而GameServer和LogServer之间的连接可以是UDP连接。这是有一个大概的图，很多地方需要细化。<br /></span></div><div><span style="font-size: 12pt; color: #0000ff;">GateServer:网关服务器,AgentServer、ProxyServer</span><br /><br />&nbsp;<span style="font-size: 12pt; color: #0000ff;">优点</span><span style="font-size: 12pt;">:&nbsp;</span><br /><span style="font-size: 12pt;">&nbsp;&nbsp;&nbsp; (1)作为网络通信的中转站，负责维护将内网和外网隔离开，使外部无法直接访问内部服务器，保障内网服务器的安全，一定程度上较少外挂的攻击。</span><br /><span style="font-size: 12pt;">&nbsp;&nbsp;&nbsp; (2)网关服务器负责解析数据包、加解密、超时处理和一定逻辑处理，这样可以提前过滤掉错误包和非法数据包。</span><br /><span style="font-size: 12pt;">&nbsp;&nbsp;&nbsp; (3)客户端程序只需建立与网关服务器的连接即可进入游戏，无需与其它游戏服务器同时建立多条连接，节省了客户端和服务器程序的网络资源开销。</span><br /><span style="font-size: 12pt;">&nbsp;&nbsp;&nbsp; (4)在玩家跳服务器时，不需要断开与网关服务器的连接，玩家数据在不同游戏服务器间的切换是内网切换，切换工作瞬问完成，玩家几乎察觉不到，这保证了游戏的流畅性和良好的用户体验。</span><br /><br />&nbsp;&nbsp;&nbsp;<span style="font-size: 12pt; color: #0000ff;">缺点</span><span style="font-size: 12pt;">:&nbsp;</span><br /><span style="font-size: 12pt;">1.网关服务器成为高负载情况下的通讯瓶颈问题</span><br /><span style="font-size: 12pt;">2由于网关的单节点故障导致整组服务器无法对外提供服务的问题</span><br /><br />&nbsp;&nbsp;&nbsp;<span style="font-size: 12pt; color: #0000ff;">解决：</span><span style="font-size: 12pt; color: #0000ff;">多网关</span><span style="font-size: 12pt;">技术。顾名思义，&#8220;多网关&#8221; 就是同时存在多个网关服务器，比如一组服务器可以配置三台GameGme。当负载较大时，可以通过增加网关服务器来增加网关的总体通讯流量，当一台网关服务器宕机时，它只会影响连接到本服务器的客户端，其它客户端不会受到任何影响。</span><br /><br /><span style="font-size: 12pt; color: #0000ff;">DCServer</span><span style="font-size: 12pt;">:数据中心服务器。主要的功能是缓存玩家角色数据，保证角色数据能快速的读取和保存</span><br /><span style="font-size: 12pt; color: #0000ff;">CenterServer</span><span style="font-size: 12pt;">:全局服务器/中心服务器,也叫</span><span style="font-size: 12pt; color: #0000ff;">WorldServer</span><span style="font-size: 12pt;">. 主要负责维持GameServer之间数据的转发和数据广播。另外一些游戏系统也可能会放到Center上处理，比如好友系统,公会系统。</span><br /><br />&nbsp;&nbsp;&nbsp;&nbsp;<span style="font-size: 12pt; color: #0000ff;">改进</span><span style="font-size: 12pt;">:将网关服务器细化为LogingateServer和多个GameGateServer.</span><br /><br /><div><span style="font-size: 12pt;">5.&nbsp;&nbsp; &nbsp;按</span><span style="font-size: 12pt; color: #0000ff;">业务分离式</span><span style="font-size: 12pt;">集群</span><br /><span style="font-size: 12pt;">由于网络游戏存在很多的业务，如聊天，战斗，行走，NPC等，可以将某些业务分到单独的服务器上。这样每个服务器的程序则会精简很多。而且一些大流量业务的分离,可以有效的提高游戏服务器人数上限。</span><br /><br /><div>&nbsp;</div><div><span style="font-size: 12pt; color: #0000ff;"><img height="232" alt="" src="http://www.blogjava.net/images/blogjava_net/landon/ar6.jpg" width="563" border="0" longdesc="http://www.blogjava.net/landon/archive/2012/07/14/383092.html" /><br />优点：</span><br /><span style="font-size: 12pt;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 1.业务的分离使得每种服务器的程序变的简单，这样可以降低出错的几率。即使出错，也不至于影响到每一个整个游戏的进行,而且通过快速启动另一台备用服务器替换出错的服务器。</span><br /><span style="font-size: 12pt;">&nbsp;&nbsp;&nbsp;&nbsp; 2.业务的分离使得流量得到了分散，进而相应速度回得到提升 。</span><br /><span style="font-size: 12pt;">&nbsp;&nbsp;&nbsp;&nbsp; 3.大部分业务都分离了成了单独的服务器,所以可以动态的添加，从而提高人数上限。</span><br /><br /><span style="font-size: 12pt; color: #0000ff;">改进</span><span style="font-size: 12pt;">：甚至可以将登陆服务器细化拆分建角色,选择角色服务器</span></div><br /><div><span style="font-size: 12pt;">6.&nbsp;&nbsp; &nbsp;</span><span style="font-size: 12pt; color: #0000ff;">一种简单实用的网络游戏服务器架构</span><br /><br /><span style="font-size: 12pt;">下图中每个方框表示一个独立的进程APP组件，每个服务进程如果发生宕机会影响部分用户，整体服务但不会全部中断。在宕机进程重启后，又可以并入整体，全部服务得以继续。</span><br /><br /><div><span style="font-size: 10.5pt; font-family: &quot;Times New Roman&quot;, serif;"><img height="508" alt="" src="http://www.blogjava.net/images/blogjava_net/landon/ar3.png" width="383" /><br /><br /></span></div></div></div><div><span style="font-size: 12pt; color: #0000ff;">gls</span><span style="font-size: 12pt;">：game login server，游戏登录服务器，某种程序上，其不是核心组件，gls调用外部的接口，进行基本的用户名密码认证。此外需要实现很多附属的功能：登录排队（对开服非常有帮助），GM超级登录通道（GM可以不排队进入游戏），封测期间激活用户控制，限制用户登录，控制客户端版本等。</span><br /><span style="font-size: 12pt; color: #0000ff;">db</span><span style="font-size: 12pt;">：实质上是后台sql的</span><span style="font-size: 12pt; color: #0000ff;">大内存缓冲</span><span style="font-size: 12pt;">，隔离了数据库操作，比较内存中的数据，只把改变的数据定时批量写入sql。系统的算法，开发稳定性都要求非常高。</span><br /><span style="font-size: 12pt; color: #0000ff;">center</span><span style="font-size: 12pt;">：所有组件都要在这里注册，在线玩家的session状态都在这里集中存放，和各组件有心跳连接。所有对外的接口也全部通过这里。</span><br /><span style="font-size: 12pt;">角色入口：玩家登录游戏后的选择角色</span><br /><span style="font-size: 12pt; color: #0000ff;">gs</span><span style="font-size: 12pt;">：game server，最核心组件，同一地图，所有游戏逻辑相关的功能，都在这里完成。</span><br /><span style="font-size: 12pt; color: #0000ff;">gate</span><span style="font-size: 12pt;">：建立和用户的常链接，主要作sockt转发，屏蔽恶意包，对gs进行保护。协议加密解密功能，一个gate共享多个gs，降低跳转地图连接不上的风险。</span><br /><span style="font-size: 12pt;">IM，关系，寄售：表示其它组件，负责对应的跨地图发生全局的游戏逻辑。</span><br /><br /><span style="font-size: 12pt;">7.</span><span style="font-size: 12pt; color: #0000ff;">另一个架构图</span><br /><br /><img height="457" alt="" src="http://www.blogjava.net/images/blogjava_net/landon/ar4.jpg" width="588" /></div><br /><div>&nbsp;&nbsp;&nbsp;&nbsp;<span style="font-size: 12pt;">1-&nbsp;&nbsp; 这是一条</span><span style="font-size: 12pt; color: #0000ff;">WebService</span><span style="font-size: 12pt;">的管道，在用户激活该区帐号，或者修改帐号密码的时候，通过这条通道来插入和更新用户的帐号信息。</span><br />&nbsp;&nbsp;&nbsp;&nbsp;<span style="font-size: 12pt;">2-&nbsp;&nbsp; 这也是一条</span><span style="font-size: 12pt; color: #0000ff;">WebService</span><span style="font-size: 12pt;">管道，用来获取和控制用户该该组内的角色信息，以及进行付费商城代币之类的更新操作。</span><br />&nbsp;&nbsp;&nbsp;&nbsp;<span style="font-size: 12pt;">3-&nbsp;&nbsp; 这是一条</span><span style="font-size: 12pt; color: #0000ff;">本地的TCP/IP</span><span style="font-size: 12pt;">连接，这条连接主要用来进行服务器组在登陆服务器的注册，以及登陆服务器验证帐户后，向用户服务器注册帐户登陆信息，以及进行对已经登陆的帐户角色信息进行操作（比如踢掉当前登陆的角色），还有服务器组的信息更新（当前在线玩家数量等）。</span><br />&nbsp;&nbsp;&nbsp;&nbsp;<span style="font-size: 12pt;">4-&nbsp;&nbsp; 这也是一条</span><span style="font-size: 12pt; color: #0000ff;">本地TCP/IP</span><span style="font-size: 12pt;">连接，这条连接用来对连接到GameServer的客户端进行验证，以及获取角色数据信息，还有传回GameServer上角色的数据信息改变。</span><br />&nbsp;&nbsp;&nbsp;&nbsp;<span style="font-size: 12pt;">5-&nbsp;&nbsp; 这条连接也是一条</span><span style="font-size: 12pt; color: #0000ff;">本地的TCP/IP</span><span style="font-size: 12pt;">连接，它用来进行公共信息服务器和数个游戏服务器间的交互，用来交换一些游戏世界级的信息（比如公会信息，跨服组队信息，跨服聊天频道等）。</span><br />&nbsp;&nbsp;&nbsp;&nbsp;<span style="font-size: 12pt;">6-&nbsp;&nbsp; 这里的两条连接，想表达的意思是，UserServer和GameServer的Agent是可以互换使用的，也就是玩家进入组内之后，就不需要再切换Agent。如果不怕乱套，也可以把登陆服务器的Agent也算上，这样用户整个过程里就不需要再更换Agent，减少重复连接的次数，也提高了稳定性。（毕竟连接次数少了，也降低了连不上服务器的出现几率）</span><br /><span style="font-size: 12pt;">在这个架构里面，GameServer实际上是一个游戏逻辑的综合体，里面可以再去扩展成几个不同的逻辑服务器，通过PublicServer进行公共数据交换。</span><br />&nbsp;&nbsp;&nbsp;&nbsp;<span style="font-size: 12pt; color: #0000ff;">UserServer</span><span style="font-size: 12pt;">实际上扮演了一个ServerGroup的领头羊的角色，它负责向LoginServer注册和更新服务器组的信息（名字，当前人数），并且对Agent进行调度，对选择了该组的玩家提供一个用户量最少的Agent。同时，它也兼了一个角色管理服务器的功能，发送给客户端当前的角色列表，角色的创建，删除，选择等管理操作，都是在这里进行的。而且，它还是一个用户信息的验证服务器，GameServer需要通过它来进行客户端的合法性验证，以及获取玩家选择的角色数据信息。</span><br /><span style="font-size: 12pt;">采用这种架构的游戏，通常有以下表现。</span><br />&nbsp;&nbsp;&nbsp;&nbsp;<span style="font-size: 12pt;">1- 用户必须激活一个大区，才能在大区内登陆自己的帐号。</span><br />&nbsp;&nbsp;&nbsp;&nbsp;<span style="font-size: 12pt;">2- 用户启动客户端的时候，弹出一个登陆器，选择大区。</span><br />&nbsp;&nbsp;&nbsp;&nbsp;<span style="font-size: 12pt;">3- 用户启动真正的客户端的时候，一开始就是输入帐号密码。</span><br />&nbsp;&nbsp;&nbsp;&nbsp;<span style="font-size: 12pt;">4- 帐号验证完成之后，进行区内的服务器选择。</span><br />&nbsp;&nbsp;&nbsp;&nbsp;<span style="font-size: 12pt;">5- 服务器选择完成之后，进入角色管理。同时，角色在不同的服务器里不能共享。</span></div></div></div></div></div><span style="font-size: 12pt; color: #0000ff;">四.正文网络通讯</span><br /><br /><div><span style="font-size: 12pt;">1.</span><span style="font-size: 12pt; color: #0000ff;">网络协议</span><br /><span style="font-size: 12pt;">&nbsp;根据游戏类型&nbsp;&nbsp; &nbsp;实时性要求/是否允许丢包 来决定&nbsp;</span><span style="font-size: 12pt; color: #0000ff;">TCP/UDP</span><span style="font-size: 12pt;">协议</span><br /><br /><span style="font-size: 12pt;">a.</span><span style="font-size: 12pt; color: #0000ff;">TCP:面向连接,可靠,保证顺序,慢,有延迟</span><br /><span style="font-size: 12pt;">&nbsp;&nbsp;&nbsp;&nbsp; TCP每次发送一个数据包后都要等待接收方发送一个应答信息，这样TCP才可以确认数据包通过因特网完整地送到了接收方。如果在一段时间内TCP没有收到接收方的应答，他就会停止发送新的数据包，转而去重新发送没有收到应答2的数据包，并且持续这种发送状态知道收到接收方的应答。所以这会造成网络数据传输的延迟，若网络情况不好，发送方会等待相当长一段时间</span><br /><span style="font-size: 12pt;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="font-size: 12pt; color: #0000ff;">UDP:无连接,不可靠,不保证顺序,快</span><br /><br /><span style="font-size: 12pt;">b.</span><span style="font-size: 12pt; color: #0000ff;">长连接/短连接</span><br /><span style="font-size: 12pt;">长连接，指在一个TCP连接上可以连续发送多个数据包，在TCP连接保持期间，如果没有数据包发送，需要双方发检测包以维持此连接，一般需要自己做在线维</span><br /><span style="font-size: 12pt;">&nbsp;&nbsp;&nbsp; 连接&#8594;数据传输&#8594;保持连接(心跳)&#8594;数据传输&#8594;保持连接(心跳)&#8594;&#8230;&#8230;&#8594;关闭连接</span><br /><span style="font-size: 12pt;">短连接是指通信双方有数据交互时，就建立一个TCP连接，数据发送完成后，则断开此TCP连接,如Http</span><br /><span style="font-size: 12pt;">&nbsp;&nbsp;&nbsp; 连接&#8594;数据传输&#8594;关闭连接</span></div><br /><div><span style="font-size: 12pt;">2.</span><span style="font-size: 12pt; color: #0000ff;">IO模型</span><br /><br /><span style="font-size: 12pt;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="font-size: 12pt; color: #0000ff;">Unix5中io模型</span><br /><span style="font-size: 12pt; color: #0000ff;">1.&nbsp;&nbsp; &nbsp;阻塞IO (Blocking I/O Model)</span><br /><span style="font-size: 12pt; color: #0000ff;">2.&nbsp;&nbsp; &nbsp;非阻塞IO (Nonblocking I/O Model)</span><br /><span style="font-size: 12pt; color: #0000ff;">3.&nbsp;&nbsp; &nbsp;IO复用 (I/O Multiplexing Model)</span><br /><span style="font-size: 12pt; color: #0000ff;">4.&nbsp;&nbsp; &nbsp;信号驱动IO (Signal-Driven I/O Model)</span><br /><span style="font-size: 12pt; color: #0000ff;">5.&nbsp;&nbsp; &nbsp;异步IO (Asynchronous I/O Model)</span><br /><br /><div><span style="font-size: 12pt; color: #0000ff;">IO分两个阶段：</span><br /><span style="font-size: 12pt; color: #0000ff;">1.通知内核准备数据。2.数据从内核缓冲区拷贝到应用缓冲区</span><br /><br /><span style="font-size: 12pt;">根据这2点IO类型可以分成：</span><br />&nbsp;&nbsp;&nbsp;&nbsp;<span style="font-size: 12pt;">1.阻塞IO，在两个阶段上面都是阻塞的。</span><br />&nbsp;&nbsp;&nbsp;&nbsp;<span style="font-size: 12pt;">2.非阻塞IO，在第1阶段，程序不断的轮询直到数据准备好，第2阶段还是阻塞的</span><br />&nbsp;&nbsp;&nbsp;&nbsp;<span style="font-size: 12pt;">3.IO复用，在第1阶段，当一个或者多个IO准备就绪时，通知程序，第2阶段还是阻塞的，在第1阶段还是轮询实现的，只是所有的IO都集中在一个地方，这个地方进行轮询</span><br />&nbsp;&nbsp;&nbsp;&nbsp;<span style="font-size: 12pt;">4.信号IO，当数据准备完毕的时候，信号通知程序数据准备完毕，第2阶段阻塞</span><br />&nbsp;&nbsp;&nbsp;&nbsp;<span style="font-size: 12pt;">5.异步IO，1，2都不阻塞</span><br /><br /><img height="325" alt="" src="http://www.blogjava.net/images/blogjava_net/landon/io1.png" width="640" /><br /><br /><img height="373" alt="" src="http://www.blogjava.net/images/blogjava_net/landon/io3.png" width="642" /><br /><br /><br /><img height="410" alt="" src="http://www.blogjava.net/images/blogjava_net/landon/io2.png" width="640" /><br />&nbsp;&nbsp;&nbsp;<div><span style="font-size: 12pt;">同时阻塞多个I/O操作。而且可以同时对多个读操作，多个写操作的I/O函数进行检测，直到有数据可读或可写时，才真正调用I/O操作函数</span><br /><span style="font-size: 12pt;">J</span><span style="font-size: 12pt; color: #0000ff;">ava#Selector</span><br /><br /><img height="406" alt="" src="http://www.blogjava.net/images/blogjava_net/landon/io4.png" width="640" /></div></div></div>&nbsp;&nbsp;&nbsp;<div><p><span style="font-size: 12pt; background: white; color: #333333; font-family: 宋体;">允许套接口进行信号驱动</span><span style="font-size: 12pt; background: white; color: #333333; font-family: Arial, sans-serif;">I/O,</span><span style="font-size: 12pt; background: white; color: #333333; font-family: 宋体;">并安装一个信号处理函数，进程继续运行并不阻塞。当数据准备好时，进程会收到一个</span><span style="font-size: 12pt; background: white; color: #333333; font-family: Arial, sans-serif;">SIGIO</span><span style="font-size: 12pt; background: white; color: #333333; font-family: 宋体;">信号，可以在信号处理函数中调用</span><span style="font-size: 12pt; background: white; color: #333333; font-family: Arial, sans-serif;">I/O</span><span style="font-size: 12pt; background: white; color: #333333; font-family: 宋体;">操作函数处理数据</span><span style="font-size: 12pt; background: white; color: #333333; font-family: Arial, sans-serif;">.</span></p><p><br /></p><p><span style="background: white; color: #333333; font-family: Arial, sans-serif;"><img height="365" alt="" src="http://www.blogjava.net/images/blogjava_net/landon/io5.png" width="640" /></span></p><p>&nbsp;</p><div><span style="color: #0000ff;">J</span><span style="font-size: 12pt; color: #0000ff;">ava#NIO2</span><br /><span style="font-size: 12pt;">发出系统调用后,直接返回。通知IO操作完成。</span><br /><span style="font-size: 12pt;">前四种同步IO，最后一种异步IO.二者区别:第二个阶段必须要求进程主动调用recvfrom.而异步io则将io操作全部交给内核完成,完成后发信号通知。此期间,用户不需要去检查IO操作的状态，也不需要主动的去拷贝数据。</span><br /><br /><div><span style="font-size: 12pt;">3.</span><span style="font-size: 12pt; color: #0000ff;">线程阻塞的原因:</span><br /><br />&nbsp;&nbsp;&nbsp;&nbsp;<span style="font-size: 12pt;">1.Thread.sleep(),线程放弃CPU，睡眠N秒,然后恢复运行</span><br />&nbsp;&nbsp;&nbsp;&nbsp;<span style="font-size: 12pt;">2.线程要执行一段同步代码,由于无法获得相关的锁,阻塞。获得同步锁后，才可以恢复运行。</span><br />&nbsp;&nbsp;&nbsp;&nbsp;<span style="font-size: 12pt;">.线程执行了一个对象的wait方法，进入阻塞状态,只有等到其他线程执行了该对象的notify、nnotifyAll，才能将其唤醒。</span><br />&nbsp;&nbsp;&nbsp;&nbsp;<span style="font-size: 12pt;">4.IO操作,等待相关资源</span><br /><span style="font-size: 12pt; color: #0000ff;">阻塞线程的共同特点是：放弃CPU,停止运行，只有等到导致阻塞的原因消除，才能恢复运行 。或者被其他线程中断，该线程会退出阻塞状态，并抛出InterruptedException.</span><br /><br /><span style="font-size: 12pt;">4.</span><div><span style="font-size: 12pt; color: #0000ff;">阻塞/非阻塞/同步/异步</span><br /><span style="font-size: 12pt; color: #0000ff;">同步/异步关注的是消息如何通知的机制。而阻塞和非阻塞关注的是处理消息。是两组完全不同的概念。</span><br /><br /><div><span style="font-size: 12pt;">5.几个常用概念</span><br /><span style="font-size: 12pt; color: #0000ff;">Select Poll</span><br /><span style="font-size: 12pt; color: #0000ff;">Epoll(Linux) Kqueue(FreeBSD)&nbsp;&nbsp;&nbsp;</span><br /><div><span style="font-size: 12pt; background: white; color: #0000ff; font-family: Arial, sans-serif;">IOCP&nbsp;&nbsp;&nbsp; windows</span></div>&nbsp;<br /><span style="font-size: 12pt; color: #0000ff;">Reactor</span><br /><span style="font-size: 12pt;">Dispatcher（分发器），Notifer（通知器）, 事件到来时，使用Dispatcher（分发器）对Handler进行分派，这个Dispatcher要对所有注册的Handler进行维护。同时，有一个Demultiplexer（分拣器）对多路的同步事件进行分拣。&nbsp;&nbsp; &nbsp;</span><br /><br /><span style="font-size: 12pt; color: #0000ff;">Proactor</span><br /><span style="font-size: 12pt; color: #0000ff;">Proactor和Reactor都是并发编程中的设计模式.用于派发/分离IO操作事件的</span><span style="font-size: 12pt;">。这里所谓的IO事件也就是诸如read/write的IO操作。"派发/分离"就是将单独的IO事件通知到上层模块。两个模式不同的地方在于，Proactor用于异步IO，而Reactor用于同步IO。</span><br /><br /><span style="font-size: 12pt;">两个模式的相同点，都是对某个IO事件的事件通知(即告诉某个模块，这个IO操作可以进行或已经完成)。在结构上，两者也有相同点：demultiplexor负责提交IO操作(异步)、查询设备是否可操作(同步)，然后当条件满足时，就回调handler。</span><br /><span style="font-size: 12pt;">不同点在于，异步情况下(Proactor)，当回调handler时，表示IO操作已经完成；同步情况下(Reactor)，回调handler时，表示IO设备可以进行某个操作(can read or can write)，handler这个时候开始提交操作。</span><br /><br /><span style="font-size: 12pt;">6.</span><div><span style="font-size: 12pt; color: #0000ff;">网络通讯框架</span><br /><span style="font-size: 12pt;">TCP Server框架</span>:<br /><span style="font-size: 12pt;">Apache&nbsp;</span><span style="font-size: 12pt; color: #0000ff;">MINA</span><span style="font-size: 12pt;">(Multipurpose Infrastructure for Network Applications)2.0.4</span><br /><span style="font-size: 12pt; color: #0000ff;">Netty&nbsp;</span><span style="font-size: 12pt;">3.5.0Final</span><br /><span style="font-size: 12pt; color: #0000ff;">Grizzly&nbsp;</span><span style="font-size: 12pt;">2.2</span><br /><span style="font-size: 12pt; color: #0000ff;">Quickserver</span><span style="font-size: 12pt;">是一个免费的开源Java库，用于快速创建健壮的多线程、多客户端TCP服务器应用程序。使用QuickServer，用户可以只集中处理应用程序的逻辑/协议</span><br /><span style="font-size: 12pt; color: #0000ff;">Cindy&nbsp;</span><span style="font-size: 12pt;">强壮，可扩展，高效的异步I/O框架</span><br /><span style="font-size: 12pt; color: #0000ff;">xSocket</span><span style="font-size: 12pt;">一个轻量级的基于nio的服务器框架用于开发高性能、可扩展、多线程的服务器。该框架封装了线程处理、异步读/写等方面</span><br /><span style="font-size: 12pt; color: #0000ff;">ACE&nbsp;</span><span style="font-size: 12pt;">6.1.0 C++ADAPTIVE CommunicationEnvironment,</span><br /><span style="font-size: 12pt; color: #0000ff;">SmaxFoxServer&nbsp;</span><span style="font-size: 12pt; color: #0000ff;">2.X 专门为Adobe Flash设计的跨平台socket服务器</span><br /><br /><span style="font-size: 12pt;">7.</span><span style="font-size: 12pt; color: #0000ff;">消息编码协议</span><br /><div><span style="font-size: 12pt; color: #0000ff;">AMF/JSON/XML/自定义/ProtocolBuffer</span><br /><br /><span style="font-size: 12pt;">无论是做何种网络应用，必须要解决的问题之一就是</span><span style="font-size: 12pt; color: #0000ff;">应用层从字节流中拆分出消息</span><span style="font-size: 12pt;">的问题，也就是对于 TCP 这种字节流协议，接收方应用层能够从字节流中识别发送方传输的消息.</span><br /><span style="font-size: 12pt;">1.使用特殊字符或者字符串作为消息的边界，应用层解析收到的字节流时，遇见此字符或者字符串则认为收到一个完整的消息&nbsp;</span><br /><span style="font-size: 12pt;">2.为每个消息定义一个长度，应用层收到指定长度的字节流则认为收到了一个完整的消息</span><br /><span style="font-size: 12pt;">消息分隔标识（separator）、消息头（header）、消息体（body）</span><br /><span style="font-size: 12pt;">&nbsp;len | message_id | data&nbsp;</span><br /><br /><span style="font-size: 12pt;">&nbsp;|separator |&nbsp;&nbsp;&nbsp;&nbsp; header&nbsp;&nbsp; | body |</span><br />&nbsp;<span style="font-size: 12pt; color: #0000ff;">| len&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; | message_id | data</span><br /><br /><span style="font-size: 12pt;">8.&nbsp;</span><span style="font-size: 12pt; color: #0000ff;">粘包:</span><br /><span style="font-size: 12pt;">TCP粘包是指发送方发送的若干包数据到接收方接收时粘成一包，从接收缓冲区看，后一包数据的头紧接着前一包数据的尾。&nbsp;</span><br />&nbsp;&nbsp;&nbsp;&nbsp;<span style="font-size: 12pt;">1.发送方引起的粘包是由TCP协议本身造成的，TCP为提高传输效率，发送方往往要收集到足够多的数据后才发送一包数据。若连续发送几次的数据都很少，通常TCP会根据优化算法把这些数据合成一包后一次发送出去，这样接收方就收到了粘包数据。</span><br />&nbsp;&nbsp;&nbsp;&nbsp;<span style="font-size: 12pt;">2.接收方引起的粘包是由于接收方用户进程不及时接收数据，从而导致粘包现象。这是因为接收方先把收到的数据放在系统接收缓冲区，用户进程从该缓冲区取数据，若下一包数据到达时前一包数据尚未被用户进程取走，则下一包数据放到系统接收缓冲区时就接到前一包数据之后，而用户进程根据预先设定的缓冲区大小从系统接收缓冲区取数据，这样就一次取到了多包数据</span><br /><br /><span style="font-size: 12pt;">解决措施:</span><br />&nbsp;&nbsp;&nbsp;&nbsp;<span style="font-size: 12pt;">1.对于发送方引起的粘包现象，用户可通过编程设置来避免，TCP提供了强制数据立即传送的操作指令push，TCP软件接收到该操作指令后，就立即将本段数据发送出去，而不必等待发送缓冲区满；</span><br /><span style="font-size: 12pt;">TCP-NO-DELAY-关闭了优化算法,不推荐</span><br />&nbsp;&nbsp;&nbsp;&nbsp;<span style="font-size: 12pt;">2.对于接收方引起的粘包，则可通过优化程序设计、精简接收进程工作量、提高接收进程优先级等措施，使其及时接收数据，从而尽量避免出现粘包现象-当发送频率高时依然可能出现粘包</span><br />&nbsp;&nbsp;&nbsp;&nbsp;<span style="font-size: 12pt;">3.接收方控制，将一包数据按结构字段，人为控制分多次接收，然后合并，通过这种手段来避免粘包。-效率低</span><br />&nbsp;&nbsp;&nbsp;&nbsp;<span style="font-size: 12pt;">4.接收方创建一预处理线程，对接收到的数据包进行预处理，将粘连的包分开</span><br /><br /><span style="font-size: 12pt; color: #0000ff;">分包算法思路:</span><br /><span style="font-size: 12pt;">基本思路是首先将待处理的接收数据（长度设为m）强行转换成预定的结构数据形式，并从中取出数据结构长度字段，即n，而后根据n计算得到第一包数据长度</span><br /><span style="font-size: 12pt;">1) 若n&lt;m，则表明数据流包含多包数据，从其头部截取n个字节存入临时缓冲区，剩余部分数据一次继续循环处理，直至结束。</span><br /><span style="font-size: 12pt;">2) 若n=m，则表明数据流内容恰好是一完整结构数据，直接将其存入临时缓冲区即可。</span><br /><span style="font-size: 12pt;">3) 若n&gt;m，则表明数据流内容尚不够构成一个完整结构数据，需留待与下一包数据合并后再行处理。</span><br /><br /><span style="font-size: 12pt;">五</span><span style="font-size: 12pt; color: #0000ff;">.正文之场景管理、ai</span><span style="font-size: 12pt; color: #0000ff;">、脚本</span><br /><br /><div>&nbsp;<span style="font-size: 12pt; color: #0000ff;">AOI</span><span style="font-size: 12pt;">: (Area Of Interest),广义上，AOI系统支持任何游戏世界中的物体个体对一定半径范围内发生的事件进行处理；但MMOPRG上绝大多数需求只是对半径范围内发生的物体离开/进入事件进行处理。当你进入一个游戏场景时，如果你能看到其他玩家，那背后AOI系统就正在运作.</span><br /><br />&nbsp;&nbsp;&nbsp;&nbsp;<span style="font-size: 12pt;">1. 很容易想象，AOI的需求最简单的做法是全世界玩家信息全部同步给客户端。这个方案是O(n^2)的复杂度，对服务器来说是不能承受之重。但如果是超小地图十人以下的特殊需求倒可能是个简洁的方案。</span><br />&nbsp;&nbsp;&nbsp;&nbsp;<span style="font-size: 12pt;">2. 比较流行的方案是网格法，简单，高效：将地图按设定的格子大小划分为网格，设玩家移动到某坐标，我们很容易地将玩家归入该坐标所属的网格G的玩家链中，而这个玩家的可见集可以简单地将以网格G为中心的九宫格中的玩家链聚合而得到。而要获得两次移动间的可见集差异，也非难事.</span><br /><br /><span style="font-size: 12pt; color: #0000ff;">转自云风Blog:</span><br /><span style="font-size: 12pt;">所谓 AOI ( Area Of Interest ) ，大致有两个用途。</span><br />&nbsp;&nbsp;&nbsp;&nbsp;<span style="font-size: 12pt;">一则是解决</span><span style="font-size: 12pt; color: #0000ff;">&nbsp;NPC 的 AI 事件触发问题</span><span style="font-size: 12pt;">。游戏场景中有众多的 NPC ，比 PC 大致要多一个数量级。NPC 的 AI 触发条件往往是和其它 NPC 或 PC 距离接近。如果没有 AOI 模块，每个 NPC 都需要遍历场景中其它对象，判断与之距离。这个检索量是非常巨大的（复杂度 O(N*N) ）。一般我们会设计一个 AOI 模块，统一处理，并优化比较次数，当两个对象距离接近时，以消息的形式通知它们。</span><br />&nbsp;&nbsp;&nbsp;&nbsp;<span style="font-size: 12pt;">二则用于</span><span style="font-size: 12pt; color: #0000ff;">减少向 PC 发送的同步消息数量</span><span style="font-size: 12pt;">。把离 PC 较远的物体状态变化的消息过滤掉。PC 身上可以带一个附近对象列表，由 AOI 消息来增减这个列表的内容。</span><br /><span style="font-size: 12pt;">在服务器上，我们一般推荐把 AOI 模块做成一个独立服务 。场景模块通知它改变对象的位置信息。AOI 服务则发送 AOI 消息给场景</span><br /><span style="font-size: 12pt;">AOI 的传统实现方法大致有三种：</span><br /><br /><span style="font-size: 12pt;">第一，也是最苯的方案。直接定期比较所有对象间的关系，发现能够触发 AOI 事件就发送消息。这种方案实现起来相当简洁，几乎不可能有 bug ，可以用来验证服务协议的正确性。在场景中对象不对的情况下其实也是不错的一个方案。如果我们独立出来的话，利用一个单独的核，其实可以定期处理相当大的对象数量。</span><br /><br /><span style="font-size: 12pt;">第二，空间切割监视的方法。把场景划分为等大的格子，在每个格子里树立灯塔。在对象进入或退出格子时，维护每个灯塔上的对象列表。对于每个灯塔还是 O(N * N) 的复杂度，但由于把对象数据量大量降了下来，所以性能要好的多，实现也很容易。缺点是，存储空间不仅和对象数量有关，还和场景大小有关。更浪费内存。且当场景规模大过对象数量规模时，性能还会下降。因为要遍历整个场景。对大地图不太合适。这里还有一些优化技巧，比如可以把格子划分为六边形 的。</span><br /><br /><span style="font-size: 12pt;">第三，使用十字链表 (3d 空间则再增加一个链表维度) 保存一系列线段，当线段移动时触发 AOI 事件。算法不展开解释，这个用的很多应该搜的到。优点是可以混用于不同半径的 AOI 区域。</span><br /><br /><span style="font-size: 12pt; color: #0000ff;">2.AI</span><br /><div><span style="font-size: 12pt;">&nbsp;&nbsp;&nbsp; 1.怪物AI</span><br /><span style="font-size: 12pt;">&nbsp;&nbsp;&nbsp; 2.NPC AI</span><br /><span style="font-size: 12pt;">&nbsp;&nbsp;&nbsp; 3.世界环境AI</span><br /><span style="font-size: 12pt;">实现方法：</span><span style="font-size: 12pt; color: #0000ff;">状态机</span><br /><span style="font-size: 12pt;">&nbsp;其他:</span><br /><span style="font-size: 12pt;">&nbsp;寻路：A*</span><br /><span style="font-size: 12pt;">&nbsp;神经网络</span><br /><span style="font-size: 12pt;">&nbsp;遗传算法</span><br /><br /><span style="font-size: 12pt;">3.</span><span style="font-size: 12pt; color: #0000ff;">脚本语言的选择：</span><br /><div><span style="font-size: 12pt; color: #0000ff;">Lua/Python/Erlang</span><br /><span style="font-size: 12pt; color: #0000ff;">Groovy/JRuby/Scala/Fantom/JPython-五大基于JVM的语言</span><br /><span style="font-size: 12pt;">&nbsp; 作用:可应用于部分应用层逻辑经常发生变化的系统。如任务系统。以在不需要重新编译整个工程的情况下调整、 测试和修改游戏运行的机制和特性</span></div></div><span style="font-size: 12pt;">六</span><span style="font-size: 12pt; color: #0000ff;">.正文之开源网络游戏服务器</span><br /><br /><div><span style="font-size: 12pt; color: #0000ff;">魔兽世界模拟器</span><br /><span style="font-size: 12pt; color: #0000ff;">mangosTrinity&nbsp; TrinityCore2</span><br /><br /><span style="font-size: 12pt; color: #0000ff;">天堂2模拟器</span><br /><span style="font-size: 12pt; color: #0000ff;">L2J<br /></span><br /><span style="font-size: 12pt; color: #0000ff;">永恒之塔模拟器</span><br /><br /><span style="font-size: 12pt;">Arianne</span><br /><br /><span style="font-size: 12pt;">七.正文之参考书籍，博客</span><br /><div><span style="font-size: 12pt;">1.</span><span style="font-size: 12pt; color: #0000ff;">云风Blog&nbsp; ttp://codingnow.com/</span><br /><span style="font-size: 12pt;">2.书籍&lt;大型多人在线游戏开发&gt;&lt;网络游戏服务器编程&gt;&lt;UNIX网络编程&gt;</span><br /><br /><span style="font-size: 12pt; color: #0000ff;">注：有部分内容来自网络,谢谢你们！</span></div></div></div></div></div></div></div></div></div></div></div></div><img src ="http://www.cppblog.com/API/aggbug/214728.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/API/" target="_blank">C++技术中心</a> 2017-03-06 16:01 <a href="http://www.cppblog.com/API/archive/2017/03/06/214728.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>tolua++使用示例</title><link>http://www.cppblog.com/API/archive/2014/05/07/206855.html</link><dc:creator>C++技术中心</dc:creator><author>C++技术中心</author><pubDate>Wed, 07 May 2014 06:06:00 GMT</pubDate><guid>http://www.cppblog.com/API/archive/2014/05/07/206855.html</guid><wfw:comment>http://www.cppblog.com/API/comments/206855.html</wfw:comment><comments>http://www.cppblog.com/API/archive/2014/05/07/206855.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cppblog.com/API/comments/commentRss/206855.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/API/services/trackbacks/206855.html</trackback:ping><description><![CDATA[&nbsp;&nbsp;&nbsp;&nbsp; 摘要: 1.近来想研究tolua++,正好忙里抽闲，看些文章，并做了些总结。一.环境设置2.下载lua5.1&nbsp;&nbsp;并安装3.下载tolua++/Files/API/tolua-1.0.93.tar.bz2.zip &nbsp;（下载完成后，把.zip去掉）4.新建工程lua++（生成静态库），将tolua++解压后的tolua++-1.0.93\src\lib目录下的6个.c和.h文件。...&nbsp;&nbsp;<a href='http://www.cppblog.com/API/archive/2014/05/07/206855.html'>阅读全文</a><img src ="http://www.cppblog.com/API/aggbug/206855.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/API/" target="_blank">C++技术中心</a> 2014-05-07 14:06 <a href="http://www.cppblog.com/API/archive/2014/05/07/206855.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>游戏运营18种方式</title><link>http://www.cppblog.com/API/archive/2013/10/12/203692.html</link><dc:creator>C++技术中心</dc:creator><author>C++技术中心</author><pubDate>Sat, 12 Oct 2013 10:29:00 GMT</pubDate><guid>http://www.cppblog.com/API/archive/2013/10/12/203692.html</guid><wfw:comment>http://www.cppblog.com/API/comments/203692.html</wfw:comment><comments>http://www.cppblog.com/API/archive/2013/10/12/203692.html#Feedback</comments><slash:comments>1</slash:comments><wfw:commentRss>http://www.cppblog.com/API/comments/commentRss/203692.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/API/services/trackbacks/203692.html</trackback:ping><description><![CDATA[<p align="left" style="word-wrap: break-word; margin: 0px; padding: 0px; font-family: Tahoma, 'Microsoft Yahei', Simsun; font-size: 16px; line-height: 22.399999618530273px; background-color: #ffffff;">&nbsp;游戏要成功，运营活动必不可少，各种庞杂的活动到底有怎样的规律？TalkingData为行业提供了一张制作精美的信息图，总结18种游戏运营活动的优缺点。其实再多的方法论都需要结合实践，以下所述，所有的活动类型都有利弊，选择一个最适合你的，才是最好的。</p><br style="word-wrap: break-word; font-family: Tahoma, 'Microsoft Yahei', Simsun; font-size: 16px; line-height: 22.399999618530273px; background-color: #ffffff;" /><br style="word-wrap: break-word; font-family: Tahoma, 'Microsoft Yahei', Simsun; font-size: 16px; line-height: 22.399999618530273px; background-color: #ffffff;" /><p align="left" style="word-wrap: break-word; margin: 0px; padding: 0px; font-family: Tahoma, 'Microsoft Yahei', Simsun; font-size: 16px; line-height: 22.399999618530273px; background-color: #ffffff;"><span style="word-wrap: break-word; font-weight: 700;">一 征集式活动</span></p><br style="word-wrap: break-word; font-family: Tahoma, 'Microsoft Yahei', Simsun; font-size: 16px; line-height: 22.399999618530273px; background-color: #ffffff;" /><p align="left" style="word-wrap: break-word; margin: 0px; padding: 0px; font-family: Tahoma, 'Microsoft Yahei', Simsun; font-size: 16px; line-height: 22.399999618530273px; background-color: #ffffff;">特点：</p><br style="word-wrap: break-word; font-family: Tahoma, 'Microsoft Yahei', Simsun; font-size: 16px; line-height: 22.399999618530273px; background-color: #ffffff;" /><p align="left" style="word-wrap: break-word; margin: 0px; padding: 0px; font-family: Tahoma, 'Microsoft Yahei', Simsun; font-size: 16px; line-height: 22.399999618530273px; background-color: #ffffff;">1）易在玩家之剑形成讨论点和话题，可在网站和论坛一定时间内聚集人气</p><p align="left" style="word-wrap: break-word; margin: 0px; padding: 0px; font-family: Tahoma, 'Microsoft Yahei', Simsun; font-size: 16px; line-height: 22.399999618530273px; background-color: #ffffff;"><br style="word-wrap: break-word;" />2）玩家的截图和征文可作为软文素材，可提供一定的宣传点</p><p align="left" style="word-wrap: break-word; margin: 0px; padding: 0px; font-family: Tahoma, 'Microsoft Yahei', Simsun; font-size: 16px; line-height: 22.399999618530273px; background-color: #ffffff;"><br style="word-wrap: break-word;" />3）讷讷感了解玩家的游戏建议和想法，可作为游戏运营和修改的参考</p><br style="word-wrap: break-word; font-family: Tahoma, 'Microsoft Yahei', Simsun; font-size: 16px; line-height: 22.399999618530273px; background-color: #ffffff;" /><p align="left" style="word-wrap: break-word; margin: 0px; padding: 0px; font-family: Tahoma, 'Microsoft Yahei', Simsun; font-size: 16px; line-height: 22.399999618530273px; background-color: #ffffff;">缺点：</p><br style="word-wrap: break-word; font-family: Tahoma, 'Microsoft Yahei', Simsun; font-size: 16px; line-height: 22.399999618530273px; background-color: #ffffff;" /><p align="left" style="word-wrap: break-word; margin: 0px; padding: 0px; font-family: Tahoma, 'Microsoft Yahei', Simsun; font-size: 16px; line-height: 22.399999618530273px; background-color: #ffffff;">需要专门人员对征集的信息进行分类整理，审核周期较长</p><br style="word-wrap: break-word; font-family: Tahoma, 'Microsoft Yahei', Simsun; font-size: 16px; line-height: 22.399999618530273px; background-color: #ffffff;" /><p align="left" style="word-wrap: break-word; margin: 0px; padding: 0px; font-family: Tahoma, 'Microsoft Yahei', Simsun; font-size: 16px; line-height: 22.399999618530273px; background-color: #ffffff;"><span style="word-wrap: break-word; font-weight: 700;">二 注册式活动</span></p><br style="word-wrap: break-word; font-family: Tahoma, 'Microsoft Yahei', Simsun; font-size: 16px; line-height: 22.399999618530273px; background-color: #ffffff;" /><p align="left" style="word-wrap: break-word; margin: 0px; padding: 0px; font-family: Tahoma, 'Microsoft Yahei', Simsun; font-size: 16px; line-height: 22.399999618530273px; background-color: #ffffff;">特点：</p><br style="word-wrap: break-word; font-family: Tahoma, 'Microsoft Yahei', Simsun; font-size: 16px; line-height: 22.399999618530273px; background-color: #ffffff;" /><p align="left" style="word-wrap: break-word; margin: 0px; padding: 0px; font-family: Tahoma, 'Microsoft Yahei', Simsun; font-size: 16px; line-height: 22.399999618530273px; background-color: #ffffff;">1）短时间内吸引大量玩家注册帐号，利于游戏人数提升</p><br style="word-wrap: break-word; font-family: Tahoma, 'Microsoft Yahei', Simsun; font-size: 16px; line-height: 22.399999618530273px; background-color: #ffffff;" /><p align="left" style="word-wrap: break-word; margin: 0px; padding: 0px; font-family: Tahoma, 'Microsoft Yahei', Simsun; font-size: 16px; line-height: 22.399999618530273px; background-color: #ffffff;">2）可为市场提供宣传点，增加媒体曝光量</p><br style="word-wrap: break-word; font-family: Tahoma, 'Microsoft Yahei', Simsun; font-size: 16px; line-height: 22.399999618530273px; background-color: #ffffff;" /><p align="left" style="word-wrap: break-word; margin: 0px; padding: 0px; font-family: Tahoma, 'Microsoft Yahei', Simsun; font-size: 16px; line-height: 22.399999618530273px; background-color: #ffffff;">缺点：</p><br style="word-wrap: break-word; font-family: Tahoma, 'Microsoft Yahei', Simsun; font-size: 16px; line-height: 22.399999618530273px; background-color: #ffffff;" /><p align="left" style="word-wrap: break-word; margin: 0px; padding: 0px; font-family: Tahoma, 'Microsoft Yahei', Simsun; font-size: 16px; line-height: 22.399999618530273px; background-color: #ffffff;">实体奖比虚拟奖对新玩家更具有吸引力，但也容易造成活动结束后人气和在线人数的急剧滑落，还容易造成大量小号生成对数据模型产生偏移。</p><br style="word-wrap: break-word; font-family: Tahoma, 'Microsoft Yahei', Simsun; font-size: 16px; line-height: 22.399999618530273px; background-color: #ffffff;" /><p align="left" style="word-wrap: break-word; margin: 0px; padding: 0px; font-family: Tahoma, 'Microsoft Yahei', Simsun; font-size: 16px; line-height: 22.399999618530273px; background-color: #ffffff;"><span style="word-wrap: break-word; font-weight: 700;">三 评选式活动</span></p><br style="word-wrap: break-word; font-family: Tahoma, 'Microsoft Yahei', Simsun; font-size: 16px; line-height: 22.399999618530273px; background-color: #ffffff;" /><p align="left" style="word-wrap: break-word; margin: 0px; padding: 0px; font-family: Tahoma, 'Microsoft Yahei', Simsun; font-size: 16px; line-height: 22.399999618530273px; background-color: #ffffff;">特点:</p><br style="word-wrap: break-word; font-family: Tahoma, 'Microsoft Yahei', Simsun; font-size: 16px; line-height: 22.399999618530273px; background-color: #ffffff;" /><p align="left" style="word-wrap: break-word; margin: 0px; padding: 0px; font-family: Tahoma, 'Microsoft Yahei', Simsun; font-size: 16px; line-height: 22.399999618530273px; background-color: #ffffff;">1)利于拉票，曾近玩家间互动，通过玩家和好友间拉票行为，引入潜在用户并能宣传游戏</p><br style="word-wrap: break-word; font-family: Tahoma, 'Microsoft Yahei', Simsun; font-size: 16px; line-height: 22.399999618530273px; background-color: #ffffff;" /><p align="left" style="word-wrap: break-word; margin: 0px; padding: 0px; font-family: Tahoma, 'Microsoft Yahei', Simsun; font-size: 16px; line-height: 22.399999618530273px; background-color: #ffffff;">2）利于美女、帅哥、最强等词眼做噱头，吸引媒体注意，提升关注度</p><br style="word-wrap: break-word; font-family: Tahoma, 'Microsoft Yahei', Simsun; font-size: 16px; line-height: 22.399999618530273px; background-color: #ffffff;" /><p align="left" style="word-wrap: break-word; margin: 0px; padding: 0px; font-family: Tahoma, 'Microsoft Yahei', Simsun; font-size: 16px; line-height: 22.399999618530273px; background-color: #ffffff;">3）通过参赛人员的八卦新闻来延长曝光周期，配合软文达到炒作效果</p><br style="word-wrap: break-word; font-family: Tahoma, 'Microsoft Yahei', Simsun; font-size: 16px; line-height: 22.399999618530273px; background-color: #ffffff;" /><p align="left" style="word-wrap: break-word; margin: 0px; padding: 0px; font-family: Tahoma, 'Microsoft Yahei', Simsun; font-size: 16px; line-height: 22.399999618530273px; background-color: #ffffff;">缺点：</p><br style="word-wrap: break-word; font-family: Tahoma, 'Microsoft Yahei', Simsun; font-size: 16px; line-height: 22.399999618530273px; background-color: #ffffff;" /><p align="left" style="word-wrap: break-word; margin: 0px; padding: 0px; font-family: Tahoma, 'Microsoft Yahei', Simsun; font-size: 16px; line-height: 22.399999618530273px; background-color: #ffffff;">投入成本较高，在活动期需要不断制造花边新闻来维持热点</p><br style="word-wrap: break-word; font-family: Tahoma, 'Microsoft Yahei', Simsun; font-size: 16px; line-height: 22.399999618530273px; background-color: #ffffff;" /><p align="left" style="word-wrap: break-word; margin: 0px; padding: 0px; font-family: Tahoma, 'Microsoft Yahei', Simsun; font-size: 16px; line-height: 22.399999618530273px; background-color: #ffffff;"><span style="word-wrap: break-word; font-weight: 700;">四 充值式活动</span></p><br style="word-wrap: break-word; font-family: Tahoma, 'Microsoft Yahei', Simsun; font-size: 16px; line-height: 22.399999618530273px; background-color: #ffffff;" /><p align="left" style="word-wrap: break-word; margin: 0px; padding: 0px; font-family: Tahoma, 'Microsoft Yahei', Simsun; font-size: 16px; line-height: 22.399999618530273px; background-color: #ffffff;">特点：</p><br style="word-wrap: break-word; font-family: Tahoma, 'Microsoft Yahei', Simsun; font-size: 16px; line-height: 22.399999618530273px; background-color: #ffffff;" /><p align="left" style="word-wrap: break-word; margin: 0px; padding: 0px; font-family: Tahoma, 'Microsoft Yahei', Simsun; font-size: 16px; line-height: 22.399999618530273px; background-color: #ffffff;">1）能在短期内促使付费玩家充值大量现金，提升营业收入</p><br style="word-wrap: break-word; font-family: Tahoma, 'Microsoft Yahei', Simsun; font-size: 16px; line-height: 22.399999618530273px; background-color: #ffffff;" /><p align="left" style="word-wrap: break-word; margin: 0px; padding: 0px; font-family: Tahoma, 'Microsoft Yahei', Simsun; font-size: 16px; line-height: 22.399999618530273px; background-color: #ffffff;">2）吸引潜在的未付费用户进行付费，提升付费率</p><br style="word-wrap: break-word; font-family: Tahoma, 'Microsoft Yahei', Simsun; font-size: 16px; line-height: 22.399999618530273px; background-color: #ffffff;" /><p align="left" style="word-wrap: break-word; margin: 0px; padding: 0px; font-family: Tahoma, 'Microsoft Yahei', Simsun; font-size: 16px; line-height: 22.399999618530273px; background-color: #ffffff;">3）能促使付费用户在之后较长的期间内驻留不易于流失</p><br style="word-wrap: break-word; font-family: Tahoma, 'Microsoft Yahei', Simsun; font-size: 16px; line-height: 22.399999618530273px; background-color: #ffffff;" /><p align="left" style="word-wrap: break-word; margin: 0px; padding: 0px; font-family: Tahoma, 'Microsoft Yahei', Simsun; font-size: 16px; line-height: 22.399999618530273px; background-color: #ffffff;">缺点：</p><br style="word-wrap: break-word; font-family: Tahoma, 'Microsoft Yahei', Simsun; font-size: 16px; line-height: 22.399999618530273px; background-color: #ffffff;" /><p align="left" style="word-wrap: break-word; margin: 0px; padding: 0px; font-family: Tahoma, 'Microsoft Yahei', Simsun; font-size: 16px; line-height: 22.399999618530273px; background-color: #ffffff;">促销会减少部分收益，同时出现较多的高级道具会间接影响游戏平衡，加剧了付费用户和非付费用户之间的差距。应当注意首次促销给予的奖励数量，避免恶性循环造成盲目送礼。</p><br style="word-wrap: break-word; font-family: Tahoma, 'Microsoft Yahei', Simsun; font-size: 16px; line-height: 22.399999618530273px; background-color: #ffffff;" /><p align="left" style="word-wrap: break-word; margin: 0px; padding: 0px; font-family: Tahoma, 'Microsoft Yahei', Simsun; font-size: 16px; line-height: 22.399999618530273px; background-color: #ffffff;"><span style="word-wrap: break-word; font-weight: 700;">五 抽奖式活动</span></p><br style="word-wrap: break-word; font-family: Tahoma, 'Microsoft Yahei', Simsun; font-size: 16px; line-height: 22.399999618530273px; background-color: #ffffff;" /><p align="left" style="word-wrap: break-word; margin: 0px; padding: 0px; font-family: Tahoma, 'Microsoft Yahei', Simsun; font-size: 16px; line-height: 22.399999618530273px; background-color: #ffffff;">特点：</p><br style="word-wrap: break-word; font-family: Tahoma, 'Microsoft Yahei', Simsun; font-size: 16px; line-height: 22.399999618530273px; background-color: #ffffff;" /><p align="left" style="word-wrap: break-word; margin: 0px; padding: 0px; font-family: Tahoma, 'Microsoft Yahei', Simsun; font-size: 16px; line-height: 22.399999618530273px; background-color: #ffffff;">1）以极品道具或者实体奖为诱饵，利用玩家赌博心理，获取高额收入</p><br style="word-wrap: break-word; font-family: Tahoma, 'Microsoft Yahei', Simsun; font-size: 16px; line-height: 22.399999618530273px; background-color: #ffffff;" /><p align="left" style="word-wrap: break-word; margin: 0px; padding: 0px; font-family: Tahoma, 'Microsoft Yahei', Simsun; font-size: 16px; line-height: 22.399999618530273px; background-color: #ffffff;">2）准入门槛低，通过页面抽奖形式让更多的用户浏览官网，利于游戏推广</p><br style="word-wrap: break-word; font-family: Tahoma, 'Microsoft Yahei', Simsun; font-size: 16px; line-height: 22.399999618530273px; background-color: #ffffff;" /><p align="left" style="word-wrap: break-word; margin: 0px; padding: 0px; font-family: Tahoma, 'Microsoft Yahei', Simsun; font-size: 16px; line-height: 22.399999618530273px; background-color: #ffffff;">缺点：</p><br style="word-wrap: break-word; font-family: Tahoma, 'Microsoft Yahei', Simsun; font-size: 16px; line-height: 22.399999618530273px; background-color: #ffffff;" /><p align="left" style="word-wrap: break-word; margin: 0px; padding: 0px; font-family: Tahoma, 'Microsoft Yahei', Simsun; font-size: 16px; line-height: 22.399999618530273px; background-color: #ffffff;">1）活动页面涉及小游戏需要进行开发和测试，策划周期长</p><br style="word-wrap: break-word; font-family: Tahoma, 'Microsoft Yahei', Simsun; font-size: 16px; line-height: 22.399999618530273px; background-color: #ffffff;" /><p align="left" style="word-wrap: break-word; margin: 0px; padding: 0px; font-family: Tahoma, 'Microsoft Yahei', Simsun; font-size: 16px; line-height: 22.399999618530273px; background-color: #ffffff;">2）对于抽奖类的活动玩家会认为有内定中奖嫌疑，公开公平公正是玩家所关心的重点</p><br style="word-wrap: break-word; font-family: Tahoma, 'Microsoft Yahei', Simsun; font-size: 16px; line-height: 22.399999618530273px; background-color: #ffffff;" /><p align="left" style="word-wrap: break-word; margin: 0px; padding: 0px; font-family: Tahoma, 'Microsoft Yahei', Simsun; font-size: 16px; line-height: 22.399999618530273px; background-color: #ffffff;">3）政府监管下对赌博性质的处罚</p><br style="word-wrap: break-word; font-family: Tahoma, 'Microsoft Yahei', Simsun; font-size: 16px; line-height: 22.399999618530273px; background-color: #ffffff;" /><p align="left" style="word-wrap: break-word; margin: 0px; padding: 0px; font-family: Tahoma, 'Microsoft Yahei', Simsun; font-size: 16px; line-height: 22.399999618530273px; background-color: #ffffff;"><span style="word-wrap: break-word; font-weight: 700;">六 其他式活动</span></p><br style="word-wrap: break-word; font-family: Tahoma, 'Microsoft Yahei', Simsun; font-size: 16px; line-height: 22.399999618530273px; background-color: #ffffff;" /><p align="left" style="word-wrap: break-word; margin: 0px; padding: 0px; font-family: Tahoma, 'Microsoft Yahei', Simsun; font-size: 16px; line-height: 22.399999618530273px; background-color: #ffffff;">特点：</p><br style="word-wrap: break-word; font-family: Tahoma, 'Microsoft Yahei', Simsun; font-size: 16px; line-height: 22.399999618530273px; background-color: #ffffff;" /><p align="left" style="word-wrap: break-word; margin: 0px; padding: 0px; font-family: Tahoma, 'Microsoft Yahei', Simsun; font-size: 16px; line-height: 22.399999618530273px; background-color: #ffffff;">此类活动基本属于穷吆喝，获奖门槛很高，主要为的是媒体曝光度</p><br style="word-wrap: break-word; font-family: Tahoma, 'Microsoft Yahei', Simsun; font-size: 16px; line-height: 22.399999618530273px; background-color: #ffffff;" /><p align="left" style="word-wrap: break-word; margin: 0px; padding: 0px; font-family: Tahoma, 'Microsoft Yahei', Simsun; font-size: 16px; line-height: 22.399999618530273px; background-color: #ffffff;">缺点:</p><br style="word-wrap: break-word; font-family: Tahoma, 'Microsoft Yahei', Simsun; font-size: 16px; line-height: 22.399999618530273px; background-color: #ffffff;" /><p align="left" style="word-wrap: break-word; margin: 0px; padding: 0px; font-family: Tahoma, 'Microsoft Yahei', Simsun; font-size: 16px; line-height: 22.399999618530273px; background-color: #ffffff;">需要进行量度限制以及活动详细条款的指定，避免产生活动漏洞。</p><br style="word-wrap: break-word; font-family: Tahoma, 'Microsoft Yahei', Simsun; font-size: 16px; line-height: 22.399999618530273px; background-color: #ffffff;" /><p align="left" style="word-wrap: break-word; margin: 0px; padding: 0px; font-family: Tahoma, 'Microsoft Yahei', Simsun; font-size: 16px; line-height: 22.399999618530273px; background-color: #ffffff;"><span style="word-wrap: break-word; font-weight: 700;">七 比赛式活动</span></p><br style="word-wrap: break-word; font-family: Tahoma, 'Microsoft Yahei', Simsun; font-size: 16px; line-height: 22.399999618530273px; background-color: #ffffff;" /><p align="left" style="word-wrap: break-word; margin: 0px; padding: 0px; font-family: Tahoma, 'Microsoft Yahei', Simsun; font-size: 16px; line-height: 22.399999618530273px; background-color: #ffffff;">特点：</p><br style="word-wrap: break-word; font-family: Tahoma, 'Microsoft Yahei', Simsun; font-size: 16px; line-height: 22.399999618530273px; background-color: #ffffff;" /><p align="left" style="word-wrap: break-word; margin: 0px; padding: 0px; font-family: Tahoma, 'Microsoft Yahei', Simsun; font-size: 16px; line-height: 22.399999618530273px; background-color: #ffffff;">能激发潜在消费大户的热情，增加此类玩家的付费额并提升游戏兴趣度</p><br style="word-wrap: break-word; font-family: Tahoma, 'Microsoft Yahei', Simsun; font-size: 16px; line-height: 22.399999618530273px; background-color: #ffffff;" /><p align="left" style="word-wrap: break-word; margin: 0px; padding: 0px; font-family: Tahoma, 'Microsoft Yahei', Simsun; font-size: 16px; line-height: 22.399999618530273px; background-color: #ffffff;">缺点：</p><br style="word-wrap: break-word; font-family: Tahoma, 'Microsoft Yahei', Simsun; font-size: 16px; line-height: 22.399999618530273px; background-color: #ffffff;" /><p align="left" style="word-wrap: break-word; margin: 0px; padding: 0px; font-family: Tahoma, 'Microsoft Yahei', Simsun; font-size: 16px; line-height: 22.399999618530273px; background-color: #ffffff;">参与面过窄，普通玩家通常认为此类活动是专门针对特定玩家打造</p><br style="word-wrap: break-word; font-family: Tahoma, 'Microsoft Yahei', Simsun; font-size: 16px; line-height: 22.399999618530273px; background-color: #ffffff;" /><p align="left" style="word-wrap: break-word; margin: 0px; padding: 0px; font-family: Tahoma, 'Microsoft Yahei', Simsun; font-size: 16px; line-height: 22.399999618530273px; background-color: #ffffff;"><span style="word-wrap: break-word; font-weight: 700;">八 帮派式活动</span></p><br style="word-wrap: break-word; font-family: Tahoma, 'Microsoft Yahei', Simsun; font-size: 16px; line-height: 22.399999618530273px; background-color: #ffffff;" /><p align="left" style="word-wrap: break-word; margin: 0px; padding: 0px; font-family: Tahoma, 'Microsoft Yahei', Simsun; font-size: 16px; line-height: 22.399999618530273px; background-color: #ffffff;">特点：</p><br style="word-wrap: break-word; font-family: Tahoma, 'Microsoft Yahei', Simsun; font-size: 16px; line-height: 22.399999618530273px; background-color: #ffffff;" /><p align="left" style="word-wrap: break-word; margin: 0px; padding: 0px; font-family: Tahoma, 'Microsoft Yahei', Simsun; font-size: 16px; line-height: 22.399999618530273px; background-color: #ffffff;">利用团队凝聚力为启动要点，人为设置玩家之间纷争，让玩家体验团队对抗乐趣同时增加游戏道具消耗，侧面提高收入</p><br style="word-wrap: break-word; font-family: Tahoma, 'Microsoft Yahei', Simsun; font-size: 16px; line-height: 22.399999618530273px; background-color: #ffffff;" /><p align="left" style="word-wrap: break-word; margin: 0px; padding: 0px; font-family: Tahoma, 'Microsoft Yahei', Simsun; font-size: 16px; line-height: 22.399999618530273px; background-color: #ffffff;"><span style="word-wrap: break-word; font-weight: 700;">缺点：</span></p><br style="word-wrap: break-word; font-family: Tahoma, 'Microsoft Yahei', Simsun; font-size: 16px; line-height: 22.399999618530273px; background-color: #ffffff;" /><p align="left" style="word-wrap: break-word; margin: 0px; padding: 0px; font-family: Tahoma, 'Microsoft Yahei', Simsun; font-size: 16px; line-height: 22.399999618530273px; background-color: #ffffff;">程序支持的内置公会对抗活动受网络等因素影响较大</p><br style="word-wrap: break-word; font-family: Tahoma, 'Microsoft Yahei', Simsun; font-size: 16px; line-height: 22.399999618530273px; background-color: #ffffff;" /><p align="left" style="word-wrap: break-word; margin: 0px; padding: 0px; font-family: Tahoma, 'Microsoft Yahei', Simsun; font-size: 16px; line-height: 22.399999618530273px; background-color: #ffffff;"><span style="word-wrap: break-word; font-weight: 700;">九 冲级式活动</span></p><br style="word-wrap: break-word; font-family: Tahoma, 'Microsoft Yahei', Simsun; font-size: 16px; line-height: 22.399999618530273px; background-color: #ffffff;" /><p align="left" style="word-wrap: break-word; margin: 0px; padding: 0px; font-family: Tahoma, 'Microsoft Yahei', Simsun; font-size: 16px; line-height: 22.399999618530273px; background-color: #ffffff;">特点：</p><br style="word-wrap: break-word; font-family: Tahoma, 'Microsoft Yahei', Simsun; font-size: 16px; line-height: 22.399999618530273px; background-color: #ffffff;" /><p align="left" style="word-wrap: break-word; margin: 0px; padding: 0px; font-family: Tahoma, 'Microsoft Yahei', Simsun; font-size: 16px; line-height: 22.399999618530273px; background-color: #ffffff;">1）有效地提升注册用户和进入游戏数量</p><br style="word-wrap: break-word; font-family: Tahoma, 'Microsoft Yahei', Simsun; font-size: 16px; line-height: 22.399999618530273px; background-color: #ffffff;" /><p align="left" style="word-wrap: break-word; margin: 0px; padding: 0px; font-family: Tahoma, 'Microsoft Yahei', Simsun; font-size: 16px; line-height: 22.399999618530273px; background-color: #ffffff;">2）能有效地提升用户的平均等级，利于玩家驻留</p><br style="word-wrap: break-word; font-family: Tahoma, 'Microsoft Yahei', Simsun; font-size: 16px; line-height: 22.399999618530273px; background-color: #ffffff;" /><p align="left" style="word-wrap: break-word; margin: 0px; padding: 0px; font-family: Tahoma, 'Microsoft Yahei', Simsun; font-size: 16px; line-height: 22.399999618530273px; background-color: #ffffff;">3）阶段性区间奖励利于新手用户熟悉游戏，降低体验门槛</p><br style="word-wrap: break-word; font-family: Tahoma, 'Microsoft Yahei', Simsun; font-size: 16px; line-height: 22.399999618530273px; background-color: #ffffff;" /><p align="left" style="word-wrap: break-word; margin: 0px; padding: 0px; font-family: Tahoma, 'Microsoft Yahei', Simsun; font-size: 16px; line-height: 22.399999618530273px; background-color: #ffffff;">缺点：</p><br style="word-wrap: break-word; font-family: Tahoma, 'Microsoft Yahei', Simsun; font-size: 16px; line-height: 22.399999618530273px; background-color: #ffffff;" /><p align="left" style="word-wrap: break-word; margin: 0px; padding: 0px; font-family: Tahoma, 'Microsoft Yahei', Simsun; font-size: 16px; line-height: 22.399999618530273px; background-color: #ffffff;">需要考虑外挂因素和职业刷号玩家的影响</p><br style="word-wrap: break-word; font-family: Tahoma, 'Microsoft Yahei', Simsun; font-size: 16px; line-height: 22.399999618530273px; background-color: #ffffff;" /><p align="left" style="word-wrap: break-word; margin: 0px; padding: 0px; font-family: Tahoma, 'Microsoft Yahei', Simsun; font-size: 16px; line-height: 22.399999618530273px; background-color: #ffffff;"><span style="word-wrap: break-word; font-weight: 700;">十 BUFF式活动</span></p><br style="word-wrap: break-word; font-family: Tahoma, 'Microsoft Yahei', Simsun; font-size: 16px; line-height: 22.399999618530273px; background-color: #ffffff;" /><p align="left" style="word-wrap: break-word; margin: 0px; padding: 0px; font-family: Tahoma, 'Microsoft Yahei', Simsun; font-size: 16px; line-height: 22.399999618530273px; background-color: #ffffff;">特点：</p><br style="word-wrap: break-word; font-family: Tahoma, 'Microsoft Yahei', Simsun; font-size: 16px; line-height: 22.399999618530273px; background-color: #ffffff;" /><p align="left" style="word-wrap: break-word; margin: 0px; padding: 0px; font-family: Tahoma, 'Microsoft Yahei', Simsun; font-size: 16px; line-height: 22.399999618530273px; background-color: #ffffff;">能在短时间内急剧提升在线人数，侧面提高玩家在线时长和各项数据指标，结合其他形式活动可以有效的提升销售和消耗</p><br style="word-wrap: break-word; font-family: Tahoma, 'Microsoft Yahei', Simsun; font-size: 16px; line-height: 22.399999618530273px; background-color: #ffffff;" /><p align="left" style="word-wrap: break-word; margin: 0px; padding: 0px; font-family: Tahoma, 'Microsoft Yahei', Simsun; font-size: 16px; line-height: 22.399999618530273px; background-color: #ffffff;">缺点：</p><br style="word-wrap: break-word; font-family: Tahoma, 'Microsoft Yahei', Simsun; font-size: 16px; line-height: 22.399999618530273px; background-color: #ffffff;" /><p align="left" style="word-wrap: break-word; margin: 0px; padding: 0px; font-family: Tahoma, 'Microsoft Yahei', Simsun; font-size: 16px; line-height: 22.399999618530273px; background-color: #ffffff;">1）此类活动容易缩短产品生命周期，活动时长和次数需要严格控制</p><br style="word-wrap: break-word; font-family: Tahoma, 'Microsoft Yahei', Simsun; font-size: 16px; line-height: 22.399999618530273px; background-color: #ffffff;" /><p align="left" style="word-wrap: break-word; margin: 0px; padding: 0px; font-family: Tahoma, 'Microsoft Yahei', Simsun; font-size: 16px; line-height: 22.399999618530273px; background-color: #ffffff;">2）活动结束后在线会陷入低潮期，需要其他活动方式来进行拖延</p><br style="word-wrap: break-word; font-family: Tahoma, 'Microsoft Yahei', Simsun; font-size: 16px; line-height: 22.399999618530273px; background-color: #ffffff;" /><p align="left" style="word-wrap: break-word; margin: 0px; padding: 0px; font-family: Tahoma, 'Microsoft Yahei', Simsun; font-size: 16px; line-height: 22.399999618530273px; background-color: #ffffff;">3）在线人数峰值对服务器会有一定短期压力</p><br style="word-wrap: break-word; font-family: Tahoma, 'Microsoft Yahei', Simsun; font-size: 16px; line-height: 22.399999618530273px; background-color: #ffffff;" /><p align="left" style="word-wrap: break-word; margin: 0px; padding: 0px; font-family: Tahoma, 'Microsoft Yahei', Simsun; font-size: 16px; line-height: 22.399999618530273px; background-color: #ffffff;"><span style="word-wrap: break-word; font-weight: 700;">十一 BOSS式活动</span></p><br style="word-wrap: break-word; font-family: Tahoma, 'Microsoft Yahei', Simsun; font-size: 16px; line-height: 22.399999618530273px; background-color: #ffffff;" /><p align="left" style="word-wrap: break-word; margin: 0px; padding: 0px; font-family: Tahoma, 'Microsoft Yahei', Simsun; font-size: 16px; line-height: 22.399999618530273px; background-color: #ffffff;">特点：</p><br style="word-wrap: break-word; font-family: Tahoma, 'Microsoft Yahei', Simsun; font-size: 16px; line-height: 22.399999618530273px; background-color: #ffffff;" /><p align="left" style="word-wrap: break-word; margin: 0px; padding: 0px; font-family: Tahoma, 'Microsoft Yahei', Simsun; font-size: 16px; line-height: 22.399999618530273px; background-color: #ffffff;">娱乐性强，能让玩家更好的感受击杀BOSS的快感；提升玩家在线人数，侧面提高销售和消耗。</p><br style="word-wrap: break-word; font-family: Tahoma, 'Microsoft Yahei', Simsun; font-size: 16px; line-height: 22.399999618530273px; background-color: #ffffff;" /><p align="left" style="word-wrap: break-word; margin: 0px; padding: 0px; font-family: Tahoma, 'Microsoft Yahei', Simsun; font-size: 16px; line-height: 22.399999618530273px; background-color: #ffffff;">缺点：</p><br style="word-wrap: break-word; font-family: Tahoma, 'Microsoft Yahei', Simsun; font-size: 16px; line-height: 22.399999618530273px; background-color: #ffffff;" /><p align="left" style="word-wrap: break-word; margin: 0px; padding: 0px; font-family: Tahoma, 'Microsoft Yahei', Simsun; font-size: 16px; line-height: 22.399999618530273px; background-color: #ffffff;">容易被专业BOSS队包团，影响其他玩家游戏体验</p><br style="word-wrap: break-word; font-family: Tahoma, 'Microsoft Yahei', Simsun; font-size: 16px; line-height: 22.399999618530273px; background-color: #ffffff;" /><p align="left" style="word-wrap: break-word; margin: 0px; padding: 0px; font-family: Tahoma, 'Microsoft Yahei', Simsun; font-size: 16px; line-height: 22.399999618530273px; background-color: #ffffff;"><span style="word-wrap: break-word; font-weight: 700;">十二 问答式活动</span></p><br style="word-wrap: break-word; font-family: Tahoma, 'Microsoft Yahei', Simsun; font-size: 16px; line-height: 22.399999618530273px; background-color: #ffffff;" /><p align="left" style="word-wrap: break-word; margin: 0px; padding: 0px; font-family: Tahoma, 'Microsoft Yahei', Simsun; font-size: 16px; line-height: 22.399999618530273px; background-color: #ffffff;">特点：</p><br style="word-wrap: break-word; font-family: Tahoma, 'Microsoft Yahei', Simsun; font-size: 16px; line-height: 22.399999618530273px; background-color: #ffffff;" /><p align="left" style="word-wrap: break-word; margin: 0px; padding: 0px; font-family: Tahoma, 'Microsoft Yahei', Simsun; font-size: 16px; line-height: 22.399999618530273px; background-color: #ffffff;">通过答题的形式降低活动参与门槛，给予所有玩家公平竞技的机会；问题内容多样化，可间接宣传游戏</p><br style="word-wrap: break-word; font-family: Tahoma, 'Microsoft Yahei', Simsun; font-size: 16px; line-height: 22.399999618530273px; background-color: #ffffff;" /><p align="left" style="word-wrap: break-word; margin: 0px; padding: 0px; font-family: Tahoma, 'Microsoft Yahei', Simsun; font-size: 16px; line-height: 22.399999618530273px; background-color: #ffffff;">缺点：</p><br style="word-wrap: break-word; font-family: Tahoma, 'Microsoft Yahei', Simsun; font-size: 16px; line-height: 22.399999618530273px; background-color: #ffffff;" /><p align="left" style="word-wrap: break-word; margin: 0px; padding: 0px; font-family: Tahoma, 'Microsoft Yahei', Simsun; font-size: 16px; line-height: 22.399999618530273px; background-color: #ffffff;">需要大量题库支持</p><br style="word-wrap: break-word; font-family: Tahoma, 'Microsoft Yahei', Simsun; font-size: 16px; line-height: 22.399999618530273px; background-color: #ffffff;" /><p align="left" style="word-wrap: break-word; margin: 0px; padding: 0px; font-family: Tahoma, 'Microsoft Yahei', Simsun; font-size: 16px; line-height: 22.399999618530273px; background-color: #ffffff;"><span style="word-wrap: break-word; font-weight: 700;">十三 收集式活动</span></p><br style="word-wrap: break-word; font-family: Tahoma, 'Microsoft Yahei', Simsun; font-size: 16px; line-height: 22.399999618530273px; background-color: #ffffff;" /><p align="left" style="word-wrap: break-word; margin: 0px; padding: 0px; font-family: Tahoma, 'Microsoft Yahei', Simsun; font-size: 16px; line-height: 22.399999618530273px; background-color: #ffffff;">特点：</p><br style="word-wrap: break-word; font-family: Tahoma, 'Microsoft Yahei', Simsun; font-size: 16px; line-height: 22.399999618530273px; background-color: #ffffff;" /><p align="left" style="word-wrap: break-word; margin: 0px; padding: 0px; font-family: Tahoma, 'Microsoft Yahei', Simsun; font-size: 16px; line-height: 22.399999618530273px; background-color: #ffffff;">1）收集指定道具的概率可控，兑换奖励的风险认为可控</p><br style="word-wrap: break-word; font-family: Tahoma, 'Microsoft Yahei', Simsun; font-size: 16px; line-height: 22.399999618530273px; background-color: #ffffff;" /><p align="left" style="word-wrap: break-word; margin: 0px; padding: 0px; font-family: Tahoma, 'Microsoft Yahei', Simsun; font-size: 16px; line-height: 22.399999618530273px; background-color: #ffffff;">2）参与门槛低，玩家易于接受</p><br style="word-wrap: break-word; font-family: Tahoma, 'Microsoft Yahei', Simsun; font-size: 16px; line-height: 22.399999618530273px; background-color: #ffffff;" /><p align="left" style="word-wrap: break-word; margin: 0px; padding: 0px; font-family: Tahoma, 'Microsoft Yahei', Simsun; font-size: 16px; line-height: 22.399999618530273px; background-color: #ffffff;">缺点：</p><br style="word-wrap: break-word; font-family: Tahoma, 'Microsoft Yahei', Simsun; font-size: 16px; line-height: 22.399999618530273px; background-color: #ffffff;" /><p align="left" style="word-wrap: break-word; margin: 0px; padding: 0px; font-family: Tahoma, 'Microsoft Yahei', Simsun; font-size: 16px; line-height: 22.399999618530273px; background-color: #ffffff;">需要开发支持</p><br style="word-wrap: break-word; font-family: Tahoma, 'Microsoft Yahei', Simsun; font-size: 16px; line-height: 22.399999618530273px; background-color: #ffffff;" /><p align="left" style="word-wrap: break-word; margin: 0px; padding: 0px; font-family: Tahoma, 'Microsoft Yahei', Simsun; font-size: 16px; line-height: 22.399999618530273px; background-color: #ffffff;"><span style="word-wrap: break-word; font-weight: 700;">十四 寻宝式活动</span></p><br style="word-wrap: break-word; font-family: Tahoma, 'Microsoft Yahei', Simsun; font-size: 16px; line-height: 22.399999618530273px; background-color: #ffffff;" /><p align="left" style="word-wrap: break-word; margin: 0px; padding: 0px; font-family: Tahoma, 'Microsoft Yahei', Simsun; font-size: 16px; line-height: 22.399999618530273px; background-color: #ffffff;">特点：</p><br style="word-wrap: break-word; font-family: Tahoma, 'Microsoft Yahei', Simsun; font-size: 16px; line-height: 22.399999618530273px; background-color: #ffffff;" /><p align="left" style="word-wrap: break-word; margin: 0px; padding: 0px; font-family: Tahoma, 'Microsoft Yahei', Simsun; font-size: 16px; line-height: 22.399999618530273px; background-color: #ffffff;">纯粹娱乐性，增加玩家在线乐趣，可通过其他辅助活动形式来优化此类活动，侧面提升销售和消耗。</p><br style="word-wrap: break-word; font-family: Tahoma, 'Microsoft Yahei', Simsun; font-size: 16px; line-height: 22.399999618530273px; background-color: #ffffff;" /><p align="left" style="word-wrap: break-word; margin: 0px; padding: 0px; font-family: Tahoma, 'Microsoft Yahei', Simsun; font-size: 16px; line-height: 22.399999618530273px; background-color: #ffffff;">缺点：</p><br style="word-wrap: break-word; font-family: Tahoma, 'Microsoft Yahei', Simsun; font-size: 16px; line-height: 22.399999618530273px; background-color: #ffffff;" /><p align="left" style="word-wrap: break-word; margin: 0px; padding: 0px; font-family: Tahoma, 'Microsoft Yahei', Simsun; font-size: 16px; line-height: 22.399999618530273px; background-color: #ffffff;">活动本身对销售和消耗没有直接影响力</p><br style="word-wrap: break-word; font-family: Tahoma, 'Microsoft Yahei', Simsun; font-size: 16px; line-height: 22.399999618530273px; background-color: #ffffff;" /><p align="left" style="word-wrap: break-word; margin: 0px; padding: 0px; font-family: Tahoma, 'Microsoft Yahei', Simsun; font-size: 16px; line-height: 22.399999618530273px; background-color: #ffffff;"><span style="word-wrap: break-word; font-weight: 700;">十五 送信式活动</span></p><br style="word-wrap: break-word; font-family: Tahoma, 'Microsoft Yahei', Simsun; font-size: 16px; line-height: 22.399999618530273px; background-color: #ffffff;" /><p align="left" style="word-wrap: break-word; margin: 0px; padding: 0px; font-family: Tahoma, 'Microsoft Yahei', Simsun; font-size: 16px; line-height: 22.399999618530273px; background-color: #ffffff;">特点：</p><br style="word-wrap: break-word; font-family: Tahoma, 'Microsoft Yahei', Simsun; font-size: 16px; line-height: 22.399999618530273px; background-color: #ffffff;" /><p align="left" style="word-wrap: break-word; margin: 0px; padding: 0px; font-family: Tahoma, 'Microsoft Yahei', Simsun; font-size: 16px; line-height: 22.399999618530273px; background-color: #ffffff;">此类任务已经过多泛滥，俗称泡菜任务，因此活动形式结合当前时政热点让玩家切身感受与众不同才是上策。</p><p align="left" style="word-wrap: break-word; margin: 0px; padding: 0px; font-family: Tahoma, 'Microsoft Yahei', Simsun; font-size: 16px; line-height: 22.399999618530273px; background-color: #ffffff;">缺点:</p><br style="word-wrap: break-word; font-family: Tahoma, 'Microsoft Yahei', Simsun; font-size: 16px; line-height: 22.399999618530273px; background-color: #ffffff;" /><p align="left" style="word-wrap: break-word; margin: 0px; padding: 0px; font-family: Tahoma, 'Microsoft Yahei', Simsun; font-size: 16px; line-height: 22.399999618530273px; background-color: #ffffff;">需要适时开发</p><br style="word-wrap: break-word; font-family: Tahoma, 'Microsoft Yahei', Simsun; font-size: 16px; line-height: 22.399999618530273px; background-color: #ffffff;" /><p align="left" style="word-wrap: break-word; margin: 0px; padding: 0px; font-family: Tahoma, 'Microsoft Yahei', Simsun; font-size: 16px; line-height: 22.399999618530273px; background-color: #ffffff;"><span style="word-wrap: break-word; font-weight: 700;">十六 在线式活动</span></p><br style="word-wrap: break-word; font-family: Tahoma, 'Microsoft Yahei', Simsun; font-size: 16px; line-height: 22.399999618530273px; background-color: #ffffff;" /><p align="left" style="word-wrap: break-word; margin: 0px; padding: 0px; font-family: Tahoma, 'Microsoft Yahei', Simsun; font-size: 16px; line-height: 22.399999618530273px; background-color: #ffffff;">特点：</p><br style="word-wrap: break-word; font-family: Tahoma, 'Microsoft Yahei', Simsun; font-size: 16px; line-height: 22.399999618530273px; background-color: #ffffff;" /><p align="left" style="word-wrap: break-word; margin: 0px; padding: 0px; font-family: Tahoma, 'Microsoft Yahei', Simsun; font-size: 16px; line-height: 22.399999618530273px; background-color: #ffffff;">能极大地提升在线各项指标数据，因目前市面上已经很少有计时收费游戏，因此此类活动需要搭载销售活动来促进销售提升。</p><br style="word-wrap: break-word; font-family: Tahoma, 'Microsoft Yahei', Simsun; font-size: 16px; line-height: 22.399999618530273px; background-color: #ffffff;" /><p align="left" style="word-wrap: break-word; margin: 0px; padding: 0px; font-family: Tahoma, 'Microsoft Yahei', Simsun; font-size: 16px; line-height: 22.399999618530273px; background-color: #ffffff;">缺点：</p><br style="word-wrap: break-word; font-family: Tahoma, 'Microsoft Yahei', Simsun; font-size: 16px; line-height: 22.399999618530273px; background-color: #ffffff;" /><p align="left" style="word-wrap: break-word; margin: 0px; padding: 0px; font-family: Tahoma, 'Microsoft Yahei', Simsun; font-size: 16px; line-height: 22.399999618530273px; background-color: #ffffff;">容易产生大量小号挂机，对游戏部分数值指标产生偏移影响</p><br style="word-wrap: break-word; font-family: Tahoma, 'Microsoft Yahei', Simsun; font-size: 16px; line-height: 22.399999618530273px; background-color: #ffffff;" /><p align="left" style="word-wrap: break-word; margin: 0px; padding: 0px; font-family: Tahoma, 'Microsoft Yahei', Simsun; font-size: 16px; line-height: 22.399999618530273px; background-color: #ffffff;"><span style="word-wrap: break-word; font-weight: 700;">十七 商城式活动</span></p><br style="word-wrap: break-word; font-family: Tahoma, 'Microsoft Yahei', Simsun; font-size: 16px; line-height: 22.399999618530273px; background-color: #ffffff;" /><p align="left" style="word-wrap: break-word; margin: 0px; padding: 0px; font-family: Tahoma, 'Microsoft Yahei', Simsun; font-size: 16px; line-height: 22.399999618530273px; background-color: #ffffff;">特点：</p><br style="word-wrap: break-word; font-family: Tahoma, 'Microsoft Yahei', Simsun; font-size: 16px; line-height: 22.399999618530273px; background-color: #ffffff;" /><p align="left" style="word-wrap: break-word; margin: 0px; padding: 0px; font-family: Tahoma, 'Microsoft Yahei', Simsun; font-size: 16px; line-height: 22.399999618530273px; background-color: #ffffff;">商品打折、显示销售、捆绑销售、限人购买、团购等形式最终目的还是扩大销售额，提升付费率</p><br style="word-wrap: break-word; font-family: Tahoma, 'Microsoft Yahei', Simsun; font-size: 16px; line-height: 22.399999618530273px; background-color: #ffffff;" /><p align="left" style="word-wrap: break-word; margin: 0px; padding: 0px; font-family: Tahoma, 'Microsoft Yahei', Simsun; font-size: 16px; line-height: 22.399999618530273px; background-color: #ffffff;">缺点：</p><br style="word-wrap: break-word; font-family: Tahoma, 'Microsoft Yahei', Simsun; font-size: 16px; line-height: 22.399999618530273px; background-color: #ffffff;" /><p align="left" style="word-wrap: break-word; margin: 0px; padding: 0px; font-family: Tahoma, 'Microsoft Yahei', Simsun; font-size: 16px; line-height: 22.399999618530273px; background-color: #ffffff;">容易造成玩家大量充值后积压消耗品，应结合消耗类活动同时进行。</p><br style="word-wrap: break-word; font-family: Tahoma, 'Microsoft Yahei', Simsun; font-size: 16px; line-height: 22.399999618530273px; background-color: #ffffff;" /><p align="left" style="word-wrap: break-word; margin: 0px; padding: 0px; font-family: Tahoma, 'Microsoft Yahei', Simsun; font-size: 16px; line-height: 22.399999618530273px; background-color: #ffffff;"><span style="word-wrap: break-word; font-weight: 700;">十八 自有式活动</span></p><br style="word-wrap: break-word; font-family: Tahoma, 'Microsoft Yahei', Simsun; font-size: 16px; line-height: 22.399999618530273px; background-color: #ffffff;" /><p align="left" style="word-wrap: break-word; margin: 0px; padding: 0px; font-family: Tahoma, 'Microsoft Yahei', Simsun; font-size: 16px; line-height: 22.399999618530273px; background-color: #ffffff;">其实再多的方法论都需要结合实践，以上所述，所有的活动类型都有利弊，选择一个最适合你的，才是最好的。或许选取其中几式，大一套漂亮的组合拳，下一个武林霸主就是你。</p><br style="word-wrap: break-word; font-family: Tahoma, 'Microsoft Yahei', Simsun; font-size: 16px; line-height: 22.399999618530273px; background-color: #ffffff;" /><p align="center" style="word-wrap: break-word; margin: 0px; padding: 0px; font-family: Tahoma, 'Microsoft Yahei', Simsun; font-size: 16px; line-height: 22.399999618530273px; background-color: #ffffff;"><ignore_js_op style="word-wrap: break-word;"><img id="aimg_69707" src="http://www.gameres.com/data/attachment/forum/201309/28/1535537scc3ja7qcmqfd3o.jpg" zoomfile="data/attachment/forum/201309/28/1535537scc3ja7qcmqfd3o.jpg" file="data/attachment/forum/201309/28/1535537scc3ja7qcmqfd3o.jpg" width="440" inpost="1" alt="2013092709090426992.jpg" title="2013092709090426992.jpg" style="word-wrap: break-word; cursor: pointer; max-width: 620px;" /></ignore_js_op></p><div><ignore_js_op style="word-wrap: break-word;"><br /></ignore_js_op></div><img src ="http://www.cppblog.com/API/aggbug/203692.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/API/" target="_blank">C++技术中心</a> 2013-10-12 18:29 <a href="http://www.cppblog.com/API/archive/2013/10/12/203692.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>游戏收藏</title><link>http://www.cppblog.com/API/archive/2013/09/06/203044.html</link><dc:creator>C++技术中心</dc:creator><author>C++技术中心</author><pubDate>Fri, 06 Sep 2013 02:16:00 GMT</pubDate><guid>http://www.cppblog.com/API/archive/2013/09/06/203044.html</guid><wfw:comment>http://www.cppblog.com/API/comments/203044.html</wfw:comment><comments>http://www.cppblog.com/API/archive/2013/09/06/203044.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cppblog.com/API/comments/commentRss/203044.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/API/services/trackbacks/203044.html</trackback:ping><description><![CDATA[<div>1 魂斗罗<br />(1) <strong><font color="#333333" face="Helvetica">首先是如心使臂的操控感<br /></font></strong>&nbsp;&nbsp;&nbsp; <span style="text-transform: none; background-color: rgb(255,255,255); text-indent: 0px; letter-spacing: normal; display: inline !important; font: 14px/25px 'Helvetica Neue', Helvetica, STheiti, 微软雅黑, 黑体, Arial, Tahoma, sans-serif, serif; white-space: normal; float: none; color: rgb(51,51,51); word-spacing: 0px; -webkit-text-stroke-width: 0px">人物的跳跃动作，极为符合物理规律，起跳期的强劲、快到高点时的渐慢、坠下深崖时的加速下落(呃，这点跟操控性无关了)，都与人的生活感受一般无二，这就使得操控时具有很强的预期</span>。<span style="text-transform: none; background-color: rgb(255,255,255); text-indent: 0px; letter-spacing: normal; display: inline !important; font: 14px/25px 'Helvetica Neue', Helvetica, STheiti, 微软雅黑, 黑体, Arial, Tahoma, sans-serif, serif; white-space: normal; float: none; color: rgb(51,51,51); word-spacing: 0px; -webkit-text-stroke-width: 0px">马里奥的奔跑带有一定惯性，这可能更为拟真，但却过于复杂，提高了游戏的操控难度，也降低了通过游戏获得&#8220;爽&#8221;感的程度</span>。<br />(2)<strong><font color="#333333" face="Helvetica">魂斗罗的第二个乐趣，在于&#8220;隐藏&#8221;<br /></font></strong>&nbsp;&nbsp;&nbsp; <span style="text-transform: none; background-color: rgb(255,255,255); text-indent: 0px; letter-spacing: normal; display: inline !important; font: 14px/25px 'Helvetica Neue', Helvetica, STheiti, 微软雅黑, 黑体, Arial, Tahoma, sans-serif, serif; white-space: normal; float: none; color: rgb(51,51,51); word-spacing: 0px; -webkit-text-stroke-width: 0px">上下下左右左右BA，可以增加到30条命!这就是那个著名的KONAMI秘诀</span>。<span style="text-transform: none; background-color: rgb(255,255,255); text-indent: 0px; letter-spacing: normal; display: inline !important; font: 14px/25px 'Helvetica Neue', Helvetica, STheiti, 微软雅黑, 黑体, Arial, Tahoma, sans-serif, serif; white-space: normal; float: none; color: rgb(51,51,51); word-spacing: 0px; -webkit-text-stroke-width: 0px">既然有这个秘诀在，自然会激发大家的好奇：这个游戏是否还有别的秘技(那时候还不知道有个专用术语叫后门)?大概在魂斗罗流行一年之后，一个新的传言流传开：在某个地方进行特定操作，就可以进入全新的八个关卡，称为&#8220;水下八关&#8221;。这让刚刚玩腻了游戏的同学们再次摩拳擦掌，展开新的征程</span>。<br />(3)<strong><font color="#333333" face="Helvetica">社交</font></strong><br /><span style="text-transform: none; background-color: rgb(255,255,255); text-indent: 0px; letter-spacing: normal; display: inline !important; font: 14px/25px 'Helvetica Neue', Helvetica, STheiti, 微软雅黑, 黑体, Arial, Tahoma, sans-serif, serif; white-space: normal; float: none; color: rgb(51,51,51); word-spacing: 0px; -webkit-text-stroke-width: 0px">熟练地背诵30条命密码，几乎是那时候的社交通行证。而由于主机稀少，拥有者每次游戏叫谁不叫谁、让谁多玩会，就成为考验交情与人脉的试金石。在当时，一次玩魂斗罗的机会，几乎可以作为硬通货在孩子们之间流转</span></div>
<p>&nbsp;</p>
<p>&nbsp;</p>
<p>2.内置商店的4个设计建议</p>
<p>(1)游戏中的虚拟经济世界就会提升用户的进步感和成就感，并创造长期有趣的游戏体验<br />(2)<br />1.将商店置于用户容易找到的地方，令其成为游戏循环中的一个自然环节<br />&nbsp; <br />2.创造用户每天都要在游戏中使用的道具</p>
<p align="left">*让道具填充游戏商店（就好像猴子需要香蕉，汽车需要汽油&#8230;&#8230;）</p>
<p align="left">*为其定价，以便用户能够在玩游戏数分钟内赚到足够的钱来购买</p>
<p align="left">*创造使用有趣的道具，让游戏更有吸引力</p>
<p align="left">*赋予道具能量，以便玩家更易于赢取钱币</p>
<p><br />3.让商店体验充满趣味<br />想想让商店充满吸引力和趣味的法子&#8212;&#8212;扩大商品种类，增加神秘感，保持新鲜感。在商品多样性方面，我们以《CSR Racing》为例，该游戏中的商店拥有200多万种出售道具。你还可以通过使用一些小伎俩，在特定时间才推出某些限时道具来增加神秘感。这有助于持续吸引用户，保持他们对商店所提供商品的好奇心。最后一点是通过增加道具来保持商店新鲜感，解琐道具，推出节日和限量版道具<br /><br />4.使用&#8220;等待机制&#8221;<br />如果你真的想设计好商店，就必须限制用户不断玩游戏的能力，并添加一些短暂的间歇。这是一个有点棘手的技巧，所以你必须小心行事，确保不惹恼用户<br />例如《Candy Crush Saga》中的&#8220;生命&#8221;以及其他游戏中的能量和能源。当用户耗尽资源时，就可以在三种方法中做出选择：购买更多，不玩游戏或者暂后再返回游戏。如果你遵从以下建议，也许就能够让想打发时间的用户继续呆在游戏中并访问商店。<br /><br />5.平衡虚拟商品性能及售价<br />想象一下这种情景&#8212;&#8212;用户支付一美元并购买了些东西，现在游戏对他来说就变得太容易了，他马上就失去了兴趣。这对于用户来说是种糟糕的体验，也是一种糟糕的生意。用户通常是冲动消费，但如果没有看到其中价值，第二天就会清醒过秋，并且再也不会掏钱了。这里的矛盾在于，冲动消费通常会让事情变得简单，但最终用户还是希望游戏仍然具有挑战性。<br />这里有一些破解方法，但在我们讨论之前，重要的还是认识到平衡需要大量的调整，这意味着你需要重复衡量和调整，直到达标为止。你需要整合一个分析工具，或使用植入分析工具的商店平台。<br /></p>
<p align="left"><strong>以下就是相关建议：</strong></p>
<p align="left">*方法1：增加难度。如果游戏难度曲线波度够大，道具的影响就很有限，玩家就会跳过一些关卡，但马上就会遇到更大的挑战。</p>
<p align="left">*方法2：增加随机性。加入运气元素来帮助玩家克服挑战会限制虚拟商品的能力。《龙与地下城》、橄榄球和足球就是一些含有一定运气元素，令较弱的对手也有机会获胜的典型代表。这正是它们如此风靡的原因之一。</p>
<p align="left">*方法3：消耗资源的道具。如果你遵从本文的第二个建议，就说明你已经有了一些玩家经常消耗的资源。如果你在游戏中设计了其他经常消耗资源的虚拟商品，就会限制用户频繁使用这些道具。例如，机关枪的威力远超过手枪，并且在任何战役中无往不胜，但却很耗损子弹。如果你只有5颗子弹，机关枪也就沦为无用之物了。</p>
<p><br />&nbsp;</p><img src ="http://www.cppblog.com/API/aggbug/203044.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/API/" target="_blank">C++技术中心</a> 2013-09-06 10:16 <a href="http://www.cppblog.com/API/archive/2013/09/06/203044.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>游戏服务器</title><link>http://www.cppblog.com/API/archive/2013/07/05/201535.html</link><dc:creator>C++技术中心</dc:creator><author>C++技术中心</author><pubDate>Fri, 05 Jul 2013 08:58:00 GMT</pubDate><guid>http://www.cppblog.com/API/archive/2013/07/05/201535.html</guid><wfw:comment>http://www.cppblog.com/API/comments/201535.html</wfw:comment><comments>http://www.cppblog.com/API/archive/2013/07/05/201535.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cppblog.com/API/comments/commentRss/201535.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/API/services/trackbacks/201535.html</trackback:ping><description><![CDATA[Xeon E5506 2.13G 16G内存 硬盘100-400<img src ="http://www.cppblog.com/API/aggbug/201535.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/API/" target="_blank">C++技术中心</a> 2013-07-05 16:58 <a href="http://www.cppblog.com/API/archive/2013/07/05/201535.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>C++调用python配置及编译出现的问题</title><link>http://www.cppblog.com/API/archive/2013/04/25/199720.html</link><dc:creator>C++技术中心</dc:creator><author>C++技术中心</author><pubDate>Thu, 25 Apr 2013 15:33:00 GMT</pubDate><guid>http://www.cppblog.com/API/archive/2013/04/25/199720.html</guid><wfw:comment>http://www.cppblog.com/API/comments/199720.html</wfw:comment><comments>http://www.cppblog.com/API/archive/2013/04/25/199720.html#Feedback</comments><slash:comments>2</slash:comments><wfw:commentRss>http://www.cppblog.com/API/comments/commentRss/199720.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/API/services/trackbacks/199720.html</trackback:ping><description><![CDATA[<p align="left">&nbsp;</p>
<p style="padding-bottom: 0px; widows: 2; text-transform: none; background-color: rgb(255,255,255); text-indent: 0px; margin: 0px; padding-left: 0px; letter-spacing: normal; padding-right: 0px; font: 14px/26px Arial; white-space: normal; orphans: 2; color: rgb(0,0,0); word-spacing: 0px; padding-top: 0px; -webkit-text-size-adjust: auto; -webkit-text-stroke-width: 0px" align="left">环境配置</p>
<p style="padding-bottom: 0px; widows: 2; text-transform: none; background-color: rgb(255,255,255); text-indent: 0px; margin: 0px; padding-left: 0px; letter-spacing: normal; padding-right: 0px; font: 14px/26px Arial; white-space: normal; orphans: 2; color: rgb(0,0,0); word-spacing: 0px; padding-top: 0px; -webkit-text-size-adjust: auto; -webkit-text-stroke-width: 0px" align="left"><span style="font-size: 24px">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="font-size: 12px"><span class="Apple-converted-space">&nbsp;</span>（1）</span><span style="font-size: 14px">python的lib 以及头文件分别<span>加到vc的include/lib directories中去。</span></span></p>
<p style="padding-bottom: 0px; widows: 2; text-transform: none; background-color: rgb(255,255,255); text-indent: 0px; margin: 0px; padding-left: 0px; letter-spacing: normal; padding-right: 0px; font: 14px/26px Arial; white-space: normal; orphans: 2; color: rgb(0,0,0); word-spacing: 0px; padding-top: 0px; -webkit-text-size-adjust: auto; -webkit-text-stroke-width: 0px" align="left"><span style="font-size: 14px"><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;（2）写完编译后出现<span style="color: rgb(0,0,0)">找不到python33_d.lib 错误，出现这个错误的原因是：python_d.lib是 库的调试后形式，当我们以debug模式编译工程时，python就用这个lib文件，但是这个文件是不可用 的。对于这点，最快的办法就是强制要求python在任何情况下都是用非调试版本，就可以了。</span></span></span></p>
<p style="padding-bottom: 0px; widows: 2; text-transform: none; background-color: rgb(255,255,255); text-indent: 0px; margin: 0px; padding-left: 0px; letter-spacing: normal; padding-right: 0px; font: 14px/26px Arial; white-space: normal; orphans: 2; color: rgb(0,0,0); word-spacing: 0px; padding-top: 0px; -webkit-text-size-adjust: auto; -webkit-text-stroke-width: 0px" align="left">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 所以要做的是：</p>
<div style="text-align: left; widows: 2; text-transform: none; background-color: rgb(255,255,255); text-indent: 0px; letter-spacing: normal; font: 14px/26px Arial; white-space: normal; orphans: 2; color: rgb(0,0,0); word-spacing: 0px; -webkit-text-size-adjust: auto; -webkit-text-stroke-width: 0px">
<ul style="padding-bottom: 0px; line-height: 25px; margin: 5px 0px 5px 40px; padding-left: 0px; padding-right: 0px; padding-top: 0px"><li style="line-height: 25px"><span style="line-height: 18px; background-color: transparent; font-family: 微软雅黑; font-size: 12px">修改python头文件pyconfig.h的配置，修改指向调试库文件的参数，由</span></li></ul></div>
<div style="text-align: left; widows: 2; text-transform: none; background-color: rgb(255,255,255); text-indent: 0px; letter-spacing: normal; font: 14px/26px Arial; white-space: normal; orphans: 2; color: rgb(0,0,0); word-spacing: 0px; -webkit-text-size-adjust: auto; -webkit-text-stroke-width: 0px"><pre style="border-bottom: rgb(136,136,136) 1px solid; border-left: rgb(136,136,136) 1px solid; padding-bottom: 2px; line-height: 25px; padding-left: 2px; padding-right: 2px; word-wrap: break-word; white-space: pre-wrap; border-top: rgb(136,136,136) 1px solid; border-right: rgb(136,136,136) 1px solid; padding-top: 2px" class="prettyprint" name="code"><p style="padding-bottom: 0px; line-height: 25px; margin: 0px 0px 10px; padding-left: 0px; padding-right: 0px; padding-top: 0px"></p><div style="text-align: left; background-color: rgb(231,229,220); margin: 18px 0px; width: 1112px; font-family: Consolas, 'Courier New', Courier, mono, serif; font-size: 12px; overflow: auto; padding-top: 1px" class="dp-highlighter bg_cpp"><div style="padding-left: 45px" class="bar"><div style="border-left: rgb(108,226,108) 3px solid; padding-bottom: 10px; background-color: rgb(248,248,248); padding-left: 10px; padding-right: 8px; font: 9px Verdana, Geneva, Arial, Helvetica, sans-serif; color: silver; padding-top: 3px" class="tools"><strong>[cpp]</strong> <a style="background-image: url(http://static.blog.csdn.net/scripts/SyntaxHighlighter/styles/images/default/ico_plain.gif); border-bottom: medium none; border-left: medium none; padding-bottom: 1px; text-indent: -2000px; margin: 0px 10px 0px 0px; padding-left: 1px; width: 16px; padding-right: 1px; display: inline-block; background-position: 0% 0%; height: 16px; color: rgb(160,160,160); font-size: 9px; border-top: medium none; border-right: medium none; text-decoration: none; padding-top: 1px" class="ViewSource" title="view plain" onclick="dp.sh.Toolbar.Command('ViewSource',this);return false;" href="http://blog.csdn.net/bluels01/article/details/8511982#">view plain</a><a style="background-image: url(http://static.blog.csdn.net/scripts/SyntaxHighlighter/styles/images/default/ico_copy.gif); border-bottom: medium none; border-left: medium none; padding-bottom: 1px; text-indent: -2000px; margin: 0px 10px 0px 0px; padding-left: 1px; width: 16px; padding-right: 1px; display: inline-block; background-position: 0% 0%; height: 16px; color: rgb(160,160,160); font-size: 9px; border-top: medium none; border-right: medium none; text-decoration: none; padding-top: 1px" class="CopyToClipboard" title="copy" onclick="dp.sh.Toolbar.Command('CopyToClipboard',this);return false;" href="http://blog.csdn.net/bluels01/article/details/8511982#">copy</a><div style="z-index: 99; position: absolute; width: 18px; height: 18px; top: 653px; left: 380px"><embed id="ZeroClipboardMovie_1" height="18" name="ZeroClipboardMovie_1" type="application/x-shockwave-flash" align="center" pluginspage="http://www.macromedia.com/go/getflashplayer" width="18" src="http://static.blog.csdn.net/scripts/ZeroClipboard/ZeroClipboard.swf" loop="false" menu="false" quality="best" bgcolor="#ffffff" allowscriptaccess="always" allowfullscreen="false" flashvars="id=1&amp;width=18&amp;height=18" wmode="transparent"></div></div></div><ol style="border-bottom: medium none; border-left: medium none; padding-bottom: 0px; background-color: rgb(255,255,255); list-style-type: decimal; margin: 0px 0px 1px 45px; padding-left: 0px; padding-right: 0px; color: rgb(92,92,92); border-top: medium none; border-right: medium none; padding-top: 0px" class="dp-cpp"><li style="list-style-position: outside; border-bottom-style: none; border-left: rgb(108,226,108) 3px solid; padding-bottom: 0px !important; line-height: 18px; background-color: rgb(255,255,255); list-style-type: decimal-leading-zero; margin: 0px; padding-left: 10px !important; padding-right: 3px !important; border-top-style: none; color: ; border-right-style: none; padding-top: 0px !important" class="alt"><span style="border-bottom: medium none; border-left: medium none; padding-bottom: 0px; margin: 0px; padding-left: 0px; padding-right: 0px; color: black; border-top: medium none; border-right: medium none; padding-top: 0px"><span style="border-bottom: medium none; border-left: medium none; padding-bottom: 0px; margin: 0px; padding-left: 0px; padding-right: 0px; color: gray; border-top: medium none; border-right: medium none; padding-top: 0px" class="preprocessor">#&nbsp;&nbsp;&nbsp;if&nbsp;defined(_DEBUG)</span><span style="border-bottom: medium none; border-left: medium none; padding-bottom: 0px; margin: 0px; padding-left: 0px; padding-right: 0px; color: black; border-top: medium none; border-right: medium none; padding-top: 0px">&nbsp;&nbsp;</span></span></li><li style="list-style-position: outside; border-bottom-style: none; border-left: rgb(108,226,108) 3px solid; padding-bottom: 0px !important; line-height: 18px; background-color: rgb(248,248,248); list-style-type: decimal-leading-zero; margin: 0px; padding-left: 10px !important; padding-right: 3px !important; border-top-style: none; color: rgb(92,92,92); border-right-style: none; padding-top: 0px !important"><span style="border-bottom: medium none; border-left: medium none; padding-bottom: 0px; margin: 0px; padding-left: 0px; padding-right: 0px; color: black; border-top: medium none; border-right: medium none; padding-top: 0px"><span style="border-bottom: medium none; border-left: medium none; padding-bottom: 0px; margin: 0px; padding-left: 0px; padding-right: 0px; color: gray; border-top: medium none; border-right: medium none; padding-top: 0px" class="preprocessor">#&nbsp;&nbsp;&nbsp;&nbsp;pragma&nbsp;comment(lib,"python33_d.lib")</span><span style="border-bottom: medium none; border-left: medium none; padding-bottom: 0px; margin: 0px; padding-left: 0px; padding-right: 0px; color: black; border-top: medium none; border-right: medium none; padding-top: 0px">&nbsp;&nbsp;</span></span></li><li style="list-style-position: outside; border-bottom-style: none; border-left: rgb(108,226,108) 3px solid; padding-bottom: 0px !important; line-height: 18px; background-color: rgb(255,255,255); list-style-type: decimal-leading-zero; margin: 0px; padding-left: 10px !important; padding-right: 3px !important; border-top-style: none; color: ; border-right-style: none; padding-top: 0px !important" class="alt"><span style="border-bottom: medium none; border-left: medium none; padding-bottom: 0px; margin: 0px; padding-left: 0px; padding-right: 0px; color: black; border-top: medium none; border-right: medium none; padding-top: 0px"><span style="border-bottom: medium none; border-left: medium none; padding-bottom: 0px; margin: 0px; padding-left: 0px; padding-right: 0px; color: gray; border-top: medium none; border-right: medium none; padding-top: 0px" class="preprocessor">#&nbsp;&nbsp;&nbsp;elif&nbsp;defined(Py_LIMITED_API)</span><span style="border-bottom: medium none; border-left: medium none; padding-bottom: 0px; margin: 0px; padding-left: 0px; padding-right: 0px; color: black; border-top: medium none; border-right: medium none; padding-top: 0px">&nbsp;&nbsp;</span></span></li><li style="list-style-position: outside; border-bottom-style: none; border-left: rgb(108,226,108) 3px solid; padding-bottom: 0px !important; line-height: 18px; background-color: rgb(248,248,248); list-style-type: decimal-leading-zero; margin: 0px; padding-left: 10px !important; padding-right: 3px !important; border-top-style: none; color: rgb(92,92,92); border-right-style: none; padding-top: 0px !important"><span style="border-bottom: medium none; border-left: medium none; padding-bottom: 0px; margin: 0px; padding-left: 0px; padding-right: 0px; color: black; border-top: medium none; border-right: medium none; padding-top: 0px"><span style="border-bottom: medium none; border-left: medium none; padding-bottom: 0px; margin: 0px; padding-left: 0px; padding-right: 0px; color: gray; border-top: medium none; border-right: medium none; padding-top: 0px" class="preprocessor">#&nbsp;&nbsp;&nbsp;&nbsp;pragma&nbsp;comment(lib,"python3.lib")</span><span style="border-bottom: medium none; border-left: medium none; padding-bottom: 0px; margin: 0px; padding-left: 0px; padding-right: 0px; color: black; border-top: medium none; border-right: medium none; padding-top: 0px">&nbsp;&nbsp;</span></span></li><li style="list-style-position: outside; border-bottom-style: none; border-left: rgb(108,226,108) 3px solid; padding-bottom: 0px !important; line-height: 18px; background-color: rgb(255,255,255); list-style-type: decimal-leading-zero; margin: 0px; padding-left: 10px !important; padding-right: 3px !important; border-top-style: none; color: ; border-right-style: none; padding-top: 0px !important" class="alt"><span style="border-bottom: medium none; border-left: medium none; padding-bottom: 0px; margin: 0px; padding-left: 0px; padding-right: 0px; color: black; border-top: medium none; border-right: medium none; padding-top: 0px"><span style="border-bottom: medium none; border-left: medium none; padding-bottom: 0px; margin: 0px; padding-left: 0px; padding-right: 0px; color: gray; border-top: medium none; border-right: medium none; padding-top: 0px" class="preprocessor">#&nbsp;&nbsp;&nbsp;else</span><span style="border-bottom: medium none; border-left: medium none; padding-bottom: 0px; margin: 0px; padding-left: 0px; padding-right: 0px; color: black; border-top: medium none; border-right: medium none; padding-top: 0px">&nbsp;&nbsp;</span></span></li><li style="list-style-position: outside; border-bottom-style: none; border-left: rgb(108,226,108) 3px solid; padding-bottom: 0px !important; line-height: 18px; background-color: rgb(248,248,248); list-style-type: decimal-leading-zero; margin: 0px; padding-left: 10px !important; padding-right: 3px !important; border-top-style: none; color: rgb(92,92,92); border-right-style: none; padding-top: 0px !important"><span style="border-bottom: medium none; border-left: medium none; padding-bottom: 0px; margin: 0px; padding-left: 0px; padding-right: 0px; color: black; border-top: medium none; border-right: medium none; padding-top: 0px"><span style="border-bottom: medium none; border-left: medium none; padding-bottom: 0px; margin: 0px; padding-left: 0px; padding-right: 0px; color: gray; border-top: medium none; border-right: medium none; padding-top: 0px" class="preprocessor">#&nbsp;&nbsp;&nbsp;&nbsp;pragma&nbsp;comment(lib,"python33.lib")</span><span style="border-bottom: medium none; border-left: medium none; padding-bottom: 0px; margin: 0px; padding-left: 0px; padding-right: 0px; color: black; border-top: medium none; border-right: medium none; padding-top: 0px">&nbsp;&nbsp;</span></span></li><li style="list-style-position: outside; border-bottom-style: none; border-left: rgb(108,226,108) 3px solid; padding-bottom: 0px !important; line-height: 18px; background-color: rgb(255,255,255); list-style-type: decimal-leading-zero; margin: 0px; padding-left: 10px !important; padding-right: 3px !important; border-top-style: none; color: ; border-right-style: none; padding-top: 0px !important" class="alt"><span style="border-bottom: medium none; border-left: medium none; padding-bottom: 0px; margin: 0px; padding-left: 0px; padding-right: 0px; color: black; border-top: medium none; border-right: medium none; padding-top: 0px"><span style="border-bottom: medium none; border-left: medium none; padding-bottom: 0px; margin: 0px; padding-left: 0px; padding-right: 0px; color: gray; border-top: medium none; border-right: medium none; padding-top: 0px" class="preprocessor">#&nbsp;&nbsp;&nbsp;endif&nbsp;/*&nbsp;_DEBUG&nbsp;*/</span><span style="border-bottom: medium none; border-left: medium none; padding-bottom: 0px; margin: 0px; padding-left: 0px; padding-right: 0px; color: black; border-top: medium none; border-right: medium none; padding-top: 0px">&nbsp;&nbsp;</span></span></li></ol></div><br /></pre><span style="line-height: 23px; font-family: 微软雅黑; font-size: 12px">修改为：</span></div>
<div style="text-align: left; widows: 2; text-transform: none; background-color: rgb(255,255,255); text-indent: 0px; letter-spacing: normal; font: 14px/26px Arial; white-space: normal; orphans: 2; color: rgb(0,0,0); word-spacing: 0px; -webkit-text-size-adjust: auto; -webkit-text-stroke-width: 0px"><pre style="border-bottom: rgb(136,136,136) 1px solid; border-left: rgb(136,136,136) 1px solid; padding-bottom: 2px; line-height: 25px; padding-left: 2px; padding-right: 2px; word-wrap: break-word; white-space: pre-wrap; border-top: rgb(136,136,136) 1px solid; border-right: rgb(136,136,136) 1px solid; padding-top: 2px" class="prettyprint" name="code">&nbsp;<div style="text-align: left; background-color: rgb(231,229,220); margin: 18px 0px; width: 1112px; font-family: Consolas, 'Courier New', Courier, mono, serif; font-size: 12px; overflow: auto; padding-top: 1px" class="dp-highlighter bg_cpp"><div style="padding-left: 45px" class="bar"><div style="border-left: rgb(108,226,108) 3px solid; padding-bottom: 10px; background-color: rgb(248,248,248); padding-left: 10px; padding-right: 8px; font: 9px Verdana, Geneva, Arial, Helvetica, sans-serif; color: silver; padding-top: 3px" class="tools"><strong>[cpp]</strong> <a style="background-image: url(http://static.blog.csdn.net/scripts/SyntaxHighlighter/styles/images/default/ico_plain.gif); border-bottom: medium none; border-left: medium none; padding-bottom: 1px; text-indent: -2000px; margin: 0px 10px 0px 0px; padding-left: 1px; width: 16px; padding-right: 1px; display: inline-block; background-position: 0% 0%; height: 16px; color: rgb(160,160,160); font-size: 9px; border-top: medium none; border-right: medium none; text-decoration: none; padding-top: 1px" class="ViewSource" title="view plain" onclick="dp.sh.Toolbar.Command('ViewSource',this);return false;" href="http://blog.csdn.net/bluels01/article/details/8511982#">view plain</a><a style="background-image: url(http://static.blog.csdn.net/scripts/SyntaxHighlighter/styles/images/default/ico_copy.gif); border-bottom: medium none; border-left: medium none; padding-bottom: 1px; text-indent: -2000px; margin: 0px 10px 0px 0px; padding-left: 1px; width: 16px; padding-right: 1px; display: inline-block; background-position: 0% 0%; height: 16px; color: rgb(160,160,160); font-size: 9px; border-top: medium none; border-right: medium none; text-decoration: none; padding-top: 1px" class="CopyToClipboard" title="copy" onclick="dp.sh.Toolbar.Command('CopyToClipboard',this);return false;" href="http://blog.csdn.net/bluels01/article/details/8511982#">copy</a><div style="z-index: 99; position: absolute; width: 18px; height: 18px; top: 955px; left: 380px"><embed id="ZeroClipboardMovie_2" height="18" name="ZeroClipboardMovie_2" type="application/x-shockwave-flash" align="center" pluginspage="http://www.macromedia.com/go/getflashplayer" width="18" src="http://static.blog.csdn.net/scripts/ZeroClipboard/ZeroClipboard.swf" loop="false" menu="false" quality="best" bgcolor="#ffffff" allowscriptaccess="always" allowfullscreen="false" flashvars="id=2&amp;width=18&amp;height=18" wmode="transparent"></div></div></div><ol style="border-bottom: medium none; border-left: medium none; padding-bottom: 0px; background-color: rgb(255,255,255); list-style-type: decimal; margin: 0px 0px 1px 45px; padding-left: 0px; padding-right: 0px; color: rgb(92,92,92); border-top: medium none; border-right: medium none; padding-top: 0px" class="dp-cpp"><li style="list-style-position: outside; border-bottom-style: none; border-left: rgb(108,226,108) 3px solid; padding-bottom: 0px !important; line-height: 18px; background-color: rgb(255,255,255); list-style-type: decimal-leading-zero; margin: 0px; padding-left: 10px !important; padding-right: 3px !important; border-top-style: none; color: ; border-right-style: none; padding-top: 0px !important" class="alt"><span style="border-bottom: medium none; border-left: medium none; padding-bottom: 0px; margin: 0px; padding-left: 0px; padding-right: 0px; color: black; border-top: medium none; border-right: medium none; padding-top: 0px"><span style="border-bottom: medium none; border-left: medium none; padding-bottom: 0px; margin: 0px; padding-left: 0px; padding-right: 0px; color: gray; border-top: medium none; border-right: medium none; padding-top: 0px" class="preprocessor">#&nbsp;&nbsp;&nbsp;if&nbsp;defined(_DEBUG)</span><span style="border-bottom: medium none; border-left: medium none; padding-bottom: 0px; margin: 0px; padding-left: 0px; padding-right: 0px; color: black; border-top: medium none; border-right: medium none; padding-top: 0px">&nbsp;&nbsp;</span></span></li><li style="list-style-position: outside; border-bottom-style: none; border-left: rgb(108,226,108) 3px solid; padding-bottom: 0px !important; line-height: 18px; background-color: rgb(248,248,248); list-style-type: decimal-leading-zero; margin: 0px; padding-left: 10px !important; padding-right: 3px !important; border-top-style: none; color: rgb(92,92,92); border-right-style: none; padding-top: 0px !important"><span style="border-bottom: medium none; border-left: medium none; padding-bottom: 0px; margin: 0px; padding-left: 0px; padding-right: 0px; color: black; border-top: medium none; border-right: medium none; padding-top: 0px"><span style="border-bottom: medium none; border-left: medium none; padding-bottom: 0px; margin: 0px; padding-left: 0px; padding-right: 0px; color: gray; border-top: medium none; border-right: medium none; padding-top: 0px" class="preprocessor">#&nbsp;&nbsp;&nbsp;&nbsp;pragma&nbsp;comment(lib,"python33.lib")</span><span style="border-bottom: medium none; border-left: medium none; padding-bottom: 0px; margin: 0px; padding-left: 0px; padding-right: 0px; color: black; border-top: medium none; border-right: medium none; padding-top: 0px">&nbsp;&nbsp;</span></span></li><li style="list-style-position: outside; border-bottom-style: none; border-left: rgb(108,226,108) 3px solid; padding-bottom: 0px !important; line-height: 18px; background-color: rgb(255,255,255); list-style-type: decimal-leading-zero; margin: 0px; padding-left: 10px !important; padding-right: 3px !important; border-top-style: none; color: ; border-right-style: none; padding-top: 0px !important" class="alt"><span style="border-bottom: medium none; border-left: medium none; padding-bottom: 0px; margin: 0px; padding-left: 0px; padding-right: 0px; color: black; border-top: medium none; border-right: medium none; padding-top: 0px"><span style="border-bottom: medium none; border-left: medium none; padding-bottom: 0px; margin: 0px; padding-left: 0px; padding-right: 0px; color: gray; border-top: medium none; border-right: medium none; padding-top: 0px" class="preprocessor">#&nbsp;&nbsp;&nbsp;elif&nbsp;defined(Py_LIMITED_API)</span><span style="border-bottom: medium none; border-left: medium none; padding-bottom: 0px; margin: 0px; padding-left: 0px; padding-right: 0px; color: black; border-top: medium none; border-right: medium none; padding-top: 0px">&nbsp;&nbsp;</span></span></li><li style="list-style-position: outside; border-bottom-style: none; border-left: rgb(108,226,108) 3px solid; padding-bottom: 0px !important; line-height: 18px; background-color: rgb(248,248,248); list-style-type: decimal-leading-zero; margin: 0px; padding-left: 10px !important; padding-right: 3px !important; border-top-style: none; color: rgb(92,92,92); border-right-style: none; padding-top: 0px !important"><span style="border-bottom: medium none; border-left: medium none; padding-bottom: 0px; margin: 0px; padding-left: 0px; padding-right: 0px; color: black; border-top: medium none; border-right: medium none; padding-top: 0px"><span style="border-bottom: medium none; border-left: medium none; padding-bottom: 0px; margin: 0px; padding-left: 0px; padding-right: 0px; color: gray; border-top: medium none; border-right: medium none; padding-top: 0px" class="preprocessor">#&nbsp;&nbsp;&nbsp;&nbsp;pragma&nbsp;comment(lib,"python3.lib")</span><span style="border-bottom: medium none; border-left: medium none; padding-bottom: 0px; margin: 0px; padding-left: 0px; padding-right: 0px; color: black; border-top: medium none; border-right: medium none; padding-top: 0px">&nbsp;&nbsp;</span></span></li><li style="list-style-position: outside; border-bottom-style: none; border-left: rgb(108,226,108) 3px solid; padding-bottom: 0px !important; line-height: 18px; background-color: rgb(255,255,255); list-style-type: decimal-leading-zero; margin: 0px; padding-left: 10px !important; padding-right: 3px !important; border-top-style: none; color: ; border-right-style: none; padding-top: 0px !important" class="alt"><span style="border-bottom: medium none; border-left: medium none; padding-bottom: 0px; margin: 0px; padding-left: 0px; padding-right: 0px; color: black; border-top: medium none; border-right: medium none; padding-top: 0px"><span style="border-bottom: medium none; border-left: medium none; padding-bottom: 0px; margin: 0px; padding-left: 0px; padding-right: 0px; color: gray; border-top: medium none; border-right: medium none; padding-top: 0px" class="preprocessor">#&nbsp;&nbsp;&nbsp;else</span><span style="border-bottom: medium none; border-left: medium none; padding-bottom: 0px; margin: 0px; padding-left: 0px; padding-right: 0px; color: black; border-top: medium none; border-right: medium none; padding-top: 0px">&nbsp;&nbsp;</span></span></li><li style="list-style-position: outside; border-bottom-style: none; border-left: rgb(108,226,108) 3px solid; padding-bottom: 0px !important; line-height: 18px; background-color: rgb(248,248,248); list-style-type: decimal-leading-zero; margin: 0px; padding-left: 10px !important; padding-right: 3px !important; border-top-style: none; color: rgb(92,92,92); border-right-style: none; padding-top: 0px !important"><span style="border-bottom: medium none; border-left: medium none; padding-bottom: 0px; margin: 0px; padding-left: 0px; padding-right: 0px; color: black; border-top: medium none; border-right: medium none; padding-top: 0px"><span style="border-bottom: medium none; border-left: medium none; padding-bottom: 0px; margin: 0px; padding-left: 0px; padding-right: 0px; color: gray; border-top: medium none; border-right: medium none; padding-top: 0px" class="preprocessor">#&nbsp;&nbsp;&nbsp;&nbsp;pragma&nbsp;comment(lib,"python33.lib")</span><span style="border-bottom: medium none; border-left: medium none; padding-bottom: 0px; margin: 0px; padding-left: 0px; padding-right: 0px; color: black; border-top: medium none; border-right: medium none; padding-top: 0px">&nbsp;&nbsp;</span></span></li><li style="list-style-position: outside; border-bottom-style: none; border-left: rgb(108,226,108) 3px solid; padding-bottom: 0px !important; line-height: 18px; background-color: rgb(255,255,255); list-style-type: decimal-leading-zero; margin: 0px; padding-left: 10px !important; padding-right: 3px !important; border-top-style: none; color: ; border-right-style: none; padding-top: 0px !important" class="alt"><span style="border-bottom: medium none; border-left: medium none; padding-bottom: 0px; margin: 0px; padding-left: 0px; padding-right: 0px; color: black; border-top: medium none; border-right: medium none; padding-top: 0px"><span style="border-bottom: medium none; border-left: medium none; padding-bottom: 0px; margin: 0px; padding-left: 0px; padding-right: 0px; color: gray; border-top: medium none; border-right: medium none; padding-top: 0px" class="preprocessor">#&nbsp;&nbsp;&nbsp;endif&nbsp;/*&nbsp;_DEBUG&nbsp;*/</span><span style="border-bottom: medium none; border-left: medium none; padding-bottom: 0px; margin: 0px; padding-left: 0px; padding-right: 0px; color: black; border-top: medium none; border-right: medium none; padding-top: 0px">&nbsp;&nbsp;</span></span></li></ol></div><br /></pre></div>
<p style="padding-bottom: 0px; widows: 2; text-transform: none; background-color: rgb(255,255,255); text-indent: 0px; margin: 0px; padding-left: 0px; letter-spacing: normal; padding-right: 0px; font: 14px/26px Arial; white-space: normal; orphans: 2; color: rgb(0,0,0); word-spacing: 0px; padding-top: 0px; -webkit-text-size-adjust: auto; -webkit-text-stroke-width: 0px" align="left">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; (3)在Visual Studio中编译时必须注意，若Python为64bit而VS C++ Project Config里的platform是32bit，会报错：</p>
<p style="padding-bottom: 0px; widows: 2; text-transform: none; background-color: rgb(255,255,255); text-indent: 0px; margin: 0px; padding-left: 0px; letter-spacing: normal; padding-right: 0px; font: 14px/26px Arial; white-space: normal; orphans: 2; color: rgb(0,0,0); word-spacing: 0px; padding-top: 0px; -webkit-text-size-adjust: auto; -webkit-text-stroke-width: 0px" align="left">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</p>
<p align="left"></p>
<div style="text-align: left; widows: 2; text-transform: none; background-color: rgb(231,229,220); text-indent: 0px; margin: 18px 0px; width: 1118px; letter-spacing: normal; font: 12px/26px Consolas, 'Courier New', Courier, mono, serif; white-space: normal; orphans: 2; color: rgb(0,0,0); overflow: auto; word-spacing: 0px; padding-top: 1px; -webkit-text-size-adjust: auto; -webkit-text-stroke-width: 0px" class="dp-highlighter bg_cpp">
<div style="padding-left: 45px" class="bar">
<div style="border-left: rgb(108,226,108) 3px solid; padding-bottom: 10px; background-color: rgb(248,248,248); padding-left: 10px; padding-right: 8px; font: 9px Verdana, Geneva, Arial, Helvetica, sans-serif; color: silver; padding-top: 3px" class="tools"><strong>[cpp]</strong><span class="Apple-converted-space">&nbsp;</span><a style="background-image: url(http://static.blog.csdn.net/scripts/SyntaxHighlighter/styles/images/default/ico_plain.gif); border-bottom: medium none; border-left: medium none; padding-bottom: 1px; text-indent: -2000px; margin: 0px 10px 0px 0px; padding-left: 1px; width: 16px; padding-right: 1px; display: inline-block; background-position: 0% 0%; height: 16px; color: rgb(160,160,160); font-size: 9px; border-top: medium none; border-right: medium none; text-decoration: none; padding-top: 1px" class="ViewSource" title="view plain" onclick="dp.sh.Toolbar.Command('ViewSource',this);return false;" href="http://blog.csdn.net/bluels01/article/details/8511982#">view plain</a><a style="background-image: url(http://static.blog.csdn.net/scripts/SyntaxHighlighter/styles/images/default/ico_copy.gif); border-bottom: medium none; border-left: medium none; padding-bottom: 1px; text-indent: -2000px; margin: 0px 10px 0px 0px; padding-left: 1px; width: 16px; padding-right: 1px; display: inline-block; background-position: 0% 0%; height: 16px; color: rgb(160,160,160); font-size: 9px; border-top: medium none; border-right: medium none; text-decoration: none; padding-top: 1px" class="CopyToClipboard" title="copy" onclick="dp.sh.Toolbar.Command('CopyToClipboard',this);return false;" href="http://blog.csdn.net/bluels01/article/details/8511982#">copy</a> 
<div style="z-index: 99; position: absolute; width: 18px; height: 18px; top: 1244px; left: 377px"><embed id="ZeroClipboardMovie_3" height="18" name="ZeroClipboardMovie_3" type="application/x-shockwave-flash" align="center" pluginspage="http://www.macromedia.com/go/getflashplayer" width="18" src="http://static.blog.csdn.net/scripts/ZeroClipboard/ZeroClipboard.swf" loop="false" menu="false" quality="best" bgcolor="#ffffff" allowscriptaccess="always" allowfullscreen="false" flashvars="id=3&amp;width=18&amp;height=18" wmode="transparent"></div></div></div>
<ol style="border-bottom: medium none; border-left: medium none; padding-bottom: 0px; background-color: rgb(255,255,255); list-style-type: decimal; margin: 0px 0px 1px 45px; padding-left: 0px; padding-right: 0px; color: rgb(92,92,92); border-top: medium none; border-right: medium none; padding-top: 0px" class="dp-cpp"><li style="list-style-position: outside; border-bottom-style: none; border-left: rgb(108,226,108) 3px solid; padding-bottom: 0px !important; line-height: 18px; background-color: rgb(255,255,255); list-style-type: decimal-leading-zero; margin: 0px; padding-left: 10px !important; padding-right: 3px !important; border-top-style: none; color: ; border-right-style: none; padding-top: 0px !important" class="alt"><span style="border-bottom: medium none; border-left: medium none; padding-bottom: 0px; margin: 0px; padding-left: 0px; padding-right: 0px; color: black; border-top: medium none; border-right: medium none; padding-top: 0px"><span style="border-bottom: medium none; border-left: medium none; padding-bottom: 0px; margin: 0px; padding-left: 0px; padding-right: 0px; color: black; border-top: medium none; border-right: medium none; padding-top: 0px">Error&nbsp;&nbsp;&nbsp;1&nbsp;&nbsp;&nbsp;error&nbsp;LNK2019:&nbsp;unresolved&nbsp;external&nbsp;symbol&nbsp;__imp__Py_Finalize&nbsp;referenced&nbsp;in&nbsp;function&nbsp;_main&nbsp;&nbsp;&nbsp;&nbsp;</span></span></li><li style="list-style-position: outside; border-bottom-style: none; border-left: rgb(108,226,108) 3px solid; padding-bottom: 0px !important; line-height: 18px; background-color: rgb(248,248,248); list-style-type: decimal-leading-zero; margin: 0px; padding-left: 10px !important; padding-right: 3px !important; border-top-style: none; color: rgb(92,92,92); border-right-style: none; padding-top: 0px !important"><span style="border-bottom: medium none; border-left: medium none; padding-bottom: 0px; margin: 0px; padding-left: 0px; padding-right: 0px; color: black; border-top: medium none; border-right: medium none; padding-top: 0px">Error&nbsp;&nbsp;&nbsp;2&nbsp;&nbsp;&nbsp;error&nbsp;LNK2019:&nbsp;unresolved&nbsp;external&nbsp;symbol&nbsp;__imp__PyEval_CallObjectWithKeywords&nbsp;referenced&nbsp;in&nbsp;function&nbsp;_main&nbsp;&nbsp;&nbsp;&nbsp;</span></li><li style="list-style-position: outside; border-bottom-style: none; border-left: rgb(108,226,108) 3px solid; padding-bottom: 0px !important; line-height: 18px; background-color: rgb(255,255,255); list-style-type: decimal-leading-zero; margin: 0px; padding-left: 10px !important; padding-right: 3px !important; border-top-style: none; color: ; border-right-style: none; padding-top: 0px !important" class="alt"><span style="border-bottom: medium none; border-left: medium none; padding-bottom: 0px; margin: 0px; padding-left: 0px; padding-right: 0px; color: black; border-top: medium none; border-right: medium none; padding-top: 0px">Error&nbsp;&nbsp;&nbsp;3&nbsp;&nbsp;&nbsp;error&nbsp;LNK2019:&nbsp;unresolved&nbsp;external&nbsp;symbol&nbsp;__imp__PyObject_GetAttrString&nbsp;referenced&nbsp;in&nbsp;function&nbsp;_main&nbsp;&nbsp;&nbsp;&nbsp;</span></li><li style="list-style-position: outside; border-bottom-style: none; border-left: rgb(108,226,108) 3px solid; padding-bottom: 0px !important; line-height: 18px; background-color: rgb(248,248,248); list-style-type: decimal-leading-zero; margin: 0px; padding-left: 10px !important; padding-right: 3px !important; border-top-style: none; color: rgb(92,92,92); border-right-style: none; padding-top: 0px !important"><span style="border-bottom: medium none; border-left: medium none; padding-bottom: 0px; margin: 0px; padding-left: 0px; padding-right: 0px; color: black; border-top: medium none; border-right: medium none; padding-top: 0px">Error&nbsp;&nbsp;&nbsp;4&nbsp;&nbsp;&nbsp;error&nbsp;LNK2019:&nbsp;unresolved&nbsp;external&nbsp;symbol&nbsp;__imp__PyImport_ImportModule&nbsp;referenced&nbsp;in&nbsp;function&nbsp;_main&nbsp;&nbsp;&nbsp;&nbsp;</span></li><li style="list-style-position: outside; border-bottom-style: none; border-left: rgb(108,226,108) 3px solid; padding-bottom: 0px !important; line-height: 18px; background-color: rgb(255,255,255); list-style-type: decimal-leading-zero; margin: 0px; padding-left: 10px !important; padding-right: 3px !important; border-top-style: none; color: ; border-right-style: none; padding-top: 0px !important" class="alt"><span style="border-bottom: medium none; border-left: medium none; padding-bottom: 0px; margin: 0px; padding-left: 0px; padding-right: 0px; color: black; border-top: medium none; border-right: medium none; padding-top: 0px">Error&nbsp;&nbsp;&nbsp;5&nbsp;&nbsp;&nbsp;error&nbsp;LNK2019:&nbsp;unresolved&nbsp;external&nbsp;symbol&nbsp;__imp__Py_Initialize&nbsp;referenced&nbsp;in&nbsp;function&nbsp;_main&nbsp;&nbsp;&nbsp;&nbsp;</span></li><li style="list-style-position: outside; border-bottom-style: none; border-left: rgb(108,226,108) 3px solid; padding-bottom: 0px !important; line-height: 18px; background-color: rgb(248,248,248); list-style-type: decimal-leading-zero; margin: 0px; padding-left: 10px !important; padding-right: 3px !important; border-top-style: none; color: rgb(92,92,92); border-right-style: none; padding-top: 0px !important"><span style="border-bottom: medium none; border-left: medium none; padding-bottom: 0px; margin: 0px; padding-left: 0px; padding-right: 0px; color: black; border-top: medium none; border-right: medium none; padding-top: 0px">Error&nbsp;&nbsp;&nbsp;6&nbsp;&nbsp;&nbsp;error&nbsp;LNK1120:&nbsp;5&nbsp;unresolved&nbsp;externals&nbsp;&nbsp;&nbsp;&nbsp;</span></li></ol></div>
<p align="left"></p>
<p style="padding-bottom: 0px; widows: 2; text-transform: none; background-color: rgb(255,255,255); text-indent: 0px; margin: 0px; padding-left: 0px; letter-spacing: normal; padding-right: 0px; font: 14px/26px Arial; white-space: normal; orphans: 2; color: rgb(0,0,0); word-spacing: 0px; padding-top: 0px; -webkit-text-size-adjust: auto; -webkit-text-stroke-width: 0px" align="left">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 注意修改自己的platform</p>
<p align="left"><br class="Apple-interchange-newline" /></p><img src ="http://www.cppblog.com/API/aggbug/199720.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/API/" target="_blank">C++技术中心</a> 2013-04-25 23:33 <a href="http://www.cppblog.com/API/archive/2013/04/25/199720.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>python3简单入门</title><link>http://www.cppblog.com/API/archive/2013/04/25/199708.html</link><dc:creator>C++技术中心</dc:creator><author>C++技术中心</author><pubDate>Thu, 25 Apr 2013 08:24:00 GMT</pubDate><guid>http://www.cppblog.com/API/archive/2013/04/25/199708.html</guid><wfw:comment>http://www.cppblog.com/API/comments/199708.html</wfw:comment><comments>http://www.cppblog.com/API/archive/2013/04/25/199708.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cppblog.com/API/comments/commentRss/199708.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/API/services/trackbacks/199708.html</trackback:ping><description><![CDATA[&nbsp;&nbsp;&nbsp;&nbsp; 摘要: 转 Python3中文教程Python已经是3.1版本了,与时俱进更新教程.本文适合有Java编程经验的程序员快速熟悉Python本文程序在windows xp+python3.1a1 测试通过.本文提到的idle指python shell,即安装python后你在菜单看到的IDLE(python gui)在idle里ctrl+n可以打开一个新窗口,输入源码后ctrl+s可以保存,...&nbsp;&nbsp;<a href='http://www.cppblog.com/API/archive/2013/04/25/199708.html'>阅读全文</a><img src ="http://www.cppblog.com/API/aggbug/199708.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/API/" target="_blank">C++技术中心</a> 2013-04-25 16:24 <a href="http://www.cppblog.com/API/archive/2013/04/25/199708.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>memcached使用的几个限制 </title><link>http://www.cppblog.com/API/archive/2012/12/06/196039.html</link><dc:creator>C++技术中心</dc:creator><author>C++技术中心</author><pubDate>Thu, 06 Dec 2012 08:07:00 GMT</pubDate><guid>http://www.cppblog.com/API/archive/2012/12/06/196039.html</guid><wfw:comment>http://www.cppblog.com/API/comments/196039.html</wfw:comment><comments>http://www.cppblog.com/API/archive/2012/12/06/196039.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cppblog.com/API/comments/commentRss/196039.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/API/services/trackbacks/196039.html</trackback:ping><description><![CDATA[<h3 class="title pre fs1"><span class="tcnt"><font size="5" face="微软雅黑">有关memcached使用的几个限制</font></span> </h3>
<div></div>
<div class="nbw-blog-start"></div>
<div class="bct fc05 fc11 nbw-blog ztag js-fs2" __1354780579242__="ev_6053704689">
<div>memcached自身有几个比较重要的限制，尤其是其中的过期时间限制，得小心，否则很容易踩到地雷：</div>
<div><br /></div>
<div>1）单个缓存值大小限制：</div>
<div>memcached单个缓存值限制为1M(1000000bytes)，超过这个限制的时候会出如下错误：</div>
<div>ValueError: Values may not be more than 1000000 bytes in length; received 2000000 bytes</div>
<div><br /></div>
<div>2）key长度限制：</div>
<div>memcache的keys限制为250 bytes，超过这个长度会报错：</div>
<div>ValueError: Keys may not be more than 250 bytes in length, received 14670 bytes</div>
<div><br /></div>
<div><font color="#ff0000">3）expire过期时间限制：</font></div>
<div>
<div>
<div><span style="line-height: 22px">过期时间设置有两种方式：</span></div>
<div><span style="line-height: 22px">1、可使用 unix 时间戳格式，即距离1970.01.01 00：00：00的时间偏移量（单位为秒）</span></div>
<div><span style="line-height: 22px">2、距离当前时间的时间间隔</span> <span style="line-height: 22px">（单位也为秒）</span> </div>
<div><font color="#ff0000"><span style="line-height: 22px">设为</span> <span style="line-height: 22px">距离当前时间的时间间隔</span><span style="line-height: 22px"> </span><span style="line-height: 22px">时不能大于 2592000（30天），</span><span style="line-height: 22px">如果时间值大于</span><span style="line-height: 22px">2592000</span><span style="line-height: 22px"> </span><span style="line-height: 22px">，那么memcached会把时间理解为unix时间戳格式也就是距离1970.01.01的秒数偏移量。</span>0 为永不过期。</font></div>
<div>这个问题要特别注意，当我们设置的时间间隔大于 <span style="line-height: 22px; color: rgb(255,0,0)">2592000</span> ，例如<span style="line-height: 22px; color: rgb(255,0,0)">2592010</span>，写进去的数据会被认为是unix<span style="line-height: 22px">时间戳格式，数据刚写进去</span>已经过期，相当于导致数据写不进去，而更糟糕的是对于这种情况memcached服务端和客户端驱动都不会抛出exception或者打印任何警告信息，因为对memcached来说这样的时间值也是合理的。</div>
<div>P.S. 该expire的限制某似是从memcached 1.2.6以后才有体现，之前的版本如1.2.1中没有观察到这种现象。</div></div></div></div><img src ="http://www.cppblog.com/API/aggbug/196039.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/API/" target="_blank">C++技术中心</a> 2012-12-06 16:07 <a href="http://www.cppblog.com/API/archive/2012/12/06/196039.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>网游开发注意事项</title><link>http://www.cppblog.com/API/archive/2012/08/07/186605.html</link><dc:creator>C++技术中心</dc:creator><author>C++技术中心</author><pubDate>Tue, 07 Aug 2012 14:34:00 GMT</pubDate><guid>http://www.cppblog.com/API/archive/2012/08/07/186605.html</guid><wfw:comment>http://www.cppblog.com/API/comments/186605.html</wfw:comment><comments>http://www.cppblog.com/API/archive/2012/08/07/186605.html#Feedback</comments><slash:comments>1</slash:comments><wfw:commentRss>http://www.cppblog.com/API/comments/commentRss/186605.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/API/services/trackbacks/186605.html</trackback:ping><description><![CDATA[<div>
<h2>网络游戏服务器端一些注意事项</h2>
<div id="content" clearfix?="" text-content="">
<p>&nbsp;</p>
<p>一：IOCP和Epoll之间的异同。<br />异：<br />1：IOCP是WINDOWS系统下使用。Epoll是Linux系统下使用。<br />2：IOCP是IO操作完毕之后，通过Get函数获得一个完成的事件通知。<br />Epoll是当你希望进行一个IO操作时，向Epoll查询是否可读或者可写，若处于可读或可写状态后，Epoll会通过epoll_wait进行通知。<br />3：IOCP封装了异步的消息事件的通知机制，同时封装了部分IO操作。但Epoll仅仅封装了一个异步事件的通知机制，并不负责IO读写操作。Epoll保持了事件通知和IO操作间的独立性，更加简单灵活。<br />4： 基于上面的描述，我们可以知道Epoll不负责IO操作，所以它只告诉你当前可读可写了，并且将协议读写缓冲填充，由用户去读写控制，此时我们可以做出额 外的许多操作。IOCP则直接将IO通道里的读写操作都做完了才通知用户，当IO通道里发生了堵塞等状况我们是无法控制的。</p>
<p>同：<br />1：它们都是异步的事件驱动的网络模型。<br />2：它们都可以向底层进行指针数据传递，当返回事件时，除可通知事件类型外，还可以通知事件相关数据。</p>
<p>二：描述一下IOCP:<br />扯远点。首先传统服务器的网络IO流程如下：<br />接到一个客户端连接-&gt;创建一个线程负责这个连接的IO操作-&gt;持续对新线程进行数据处理-&gt;全部数据处理完毕-&gt;终止线程。<br />但是这样的设计代价是：<br />1：每个连接创建一个线程，将导致过多的线程。<br />2：维护线程所消耗的堆栈内存过大。<br />3：操作系统创建和销毁线程过大。<br />4：线程之间切换的上下文代价过大。<br />此时我们可以考虑使用线程池解决其中3和4的问题。这种传统的服务器网络结构称之为会话模型。<br />后来我们为防止大量线程的维护，创建了I/O模型，它被希望要求可以:<br />1：允许一个线程在不同时刻给多个客户端进行服务。<br />2：允许一个客户端在不同时间被多个线程服务。<br />这样做的话，我们的线程则会大幅度减少，这就要求以下两点：<br />1：客户端状态的分离，之前会话模式我们可以通过线程状态得知客户端状态，但现在客户端状态要通过其他方式获取。<br />2：I/O请求的分离。一个线程不再服务于一个客户端会话，则要求客户端对这个线程提交I/O处理请求。<br />那么就产生了这样一个模式，分为两部分:<br />1：会话状态管理模块。它负责接收到一个客户端连接，就创建一个会话状态。<br />2：当会话状态发生改变，例如断掉连接，接收到网络消息，就发送一个I/O请求给 I/O工作模块进行处理。<br />3：I/O工作模块接收到一个I/O请求后，从线程池里唤醒一个工作线程，让该工作线程处理这个I/O请求，处理完毕后，该工作线程继续挂起。<br />上面的做法，则将网络连接 和I/O工作线程分离为两个部分，相互通讯仅依靠 I/O请求。<br />此时可知有以下一些建议：<br />1：在进行I/O请求处理的工作线程是被唤醒的工作线程，一个CPU对应一个的话，可以最大化利用CPU。所以 活跃线程的个数 建议等于 硬件CPU个数。<br />2：工作线程我们开始创建了线程池，免除创建和销毁线程的代价。因为线程是对I/O进行操作的，且一一对应，那么当I/O全部并行时，工作线程必须满足I/O并行操作需求，所以 线程池内最大工作线程个数 建议大于或者等于 I/O并行个数。<br />3：但是我们可知CPU个数又限制了活跃的线程个数，那么线程池过大意义很低，所以按常规建议 线程池大小 等于 CPU个数*2 左右为佳。例如，8核服务器建议创建16个工作线程的线程池。<br />上面描述的依然是I/O模型并非IOCP，那么IOCP是什么呢，全称 IO完成端口。<br />它是一种WIN32的网络I/O模型，既包括了网络连接部分，也负责了部分的I/O操作功能，用于方便我们控制有并发性的网络I/O操作。它有如下特点：<br />1：它是一个WIN32内核对象，所以无法运行于Linux.<br />2：它自己负责维护了工作线程池，同时也负责了I/O通道的内存池。<br />3：它自己实现了线程的管理以及I/O请求通知，最小化的做到了线程的上下文切换。<br />4：它自己实现了线程的优化调度，提高了CPU和内存缓冲的使用率。<br />使用IOCP的基本步骤很简单：<br />1：创建IOCP对象，由它负责管理多个Socket和I/O请求。CreateIoCompletionPort需要将IOCP对象和IOCP句柄绑定。<br />2：创建一个工作线程池，以便Socket发送I/O请求给IOCP对象后，由这些工作线程进行I/O操作。注意，创建这些线程的时候，将这些线程绑定到IOCP上。<br />3：创建一个监听的socket。<br />4：轮询，当接收到了新的连接后，将socket和完成端口进行关联并且投递给IOCP一个I/O请求。注意：将Socket和IOCP进行关联的函数和创建IOCP的函数一样，都是CreateIoCompletionPort，不过注意传参必然是不同的。<br />5：因为是异步的，我们可以去做其他，等待IOCP将I/O操作完成会回馈我们一个消息，我们再进行处理。<br />其中需要知道的是：I/O请求被放在一个I/O请求队列里面，对，是队列，LIFO机制。当一个设备处理完I/O请求后，将会将这个完成后的I/O请求丢回IOCP的I/O完成队列。<br />我们应用程序则需要在GetQueuedCompletionStatus去询问IOCP，该I/O请求是否完成。<br />其中有一些特殊的事情要说明一下，我们有时有需要人工的去投递一些I/O请求，则需要使用PostQueuedCompletionStatus函数向IOCP投递一个I/O请求到它的请求队列中。</p>
<p><br />三：网络游戏服务器注意事项，优化措施<br />1：IO操作是最大的性能消耗点，注意优化余地很大。<br />2：算法数据结构。排序寻路算法的优化。list,vector,hashmap的选择。大数据寻址，不要考虑遍历，注意考虑hash.<br />3：内存管理。重载new/delete，内存池，对象池的处理。<br />4：数据的提前准备和即时计算。<br />5：CPU方面的统计监视。逻辑帧计数（应当50ms以内）。<br />6：预分配池减少切换和调度，预处理的线程池和连接池等。<br />7：基与消息队列的统计和信息监视框架。<br />8：CPU消耗排名：第一AOI同步，第二网络发包I/O操作，第三技能/BUFF判定计算处理，第四定时器的频率。<br />9：内存泄露检测，内存访问越界警惕，内存碎片的回收。<br />10：内存消耗排名：第一玩家对象包括其物品，第二网络数据缓冲。<br />11：注意32位和64位的内存容错。<br />12：减少不必要的分包发送。<br />13：减少重复包和重拷贝包的代价。<br />14：建议分紧急包（立刻发送）和非紧急包（定时轮训发送）。<br />15：带宽消耗排名：第一移动位置同步，第二对象加载，第三登陆突发包，第四状态机定时器消息。<br />16：客户端可做部分预判断机制，部分操作尽量分包发送。<br />17：大量玩家聚集时，部分非紧急包进行丢弃。<br />18：注意数据库单表内key数量。<br />19：活跃用户和非活跃用户的分割存取处理。<br />20：控制玩家操作对数据库的操作频率。<br />21：注意使用共享内存等方式对数据进行安全备份存储。<br />22：注意安全策略，对内网进行IP检查，对日志进行记录，任意两环点内均使用加密算法会更佳。<br />23：实时注意对网关，数据库等接口进行监察控制。<br />24：定时器应当存储一个队列，而非单向定位。<br />25：九宫格数据同步时，不需要直接进行九宫格的同步，对角色加一个AOI，基于圆方碰撞原理，抛弃不必要的格信息，可大幅节省。<br />26：客户端做部分的预测机制，服务器检测时注意时间戳问题。<br />27：定期心跳包，检查死链接是必要的。<br />28：为了实现更加负责多种类的AI，AI寻路独立服务器设计已经是必须的了。其次需要考虑的是聊天，同步。<br />29：服务器内网间可以考虑使用UDP。<br />30：注意所有内存池，对象池等的动态扩张分配。</p>
<p>1：以内存换取CPU的理念。<br />2：NPC不死理念。(只会disable)<br />3：动态扩展理念，负载均衡理念。<br />4：客户端不可信理念。<br />5：指针数据，消息均不可信理念。</p></div></div><img src ="http://www.cppblog.com/API/aggbug/186605.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/API/" target="_blank">C++技术中心</a> 2012-08-07 22:34 <a href="http://www.cppblog.com/API/archive/2012/08/07/186605.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item></channel></rss>