﻿<?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/</link><description /><language>zh-cn</language><lastBuildDate>Fri, 17 Apr 2026 10:00:46 GMT</lastBuildDate><pubDate>Fri, 17 Apr 2026 10:00:46 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>套接字read/write返回值</title><link>http://www.cppblog.com/API/archive/2017/12/12/215420.html</link><dc:creator>C++技术中心</dc:creator><author>C++技术中心</author><pubDate>Tue, 12 Dec 2017 02:32:00 GMT</pubDate><guid>http://www.cppblog.com/API/archive/2017/12/12/215420.html</guid><wfw:comment>http://www.cppblog.com/API/comments/215420.html</wfw:comment><comments>http://www.cppblog.com/API/archive/2017/12/12/215420.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cppblog.com/API/comments/commentRss/215420.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/API/services/trackbacks/215420.html</trackback:ping><description><![CDATA[<p style="box-sizing: border-box; margin: 0px; padding: 0px; word-wrap: break-word; word-break: normal; color: #454545; font-family: &quot;PingFang SC&quot;, &quot;Microsoft YaHei&quot;, SimHei, Arial, SimSun; font-size: 16px; background-color: #ffffff;">1、阻塞模式与非阻塞模式下recv的返回值各代表什么意思？有没有区别？（就我目前了解阻塞与非阻塞recv返回值没有区分，都是&nbsp;&lt;0：出错，=0：连接关闭，&gt;0接收到数据大小，特别：返回值&nbsp;&lt;0时并且(errno&nbsp;==&nbsp;EINTR&nbsp;||&nbsp;errno&nbsp;==&nbsp;EWOULDBLOCK&nbsp;||&nbsp;errno&nbsp;==&nbsp;EAGAIN)的情况下认为连接是正常的，继续接收。只是阻塞模式下recv会阻塞着接收数据，非阻塞模式下如果没有数据会返回，不会阻塞着读，因此需要&nbsp;循环读取</p><p style="box-sizing: border-box; margin: 0px; padding: 0px; word-wrap: break-word; word-break: normal; color: #454545; font-family: &quot;PingFang SC&quot;, &quot;Microsoft YaHei&quot;, SimHei, Arial, SimSun; font-size: 16px; background-color: #ffffff;">&nbsp;</p><p style="box-sizing: border-box; margin: 0px; padding: 0px; word-wrap: break-word; word-break: normal; color: #454545; font-family: &quot;PingFang SC&quot;, &quot;Microsoft YaHei&quot;, SimHei, Arial, SimSun; font-size: 16px; background-color: #ffffff;">&nbsp;</p><p style="box-sizing: border-box; margin: 0px; padding: 0px; word-wrap: break-word; word-break: normal; color: #454545; font-family: &quot;PingFang SC&quot;, &quot;Microsoft YaHei&quot;, SimHei, Arial, SimSun; font-size: 16px; background-color: #ffffff;">2、阻塞模式与非阻塞模式下write的返回值各代表什么意思？有没有区别？</p><p style="box-sizing: border-box; margin: 0px; padding: 0px; word-wrap: break-word; word-break: normal; color: #454545; font-family: &quot;PingFang SC&quot;, &quot;Microsoft YaHei&quot;, SimHei, Arial, SimSun; font-size: 16px; background-color: #ffffff;">阻塞与非阻塞write返回值没有区分，都是&nbsp;&lt;0：出错，=0：连接关闭，&gt;0发送数据大小，特别：返回值&nbsp;&lt;0时并且(errno&nbsp;==&nbsp;EINTR&nbsp;||&nbsp;errno&nbsp;==&nbsp;EWOULDBLOCK&nbsp;||&nbsp;errno&nbsp;==&nbsp;EAGAIN)的情况下认为连接是正常的，继续发送。只是阻塞模式下write会阻塞着发送数据，非阻塞模式下如果暂时无法发送数据会返回，不会阻塞着&nbsp;write，因此需要循环发送</p><p style="box-sizing: border-box; margin: 0px; padding: 0px; word-wrap: break-word; word-break: normal; color: #454545; font-family: &quot;PingFang SC&quot;, &quot;Microsoft YaHei&quot;, SimHei, Arial, SimSun; font-size: 16px; background-color: #ffffff;">&nbsp;</p><p style="box-sizing: border-box; margin: 0px; padding: 0px; word-wrap: break-word; word-break: normal; color: #454545; font-family: &quot;PingFang SC&quot;, &quot;Microsoft YaHei&quot;, SimHei, Arial, SimSun; font-size: 16px; background-color: #ffffff;">&nbsp;</p><p style="box-sizing: border-box; margin: 0px; padding: 0px; word-wrap: break-word; word-break: normal; color: #454545; font-family: &quot;PingFang SC&quot;, &quot;Microsoft YaHei&quot;, SimHei, Arial, SimSun; font-size: 16px; background-color: #ffffff;">3、阻塞模式下read返回值&nbsp;&lt;&nbsp;0&nbsp;&amp;&amp;&nbsp;errno&nbsp;!=&nbsp;EINTR&nbsp;&amp;&amp;&nbsp;errno&nbsp;!=&nbsp;EWOULDBLOCK&nbsp;&amp;&amp;&nbsp;errno&nbsp;!=&nbsp;EAGAIN时，连接异常，需要关闭，read返回值&nbsp;&lt;&nbsp;0&nbsp;&amp;&amp;&nbsp;(errno&nbsp;==&nbsp;EINTR&nbsp;||&nbsp;errno&nbsp;==&nbsp;EWOULDBLOCK&nbsp;||&nbsp;errno&nbsp;==&nbsp;EAGAIN)时表示没有数据，需要继续接收，如果返回值大于0表示接送到数据。&nbsp;</p><p style="box-sizing: border-box; margin: 0px; padding: 0px; word-wrap: break-word; word-break: normal; color: #454545; font-family: &quot;PingFang SC&quot;, &quot;Microsoft YaHei&quot;, SimHei, Arial, SimSun; font-size: 16px; background-color: #ffffff;">非阻塞模式下read返回值&nbsp;&lt;&nbsp;0表示没有数据，=&nbsp;0表示连接断开，&gt;&nbsp;0表示接收到数据。&nbsp;</p><p style="box-sizing: border-box; margin: 0px; padding: 0px; word-wrap: break-word; word-break: normal; color: #454545; font-family: &quot;PingFang SC&quot;, &quot;Microsoft YaHei&quot;, SimHei, Arial, SimSun; font-size: 16px; background-color: #ffffff;">这2种模式下的返回值是不是这么理解，有没有跟详细的理解或跟准确的说明？&nbsp;</p><p style="box-sizing: border-box; margin: 0px; padding: 0px; word-wrap: break-word; word-break: normal; color: #454545; font-family: &quot;PingFang SC&quot;, &quot;Microsoft YaHei&quot;, SimHei, Arial, SimSun; font-size: 16px; background-color: #ffffff;">&nbsp;</p><p style="box-sizing: border-box; margin: 0px; padding: 0px; word-wrap: break-word; word-break: normal; color: #454545; font-family: &quot;PingFang SC&quot;, &quot;Microsoft YaHei&quot;, SimHei, Arial, SimSun; font-size: 16px; background-color: #ffffff;">&nbsp;</p><p style="box-sizing: border-box; margin: 0px; padding: 0px; word-wrap: break-word; word-break: normal; color: #454545; font-family: &quot;PingFang SC&quot;, &quot;Microsoft YaHei&quot;, SimHei, Arial, SimSun; font-size: 16px; background-color: #ffffff;">4、阻塞模式与非阻塞模式下是否send返回值&nbsp;&lt;&nbsp;0&nbsp;&amp;&amp;&nbsp;(errno&nbsp;==&nbsp;EINTR&nbsp;||&nbsp;errno&nbsp;==&nbsp;EWOULDBLOCK&nbsp;||&nbsp;errno&nbsp;==&nbsp;EAGAIN)表示暂时发送失败，需要重试，如果send返回值&nbsp;&lt;=&nbsp;0,&nbsp;&amp;&amp;&nbsp;errno&nbsp;!=&nbsp;EINTR&nbsp;&amp;&amp;&nbsp;errno&nbsp;!=&nbsp;EWOULDBLOCK&nbsp;&amp;&amp;&nbsp;errno&nbsp;!=&nbsp;EAGAIN时，连接异常，需要关闭，如果send返回值&nbsp;&gt;&nbsp;0则表示发送了数据？send的返回值是否这么理解，阻塞模式与非阻塞模式下send返回值=0是否都是发送失败，还是那个模式下表示暂时不可发送，需要&nbsp;重发？</p><p style="box-sizing: border-box; margin: 0px; padding: 0px; word-wrap: break-word; word-break: normal; color: #454545; font-family: &quot;PingFang SC&quot;, &quot;Microsoft YaHei&quot;, SimHei, Arial, SimSun; font-size: 16px; background-color: #ffffff;">&nbsp;</p><p style="box-sizing: border-box; margin: 0px; padding: 0px; word-wrap: break-word; word-break: normal; color: #454545; font-family: &quot;PingFang SC&quot;, &quot;Microsoft YaHei&quot;, SimHei, Arial, SimSun; font-size: 16px; background-color: #ffffff;">&nbsp;</p><p style="box-sizing: border-box; margin: 0px; padding: 0px; word-wrap: break-word; word-break: normal; color: #454545; font-family: &quot;PingFang SC&quot;, &quot;Microsoft YaHei&quot;, SimHei, Arial, SimSun; font-size: 16px; background-color: #ffffff;">&nbsp;</p><p style="box-sizing: border-box; margin: 0px; padding: 0px; word-wrap: break-word; word-break: normal; color: #454545; font-family: &quot;PingFang SC&quot;, &quot;Microsoft YaHei&quot;, SimHei, Arial, SimSun; font-size: 16px; background-color: #ffffff;">&nbsp;</p><p style="box-sizing: border-box; margin: 0px; padding: 0px; word-wrap: break-word; word-break: normal; color: #454545; font-family: &quot;PingFang SC&quot;, &quot;Microsoft YaHei&quot;, SimHei, Arial, SimSun; font-size: 16px; background-color: #ffffff;">1.&nbsp;send函数</p><p style="box-sizing: border-box; margin: 0px; padding: 0px; word-wrap: break-word; word-break: normal; color: #454545; font-family: &quot;PingFang SC&quot;, &quot;Microsoft YaHei&quot;, SimHei, Arial, SimSun; font-size: 16px; background-color: #ffffff;">int&nbsp;send(&nbsp;SOCKET&nbsp;s,&nbsp;const&nbsp;char&nbsp;FAR&nbsp;*buf,&nbsp;int&nbsp;len,&nbsp;int&nbsp;flags&nbsp;);&nbsp;&nbsp;</p><p style="box-sizing: border-box; margin: 0px; padding: 0px; word-wrap: break-word; word-break: normal; color: #454545; font-family: &quot;PingFang SC&quot;, &quot;Microsoft YaHei&quot;, SimHei, Arial, SimSun; font-size: 16px; background-color: #ffffff;">不论是客户端还是服务器端应用程序都用send函数来向TCP连接的另一端发送数据。</p><p style="box-sizing: border-box; margin: 0px; padding: 0px; word-wrap: break-word; word-break: normal; color: #454545; font-family: &quot;PingFang SC&quot;, &quot;Microsoft YaHei&quot;, SimHei, Arial, SimSun; font-size: 16px; background-color: #ffffff;">客户端程序一般用send函数向服务器发送请求，而服务器则通常用send函数来向客户程序发送应答。</p><p style="box-sizing: border-box; margin: 0px; padding: 0px; word-wrap: break-word; word-break: normal; color: #454545; font-family: &quot;PingFang SC&quot;, &quot;Microsoft YaHei&quot;, SimHei, Arial, SimSun; font-size: 16px; background-color: #ffffff;">该函数的：</p><p style="box-sizing: border-box; margin: 0px; padding: 0px; word-wrap: break-word; word-break: normal; color: #454545; font-family: &quot;PingFang SC&quot;, &quot;Microsoft YaHei&quot;, SimHei, Arial, SimSun; font-size: 16px; background-color: #ffffff;">第一个参数指定发送端套接字描述符；</p><p style="box-sizing: border-box; margin: 0px; padding: 0px; word-wrap: break-word; word-break: normal; color: #454545; font-family: &quot;PingFang SC&quot;, &quot;Microsoft YaHei&quot;, SimHei, Arial, SimSun; font-size: 16px; background-color: #ffffff;">第二个参数指明一个存放应用程序要发送数据的缓冲区；</p><p style="box-sizing: border-box; margin: 0px; padding: 0px; word-wrap: break-word; word-break: normal; color: #454545; font-family: &quot;PingFang SC&quot;, &quot;Microsoft YaHei&quot;, SimHei, Arial, SimSun; font-size: 16px; background-color: #ffffff;">第三个参数指明实际要发送的数据的字节数；</p><p style="box-sizing: border-box; margin: 0px; padding: 0px; word-wrap: break-word; word-break: normal; color: #454545; font-family: &quot;PingFang SC&quot;, &quot;Microsoft YaHei&quot;, SimHei, Arial, SimSun; font-size: 16px; background-color: #ffffff;">第四个参数一般置0。</p><p style="box-sizing: border-box; margin: 0px; padding: 0px; word-wrap: break-word; word-break: normal; color: #454545; font-family: &quot;PingFang SC&quot;, &quot;Microsoft YaHei&quot;, SimHei, Arial, SimSun; font-size: 16px; background-color: #ffffff;">这里只描述同步Socket的send函数的执行流程。当调用该函数时，send先比较待发送数据的长度len和套接字s的发送缓冲的长度，如果len大于s的发送缓冲区的长度，该函数返回SOCKET_ERROR；如果len小于或者等于s的发送缓冲区的长度，那么send先检查协议&nbsp;是否正在发送s的发送缓冲中的数据，如果是就等待协议把数据发送完，如果协议还没有开始发送s的发送缓冲中的数据或者s的发送缓冲中没有数据，那么&nbsp;send就比较s的发送缓冲区的剩余空间和len，如果len大于剩余空间大小send就一直等待协议把s的发送缓冲中的数据发送完，如果len小于剩余&nbsp;空间大小send就仅仅把buf中的数据copy到剩余空间里（<span style="box-sizing: border-box; margin: 0px; padding: 0px; color: #000000;">注意并不是send</span>把s的发送缓冲中的数据传到连接的另一端的，而是协议传的，send仅仅是把buf中的数据copy到s的发送缓冲区的剩余空间里<span style="box-sizing: border-box; margin: 0px; padding: 0px; color: #000000;">）。</span>如果send函数copy数据成功，就返回实际copy的字节数，如果send在copy数据时出现错误，那么send就返回SOCKET_ERROR；如果send在等待协议传送数据时网络断开的话，那么send函数也返回SOCKET_ERROR。</p><p style="box-sizing: border-box; margin: 0px; padding: 0px; word-wrap: break-word; word-break: normal; color: #454545; font-family: &quot;PingFang SC&quot;, &quot;Microsoft YaHei&quot;, SimHei, Arial, SimSun; font-size: 16px; background-color: #ffffff;"><span style="box-sizing: border-box; margin: 0px; padding: 0px; color: #000000;">要注意send</span>函数把buf中的数据成功copy到s的发送缓冲的剩余空间里后它就返回了，但是此时这些数据并不一定马上被传到连接的另一端<span style="box-sizing: border-box; margin: 0px; padding: 0px; color: #000000;">。</span>如果协议在后续的传送过程中出现网络错误的话，那么下一个Socket函数就会返回SOCKET_ERROR。（每一个除send外的Socket函数在执&nbsp;行的最开始总要先等待套接字的发送缓冲中的数据被协议传送完毕才能继续，如果在等待时出现网络错误，那么该Socket函数就返回&nbsp;SOCKET_ERROR）</p><p style="box-sizing: border-box; margin: 0px; padding: 0px; word-wrap: break-word; word-break: normal; color: #454545; font-family: &quot;PingFang SC&quot;, &quot;Microsoft YaHei&quot;, SimHei, Arial, SimSun; font-size: 16px; background-color: #ffffff;">注意：在Unix系统下，如果send在等待协议传送数据时网络断开的话，调用send的进程会接收到一个SIGPIPE信号，进程对该信号的默认处理是进程终止。</p><p style="box-sizing: border-box; margin: 0px; padding: 0px; word-wrap: break-word; word-break: normal; color: #454545; font-family: &quot;PingFang SC&quot;, &quot;Microsoft YaHei&quot;, SimHei, Arial, SimSun; font-size: 16px; background-color: #ffffff;">Send函数的返回值有三类：</p><p style="box-sizing: border-box; margin: 0px; padding: 0px; word-wrap: break-word; word-break: normal; color: #454545; font-family: &quot;PingFang SC&quot;, &quot;Microsoft YaHei&quot;, SimHei, Arial, SimSun; font-size: 16px; background-color: #ffffff;">（1）返回值=0：</p><p style="box-sizing: border-box; margin: 0px; padding: 0px; word-wrap: break-word; word-break: normal; color: #454545; font-family: &quot;PingFang SC&quot;, &quot;Microsoft YaHei&quot;, SimHei, Arial, SimSun; font-size: 16px; background-color: #ffffff;">（2）返回值&lt;0：发送失败，错误原因存于全局变量errno中</p><p style="box-sizing: border-box; margin: 0px; padding: 0px; word-wrap: break-word; word-break: normal; color: #454545; font-family: &quot;PingFang SC&quot;, &quot;Microsoft YaHei&quot;, SimHei, Arial, SimSun; font-size: 16px; background-color: #ffffff;">（3）返回值&gt;0：表示发送的字节数（实际上是拷贝到发送缓冲中的字节数）</p><p style="box-sizing: border-box; margin: 0px; padding: 0px; word-wrap: break-word; word-break: normal; color: #454545; font-family: &quot;PingFang SC&quot;, &quot;Microsoft YaHei&quot;, SimHei, Arial, SimSun; font-size: 16px; background-color: #ffffff;"></p><p style="box-sizing: border-box; margin: 0px; padding: 0px; word-wrap: break-word; word-break: normal; color: #454545; font-family: &quot;PingFang SC&quot;, &quot;Microsoft YaHei&quot;, SimHei, Arial, SimSun; font-size: 16px; background-color: #ffffff;">错误代码：</p><p style="box-sizing: border-box; margin: 0px; padding: 0px; word-wrap: break-word; word-break: normal; color: #454545; font-family: &quot;PingFang SC&quot;, &quot;Microsoft YaHei&quot;, SimHei, Arial, SimSun; font-size: 16px; background-color: #ffffff;">EBADF&nbsp;参数s&nbsp;非合法的socket处理代码。<br style="box-sizing: border-box;" />EFAULT&nbsp;参数中有一指针指向无法存取的内存空间<br style="box-sizing: border-box;" />ENOTSOCK&nbsp;参数s为一文件描述词，非socket。<br style="box-sizing: border-box;" />EINTR&nbsp;被信号所中断。<br style="box-sizing: border-box;" />EAGAIN&nbsp;此操作会令进程阻断，但参数s的socket为不可阻断。<br style="box-sizing: border-box;" />ENOBUFS&nbsp;系统的缓冲内存不足<br style="box-sizing: border-box;" />ENOMEM&nbsp;核心内存不足<br style="box-sizing: border-box;" />EINVAL&nbsp;传给系统调用的参数不正确。</p><p style="box-sizing: border-box; margin: 0px; padding: 0px; word-wrap: break-word; word-break: normal; color: #454545; font-family: &quot;PingFang SC&quot;, &quot;Microsoft YaHei&quot;, SimHei, Arial, SimSun; font-size: 16px; background-color: #ffffff;"></p><p style="box-sizing: border-box; margin: 0px; padding: 0px; word-wrap: break-word; word-break: normal; color: #454545; font-family: &quot;PingFang SC&quot;, &quot;Microsoft YaHei&quot;, SimHei, Arial, SimSun; font-size: 16px; background-color: #ffffff;">2.&nbsp;&nbsp;recv函数</p><p style="box-sizing: border-box; margin: 0px; padding: 0px; word-wrap: break-word; word-break: normal; color: #454545; font-family: &quot;PingFang SC&quot;, &quot;Microsoft YaHei&quot;, SimHei, Arial, SimSun; font-size: 16px; background-color: #ffffff;">int&nbsp;recv(&nbsp;SOCKET&nbsp;s,&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;char&nbsp;FAR&nbsp;*buf,&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;int&nbsp;len,&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;int&nbsp;flags&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;);&nbsp;&nbsp;&nbsp;</p><p style="box-sizing: border-box; margin: 0px; padding: 0px; word-wrap: break-word; word-break: normal; color: #454545; font-family: &quot;PingFang SC&quot;, &quot;Microsoft YaHei&quot;, SimHei, Arial, SimSun; font-size: 16px; background-color: #ffffff;">不论是客户端还是服务器端应用程序都用recv函数从TCP连接的另一端接收数据。</p><p style="box-sizing: border-box; margin: 0px; padding: 0px; word-wrap: break-word; word-break: normal; color: #454545; font-family: &quot;PingFang SC&quot;, &quot;Microsoft YaHei&quot;, SimHei, Arial, SimSun; font-size: 16px; background-color: #ffffff;">该函数的：</p><p style="box-sizing: border-box; margin: 0px; padding: 0px; word-wrap: break-word; word-break: normal; color: #454545; font-family: &quot;PingFang SC&quot;, &quot;Microsoft YaHei&quot;, SimHei, Arial, SimSun; font-size: 16px; background-color: #ffffff;">第一个参数指定接收端套接字描述符；</p><p style="box-sizing: border-box; margin: 0px; padding: 0px; word-wrap: break-word; word-break: normal; color: #454545; font-family: &quot;PingFang SC&quot;, &quot;Microsoft YaHei&quot;, SimHei, Arial, SimSun; font-size: 16px; background-color: #ffffff;">第二个参数指明一个缓冲区，该缓冲区用来存放recv函数接收到的数据；</p><p style="box-sizing: border-box; margin: 0px; padding: 0px; word-wrap: break-word; word-break: normal; color: #454545; font-family: &quot;PingFang SC&quot;, &quot;Microsoft YaHei&quot;, SimHei, Arial, SimSun; font-size: 16px; background-color: #ffffff;">第三个参数指明buf的长度；</p><p style="box-sizing: border-box; margin: 0px; padding: 0px; word-wrap: break-word; word-break: normal; color: #454545; font-family: &quot;PingFang SC&quot;, &quot;Microsoft YaHei&quot;, SimHei, Arial, SimSun; font-size: 16px; background-color: #ffffff;">第四个参数一般置0。</p><p style="box-sizing: border-box; margin: 0px; padding: 0px; word-wrap: break-word; word-break: normal; color: #454545; font-family: &quot;PingFang SC&quot;, &quot;Microsoft YaHei&quot;, SimHei, Arial, SimSun; font-size: 16px; background-color: #ffffff;">这里只描述同步Socket的recv函数的执行流程。当应用程序调用recv函数时，recv先等待s的发送缓冲&nbsp;中的数据被协议传送完毕，如果协议在传送s的发送缓冲中的数据时出现网络错误，那么recv函数返回SOCKET_ERROR，如果s的发送缓冲中没有数&nbsp;据或者数据被协议成功发送完毕后，recv先检查套接字s的接收缓冲区，如果s接收缓冲区中没有数据或者协议正在接收数据，那么recv就一直等待，只到&nbsp;协议把数据接收完毕。当协议把数据接收完毕，recv函数就把s的接收缓冲中的数据copy到buf中（<span style="box-sizing: border-box; margin: 0px; padding: 0px; color: #000000;">注意协议接收到的数据可能大于buf</span>的长度，所以&nbsp;在这种情况下要调用几次recv函数才能把s的接收缓冲中的数据copy完。recv函数仅仅是copy数据，真正的接收数据是协议来完成的），recv函数返回其实际copy的字节数。如果recv在copy时出错，那么它返回SOCKET_ERROR；如果recv函数在等待协议接收数据时网络中断了，那么它返回0。</p><p style="box-sizing: border-box; margin: 0px; padding: 0px; word-wrap: break-word; word-break: normal; color: #454545; font-family: &quot;PingFang SC&quot;, &quot;Microsoft YaHei&quot;, SimHei, Arial, SimSun; font-size: 16px; background-color: #ffffff;">注意：在Unix系统下，如果recv函数在等待协议接收数据时网络断开了，那么调用recv的进程会接收到一个SIGPIPE信号，进程对该信号的默认处理是进程终止。</p><p style="box-sizing: border-box; margin: 0px; padding: 0px; word-wrap: break-word; word-break: normal; color: #454545; font-family: &quot;PingFang SC&quot;, &quot;Microsoft YaHei&quot;, SimHei, Arial, SimSun; font-size: 16px; background-color: #ffffff;"></p><p style="box-sizing: border-box; margin: 0px; padding: 0px; word-wrap: break-word; word-break: normal; color: #454545; font-family: &quot;PingFang SC&quot;, &quot;Microsoft YaHei&quot;, SimHei, Arial, SimSun; font-size: 16px; background-color: #ffffff;">默认情况下socket是阻塞的。</p><p style="box-sizing: border-box; margin: 0px; padding: 0px; word-wrap: break-word; word-break: normal; color: #454545; font-family: &quot;PingFang SC&quot;, &quot;Microsoft YaHei&quot;, SimHei, Arial, SimSun; font-size: 16px; background-color: #ffffff;">阻塞与非阻塞recv返回值没有区别，都是：</p><p style="box-sizing: border-box; margin: 0px; padding: 0px; word-wrap: break-word; word-break: normal; color: #454545; font-family: &quot;PingFang SC&quot;, &quot;Microsoft YaHei&quot;, SimHei, Arial, SimSun; font-size: 16px; background-color: #ffffff;">&lt;0&nbsp;出错</p><p style="box-sizing: border-box; margin: 0px; padding: 0px; word-wrap: break-word; word-break: normal; color: #454545; font-family: &quot;PingFang SC&quot;, &quot;Microsoft YaHei&quot;, SimHei, Arial, SimSun; font-size: 16px; background-color: #ffffff;">=0&nbsp;对方调用了close&nbsp;API来关闭连接</p><p style="box-sizing: border-box; margin: 0px; padding: 0px; word-wrap: break-word; word-break: normal; color: #454545; font-family: &quot;PingFang SC&quot;, &quot;Microsoft YaHei&quot;, SimHei, Arial, SimSun; font-size: 16px; background-color: #ffffff;">&gt;0&nbsp;接收到的数据大小，</p><p style="box-sizing: border-box; margin: 0px; padding: 0px; word-wrap: break-word; word-break: normal; color: #454545; font-family: &quot;PingFang SC&quot;, &quot;Microsoft YaHei&quot;, SimHei, Arial, SimSun; font-size: 16px; background-color: #ffffff;"></p><p style="box-sizing: border-box; margin: 0px; padding: 0px; word-wrap: break-word; word-break: normal; color: #454545; font-family: &quot;PingFang SC&quot;, &quot;Microsoft YaHei&quot;, SimHei, Arial, SimSun; font-size: 16px; background-color: #ffffff;">特别地：返回值&lt;0时并且(errno&nbsp;==&nbsp;EINTR&nbsp;||&nbsp;errno&nbsp;==&nbsp;EWOULDBLOCK&nbsp;||&nbsp;errno&nbsp;==&nbsp;EAGAIN)的情况下认为连接是正常的，继续接收。</p><p style="box-sizing: border-box; margin: 0px; padding: 0px; word-wrap: break-word; word-break: normal; color: #454545; font-family: &quot;PingFang SC&quot;, &quot;Microsoft YaHei&quot;, SimHei, Arial, SimSun; font-size: 16px; background-color: #ffffff;">只是阻塞模式下recv会一直阻塞直到接收到数据，非阻塞模式下如果没有数据就会返回，不会阻塞着读，因此需要循环读取）。</p><p style="box-sizing: border-box; margin: 0px; padding: 0px; word-wrap: break-word; word-break: normal; color: #454545; font-family: &quot;PingFang SC&quot;, &quot;Microsoft YaHei&quot;, SimHei, Arial, SimSun; font-size: 16px; background-color: #ffffff;"></p><p style="box-sizing: border-box; margin: 0px; padding: 0px; word-wrap: break-word; word-break: normal; color: #454545; font-family: &quot;PingFang SC&quot;, &quot;Microsoft YaHei&quot;, SimHei, Arial, SimSun; font-size: 16px; background-color: #ffffff;">返回说明：&nbsp;&nbsp;&nbsp;</p><p style="box-sizing: border-box; margin: 0px; padding: 0px; word-wrap: break-word; word-break: normal; color: #454545; font-family: &quot;PingFang SC&quot;, &quot;Microsoft YaHei&quot;, SimHei, Arial, SimSun; font-size: 16px; background-color: #ffffff;">（1）成功执行时，返回接收到的字节数。</p><p style="box-sizing: border-box; margin: 0px; padding: 0px; word-wrap: break-word; word-break: normal; color: #454545; font-family: &quot;PingFang SC&quot;, &quot;Microsoft YaHei&quot;, SimHei, Arial, SimSun; font-size: 16px; background-color: #ffffff;">（2）若另一端已关闭连接则返回0，这种关闭是对方主动且正常的关闭</p><p style="box-sizing: border-box; margin: 0px; padding: 0px; word-wrap: break-word; word-break: normal; color: #454545; font-family: &quot;PingFang SC&quot;, &quot;Microsoft YaHei&quot;, SimHei, Arial, SimSun; font-size: 16px; background-color: #ffffff;">（3）失败返回-1，errno被设为以下的某个值&nbsp;&nbsp;&nbsp;</p><p style="box-sizing: border-box; margin: 0px; padding: 0px; word-wrap: break-word; word-break: normal; color: #454545; font-family: &quot;PingFang SC&quot;, &quot;Microsoft YaHei&quot;, SimHei, Arial, SimSun; font-size: 16px; background-color: #ffffff;">EAGAIN：套接字已标记为非阻塞，而接收操作被阻塞或者接收超时</p><p style="box-sizing: border-box; margin: 0px; padding: 0px; word-wrap: break-word; word-break: normal; color: #454545; font-family: &quot;PingFang SC&quot;, &quot;Microsoft YaHei&quot;, SimHei, Arial, SimSun; font-size: 16px; background-color: #ffffff;">EBADF：sock不是有效的描述词</p><p style="box-sizing: border-box; margin: 0px; padding: 0px; word-wrap: break-word; word-break: normal; color: #454545; font-family: &quot;PingFang SC&quot;, &quot;Microsoft YaHei&quot;, SimHei, Arial, SimSun; font-size: 16px; background-color: #ffffff;">ECONNREFUSE：远程主机阻绝网络连接</p><p style="box-sizing: border-box; margin: 0px; padding: 0px; word-wrap: break-word; word-break: normal; color: #454545; font-family: &quot;PingFang SC&quot;, &quot;Microsoft YaHei&quot;, SimHei, Arial, SimSun; font-size: 16px; background-color: #ffffff;">EFAULT：内存空间访问出错</p><p style="box-sizing: border-box; margin: 0px; padding: 0px; word-wrap: break-word; word-break: normal; color: #454545; font-family: &quot;PingFang SC&quot;, &quot;Microsoft YaHei&quot;, SimHei, Arial, SimSun; font-size: 16px; background-color: #ffffff;">EINTR：操作被信号中断</p><p style="box-sizing: border-box; margin: 0px; padding: 0px; word-wrap: break-word; word-break: normal; color: #454545; font-family: &quot;PingFang SC&quot;, &quot;Microsoft YaHei&quot;, SimHei, Arial, SimSun; font-size: 16px; background-color: #ffffff;">EINVAL：参数无效</p><p style="box-sizing: border-box; margin: 0px; padding: 0px; word-wrap: break-word; word-break: normal; color: #454545; font-family: &quot;PingFang SC&quot;, &quot;Microsoft YaHei&quot;, SimHei, Arial, SimSun; font-size: 16px; background-color: #ffffff;">ENOMEM：内存不足</p><p style="box-sizing: border-box; margin: 0px; padding: 0px; word-wrap: break-word; word-break: normal; color: #454545; font-family: &quot;PingFang SC&quot;, &quot;Microsoft YaHei&quot;, SimHei, Arial, SimSun; font-size: 16px; background-color: #ffffff;">ENOTCONN：与面向连接关联的套接字尚未被连接上</p><p style="box-sizing: border-box; margin: 0px; padding: 0px; word-wrap: break-word; word-break: normal; color: #454545; font-family: &quot;PingFang SC&quot;, &quot;Microsoft YaHei&quot;, SimHei, Arial, SimSun; font-size: 16px; background-color: #ffffff;">ENOTSOCK：sock索引的不是套接字</p><img src ="http://www.cppblog.com/API/aggbug/215420.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-12-12 10:32 <a href="http://www.cppblog.com/API/archive/2017/12/12/215420.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>关于linux信号总结</title><link>http://www.cppblog.com/API/archive/2017/09/27/215270.html</link><dc:creator>C++技术中心</dc:creator><author>C++技术中心</author><pubDate>Wed, 27 Sep 2017 09:51:00 GMT</pubDate><guid>http://www.cppblog.com/API/archive/2017/09/27/215270.html</guid><wfw:comment>http://www.cppblog.com/API/comments/215270.html</wfw:comment><comments>http://www.cppblog.com/API/archive/2017/09/27/215270.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cppblog.com/API/comments/commentRss/215270.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/API/services/trackbacks/215270.html</trackback:ping><description><![CDATA[一.信号列表<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 />
-->$&nbsp;kill&nbsp;-l<br />
1)&nbsp;SIGHUP&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;2)&nbsp;SIGINT&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;3)&nbsp;SIGQUIT&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;4)&nbsp;SIGILL<br />
&nbsp;5)&nbsp;SIGTRAP&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;6)&nbsp;SIGABRT&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;7)&nbsp;SIGBUS&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;8)&nbsp;SIGFPE<br />
&nbsp;9)&nbsp;SIGKILL&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;10)&nbsp;SIGUSR1&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;11)&nbsp;SIGSEGV&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;12)&nbsp;SIGUSR2<br />
13)&nbsp;SIGPIPE&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;14)&nbsp;SIGALRM&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;15)&nbsp;SIGTERM&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;17)&nbsp;SIGCHLD<br />
18)&nbsp;SIGCONT&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;19)&nbsp;SIGSTOP&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;20)&nbsp;SIGTSTP&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;21)&nbsp;SIGTTIN<br />
22)&nbsp;SIGTTOU&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;23)&nbsp;SIGURG&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;24)&nbsp;SIGXCPU&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;25)&nbsp;SIGXFSZ<br />
26)&nbsp;SIGVTALRM&nbsp;&nbsp;&nbsp;27)&nbsp;SIGPROF&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;28)&nbsp;SIGWINCH&nbsp;&nbsp;&nbsp;&nbsp;29)&nbsp;SIGIO<br />
30)&nbsp;SIGPWR&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;31)&nbsp;SIGSYS&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;34)&nbsp;SIGRTMIN&nbsp;&nbsp;&nbsp;&nbsp;35)&nbsp;SIGRTMIN+1<br />
36)&nbsp;SIGRTMIN+2&nbsp;&nbsp;37)&nbsp;SIGRTMIN+3&nbsp;&nbsp;38)&nbsp;SIGRTMIN+4&nbsp;&nbsp;39)&nbsp;SIGRTMIN+5<br />
40)&nbsp;SIGRTMIN+6&nbsp;&nbsp;41)&nbsp;SIGRTMIN+7&nbsp;&nbsp;42)&nbsp;SIGRTMIN+8&nbsp;&nbsp;43)&nbsp;SIGRTMIN+9<br />
44)&nbsp;SIGRTMIN+10&nbsp;45)&nbsp;SIGRTMIN+11&nbsp;46)&nbsp;SIGRTMIN+12&nbsp;47)&nbsp;SIGRTMIN+13<br />
48)&nbsp;SIGRTMIN+14&nbsp;49)&nbsp;SIGRTMIN+15&nbsp;50)&nbsp;SIGRTMAX-14&nbsp;51)&nbsp;SIGRTMAX-13<br />
52)&nbsp;SIGRTMAX-12&nbsp;53)&nbsp;SIGRTMAX-11&nbsp;54)&nbsp;SIGRTMAX-10&nbsp;55)&nbsp;SIGRTMAX-9<br />
56)&nbsp;SIGRTMAX-8&nbsp;&nbsp;57)&nbsp;SIGRTMAX-7&nbsp;&nbsp;58)&nbsp;SIGRTMAX-6&nbsp;&nbsp;59)&nbsp;SIGRTMAX-5<br />
60)&nbsp;SIGRTMAX-4&nbsp;&nbsp;61)&nbsp;SIGRTMAX-3&nbsp;&nbsp;62)&nbsp;SIGRTMAX-2&nbsp;&nbsp;63)&nbsp;SIGRTMAX-1<br />
64)&nbsp;SIGRTMAX</div>
<br />(1)<span style="color: #454545; font-family: &quot;PingFang SC&quot;, &quot;Microsoft YaHei&quot;, SimHei, Arial, SimSun; font-size: 16px; background-color: #ffffff;">1 ~ 31的信号为传统UNIX支持的信号，是不可靠信号(非实时的)<br /></span>(2)<span style="color: #454545; font-family: &quot;PingFang SC&quot;, &quot;Microsoft YaHei&quot;, SimHei, Arial, SimSun; font-size: 16px; background-color: #ffffff;">32 ~ 63的信号是后来扩充的，称做可靠信号(实时信号)。不可靠信号和可靠信号的区别在于前者不支持排队，可能会造成信号丢失，而后者不会。</span><br /><br />二.具体每个信号的产生原因<br /><span style="color: #555555; font-family: &quot;microsoft yahei&quot;; font-size: 15px; background-color: #ffffff;">1) SIGHUP:当用户退出shell时，由该shell启动的所有进程将收到这个信号，默认动作为终止进程<br /></span><p style="box-sizing: border-box; margin: 0px; padding: 0px; color: #454545; font-family: &quot;PingFang SC&quot;, &quot;Microsoft YaHei&quot;, SimHei, Arial, SimSun; font-size: 16px; background-color: #ffffff;">本信号在用户终端连接(正常或非正常)结束时发出, 通常是在终端的控制进程结束时, 通知同一session内的各个作业, 这时它们与控制终端不再关联。</p><p style="box-sizing: border-box; margin: 0px; padding: 0px; color: #454545; font-family: &quot;PingFang SC&quot;, &quot;Microsoft YaHei&quot;, SimHei, Arial, SimSun; font-size: 16px; background-color: #ffffff;">登录Linux时，系统会分配给登录用户一个终端(Session)。在这个终端运行的所有程序，包括前台进程组和后台进程组，一般都属于这个Session。当用户退出Linux登录时，前台进程组和后台有对终端输出的进程将会收到SIGHUP信号。这个信号的默认操作为终止进程，因此前台进程组和后台有终端输出的进程就会中止。不过可以捕获这个信号，比如wget能捕获SIGHUP信号，并忽略它，这样就算退出了Linux登录，wget也能继续下载。</p><p style="box-sizing: border-box; margin: 0px; padding: 0px; color: #454545; font-family: &quot;PingFang SC&quot;, &quot;Microsoft YaHei&quot;, SimHei, Arial, SimSun; font-size: 16px; background-color: #ffffff;">此外，对于与终端脱离关系的守护进程，这个信号用于通知它重新读取配置文件。</p><span style="color: #555555; font-family: &quot;microsoft yahei&quot;; font-size: 15px; background-color: #ffffff;"><br />2）SIGINT：当用户按下了&lt;Ctrl+C&gt;组合键时，用户终端向正在运行中的由该终端启动的程序发出此信号。默认动<br />作为终止里程。<br /></span><span style="color: #555555; font-family: &quot;microsoft yahei&quot;; font-size: 15px; background-color: #ffffff;"><br /></span><span style="color: #555555; font-family: &quot;microsoft yahei&quot;; font-size: 15px; background-color: #ffffff;">3）SIGQUIT：当用户按下&lt;ctrl+\&gt;组合键时产生该信号，用户终端向正在运行中的由该终端启动的程序发出些信<br />号。默认动作为终止进程。</span><span style="color: #454545; font-family: &quot;PingFang SC&quot;, &quot;Microsoft YaHei&quot;, SimHei, Arial, SimSun; font-size: 16px; background-color: #ffffff;">进程在因收到SIGQUIT退出时会产生core文件, 在这个意义上类似于一个程序错误信号。<br /></span><br /><span style="color: #555555; font-family: &quot;microsoft yahei&quot;; font-size: 15px; background-color: #ffffff;">4）SIGILL：CPU检测到某进程执行了非法指令。默认动作为终止进程并产生core文件<br /></span><span style="color: #454545; font-family: &quot;PingFang SC&quot;, &quot;Microsoft YaHei&quot;, SimHei, Arial, SimSun; font-size: 16px; background-color: #ffffff;">执行了非法指令. 通常是因为可执行文件本身出现错误, 或者试图执行数据段. 堆栈溢出时也有可能产生这个信号。<br /></span><br /><span style="color: #555555; font-family: &quot;microsoft yahei&quot;; font-size: 15px; background-color: #ffffff;">5）SIGTRAP：该信号由断点指令或其他 trap指令产生。默认动作为终止里程 并产生core文件。<br /></span><span style="color: #454545; font-family: &quot;PingFang SC&quot;, &quot;Microsoft YaHei&quot;, SimHei, Arial, SimSun; font-size: 16px; background-color: #ffffff;">由断点指令或其它trap指令产生. 由debugger使用。<br /></span><br /><span style="color: #555555; font-family: &quot;microsoft yahei&quot;; font-size: 15px; background-color: #ffffff;">6 ) SIGABRT:调用abort函数时产生该信号。默认动作为终止进程并产生core文件。<br /><br />7）SIGBUS：非法访问内存地址，包括内存对齐出错，默认动作为终止进程并产生core文件。<br /></span><span style="color: #454545; font-family: &quot;PingFang SC&quot;, &quot;Microsoft YaHei&quot;, SimHei, Arial, SimSun; font-size: 16px; background-color: #ffffff;">非法地址, 包括内存地址对齐(alignment)出错。比如访问一个四个字长的整数, 但其地址不是4的倍数。它与SIGSEGV的区别在于后者是由于对合法存储地址的非法访问触发的(如访问不属于自己存储空间或只读存储空间)。<br /></span><br /><span style="color: #555555; font-family: &quot;microsoft yahei&quot;; font-size: 15px; background-color: #ffffff;">8）SIGFPE：在发生致命的运算错误时发出。不仅包括浮点运算错误，还包括溢出及除数为0等所有的算法错误。默<br />认动作为终止进程并产生core文件。<br /><br /><br />9）SIGKILL：无条件终止进程。本信号不能被忽略，处理和阻塞。默认动作为终止进程。它向系统管理员提供了可<br />以杀死任何进程的方法。<br /><br />10）SIGUSE1：用户定义 的信号。即程序员可以在程序中定义并使用该信号。默认动作为终止进程。<br /><br />11）SIGSEGV：指示进程进行了无效内存访问。默认动作为终止进程并产生core文件。<br /></span><span style="color: #454545; font-family: &quot;PingFang SC&quot;, &quot;Microsoft YaHei&quot;, SimHei, Arial, SimSun; font-size: 16px; background-color: #ffffff;">试图访问未分配给自己的内存, 或试图往没有写权限的内存地址写数据<br /></span><br /><span style="color: #555555; font-family: &quot;microsoft yahei&quot;; font-size: 15px; background-color: #ffffff;">12）SIGUSR2：这是另外一个用户自定义信号 ，程序员可以在程序中定义 并使用该信号。默认动作为终止进程。<br /><br />13）SIGPIPE：Broken pipe向一个没有读端的管道写数据。默认动作为终止进程。<br /><br />14) SIGALRM:定时器超时，超时的时间 由系统调用alarm设置。默认动作为终止进程。<br /></span><span style="color: #454545; font-family: &quot;PingFang SC&quot;, &quot;Microsoft YaHei&quot;, SimHei, Arial, SimSun; font-size: 16px; background-color: #ffffff;">时钟定时信号, 计算的是实际的时间或时钟时间. alarm函数使用该信号.<br /></span><br /><span style="color: #555555; font-family: &quot;microsoft yahei&quot;; font-size: 15px; background-color: #ffffff;">15）SIGTERM：程序结束信号，与SIGKILL不同的是，该信号可以被阻塞和终止。通常用来要示程序正常退出。执行<br />shell命令Kill时，缺省产生这个信号。默认动作为终止进程。<br /></span><span style="color: #454545; font-family: &quot;PingFang SC&quot;, &quot;Microsoft YaHei&quot;, SimHei, Arial, SimSun; font-size: 16px; background-color: #ffffff;">程序结束(terminate)信号, 与SIGKILL不同的是该信号可以被阻塞和处理。通常用来要求程序自己正常退出，shell命令kill缺省产生这个信号。如果进程终止不了，我们才会尝试SIGKILL。<br /></span><br /><span style="color: #555555; font-family: &quot;microsoft yahei&quot;; font-size: 15px; background-color: #ffffff;">17）SIGCHLD：子进程结束时，父进程会收到这个信号。默认动作为忽略这个信号。<br /></span><p style="box-sizing: border-box; margin: 0px; padding: 0px; color: #454545; font-family: &quot;PingFang SC&quot;, &quot;Microsoft YaHei&quot;, SimHei, Arial, SimSun; font-size: 16px; background-color: #ffffff;">子进程结束时, 父进程会收到这个信号。</p><p style="box-sizing: border-box; margin: 0px; padding: 0px; color: #454545; font-family: &quot;PingFang SC&quot;, &quot;Microsoft YaHei&quot;, SimHei, Arial, SimSun; font-size: 16px; background-color: #ffffff;">如果父进程没有处理这个信号，也没有等待(wait)子进程，子进程虽然终止，但是还会在内核进程表中占有表项，这时的子进程称为僵尸进程。这种情况我们应该避免(父进程或者忽略SIGCHILD信号，或者捕捉它，或者wait它派生的子进程，或者父进程先终止，这时子进程的终止自动由init进程来接管)。</p><span style="color: #555555; font-family: &quot;microsoft yahei&quot;; font-size: 15px; background-color: #ffffff;"><br />18）SIGCONT：停止进程的执行。信号不能被忽略，处理和阻塞。默认动作为终止进程。<br /></span><span style="color: #454545; font-family: &quot;PingFang SC&quot;, &quot;Microsoft YaHei&quot;, SimHei, Arial, SimSun; font-size: 16px; background-color: #ffffff;">让一个停止(stopped)的进程继续执行. 本信号不能被阻塞. 可以用一个handler来让程序在由stopped状态变为继续执行时完成特定的工作. 例如, 重新显示提示符<br /></span><br />19)<span style="color: #454545; font-family: &quot;PingFang SC&quot;, &quot;Microsoft YaHei&quot;, SimHei, Arial, SimSun; font-size: 16px; background-color: #ffffff;">SIGSTOP</span><br style="box-sizing: border-box; color: #454545; font-family: &quot;PingFang SC&quot;, &quot;Microsoft YaHei&quot;, SimHei, Arial, SimSun; font-size: 16px; background-color: #ffffff;" /><span style="color: #454545; font-family: &quot;PingFang SC&quot;, &quot;Microsoft YaHei&quot;, SimHei, Arial, SimSun; font-size: 16px; background-color: #ffffff;">停止(stopped)进程的执行. 注意它和terminate以及interrupt的区别:该进程还未结束, 只是暂停执行. 本信号不能被阻塞, 处理或忽略.<br /></span><span style="color: #555555; font-family: &quot;microsoft yahei&quot;; font-size: 15px; background-color: #ffffff;"><br />20）SIGTSTP：停止进程的运行。按下&lt;ctrl+z&gt;组合键时发出这个信号。默认动作为暂停进程。<br /></span><span style="color: #454545; font-family: &quot;PingFang SC&quot;, &quot;Microsoft YaHei&quot;, SimHei, Arial, SimSun; font-size: 16px; background-color: #ffffff;">停止进程的运行, 但该信号可以被处理和忽略. 用户键入SUSP字符时(通常是Ctrl-Z)发出这个信号<br /><br /></span><span style="color: #555555; font-family: &quot;microsoft yahei&quot;; font-size: 15px; background-color: #ffffff;">21）SIGTTIN：后台进程读终端控制台。默认动作为暂停进程。<br /></span><span style="color: #454545; font-family: &quot;PingFang SC&quot;, &quot;Microsoft YaHei&quot;, SimHei, Arial, SimSun; font-size: 16px; background-color: #ffffff;">当后台作业要从用户终端读数据时, 该作业中的所有进程会收到SIGTTIN信号. 缺省时这些进程会停止执行.<br /></span><br /><span style="color: #555555; font-family: &quot;microsoft yahei&quot;; font-size: 15px; background-color: #ffffff;">22）SIGTTOU:该信号类似于SIGTTIN，在后台进程要向终端输出数据时发生。默认动作为暂停进程。<br /></span><span style="color: #454545; font-family: &quot;PingFang SC&quot;, &quot;Microsoft YaHei&quot;, SimHei, Arial, SimSun; font-size: 16px; background-color: #ffffff;">类似于SIGTTIN, 但在写终端(或修改终端模式)时收到.</span><br /><span style="color: #555555; font-family: &quot;microsoft yahei&quot;; font-size: 15px; background-color: #ffffff;"><br />23）SIGURG：套接字上有紧急数据时，向当前正在运行的进程发出些信号，报告有紧急数据到达。如网络带外数据<br />到达，默认动作为忽略该信号。<br /></span><span style="color: #454545; font-family: &quot;PingFang SC&quot;, &quot;Microsoft YaHei&quot;, SimHei, Arial, SimSun; font-size: 16px; background-color: #ffffff;">有"紧急"数据或out-of-band数据到达socket时产生.</span><br /><span style="color: #555555; font-family: &quot;microsoft yahei&quot;; font-size: 15px; background-color: #ffffff;"><br />24）SIGXCPU：进程执行时间超过了分配给该进程的CPU时间 ，系统产生该信号并发送给该进程。默认动作为终止<br />进程。<br /></span><span style="color: #454545; font-family: &quot;PingFang SC&quot;, &quot;Microsoft YaHei&quot;, SimHei, Arial, SimSun; font-size: 16px; background-color: #ffffff;">超过CPU时间资源限制. 这个限制可以由getrlimit/setrlimit来读取/改变</span><br /><span style="color: #555555; font-family: &quot;microsoft yahei&quot;; font-size: 15px; background-color: #ffffff;"><br />25）SIGXFSZ：超过文件的最大长度设置。默认动作为终止进程。<br /></span><span style="color: #454545; font-family: &quot;PingFang SC&quot;, &quot;Microsoft YaHei&quot;, SimHei, Arial, SimSun; font-size: 16px; background-color: #ffffff;">当进程企图扩大文件以至于超过文件大小资源限制。</span><br /><span style="color: #555555; font-family: &quot;microsoft yahei&quot;; font-size: 15px; background-color: #ffffff;"><br />26）SIGVTALRM：虚拟时钟超时时产生该信号。类似于SIGALRM，但是该信号只计算该进程占用CPU的使用时间。默<br />认动作为终止进程。<br /></span><span style="color: #454545; font-family: &quot;PingFang SC&quot;, &quot;Microsoft YaHei&quot;, SimHei, Arial, SimSun; font-size: 16px; background-color: #ffffff;">虚拟时钟信号. 类似于SIGALRM, 但是计算的是该进程占用的CPU时间.</span><br /><span style="color: #555555; font-family: &quot;microsoft yahei&quot;; font-size: 15px; background-color: #ffffff;"><br />27）SGIPROF：类似于SIGVTALRM，它不公包括该进程占用CPU时间还包括执行系统调用时间。默认动作为终止进<br />程。<br /></span><span style="color: #454545; font-family: &quot;PingFang SC&quot;, &quot;Microsoft YaHei&quot;, SimHei, Arial, SimSun; font-size: 16px; background-color: #ffffff;">包括该进程用的CPU时间以及系统调用的时间</span><br /><span style="color: #555555; font-family: &quot;microsoft yahei&quot;; font-size: 15px; background-color: #ffffff;"><br />28）SIGWINCH：窗口变化大小时发出。默认动作为忽略该信号。<br /><br />29）SIGIO：此信号向进程指示发出了一个异步IO事件。默认动作为忽略。<br /></span><span style="color: #454545; font-family: &quot;PingFang SC&quot;, &quot;Microsoft YaHei&quot;, SimHei, Arial, SimSun; font-size: 16px; background-color: #ffffff;">文件描述符准备就绪, 可以开始进行输入/输出操作<br /></span><br /><span style="color: #555555; font-family: &quot;microsoft yahei&quot;; font-size: 15px; background-color: #ffffff;">30）SIGPWR：关机。默认动作为终止进程。<br /><br />31）SIGSYS：无效的系统调用。默认动作为终止进程并产生core文件。<br /><br />32）SIGRTMIN～（64）SIGRTMAX：LINUX的实时信号，它们没有固定的含义（可以由用户自定义）。<br /></span><span style="color: red; font-family: &quot;microsoft yahei&quot;; font-size: 15px; background-color: #ffffff;">所有的实时信</span><span style="color: red; font-family: &quot;microsoft yahei&quot;; font-size: 15px; background-color: #ffffff;">号的默认动作都为终止进程。</span>&nbsp;<br /><br /><p style="box-sizing: border-box; margin: 0px; padding: 0px; color: #454545; font-family: &quot;PingFang SC&quot;, &quot;Microsoft YaHei&quot;, SimHei, Arial, SimSun; font-size: 16px; background-color: #ffffff;">在以上列出的信号中，程序不可捕获、阻塞或忽略的信号有：SIGKILL,SIGSTOP<br style="box-sizing: border-box;" />不能恢复至默认动作的信号有：SIGILL,SIGTRAP<br style="box-sizing: border-box;" />默认会导致进程流产的信号有：SIGABRT,SIGBUS,SIGFPE,SIGILL,SIGIOT,SIGQUIT,SIGSEGV,SIGTRAP,SIGXCPU,SIGXFSZ<br style="box-sizing: border-box;" />默认会导致进程退出的信号有：SIGALRM,SIGHUP,SIGINT,SIGKILL,SIGPIPE,SIGPOLL,SIGPROF,SIGSYS,SIGTERM,SIGUSR1,SIGUSR2,SIGVTALRM<br style="box-sizing: border-box;" />默认会导致进程停止的信号有：SIGSTOP,SIGTSTP,SIGTTIN,SIGTTOU<br style="box-sizing: border-box;" />默认进程忽略的信号有：SIGCHLD,SIGPWR,SIGURG,SIGWINCH</p><p style="box-sizing: border-box; margin: 0px; padding: 0px; color: #454545; font-family: &quot;PingFang SC&quot;, &quot;Microsoft YaHei&quot;, SimHei, Arial, SimSun; font-size: 16px; background-color: #ffffff;">此外，SIGIO在SVR4是退出，在4.3BSD中是忽略；SIGCONT在进程挂起时是继续，否则是忽略，不能被阻塞。&nbsp;</p><img src ="http://www.cppblog.com/API/aggbug/215270.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-09-27 17:51 <a href="http://www.cppblog.com/API/archive/2017/09/27/215270.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>1</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>vc内存地址填充</title><link>http://www.cppblog.com/API/archive/2017/07/06/215056.html</link><dc:creator>C++技术中心</dc:creator><author>C++技术中心</author><pubDate>Thu, 06 Jul 2017 03:33:00 GMT</pubDate><guid>http://www.cppblog.com/API/archive/2017/07/06/215056.html</guid><wfw:comment>http://www.cppblog.com/API/comments/215056.html</wfw:comment><comments>http://www.cppblog.com/API/archive/2017/07/06/215056.html#Feedback</comments><slash:comments>1</slash:comments><wfw:commentRss>http://www.cppblog.com/API/comments/commentRss/215056.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/API/services/trackbacks/215056.html</trackback:ping><description><![CDATA[<p style="padding: 0px; font-family: Arial; background-color: #ffffff; font-stretch: normal; line-height: 26px; color: #333333;"><span style="font-family: &quot;WenQuanYi Micro Hei Mono&quot;, &quot;WenQuanYi Micro Hei&quot;, &quot;Microsoft Yahei Mono&quot;, &quot;Microsoft Yahei&quot;, sans-serif;">0xcdcdcdcd - Created but not initialised</span></p><p style="padding: 0px; font-family: Arial; background-color: #ffffff; font-stretch: normal; line-height: 26px; color: #333333;"><span style="font-family: &quot;WenQuanYi Micro Hei Mono&quot;, &quot;WenQuanYi Micro Hei&quot;, &quot;Microsoft Yahei Mono&quot;, &quot;Microsoft Yahei&quot;, sans-serif;">0xdddddddd - Deleted<br /></span><span style="font-family: &quot;WenQuanYi Micro Hei Mono&quot;, &quot;WenQuanYi Micro Hei&quot;, &quot;Microsoft Yahei Mono&quot;, &quot;Microsoft Yahei&quot;, sans-serif;">0xfeeefeee - Freed memory set by NT's heap manager<br /></span><span style="font-family: &quot;WenQuanYi Micro Hei Mono&quot;, &quot;WenQuanYi Micro Hei&quot;, &quot;Microsoft Yahei Mono&quot;, &quot;Microsoft Yahei&quot;, sans-serif;">0xcccccccc - Uninitialized locals in VC6 when you compile w/ /GZ<br /></span><span style="font-family: &quot;WenQuanYi Micro Hei Mono&quot;, &quot;WenQuanYi Micro Hei&quot;, &quot;Microsoft Yahei Mono&quot;, &quot;Microsoft Yahei&quot;, sans-serif;">0xabababab - Memory following a block allocated by LocalAlloc()</span></p><p style="padding: 0px; font-family: Arial; background-color: #ffffff; font-stretch: normal; line-height: 26px; color: #333333;"><span style="color: #000000; font-family: &quot;WenQuanYi Micro Hei Mono&quot;, &quot;WenQuanYi Micro Hei&quot;, &quot;Microsoft Yahei Mono&quot;, &quot;Microsoft Yahei&quot;, sans-serif; line-height: 23px;"><br /></span></p><p style="padding: 0px; font-family: Arial; background-color: #ffffff; font-stretch: normal; line-height: 26px; color: #333333;"><span style="color: #000000; font-family: &quot;WenQuanYi Micro Hei Mono&quot;, &quot;WenQuanYi Micro Hei&quot;, &quot;Microsoft Yahei Mono&quot;, &quot;Microsoft Yahei&quot;, sans-serif; line-height: 23px;">VC++在Debug编译方式编译的程序中，会跟踪用new分配的内存。新分配的内存会用0xcd(助记词为Cleared Data)填充，防止未初始化；当它被delete后，又会被0xdd(Dead &nbsp; Data)填充，防止再次被使用。这样有利于调试内存错误。之所以选这样的填充模式，是因为：</span></p><p style="padding: 0px; font-family: Arial; background-color: #ffffff; font-stretch: normal; line-height: 26px; color: #333333;"><span style="font-stretch: normal; line-height: 23px; color: #000000; font-family: &quot;WenQuanYi Micro Hei Mono&quot;, &quot;WenQuanYi Micro Hei&quot;, &quot;Microsoft Yahei Mono&quot;, &quot;Microsoft Yahei&quot;, sans-serif !important;">1.大数，若被当成指针就会越界&nbsp;</span></p><p style="padding: 0px; font-family: Arial; background-color: #ffffff; font-stretch: normal; line-height: 26px; color: #333333;"><span style="font-stretch: normal; line-height: 23px; color: #000000; font-family: &quot;WenQuanYi Micro Hei Mono&quot;, &quot;WenQuanYi Micro Hei&quot;, &quot;Microsoft Yahei Mono&quot;, &quot;Microsoft Yahei&quot;, sans-serif !important;">2.奇数，指针通常指向偶数地址 &nbsp;</span></p><p style="padding: 0px; font-family: Arial; background-color: #ffffff; font-stretch: normal; line-height: 26px; color: #333333;"><span style="font-stretch: normal; line-height: 23px; color: #000000; font-family: &quot;WenQuanYi Micro Hei Mono&quot;, &quot;WenQuanYi Micro Hei&quot;, &quot;Microsoft Yahei Mono&quot;, &quot;Microsoft Yahei&quot;, sans-serif !important;">3.非0,这样不会和 &nbsp; NULL &nbsp; 混淆。&nbsp;</span></p><p style="padding: 0px; font-family: Arial; background-color: #ffffff; font-stretch: normal; line-height: 26px; color: #333333;"><br style="font-stretch: normal; line-height: 23px; color: #000000; font-family: &quot;WenQuanYi Micro Hei Mono&quot;, &quot;WenQuanYi Micro Hei&quot;, &quot;Microsoft Yahei Mono&quot;, &quot;Microsoft Yahei&quot;, sans-serif !important;" /><span style="font-stretch: normal; line-height: 23px; color: #000000; font-family: &quot;WenQuanYi Micro Hei Mono&quot;, &quot;WenQuanYi Micro Hei&quot;, &quot;Microsoft Yahei Mono&quot;, &quot;Microsoft Yahei&quot;, sans-serif !important;">在Release版中不会有这些字节填充。</span></p><img src ="http://www.cppblog.com/API/aggbug/215056.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-07-06 11:33 <a href="http://www.cppblog.com/API/archive/2017/07/06/215056.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>c++函数throw()</title><link>http://www.cppblog.com/API/archive/2017/06/30/215041.html</link><dc:creator>C++技术中心</dc:creator><author>C++技术中心</author><pubDate>Fri, 30 Jun 2017 08:28:00 GMT</pubDate><guid>http://www.cppblog.com/API/archive/2017/06/30/215041.html</guid><wfw:comment>http://www.cppblog.com/API/comments/215041.html</wfw:comment><comments>http://www.cppblog.com/API/archive/2017/06/30/215041.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cppblog.com/API/comments/commentRss/215041.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/API/services/trackbacks/215041.html</trackback:ping><description><![CDATA[<div>#define _NOEXCEPT<span style="white-space:pre">	</span>throw ()<br />shared_ptr&lt;_Ty&gt; lock() const _NOEXCEPT<br /><p style="margin-top: 10px; margin-bottom: 10px; padding: 0px; line-height: 1.5; font-size: 13px; font-family: Verdana, Arial, Helvetica, sans-serif; background-color: #fefef2;">它是函数提供者和使用者的一种君子协定，标明该函数不抛出任何异常。</p><p style="margin-top: 10px; margin-bottom: 10px; padding: 0px; line-height: 1.5; font-size: 13px; font-family: Verdana, Arial, Helvetica, sans-serif; background-color: #fefef2;">之所以说是君子协定，是因为实际上内部实现是需要人肉确保。&nbsp;</p><p style="margin-top: 10px; margin-bottom: 10px; padding: 0px; line-height: 1.5; font-size: 13px; font-family: Verdana, Arial, Helvetica, sans-serif; background-color: #fefef2;">如果一个标明throw()的函数内部发生了throw：</p><p style="margin-top: 10px; margin-bottom: 10px; padding: 0px; line-height: 1.5; font-size: 13px; font-family: Verdana, Arial, Helvetica, sans-serif; background-color: #fefef2;">1，如果内部直接throw something，编译器会发现并指出；</p><p style="margin-top: 10px; margin-bottom: 10px; padding: 0px; line-height: 1.5; font-size: 13px; font-family: Verdana, Arial, Helvetica, sans-serif; background-color: #fefef2;">2. 如果是内部调用了一个可能throw something的函数，编译器无法发现，运行时一旦这个内部的函数throw，程序会abort。</p><p style="margin-top: 10px; margin-bottom: 10px; padding: 0px; line-height: 1.5; font-size: 13px; font-family: Verdana, Arial, Helvetica, sans-serif; background-color: #fefef2;">&nbsp;</p><p style="margin-top: 10px; margin-bottom: 10px; padding: 0px; line-height: 1.5; font-size: 13px; font-family: Verdana, Arial, Helvetica, sans-serif; background-color: #fefef2;">****&nbsp;</p><p style="margin-top: 10px; margin-bottom: 10px; padding: 0px; line-height: 1.5; font-size: 13px; font-family: Verdana, Arial, Helvetica, sans-serif; background-color: #fefef2;">func() throw(type) ,会抛出某种异常</p><p style="margin-top: 10px; margin-bottom: 10px; padding: 0px; line-height: 1.5; font-size: 13px; font-family: Verdana, Arial, Helvetica, sans-serif; background-color: #fefef2;">func() throw(),不会抛出</p><p style="margin-top: 10px; margin-bottom: 10px; padding: 0px; line-height: 1.5; font-size: 13px; font-family: Verdana, Arial, Helvetica, sans-serif; background-color: #fefef2;">func() throw(...)，可能是任何类型的异常</p></div><img src ="http://www.cppblog.com/API/aggbug/215041.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-30 16:28 <a href="http://www.cppblog.com/API/archive/2017/06/30/215041.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>EA类的关系</title><link>http://www.cppblog.com/API/archive/2017/06/19/215008.html</link><dc:creator>C++技术中心</dc:creator><author>C++技术中心</author><pubDate>Mon, 19 Jun 2017 02:34:00 GMT</pubDate><guid>http://www.cppblog.com/API/archive/2017/06/19/215008.html</guid><wfw:comment>http://www.cppblog.com/API/comments/215008.html</wfw:comment><comments>http://www.cppblog.com/API/archive/2017/06/19/215008.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cppblog.com/API/comments/commentRss/215008.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/API/services/trackbacks/215008.html</trackback:ping><description><![CDATA[<p style="color: #333333; font-family: Verdana, Arial, Helvetica, sans-serif; background-color: #ffffff; margin-top: 10px; margin-bottom: 10px; padding-top: 0px; padding-bottom: 0px; padding-left: 30px;">Enterprise Architect中定义的关系主要有一下几种：</p><p style="color: #333333; font-family: Verdana, Arial, Helvetica, sans-serif; background-color: #ffffff; margin-top: 10px; margin-bottom: 10px; padding-top: 0px; padding-bottom: 0px; padding-left: 30px;">&#9679;Associate（关联）：类之间有关联，通常是作为变量存在；</p><p style="color: #333333; font-family: Verdana, Arial, Helvetica, sans-serif; background-color: #ffffff; margin-top: 10px; margin-bottom: 10px; padding-top: 0px; padding-bottom: 0px; padding-left: 30px;">&#9679;Aggregate(聚合)：类A包含类B或由类B组成；</p><p style="color: #333333; font-family: Verdana, Arial, Helvetica, sans-serif; background-color: #ffffff; margin-top: 10px; margin-bottom: 10px; padding-top: 0px; padding-bottom: 0px; padding-left: 30px;">&#9679;Compose（组合）：类A是由其他类组成；</p><p style="color: #333333; font-family: Verdana, Arial, Helvetica, sans-serif; background-color: #ffffff; margin-top: 10px; margin-bottom: 10px; padding-top: 0px; padding-bottom: 0px; padding-left: 30px;">&#9679;Dependency（依赖）：类A需要类B的协助，类B变化会影响类A,反过来不成立；</p><p style="color: #333333; font-family: Verdana, Arial, Helvetica, sans-serif; background-color: #ffffff; margin-top: 10px; margin-bottom: 10px; padding-top: 0px; padding-bottom: 0px; padding-left: 30px;">&#9679;Generalize（泛化）：一般到具体的关系；</p><p style="color: #333333; font-family: Verdana, Arial, Helvetica, sans-serif; background-color: #ffffff; margin-top: 10px; margin-bottom: 10px; padding-top: 0px; padding-bottom: 0px; padding-left: 30px;">&#9679;Realize（实现）：类A实现类B；</p><p style="color: #333333; font-family: Verdana, Arial, Helvetica, sans-serif; background-color: #ffffff; margin-top: 10px; margin-bottom: 10px; padding-top: 0px; padding-bottom: 0px; padding-left: 30px;">&nbsp;注意：其中，聚合，组成属于关联关系，泛化关系表现为继承或实现关系(is a)，关联关系表现为变量(has a )，依赖关系表现为函数中的参数(use a)。</p><p style="color: #333333; font-family: Verdana, Arial, Helvetica, sans-serif; background-color: #ffffff; margin-top: 10px; margin-bottom: 10px; padding-top: 0px; padding-bottom: 0px; padding-left: 30px;">&nbsp;</p><p style="color: #333333; font-family: Verdana, Arial, Helvetica, sans-serif; background-color: #ffffff; margin-top: 10px; margin-bottom: 10px; padding-top: 0px; padding-bottom: 0px; padding-left: 30px;">1.关联(Associate)</p><p style="color: #333333; font-family: Verdana, Arial, Helvetica, sans-serif; background-color: #ffffff; margin-top: 10px; margin-bottom: 10px; padding-top: 0px; padding-bottom: 0px; padding-left: 30px;">表示方法： 箭头＋实线，箭头指向被使用的类；</p><p style="color: #333333; font-family: Verdana, Arial, Helvetica, sans-serif; background-color: #ffffff; margin-top: 10px; margin-bottom: 10px; padding-top: 0px; padding-bottom: 0px; padding-left: 30px;">系统图标：<img src="http://www.cppblog.com/images/cppblog_com/api/a.gif" border="0" alt="" width="131" height="18" /></p><p style="color: #333333; font-family: Verdana, Arial, Helvetica, sans-serif; background-color: #ffffff; margin-top: 10px; margin-bottom: 10px; padding-top: 0px; padding-bottom: 0px; padding-left: 30px;">使用说明：类与类之间的联接，它使一个类知道另一个类的属性和方法，如下图所示：</p><p align="center" style="color: #333333; font-family: Verdana, Arial, Helvetica, sans-serif; background-color: #ffffff; margin-top: 10px; margin-bottom: 10px; padding-top: 0px; padding-bottom: 0px; padding-left: 30px;"><img src="http://www.cppblog.com/images/cppblog_com/api/a2.gif" alt="" /><br /></p><p align="center" style="color: #333333; font-family: Verdana, Arial, Helvetica, sans-serif; background-color: #ffffff; margin-top: 10px; margin-bottom: 10px; padding-top: 0px; padding-bottom: 0px; padding-left: 30px;">&nbsp;</p><p style="color: #333333; font-family: Verdana, Arial, Helvetica, sans-serif; background-color: #ffffff; margin-top: 10px; margin-bottom: 10px; padding-top: 0px; padding-bottom: 0px; padding-left: 30px;">2. 聚合关系（Aggregation）</p><p style="color: #333333; font-family: Verdana, Arial, Helvetica, sans-serif; background-color: #ffffff; margin-top: 10px; margin-bottom: 10px; padding-top: 0px; padding-bottom: 0px; padding-left: 30px;">表示方法：空心菱形＋实线，空心菱形指向整体</p><p style="color: #333333; font-family: Verdana, Arial, Helvetica, sans-serif; background-color: #ffffff; margin-top: 10px; margin-bottom: 10px; padding-top: 0px; padding-bottom: 0px; padding-left: 30px;">系统图标：<img src="http://pic002.cnblogs.com/img/allanbolt/200912/2009122416523521.gif" alt="" style="border: 0px; max-width: 100%; margin: 0px; padding: 0px;" /></p><p style="color: #333333; font-family: Verdana, Arial, Helvetica, sans-serif; background-color: #ffffff; margin-top: 10px; margin-bottom: 10px; padding-top: 0px; padding-bottom: 0px; padding-left: 30px;">使用说明：聚合关系是整体和个体的关系。下图应用程序聚合功能模块，但是功能模块可以离开应用程序而独立存在，如下图所示：</p><p align="center" style="color: #333333; font-family: Verdana, Arial, Helvetica, sans-serif; background-color: #ffffff; margin-top: 10px; margin-bottom: 10px; padding-top: 0px; padding-bottom: 0px; padding-left: 30px;"><img src="http://pic002.cnblogs.com/img/allanbolt/200912/2009122416524572.gif" alt="" style="border: 0px; max-width: 100%; margin: 0px; padding: 0px;" />&nbsp;</p><p style="color: #333333; font-family: Verdana, Arial, Helvetica, sans-serif; background-color: #ffffff; margin-top: 10px; margin-bottom: 10px; padding-top: 0px; padding-bottom: 0px; padding-left: 30px;">&nbsp;</p><p style="color: #333333; font-family: Verdana, Arial, Helvetica, sans-serif; background-color: #ffffff; margin-top: 10px; margin-bottom: 10px; padding-top: 0px; padding-bottom: 0px; padding-left: 30px;">3. 组合关系（Composition）</p><p style="color: #333333; font-family: Verdana, Arial, Helvetica, sans-serif; background-color: #ffffff; margin-top: 10px; margin-bottom: 10px; padding-top: 0px; padding-bottom: 0px; padding-left: 30px;">表示方法：实心菱形＋实线　实心菱形指向整体</p><p style="color: #333333; font-family: Verdana, Arial, Helvetica, sans-serif; background-color: #ffffff; margin-top: 10px; margin-bottom: 10px; padding-top: 0px; padding-bottom: 0px; padding-left: 30px;">系统图标：<img src="http://pic002.cnblogs.com/img/allanbolt/200912/2009122416530521.gif" alt="" style="border: 0px; max-width: 100%; margin: 0px; padding: 0px;" /></p><p style="color: #333333; font-family: Verdana, Arial, Helvetica, sans-serif; background-color: #ffffff; margin-top: 10px; margin-bottom: 10px; padding-top: 0px; padding-bottom: 0px; padding-left: 30px;">使用说明：是关联关系的一种，是比聚合关系强的关系。它要求普通的聚合关系中代表整体的<a target="_blank" href="http://www.itisedu.com/phrase/200603090845215.html" style="color: black; text-decoration: none; margin: 0px; padding: 0px;">对象</a>负责代表部分的对象的生命周期，下图功能模块组合操作方法，这个操作方法不能脱离功能模块单独的存在，功能模块消失后操作方法也随即消失：</p><p align="center" style="color: #333333; font-family: Verdana, Arial, Helvetica, sans-serif; background-color: #ffffff; margin-top: 10px; margin-bottom: 10px; padding-top: 0px; padding-bottom: 0px; padding-left: 30px;"><img src="http://pic002.cnblogs.com/img/allanbolt/200912/2009122416532019.gif" alt="" style="border: 0px; max-width: 100%; margin: 0px; padding: 0px;" />&nbsp;</p><p style="color: #333333; font-family: Verdana, Arial, Helvetica, sans-serif; background-color: #ffffff; margin-top: 10px; margin-bottom: 10px; padding-top: 0px; padding-bottom: 0px; padding-left: 30px;">4. 依赖(Dependency)</p><p style="color: #333333; font-family: Verdana, Arial, Helvetica, sans-serif; background-color: #ffffff; margin-top: 10px; margin-bottom: 10px; padding-top: 0px; padding-bottom: 0px; padding-left: 30px;">表示方法：虚线＋箭头　箭头指向被依赖类；</p><p style="color: #333333; font-family: Verdana, Arial, Helvetica, sans-serif; background-color: #ffffff; margin-top: 10px; margin-bottom: 10px; padding-top: 0px; padding-bottom: 0px; padding-left: 30px;">系统图标：<img src="http://pic002.cnblogs.com/img/allanbolt/200912/2009122416534398.gif" alt="" style="border: 0px; max-width: 100%; margin: 0px; padding: 0px;" /></p><p style="color: #333333; font-family: Verdana, Arial, Helvetica, sans-serif; background-color: #ffffff; margin-top: 10px; margin-bottom: 10px; padding-top: 0px; padding-bottom: 0px; padding-left: 30px;">使用说明：如果类A访问类B的属性或者方法，或者类A负责实例化类B，那么可以说类A依赖类B。和关联关系不同，无须在类A中定义类B类型的属性：</p><p align="center" style="color: #333333; font-family: Verdana, Arial, Helvetica, sans-serif; background-color: #ffffff; margin-top: 10px; margin-bottom: 10px; padding-top: 0px; padding-bottom: 0px; padding-left: 30px;"><img src="http://pic002.cnblogs.com/img/allanbolt/200912/2009122416535531.gif" alt="" style="border: 0px; max-width: 100%; margin: 0px; padding: 0px;" />&nbsp;</p><p style="color: #333333; font-family: Verdana, Arial, Helvetica, sans-serif; background-color: #ffffff; margin-top: 10px; margin-bottom: 10px; padding-top: 0px; padding-bottom: 0px; padding-left: 30px;">&nbsp;</p><p style="color: #333333; font-family: Verdana, Arial, Helvetica, sans-serif; background-color: #ffffff; margin-top: 10px; margin-bottom: 10px; padding-top: 0px; padding-bottom: 0px; padding-left: 30px;">5. 泛化(Generalization)</p><p style="color: #333333; font-family: Verdana, Arial, Helvetica, sans-serif; background-color: #ffffff; margin-top: 10px; margin-bottom: 10px; padding-top: 0px; padding-bottom: 0px; padding-left: 30px;">表示方法：实线＋三角箭头　三角箭头指向一般类；</p><p style="color: #333333; font-family: Verdana, Arial, Helvetica, sans-serif; background-color: #ffffff; margin-top: 10px; margin-bottom: 10px; padding-top: 0px; padding-bottom: 0px; padding-left: 30px;">系统图标：<img src="http://pic002.cnblogs.com/img/allanbolt/200912/2009122416540576.gif" alt="" style="border: 0px; max-width: 100%; margin: 0px; padding: 0px;" /></p><p style="color: #333333; font-family: Verdana, Arial, Helvetica, sans-serif; background-color: #ffffff; margin-top: 10px; margin-bottom: 10px; padding-top: 0px; padding-bottom: 0px; padding-left: 30px;">使用说明：两个类存在泛化的关系时就使用此关系，例如父和子，动物和老虎，植物和花等，在面向对象中，我们一般称之为继承关系：</p><p align="center" style="color: #333333; font-family: Verdana, Arial, Helvetica, sans-serif; background-color: #ffffff; margin-top: 10px; margin-bottom: 10px; padding-top: 0px; padding-bottom: 0px; padding-left: 30px;"><img src="http://pic002.cnblogs.com/img/allanbolt/200912/2009122416541783.gif" alt="" style="border: 0px; max-width: 100%; margin: 0px; padding: 0px;" />&nbsp;</p><p style="color: #333333; font-family: Verdana, Arial, Helvetica, sans-serif; background-color: #ffffff; margin-top: 10px; margin-bottom: 10px; padding-top: 0px; padding-bottom: 0px; padding-left: 30px;">&nbsp;</p><p style="color: #333333; font-family: Verdana, Arial, Helvetica, sans-serif; background-color: #ffffff; margin-top: 10px; margin-bottom: 10px; padding-top: 0px; padding-bottom: 0px; padding-left: 30px;">6. 实现(Realization)</p><p style="color: #333333; font-family: Verdana, Arial, Helvetica, sans-serif; background-color: #ffffff; margin-top: 10px; margin-bottom: 10px; padding-top: 0px; padding-bottom: 0px; padding-left: 30px;">表示方法：虚线＋三角箭头　三角箭头指向一般类；</p><p style="color: #333333; font-family: Verdana, Arial, Helvetica, sans-serif; background-color: #ffffff; margin-top: 10px; margin-bottom: 10px; padding-top: 0px; padding-bottom: 0px; padding-left: 30px;">系统图标：<img src="http://pic002.cnblogs.com/img/allanbolt/200912/2009122416544554.gif" alt="" style="border: 0px; max-width: 100%; margin: 0px; padding: 0px;" /></p><p style="color: #333333; font-family: Verdana, Arial, Helvetica, sans-serif; background-color: #ffffff; margin-top: 10px; margin-bottom: 10px; padding-top: 0px; padding-bottom: 0px; padding-left: 30px;">使用说明：类实现了另一个类的功能，一般表现在类继承接口上，如下图：</p><p align="center" style="color: #333333; font-family: Verdana, Arial, Helvetica, sans-serif; background-color: #ffffff; margin-top: 10px; margin-bottom: 10px; padding-top: 0px; padding-bottom: 0px; padding-left: 30px;">&nbsp;<img src="http://pic002.cnblogs.com/img/allanbolt/200912/2009122416545781.gif" alt="" style="border: 0px; max-width: 100%; margin: 0px; padding: 0px;" /></p><img src ="http://www.cppblog.com/API/aggbug/215008.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-19 10:34 <a href="http://www.cppblog.com/API/archive/2017/06/19/215008.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>C/C++中volatile与 mutable,explicit 关键字详解</title><link>http://www.cppblog.com/API/archive/2017/04/13/214839.html</link><dc:creator>C++技术中心</dc:creator><author>C++技术中心</author><pubDate>Thu, 13 Apr 2017 05:39:00 GMT</pubDate><guid>http://www.cppblog.com/API/archive/2017/04/13/214839.html</guid><wfw:comment>http://www.cppblog.com/API/comments/214839.html</wfw:comment><comments>http://www.cppblog.com/API/archive/2017/04/13/214839.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cppblog.com/API/comments/commentRss/214839.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/API/services/trackbacks/214839.html</trackback:ping><description><![CDATA[<div><font face="verdana">&nbsp; C/C++ 中的 volatile 关键字和 const 对应，用来修饰变量，通常用于建立语言级别的 memory barrier。这是 BS 在 "The C++ Programming Language" 对 volatile 修饰词的说明：</font></div><p style="margin-top: 10px; margin-bottom: 10px;"><font face="verdana"><br /></font></p><div></div><div>A volatile specifier is a hint to a compiler that an object may change its value in ways not specified by the language so that aggressive optimizations must be avoided.</div><div></div><div>&nbsp; &nbsp; &nbsp; volatile 关键字是一种类型修饰符，用它声明的类型变量表示可以被某些编译器未知的因素更改，比如：操作系统、硬件或者其它线程等。遇到这个关键字声明的变量，编译器对访问该变量的代码就不再进行优化，从而可以提供对特殊地址的稳定访问。声明时语法：int volatile vInt; 当要求使用 volatile 声明的变量的值的时候，系统总是重新从它所在的内存读取数据，即使它前面的指令刚刚从该处读取过数据。而且读取的数据立刻被保存。例如：</div><div></div><div>1<span style="white-space:pre">	</span>volatile int i=10;</div><div>2<span style="white-space:pre">	</span>int a = i;</div><div>3<span style="white-space:pre">	</span>...</div><div>4<span style="white-space:pre">	</span>// 其他代码，并未明确告诉编译器，对 i 进行过操作</div><div>5<span style="white-space:pre">	</span>int b = i;</div><div>&nbsp; &nbsp; volatile 指出 i 是随时可能发生变化的，每次使用它的时候必须从 i的地址中读取，因而编译器生成的汇编代码会重新从i的地址读取数据放在 b 中。而优化做法是，由于编译器发现两次从 i读数据的代码之间的代码没有对 i 进行过操作，它会自动把上次读的数据放在 b 中。而不是重新从 i 里面读。这样以来，如果 i是一个寄存器变量或者表示一个端口数据就容易出错，所以说 volatile 可以保证对特殊地址的稳定访问。注意，在 VC 6 中，一般调试模式没有进行代码优化，所以这个关键字的作用看不出来。下面通过插入汇编代码，测试有无 volatile 关键字，对程序最终代码的影响：</div><div></div><div>输入下面的代码：</div><div></div><div>01<span style="white-space:pre">	</span>#include &lt;stdio.h&gt;</div><div>02<span style="white-space:pre">	</span>&nbsp;</div><div>03<span style="white-space:pre">	</span>void main()</div><div>04<span style="white-space:pre">	</span>{</div><div>05<span style="white-space:pre">	</span> &nbsp; &nbsp;int i = 10;</div><div>06<span style="white-space:pre">	</span> &nbsp; &nbsp;int a = i;</div><div>07<span style="white-space:pre">	</span>&nbsp;</div><div>08<span style="white-space:pre">	</span> &nbsp; &nbsp;printf("i = %d", a);</div><div>09<span style="white-space:pre">	</span>&nbsp;</div><div>10<span style="white-space:pre">	</span> &nbsp; &nbsp;// 下面汇编语句的作用就是改变内存中 i 的值</div><div>11<span style="white-space:pre">	</span> &nbsp; &nbsp;// 但是又不让编译器知道</div><div>12<span style="white-space:pre">	</span> &nbsp; &nbsp;__asm {</div><div>13<span style="white-space:pre">	</span> &nbsp; &nbsp; &nbsp; &nbsp;mov dword ptr [ebp-4], 20h</div><div>14<span style="white-space:pre">	</span> &nbsp; &nbsp;}</div><div>15<span style="white-space:pre">	</span>&nbsp;</div><div>16<span style="white-space:pre">	</span> &nbsp; &nbsp;int b = i;</div><div>17<span style="white-space:pre">	</span> &nbsp; &nbsp;printf("i = %d", b);</div><div>18<span style="white-space:pre">	</span>}</div><div>&nbsp; &nbsp; 然后，在 Debug 版本模式运行程序，输出结果如下：</div><div></div><div>i = 10</div><div>i = 32</div><div>&nbsp; &nbsp; 然后，在 Release 版本模式运行程序，输出结果如下：</div><div></div><div>i = 10</div><div>i = 10</div><div>&nbsp; &nbsp; 输出的结果明显表明，Release 模式下，编译器对代码进行了优化，第二次没有输出正确的 i 值。下面，我们把 i 的声明加上 volatile 关键字，看看有什么变化：</div><div></div><div>01<span style="white-space:pre">	</span>#include &lt;stdio.h&gt;</div><div>02<span style="white-space:pre">	</span>&nbsp;</div><div>03<span style="white-space:pre">	</span>void main()</div><div>04<span style="white-space:pre">	</span>{</div><div>05<span style="white-space:pre">	</span> &nbsp; &nbsp;volatile int i = 10;</div><div>06<span style="white-space:pre">	</span> &nbsp; &nbsp;int a = i;</div><div>07<span style="white-space:pre">	</span>&nbsp;</div><div>08<span style="white-space:pre">	</span> &nbsp; &nbsp;printf("i = %d", a);</div><div>09<span style="white-space:pre">	</span> &nbsp; &nbsp;__asm {</div><div>10<span style="white-space:pre">	</span> &nbsp; &nbsp; &nbsp; &nbsp;mov dword ptr [ebp-4], 20h</div><div>11<span style="white-space:pre">	</span> &nbsp; &nbsp;}</div><div>12<span style="white-space:pre">	</span>&nbsp;</div><div>13<span style="white-space:pre">	</span> &nbsp; &nbsp;int b = i;</div><div>14<span style="white-space:pre">	</span> &nbsp; &nbsp;printf("i = %d", b);</div><div>15<span style="white-space:pre">	</span>}</div><div>&nbsp; &nbsp; 分别在 Debug 和 Release 版本运行程序，输出都是：</div><div></div><div>i = 10</div><div>i = 32</div><div>&nbsp; &nbsp; 这说明这个 volatile 关键字发挥了它的作用。其实不只是&#8220;内嵌汇编操纵栈&#8221;这种方式属于编译无法识别的变量改变，另外更多的可能是多线程并发访问共享变量时，一个线程改变了变量的值，怎样让改变后的值对其它线程 visible。一般说来，volatile用在如下的几个地方：&nbsp;</div><div>1) 中断服务程序中修改的供其它程序检测的变量需要加volatile；&nbsp;</div><div>2) 多任务环境下各任务间共享的标志应该加volatile；&nbsp;</div><div>3) 存储器映射的硬件寄存器通常也要加volatile说明，因为每次对它的读写都可能由不同意义；</div><div></div><div>2.volatile 指针</div><div></div><div>&nbsp; &nbsp; 和 const 修饰词类似，const 有常量指针和指针常量的说法，volatile 也有相应的概念：</div><div></div><div>修饰由指针指向的对象、数据是 const 或 volatile 的：</div><div></div><div>1<span style="white-space:pre">	</span>const char* cpch;</div><div>2<span style="white-space:pre">	</span>volatile char* vpch;</div><div>注意：对于 VC，这个特性实现在 VC 8 之后才是安全的。</div><div></div><div>指针自身的值&#8212;&#8212;一个代表地址的整数变量，是 const 或 volatile 的：</div><div></div><div>1<span style="white-space:pre">	</span>char* const pchc;</div><div>2<span style="white-space:pre">	</span>char* volatile pchv;</div><div>&nbsp; &nbsp; 注意：(1) 可以把一个非volatile int赋给volatile int，但是不能把非volatile对象赋给一个volatile对象。</div><div></div><div>　　 &nbsp; &nbsp; &nbsp; &nbsp;(2) 除了基本类型外，对用户定义类型也可以用volatile类型进行修饰。</div><div>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; (3) C++中一个有volatile标识符的类只能访问它接口的子集，一个由类的实现者控制的子集。用户只能用const_cast来获得对类型接口的完全访问。此外，volatile向const一样会从类传递到它的成员。</div><div></div><div>3. 多线程下的volatile &nbsp;&nbsp;</div><div></div><div>&nbsp; &nbsp; 有些变量是用volatile关键字声明的。当两个线程都要用到某一个变量且该变量的值会被改变时，应该用volatile声明，该关键字的作用是防止优化编译器把变量从内存装入CPU寄存器中。如果变量被装入寄存器，那么两个线程有可能一个使用内存中的变量，一个使用寄存器中的变量，这会造成程序的错误执行。volatile的意思是让编译器每次操作该变量时一定要从内存中真正取出，而不是使用已经存在寄存器中的值，如下：&nbsp;</div><div></div><div>&nbsp; volatile &nbsp;BOOL &nbsp;bStop &nbsp;= &nbsp;FALSE; &nbsp;</div><div>&nbsp; &nbsp;(1) 在一个线程中： &nbsp;</div><div>&nbsp; while( &nbsp;!bStop &nbsp;) &nbsp;{ &nbsp;... &nbsp;} &nbsp;</div><div>&nbsp; bStop &nbsp;= &nbsp;FALSE; &nbsp;</div><div>&nbsp; return; &nbsp; &nbsp;</div><div>&nbsp; &nbsp;(2) 在另外一个线程中，要终止上面的线程循环： &nbsp;</div><div>&nbsp; bStop &nbsp;= &nbsp;TRUE; &nbsp;</div><div>&nbsp; while( &nbsp;bStop &nbsp;); &nbsp;//等待上面的线程终止，如果bStop不使用volatile申明，那么这个循环将是一个死循环，因为bStop已经读取到了寄存器中，寄存器中bStop的值永远不会变成FALSE，加上volatile，程序在执行时，每次均从内存中读出bStop的值，就不会死循环了。</div><div>&nbsp; &nbsp; 这个关键字是用来设定某个对象的存储位置在内存中，而不是寄存器中。因为一般的对象编译器可能会将其的拷贝放在寄存器中用以加快指令的执行速度，例如下段代码中： &nbsp;</div><div>&nbsp; ... &nbsp;</div><div>&nbsp; int &nbsp;nMyCounter &nbsp;= &nbsp;0; &nbsp;</div><div>&nbsp; for(; &nbsp;nMyCounter&lt;100;nMyCounter++) &nbsp;</div><div>&nbsp; { &nbsp;</div><div>&nbsp; ... &nbsp;</div><div>&nbsp; } &nbsp;</div><div>&nbsp; ... &nbsp;</div><div>&nbsp; &nbsp;在此段代码中，nMyCounter的拷贝可能存放到某个寄存器中（循环中，对nMyCounter的测试及操作总是对此寄存器中的值进行），但是另外又有段代码执行了这样的操作：nMyCounter &nbsp;-= &nbsp;1;这个操作中，对nMyCounter的改变是对内存中的nMyCounter进行操作，于是出现了这样一个现象：nMyCounter的改变不同步。</div><img src ="http://www.cppblog.com/API/aggbug/214839.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-04-13 13:39 <a href="http://www.cppblog.com/API/archive/2017/04/13/214839.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item></channel></rss>