??xml version="1.0" encoding="utf-8" standalone="yes"?>亚洲国产精品一区二区三区久久 ,久久精品成人,久久久久久av无码免费看大片http://www.shnenglu.com/API/zh-cnTue, 06 May 2025 15:27:11 GMTTue, 06 May 2025 15:27:11 GMT60origin游戏服务器引擎介l?/title><link>http://www.shnenglu.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.shnenglu.com/API/archive/2020/05/07/217287.html</guid><wfw:comment>http://www.shnenglu.com/API/comments/217287.html</wfw:comment><comments>http://www.shnenglu.com/API/archive/2020/05/07/217287.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.shnenglu.com/API/comments/commentRss/217287.html</wfw:commentRss><trackback:ping>http://www.shnenglu.com/API/services/trackbacks/217287.html</trackback:ping><description><![CDATA[<div>origin 游戏服务器引擎简?/div><div>==================</div><div></div><div></div><div>origin 是一个由 Go 语言QgolangQ编写的分布式开源游戏服务器引擎。origin适用于各cL戏服务器的开发,包括 H5QHTML5Q游戏服务器?/div><div></div><div>origin 解决的问题:</div><div>* originM设计如go语言设计一PL可能的提供z和易用的模式,快速开发?/div><div>* 能够Ҏ业务需求快速ƈ灉|的制定服务器架构?/div><div>* 利用多核优势Q将不同的service配置C同的nodeQƈ能高效的协同工作?/div><div>* 整个引擎抽象三大对象,node,service,module。通过l一的组合模型管理游戏中各功能模块的关系?/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  github.com/duanhf2012/origin</div><div>```</div><div>于是下蝲到GOPATH环境目录?在src中加入main.go,内容如下Q?/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>一个originq程需要创Z个node对象,Start开始运行。您也可以直接下载origin引擎CZ:</div><div>```</div><div>go get -v -u github.com/duanhf2012/originserver</div><div>```</div><div>本文所有的说明都是Z该示例ؓ丅R?/div><div></div><div>origin引擎三大对象关系</div><div>---------------</div><div>* Node:   可以认ؓ每一个Node代表着一个originq程</div><div>* Service:一个独立的服务可以认ؓ是一个大的功能模块,他是Node的子集,创徏完成q安装Node对象中。服务可以支持对外部RPC{功能?/div><div>* Module: q是origin最对象单元,强烈所有的业务模块都划分成各个的Modulel合Qorigin引擎监控所有服务与Moduleq行状态,例如可以监控它们的慢处理和死循环函数。Module可以建立树状关系。Service本n也是Module的类型?/div><div></div><div>origin集群核心配置文g在config的cluster目录下,在cluster下有子网目录Q如github.com/duanhf2012/originserver的config/cluster目录下有subnet目录Q表C子|名为subnetQ可以新加多个子|的目录配置。子|与子网间是隔离的,后箋支持子|间通信规则Qorigin集群配置以子|的模式配置Q在每个子网下配|多个Node服务?子网在应对复杂的pȝ时可以应用到各个子系l,方便每个子系l的隔离。在CZ的subnet目录中有cluster.json与service.json配置Q?/div><div></div><div>cluster.json如下Q?/div><div>---------------</div><div>```</div><div>{</div><div>    "NodeList":[</div><div>        {</div><div>          "NodeId": 1,</div><div>          "ListenAddr":"127.0.0.1:8001",</div><div>          "NodeName": "Node_Test1",</div><div><span style="white-space:pre"> </span>  "remark":"http://以_打头的,表示只在本机q程Q不Ҏ个子|开?,</div><div>          "ServiceList": ["TestService1","TestService2","TestServiceCall","GateService","_TcpService","HttpService","WSService"]</div><div>        },</div><div><span style="white-space:pre"> </span> {</div><div>          "NodeId": 2,</div><div>          "ListenAddr":"127.0.0.1:8002",</div><div>          "NodeName": "Node_Test1",</div><div><span style="white-space:pre"> </span>  "remark":"http://以_打头的,表示只在本机q程Q不Ҏ个子|开?,</div><div>          "ServiceList": ["TestService1","TestService2","TestServiceCall","GateService","TcpService","HttpService","WSService"]</div><div>        }</div><div>    ]</div><div>```</div><div>---------------</div><div>以上配置了两个结Ҏ务器E序:</div><div>* NodeId: 表示originE序的结点Id标识Q不允许重复?/div><div>* ListenAddr:Rpc通信服务的监听地址</div><div>* NodeName:l点名称</div><div>* remark:备注Q可选项</div><div>* ServiceList:该Node安装的服务列表</div><div>---------------</div><div></div><div>在启动程序命令program start nodeid=1中nodeid是Ҏ该配|装载服务?/div><div>service.json如下Q?/div><div>---------------</div><div>```</div><div>{</div><div>  "Service":{</div><div><span style="white-space:pre"> </span>  "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>  },</div><div><span style="white-space:pre"> </span>  "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>  },</div><div><span style="white-space:pre"> </span>  "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>  }  </div><div>  },</div><div>  "NodeService":[</div><div>   {</div><div>      "NodeId":1,</div><div><span style="white-space:pre"> </span>  "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>  },</div><div><span style="white-space:pre"> </span>  "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>  }  </div><div>   },</div><div>   {</div><div>      "NodeId":2,</div><div><span style="white-space:pre"> </span>  "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>  },</div><div><span style="white-space:pre"> </span>  "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>  }  </div><div>   }</div><div>  ]</div><div> </div><div>}</div><div>```</div><div></div><div>---------------</div><div>以上配置分ؓ两个部分QService与NodeServiceQNodeService中配|的对应l点中服务的配置Q如果启动程序中Ҏnodeid查找该域的对应的服务Q如果找不到Ӟ从Service公共部分查找?/div><div></div><div>**HttpService配置**</div><div>* ListenAddr:Http监听地址</div><div>* ReadTimeout:ȝl超时毫U?/div><div>* WriteTimeout:写网l超时毫U?/div><div>* ProcessTimeout: 处理时毫秒</div><div>* CAFile: 证书文gQ如果您的服务器通过web服务器代理配|https可以忽略该配|?/div><div></div><div>**TcpService配置**</div><div>* ListenAddr: 监听地址</div><div>* MaxConnNum: 允许最大连接数</div><div>* PendingWriteNumQ发送网l队列最大数?/div><div>* LittleEndian:是否端</div><div>* MinMsgLen:包最长?/div><div>* MaxMsgLen:包最大长?/div><div></div><div>**WSService配置**</div><div>* ListenAddr: 监听地址</div><div>* MaxConnNum: 允许最大连接数</div><div>* PendingWriteNumQ发送网l队列最大数?/div><div>* MaxMsgLen:包最大长?/div><div>---------------</div><div></div><div></div><div></div><div></div><div>W一章:origin基础:</div><div>---------------</div><div>查看github.com/duanhf2012/originserver中的simple_service中新Z个服务,分别是TestService1.go与CTestService2.go?/div><div></div><div>simple_service/TestService1.go如下Q?/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(&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,事g驱动,定时器等</div><div><span style="white-space:pre"> </span>service.Service</div><div>}</div><div></div><div>//服务初始化函敎ͼ在安装服务时Q服务将自动调用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如下Q?/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(&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.goq行代码</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如下Q?/div><div>```</div><div>{</div><div>    "NodeList":[</div><div>        {</div><div>          "NodeId": 1,</div><div>          "ListenAddr":"127.0.0.1:8001",</div><div>          "NodeName": "Node_Test1",</div><div><span style="white-space:pre"> </span>  "remark":"http://以_打头的,表示只在本机q程Q不Ҏ个子|开?,</div><div>          "ServiceList": ["TestService1","TestService2"]</div><div>        }</div><div>    ]</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>W二章:Service中常用功?</div><div>---------------</div><div></div><div>定时?</div><div>---------------</div><div>在开发中最常用的功能有定时dQorigin提供两种定时方式Q?/div><div></div><div>一UAfterFunc函数Q可以间隔一定时间触发回调,参照simple_service/TestService2.go,实现如下Q?/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>此时日志可以看到每隔1U钟会print一?tick."Q如果下ơ还需要触发,需要重新设|定时器</div><div></div><div></div><div>另一U方式是cMLinuxpȝ的crontab命oQ用如下:</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>以上q行l果每换分钟时打?A minute passed!</div><div></div><div></div><div>打开多协E模?</div><div>---------------</div><div>在origin引擎设计中,所有的服务是单协程模式Q这样在~写逻辑代码Ӟ不用考虑U程安全问题。极大的减少开发难度,但某些开发场景下不用考虑q个问题Q而且需要ƈ发执行的情况Q比如,某服务只处理数据库操作控Ӟ而数据库处理中发生阻塞等待的问题Q因Z个协E,该服务接受的数据库操作只能是一?/div><div>一个的排队处理Q效率过低。于是可以打开此模式指定处理协E数Q代码如下:</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>//打开多线E处理模式,10个协Eƈ发处?/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>Z</div><div></div><div></div><div>性能监控功能:</div><div>---------------</div><div>我们在开发一个大型的pȝӞl常׃一些代码质量的原因Q生处理过慢或者死循环的生,该功能可以被监测到。用方法如下:</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>//监控过1U的慢处?/div><div><span style="white-space:pre"> </span>slf.GetProfiler().SetOverTime(time.Second*1)</div><div><span style="white-space:pre"> </span>//监控过10U的慢处理Q您可以用它来定位是否存在死循环</div><div><span style="white-space:pre"> </span>//比如以下讄10U,我的应用中是不会发生过10U的一ơ函数调?/div><div><span style="white-space:pre"> </span>//所以设|ؓ10U?/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>//打开多线E处理模式,10个协Eƈ发处?/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>//打开性能分析报告功能Qƈ讄10U汇报一?/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>q在main.go中,打开了性能报告器,以每10U汇报一ơ,因ؓ上面的例子中Q定时器是有d@环,所以可以得C下报告:</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>W三章:Module使用:</div><div>---------------</div><div></div><div>Module创徏与销?</div><div>---------------</div><div>可以认ؓService是一UModuleQ它有Module所有的功能。在CZ代码中可以参考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(&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 := &Module1{}</div><div><span style="white-space:pre"> </span>module2 := &Module2{}</div><div><span style="white-space:pre"> </span>//module1d到服务中</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中创Z一条线型的模块关系TestService3->module1->module2Q调用AddModule后会q回Module的IdQ自动生成的Id?0e17开?内部的idQ您可以自己讄Id。当调用ReleaseModule释放时module1Ӟ同样会将module2释放。会自动调用OnRelease函数Q日志顺序如下:</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中同样可以用定时器功能Q请参照W二章节的定时器部分?/div><div></div><div></div><div>W四章:事g使用</div><div>---------------</div><div>事g是origin中一个重要的l成部分Q可以在同一个node中的service与service或者与module之间q行事g通知。系l内|的几个服务Q如QTcpService/HttpService{都是通过事g功能实现。他也是一个典型的观察者设计模型。在event中有两个cd的interfaceQ一个是event.IEventProcessor它提供注册与卸蝲功能Q另一个是event.IEventHandler提供消息q播{功能?/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以内l系l预?/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(&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>//10U后触发q播事g</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>//q播事gQ传入event.Event对象Q类型ؓEVENT1,Data可以自定义Q何数?/div><div><span style="white-space:pre"> </span>//q样Q所有监听者都可以收到该事?/div><div><span style="white-space:pre"> </span>slf.GetEventHandler().NotifyEvent(&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(&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中注册EVENT1cd事g监听</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>//服务初始化函敎ͼ在安装服务时Q服务将自动调用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中注册EVENT1cd事g监听</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(&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>E序q行10U后Q调用slf.TriggerEvent函数q播事gQ于是在TestService5中会收到</div><div>```</div><div>OnServiceEvent type :1001 data:event data.</div><div>OnModuleEvent type :1001 data:event data.</div><div>```</div><div>在上面的TestModule中监听的事情Q当q个Module被Release时监听会自动卸蝲?/div><div></div><div>W五章:RPC使用</div><div>---------------</div><div>RPC是service与service间通信的重要方式,它允许跨q程node互相讉KQ当然也可以指定nodeidq行调用。如下示例:</div><div></div><div>simple_rpc/TestService6.go文g如下Q?/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(&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文g如下Q?/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(&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,inputZ入的rpc,output出参?/div><div><span style="white-space:pre"> </span>err := slf.Call("TestService6.RPC_Sum",&input,&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",&input,func(output *int,err error){</div><div><span style="white-space:pre"> </span>})*/</div><div><span style="white-space:pre"> </span>//异步调用Q在数据q回Ӟ会回调传入函?/div><div><span style="white-space:pre"> </span>//注意函数的第一个参C定是RPC_Sum函数的第二个参数Qerr error为RPC_Sumq回?/div><div><span style="white-space:pre"> </span>slf.AsyncCall("TestService6.RPC_Sum",&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>//在某些应用场景下不需要数据返回可以用GoQ它是不d?只需要填入输入参?/div><div><span style="white-space:pre"> </span>err := slf.Go("TestService6.RPC_Sum",&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会q播l所有的node</div><div><span style="white-space:pre"> </span>//slf.CastGo("TestService6.RPC_Sum",&input)</div><div>}</div><div></div><div>```</div><div>您可以把TestService6配置到其他的Node中,比如NodeId?中。只要在一个子|,origin引擎可以无差别调用。开发者只需要关注Service关系。同样它也是您服务器架构设计的核心需要思考的部分?/div><div></div><div></div><div>W六章:HttpService使用</div><div>---------------</div><div>HttpService是origin引擎中系l实现的http服务Qhttp接口中常用的GET,POST以及url路由处理?/div><div></div><div>simple_http/TestHttpService.go文g如下Q?/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(&sysservice.HttpService{})</div><div><span style="white-space:pre"> </span>node.Setup(&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>//获取pȝ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>//新徏q设|\由对?/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ҎQ请求url:http://127.0.0.1:9402/get/query?nickname=boyce</div><div><span style="white-space:pre"> </span>//qheader中新增key为uid,value?000的头,则用postman试q回l果为:</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Ҏ hurl:http://127.0.0.1:9402/post/query</div><div><span style="white-space:pre"> </span>//q回l果为:{"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方式获取目录下的资源Qhttp://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>//完成q回</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>//也可以采用直接返回数据对象方式,如下Q?/div><div><span style="white-space:pre"> </span>session.WriteJsonDone(http.StatusOK,&HttpRespone{Msg: "hello world"})</div><div>}</div><div></div><div>```</div><div>注意Q要在main.go中加入import _ "orginserver/simple_service"Qƈ且在config/cluster/subnet/cluster.json中的ServiceList加入服务?/div><div></div><div></div><div></div><div>W七章:TcpService服务使用</div><div>---------------</div><div>TcpService是origin引擎中系l实现的Tcp服务Q可以支持自定义消息格式处理器。只要重新实现network.Processor接口。目前内|已l实现最常用的protobuf处理器?/div><div></div><div>simple_tcp/TestTcpService.go文g如下Q?/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(&sysservice.TcpService{})</div><div><span style="white-space:pre"> </span>node.Setup(&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 =  node.GetService("TcpService").(*sysservice.TcpService)</div><div></div><div><span style="white-space:pre"> </span>//新徏内置的protobuf处理器,您也可以自定义\由器Q比如jsonQ后l会补充</div><div><span style="white-space:pre"> </span>slf.processor = processor.NewPBProcessor()</div><div></div><div><span style="white-space:pre"> </span>//注册监听客户q接断开事g</div><div><span style="white-space:pre"> </span>slf.processor.RegisterDisConnected(slf.OnDisconnected)</div><div><span style="white-space:pre"> </span>//注册监听客户q接事g</div><div><span style="white-space:pre"> </span>slf.processor.RegisterConnected(slf.OnConnected)</div><div><span style="white-space:pre"> </span>//注册监听消息cdMsgType_MsgReqQƈ注册回调</div><div><span style="white-space:pre"> </span>slf.processor.Register(uint16(msgpb.MsgType_MsgReq),&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>//解析客户端发q来的数?/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,&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>W八章:其他pȝ模块介绍</div><div>---------------</div><div>* sysservice/wsservice.go:支持了WebSocket协议Q用方法与TcpServicecM</div><div>* sysmodule/DBModule.go:对mysql数据库操?/div><div>* sysmodule/RedisModule.go:对Redis数据q行操作</div><div>* sysmodule/HttpClientPoolModule.go:Http客户端请求封?/div><div>* log/log.go:日志的封装,可以使用它构建对象记录业务文件日?/div><div>* util:在该目录下,有常用的uuid,hash,md5,协程装{工具库</div><div>* https://github.com/duanhf2012/originservice: 其他扩展支持的服务可以在该工E上看到Q目前支持firebase推送的装?/div><div></div><div></div><div>**Ƣ迎加入origin服务器开发QQ交流?168306674Q有M疑问我都会及时解{?*</div><div></div><div>提交bug及特? https://github.com/duanhf2012/origin/issues</div><div></div><div></div><div></div><img src ="http://www.shnenglu.com/API/aggbug/217287.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.shnenglu.com/API/" target="_blank">C++技术中?/a> 2020-05-07 16:06 <a href="http://www.shnenglu.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.shnenglu.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.shnenglu.com/API/archive/2020/05/07/217286.html</guid><wfw:comment>http://www.shnenglu.com/API/comments/217286.html</wfw:comment><comments>http://www.shnenglu.com/API/archive/2020/05/07/217286.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.shnenglu.com/API/comments/commentRss/217286.html</wfw:commentRss><trackback:ping>http://www.shnenglu.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, "Helvetica Neue", "PingFang SC", "Microsoft YaHei", "Source Han Sans SC", "Noto Sans CJK SC", "WenQuanYi Micro Hei", sans-serif; font-size: 15px; white-space: pre-wrap; background-color: #ffffff;"><span data-text="true">现在go语言比较行的有leaf,gowold,origin。前两个比较基础Q实现集还需要进行二ơ的~码设计。origin不一P只需要通过配置方便快速的集群?/span></span>originM设计如go语言设计一PL可能的提供z和易用的模式,快速开发?能够Ҏ业务需求快速ƈ灉|的制定服务器架构?利用多核优势Q将不同的service配置C同的nodeQƈ能高效的协同工作?整个引擎抽象三大对象,node,service,module。通过l一的组合模型管理游戏中各功能模块的关系?nbsp;<br /><br /><br /><div>origin引擎三大对象关系</div><div>---------------</div><div>* Node:   可以认ؓ每一个Node代表着一个originq程</div><div>* Service:一个独立的服务可以认ؓ是一个大的功能模块,他是Node的子集,创徏完成q安装Node对象中。服务可以支持对外部RPC{功能?/div><div>* Module: q是origin最对象单元,强烈所有的业务模块都划分成各个的Modulel合Qorigin引擎监控所有服务与Moduleq行状态,例如可以监控它们的慢处理和死循环函数。Module可以建立树状关系。Service本n也是Module的类型?br /><br />更加详细的参照项目地址Q?a >https://github.com/duanhf2012/origin</a></div><img src ="http://www.shnenglu.com/API/aggbug/217286.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.shnenglu.com/API/" target="_blank">C++技术中?/a> 2020-05-07 16:04 <a href="http://www.shnenglu.com/API/archive/2020/05/07/217286.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>套接字read/writeq回?/title><link>http://www.shnenglu.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.shnenglu.com/API/archive/2017/12/12/215420.html</guid><wfw:comment>http://www.shnenglu.com/API/comments/215420.html</wfw:comment><comments>http://www.shnenglu.com/API/archive/2017/12/12/215420.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.shnenglu.com/API/comments/commentRss/215420.html</wfw:commentRss><trackback:ping>http://www.shnenglu.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: "PingFang SC", "Microsoft YaHei", SimHei, Arial, SimSun; font-size: 16px; background-color: #ffffff;">1、阻塞模式与非阻塞模式下recv的返回值各代表什么意思?有没有区别?Q就我目前了解阻塞与非阻塞recvq回值没有区分,都是 <0Q出错,=0Q连接关闭,>0接收到数据大,特别Q返回?nbsp;<0时ƈ?errno == EINTR || errno == EWOULDBLOCK || errno == EAGAIN)的情况下认ؓq接是正常的Ql接收。只是阻塞模式下recv会阻塞着接收数据Q非d模式下如果没有数据会q回Q不会阻塞着读,因此需?nbsp;循环d</p><p style="box-sizing: border-box; margin: 0px; padding: 0px; word-wrap: break-word; word-break: normal; color: #454545; font-family: "PingFang SC", "Microsoft YaHei", 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: "PingFang SC", "Microsoft YaHei", 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: "PingFang SC", "Microsoft YaHei", 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: "PingFang SC", "Microsoft YaHei", SimHei, Arial, SimSun; font-size: 16px; background-color: #ffffff;">d与非dwriteq回值没有区分,都是 <0Q出错,=0Q连接关闭,>0发送数据大,特别Q返回?nbsp;<0时ƈ?errno == EINTR || errno == EWOULDBLOCK || errno == EAGAIN)的情况下认ؓq接是正常的Ql发送。只是阻塞模式下write会阻塞着发送数据,非阻塞模式下如果暂时无法发送数据会q回Q不会阻塞着 writeQ因此需要@环发?/p><p style="box-sizing: border-box; margin: 0px; padding: 0px; word-wrap: break-word; word-break: normal; color: #454545; font-family: "PingFang SC", "Microsoft YaHei", 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: "PingFang SC", "Microsoft YaHei", 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: "PingFang SC", "Microsoft YaHei", SimHei, Arial, SimSun; font-size: 16px; background-color: #ffffff;">3、阻塞模式下readq回?nbsp;< 0 && errno != EINTR && errno != EWOULDBLOCK && errno != EAGAINӞq接异常Q需要关闭,readq回?nbsp;< 0 && (errno == EINTR || errno == EWOULDBLOCK || errno == EAGAIN)时表C没有数据,需要l接Ӟ如果q回值大?表示接送到数据?nbsp;</p><p style="box-sizing: border-box; margin: 0px; padding: 0px; word-wrap: break-word; word-break: normal; color: #454545; font-family: "PingFang SC", "Microsoft YaHei", SimHei, Arial, SimSun; font-size: 16px; background-color: #ffffff;">非阻塞模式下readq回?nbsp;< 0表示没有数据Q? 0表示q接断开Q?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: "PingFang SC", "Microsoft YaHei", SimHei, Arial, SimSun; font-size: 16px; background-color: #ffffff;">q?U模式下的返回值是不是q么理解Q有没有跟详l的理解或跟准确的说明? </p><p style="box-sizing: border-box; margin: 0px; padding: 0px; word-wrap: break-word; word-break: normal; color: #454545; font-family: "PingFang SC", "Microsoft YaHei", 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: "PingFang SC", "Microsoft YaHei", 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: "PingFang SC", "Microsoft YaHei", SimHei, Arial, SimSun; font-size: 16px; background-color: #ffffff;">4、阻塞模式与非阻塞模式下是否sendq回?nbsp;< 0 && (errno == EINTR || errno == EWOULDBLOCK || errno == EAGAIN)表示暂时发送失败,需要重试,如果sendq回?nbsp;<= 0, && errno != EINTR && errno != EWOULDBLOCK && errno != EAGAINӞq接异常Q需要关闭,如果sendq回?nbsp;> 0则表C发送了数据Qsend的返回值是否这么理解,d模式与非d模式下sendq回?0是否都是发送失败,q是那个模式下表C暂时不可发送,需?nbsp;重发Q?/p><p style="box-sizing: border-box; margin: 0px; padding: 0px; word-wrap: break-word; word-break: normal; color: #454545; font-family: "PingFang SC", "Microsoft YaHei", 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: "PingFang SC", "Microsoft YaHei", 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: "PingFang SC", "Microsoft YaHei", 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: "PingFang SC", "Microsoft YaHei", 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: "PingFang SC", "Microsoft YaHei", SimHei, Arial, SimSun; font-size: 16px; background-color: #ffffff;">1. send函数</p><p style="box-sizing: border-box; margin: 0px; padding: 0px; word-wrap: break-word; word-break: normal; color: #454545; font-family: "PingFang SC", "Microsoft YaHei", SimHei, Arial, SimSun; font-size: 16px; background-color: #ffffff;">int send( SOCKET s, const char FAR *buf, int len, int flags );  </p><p style="box-sizing: border-box; margin: 0px; padding: 0px; word-wrap: break-word; word-break: normal; color: #454545; font-family: "PingFang SC", "Microsoft YaHei", SimHei, Arial, SimSun; font-size: 16px; background-color: #ffffff;">不论是客Lq是服务器端应用E序都用send函数来向TCPq接的另一端发送数据?/p><p style="box-sizing: border-box; margin: 0px; padding: 0px; word-wrap: break-word; word-break: normal; color: #454545; font-family: "PingFang SC", "Microsoft YaHei", SimHei, Arial, SimSun; font-size: 16px; background-color: #ffffff;">客户端程序一般用send函数向服务器发送请求,而服务器则通常用send函数来向客户E序发送应{?/p><p style="box-sizing: border-box; margin: 0px; padding: 0px; word-wrap: break-word; word-break: normal; color: #454545; font-family: "PingFang SC", "Microsoft YaHei", SimHei, Arial, SimSun; font-size: 16px; background-color: #ffffff;">该函数的Q?/p><p style="box-sizing: border-box; margin: 0px; padding: 0px; word-wrap: break-word; word-break: normal; color: #454545; font-family: "PingFang SC", "Microsoft YaHei", SimHei, Arial, SimSun; font-size: 16px; background-color: #ffffff;">W一个参数指定发送端套接字描q符Q?/p><p style="box-sizing: border-box; margin: 0px; padding: 0px; word-wrap: break-word; word-break: normal; color: #454545; font-family: "PingFang SC", "Microsoft YaHei", SimHei, Arial, SimSun; font-size: 16px; background-color: #ffffff;">W二个参数指明一个存攑ֺ用程序要发送数据的~冲区;</p><p style="box-sizing: border-box; margin: 0px; padding: 0px; word-wrap: break-word; word-break: normal; color: #454545; font-family: "PingFang SC", "Microsoft YaHei", SimHei, Arial, SimSun; font-size: 16px; background-color: #ffffff;">W三个参数指明实际要发送的数据的字节数Q?/p><p style="box-sizing: border-box; margin: 0px; padding: 0px; word-wrap: break-word; word-break: normal; color: #454545; font-family: "PingFang SC", "Microsoft YaHei", SimHei, Arial, SimSun; font-size: 16px; background-color: #ffffff;">W四个参C般置0?/p><p style="box-sizing: border-box; margin: 0px; padding: 0px; word-wrap: break-word; word-break: normal; color: #454545; font-family: "PingFang SC", "Microsoft YaHei", SimHei, Arial, SimSun; font-size: 16px; background-color: #ffffff;">q里只描q同步Socket的send函数的执行流E。当调用该函数时Qsend先比较待发送数据的长度len和套接字s的发送缓冲的长度Q如果len大于s的发送缓冲区的长度,该函数返回SOCKET_ERRORQ如果len于或者等于s的发送缓冲区的长度,那么send先检查协?nbsp;是否正在发送s的发送缓冲中的数据,如果是就{待协议把数据发送完Q如果协议还没有开始发送s的发送缓冲中的数据或者s的发送缓冲中没有数据Q那?nbsp;send比较s的发送缓冲区的剩余空间和lenQ如果len大于剩余I间大小send׃直等待协议把s的发送缓冲中的数据发送完Q如果len于剩余 I间大小send׃仅把buf中的数据copy到剩余空间里Q?span style="box-sizing: border-box; margin: 0px; padding: 0px; color: #000000;">注意q不是send</span>把s的发送缓冲中的数据传到连接的另一端的Q而是协议传的Qsend仅仅是把buf中的数据copy到s的发送缓冲区的剩余空间里<span style="box-sizing: border-box; margin: 0px; padding: 0px; color: #000000;">Q?/span>如果send函数copy数据成功Q就q回实际copy的字节数Q如果send在copy数据时出现错误,那么sendp回SOCKET_ERRORQ如果send在等待协议传送数据时|络断开的话Q那么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: "PingFang SC", "Microsoft YaHei", 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的发送缓冲的剩余I间里后它就q回了,但是此时q些数据q不一定马上被传到q接的另一?span style="box-sizing: border-box; margin: 0px; padding: 0px; color: #000000;">?/span>如果协议在后l的传送过E中出现|络错误的话Q那么下一个Socket函数׃q回SOCKET_ERROR。(每一个除send外的Socket函数在执 行的最开始总要先等待套接字的发送缓冲中的数据被协议传送完毕才能l,如果在等待时出现|络错误Q那么该Socket函数p?nbsp;SOCKET_ERRORQ?/p><p style="box-sizing: border-box; margin: 0px; padding: 0px; word-wrap: break-word; word-break: normal; color: #454545; font-family: "PingFang SC", "Microsoft YaHei", SimHei, Arial, SimSun; font-size: 16px; background-color: #ffffff;">注意Q在Unixpȝ下,如果send在等待协议传送数据时|络断开的话Q调用send的进E会接收C个SIGPIPE信号Q进E对该信L默认处理是进E终止?/p><p style="box-sizing: border-box; margin: 0px; padding: 0px; word-wrap: break-word; word-break: normal; color: #454545; font-family: "PingFang SC", "Microsoft YaHei", SimHei, Arial, SimSun; font-size: 16px; background-color: #ffffff;">Send函数的返回值有三类Q?/p><p style="box-sizing: border-box; margin: 0px; padding: 0px; word-wrap: break-word; word-break: normal; color: #454545; font-family: "PingFang SC", "Microsoft YaHei", SimHei, Arial, SimSun; font-size: 16px; background-color: #ffffff;">Q?Q返回?0Q?/p><p style="box-sizing: border-box; margin: 0px; padding: 0px; word-wrap: break-word; word-break: normal; color: #454545; font-family: "PingFang SC", "Microsoft YaHei", SimHei, Arial, SimSun; font-size: 16px; background-color: #ffffff;">Q?Q返回?lt;0Q发送失败,错误原因存于全局变量errno?/p><p style="box-sizing: border-box; margin: 0px; padding: 0px; word-wrap: break-word; word-break: normal; color: #454545; font-family: "PingFang SC", "Microsoft YaHei", SimHei, Arial, SimSun; font-size: 16px; background-color: #ffffff;">Q?Q返回?gt;0Q表C发送的字节敎ͼ实际上是拯到发送缓冲中的字节数Q?/p><p style="box-sizing: border-box; margin: 0px; padding: 0px; word-wrap: break-word; word-break: normal; color: #454545; font-family: "PingFang SC", "Microsoft YaHei", 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: "PingFang SC", "Microsoft YaHei", SimHei, Arial, SimSun; font-size: 16px; background-color: #ffffff;">错误代码Q?/p><p style="box-sizing: border-box; margin: 0px; padding: 0px; word-wrap: break-word; word-break: normal; color: #454545; font-family: "PingFang SC", "Microsoft YaHei", SimHei, Arial, SimSun; font-size: 16px; background-color: #ffffff;">EBADF 参数s 非合法的socket处理代码?br style="box-sizing: border-box;" />EFAULT 参数中有一指针指向无法存取的内存空?br style="box-sizing: border-box;" />ENOTSOCK 参数sZ文g描述词,非socket?br style="box-sizing: border-box;" />EINTR 被信h中断?br style="box-sizing: border-box;" />EAGAIN 此操作会令进E阻断,但参数s的socketZ可阻断?br style="box-sizing: border-box;" />ENOBUFS pȝ的缓冲内存不?br style="box-sizing: border-box;" />ENOMEM 核心内存不<br style="box-sizing: border-box;" />EINVAL 传给pȝ调用的参C正确?/p><p style="box-sizing: border-box; margin: 0px; padding: 0px; word-wrap: break-word; word-break: normal; color: #454545; font-family: "PingFang SC", "Microsoft YaHei", 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: "PingFang SC", "Microsoft YaHei", SimHei, Arial, SimSun; font-size: 16px; background-color: #ffffff;">2.  recv函数</p><p style="box-sizing: border-box; margin: 0px; padding: 0px; word-wrap: break-word; word-break: normal; color: #454545; font-family: "PingFang SC", "Microsoft YaHei", SimHei, Arial, SimSun; font-size: 16px; background-color: #ffffff;">int recv( SOCKET s,     char FAR *buf,      int len,     int flags     );   </p><p style="box-sizing: border-box; margin: 0px; padding: 0px; word-wrap: break-word; word-break: normal; color: #454545; font-family: "PingFang SC", "Microsoft YaHei", SimHei, Arial, SimSun; font-size: 16px; background-color: #ffffff;">不论是客Lq是服务器端应用E序都用recv函数从TCPq接的另一端接收数据?/p><p style="box-sizing: border-box; margin: 0px; padding: 0px; word-wrap: break-word; word-break: normal; color: #454545; font-family: "PingFang SC", "Microsoft YaHei", SimHei, Arial, SimSun; font-size: 16px; background-color: #ffffff;">该函数的Q?/p><p style="box-sizing: border-box; margin: 0px; padding: 0px; word-wrap: break-word; word-break: normal; color: #454545; font-family: "PingFang SC", "Microsoft YaHei", SimHei, Arial, SimSun; font-size: 16px; background-color: #ffffff;">W一个参数指定接收端套接字描q符Q?/p><p style="box-sizing: border-box; margin: 0px; padding: 0px; word-wrap: break-word; word-break: normal; color: #454545; font-family: "PingFang SC", "Microsoft YaHei", SimHei, Arial, SimSun; font-size: 16px; background-color: #ffffff;">W二个参数指明一个缓冲区Q该~冲区用来存放recv函数接收到的数据Q?/p><p style="box-sizing: border-box; margin: 0px; padding: 0px; word-wrap: break-word; word-break: normal; color: #454545; font-family: "PingFang SC", "Microsoft YaHei", SimHei, Arial, SimSun; font-size: 16px; background-color: #ffffff;">W三个参数指明buf的长度;</p><p style="box-sizing: border-box; margin: 0px; padding: 0px; word-wrap: break-word; word-break: normal; color: #454545; font-family: "PingFang SC", "Microsoft YaHei", SimHei, Arial, SimSun; font-size: 16px; background-color: #ffffff;">W四个参C般置0?/p><p style="box-sizing: border-box; margin: 0px; padding: 0px; word-wrap: break-word; word-break: normal; color: #454545; font-family: "PingFang SC", "Microsoft YaHei", SimHei, Arial, SimSun; font-size: 16px; background-color: #ffffff;">q里只描q同步Socket的recv函数的执行流E。当应用E序调用recv函数Ӟrecv先等待s的发送缓?nbsp;中的数据被协议传送完毕,如果协议在传送s的发送缓冲中的数据时出现|络错误Q那么recv函数q回SOCKET_ERRORQ如果s的发送缓冲中没有?nbsp;据或者数据被协议成功发送完毕后Qrecv先检查套接字s的接收缓冲区Q如果s接收~冲Z没有数据或者协议正在接收数据,那么recv׃直等待,只到 协议把数据接收完毕。当协议把数据接收完毕,recv函数把s的接收缓冲中的数据copy到buf中(<span style="box-sizing: border-box; margin: 0px; padding: 0px; color: #000000;">注意协议接收到的数据可能大于buf</span>的长度,所?nbsp;在这U情况下要调用几ơrecv函数才能把s的接收缓冲中的数据copy完。recv函数仅仅是copy数据Q真正的接收数据是协议来完成的)Qrecv函数q回其实际copy的字节数。如果recv在copy时出错,那么它返回SOCKET_ERRORQ如果recv函数在等待协议接收数据时|络中断了,那么它返??/p><p style="box-sizing: border-box; margin: 0px; padding: 0px; word-wrap: break-word; word-break: normal; color: #454545; font-family: "PingFang SC", "Microsoft YaHei", SimHei, Arial, SimSun; font-size: 16px; background-color: #ffffff;">注意Q在Unixpȝ下,如果recv函数在等待协议接收数据时|络断开了,那么调用recv的进E会接收C个SIGPIPE信号Q进E对该信L默认处理是进E终止?/p><p style="box-sizing: border-box; margin: 0px; padding: 0px; word-wrap: break-word; word-break: normal; color: #454545; font-family: "PingFang SC", "Microsoft YaHei", 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: "PingFang SC", "Microsoft YaHei", 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: "PingFang SC", "Microsoft YaHei", SimHei, Arial, SimSun; font-size: 16px; background-color: #ffffff;">d与非drecvq回值没有区别,都是Q?/p><p style="box-sizing: border-box; margin: 0px; padding: 0px; word-wrap: break-word; word-break: normal; color: #454545; font-family: "PingFang SC", "Microsoft YaHei", 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: "PingFang SC", "Microsoft YaHei", SimHei, Arial, SimSun; font-size: 16px; background-color: #ffffff;">=0 Ҏ调用了close API来关闭连?/p><p style="box-sizing: border-box; margin: 0px; padding: 0px; word-wrap: break-word; word-break: normal; color: #454545; font-family: "PingFang SC", "Microsoft YaHei", SimHei, Arial, SimSun; font-size: 16px; background-color: #ffffff;">>0 接收到的数据大小Q?/p><p style="box-sizing: border-box; margin: 0px; padding: 0px; word-wrap: break-word; word-break: normal; color: #454545; font-family: "PingFang SC", "Microsoft YaHei", 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: "PingFang SC", "Microsoft YaHei", SimHei, Arial, SimSun; font-size: 16px; background-color: #ffffff;">特别圎ͼq回?lt;0时ƈ?errno == EINTR || errno == EWOULDBLOCK || errno == EAGAIN)的情况下认ؓq接是正常的Ql接收?/p><p style="box-sizing: border-box; margin: 0px; padding: 0px; word-wrap: break-word; word-break: normal; color: #454545; font-family: "PingFang SC", "Microsoft YaHei", SimHei, Arial, SimSun; font-size: 16px; background-color: #ffffff;">只是d模式下recv会一直阻塞直到接收到数据Q非d模式下如果没有数据就会返回,不会d着读,因此需要@环读取)?/p><p style="box-sizing: border-box; margin: 0px; padding: 0px; word-wrap: break-word; word-break: normal; color: #454545; font-family: "PingFang SC", "Microsoft YaHei", 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: "PingFang SC", "Microsoft YaHei", SimHei, Arial, SimSun; font-size: 16px; background-color: #ffffff;">q回说明Q?nbsp;  </p><p style="box-sizing: border-box; margin: 0px; padding: 0px; word-wrap: break-word; word-break: normal; color: #454545; font-family: "PingFang SC", "Microsoft YaHei", SimHei, Arial, SimSun; font-size: 16px; background-color: #ffffff;">Q?Q成功执行时Q返回接收到的字节数?/p><p style="box-sizing: border-box; margin: 0px; padding: 0px; word-wrap: break-word; word-break: normal; color: #454545; font-family: "PingFang SC", "Microsoft YaHei", SimHei, Arial, SimSun; font-size: 16px; background-color: #ffffff;">Q?Q若另一端已关闭q接则返?Q这U关闭是Ҏd且正常的关闭</p><p style="box-sizing: border-box; margin: 0px; padding: 0px; word-wrap: break-word; word-break: normal; color: #454545; font-family: "PingFang SC", "Microsoft YaHei", SimHei, Arial, SimSun; font-size: 16px; background-color: #ffffff;">Q?Q失败返?1Qerrno被设Z下的某个?nbsp;  </p><p style="box-sizing: border-box; margin: 0px; padding: 0px; word-wrap: break-word; word-break: normal; color: #454545; font-family: "PingFang SC", "Microsoft YaHei", SimHei, Arial, SimSun; font-size: 16px; background-color: #ffffff;">EAGAINQ套接字已标Cؓ非阻塞,而接收操作被d或者接收超?/p><p style="box-sizing: border-box; margin: 0px; padding: 0px; word-wrap: break-word; word-break: normal; color: #454545; font-family: "PingFang SC", "Microsoft YaHei", SimHei, Arial, SimSun; font-size: 16px; background-color: #ffffff;">EBADFQsock不是有效的描q词</p><p style="box-sizing: border-box; margin: 0px; padding: 0px; word-wrap: break-word; word-break: normal; color: #454545; font-family: "PingFang SC", "Microsoft YaHei", SimHei, Arial, SimSun; font-size: 16px; background-color: #ffffff;">ECONNREFUSEQ远E主机阻l网l连?/p><p style="box-sizing: border-box; margin: 0px; padding: 0px; word-wrap: break-word; word-break: normal; color: #454545; font-family: "PingFang SC", "Microsoft YaHei", SimHei, Arial, SimSun; font-size: 16px; background-color: #ffffff;">EFAULTQ内存空间访问出?/p><p style="box-sizing: border-box; margin: 0px; padding: 0px; word-wrap: break-word; word-break: normal; color: #454545; font-family: "PingFang SC", "Microsoft YaHei", SimHei, Arial, SimSun; font-size: 16px; background-color: #ffffff;">EINTRQ操作被信号中断</p><p style="box-sizing: border-box; margin: 0px; padding: 0px; word-wrap: break-word; word-break: normal; color: #454545; font-family: "PingFang SC", "Microsoft YaHei", SimHei, Arial, SimSun; font-size: 16px; background-color: #ffffff;">EINVALQ参数无?/p><p style="box-sizing: border-box; margin: 0px; padding: 0px; word-wrap: break-word; word-break: normal; color: #454545; font-family: "PingFang SC", "Microsoft YaHei", SimHei, Arial, SimSun; font-size: 16px; background-color: #ffffff;">ENOMEMQ内存不?/p><p style="box-sizing: border-box; margin: 0px; padding: 0px; word-wrap: break-word; word-break: normal; color: #454545; font-family: "PingFang SC", "Microsoft YaHei", SimHei, Arial, SimSun; font-size: 16px; background-color: #ffffff;">ENOTCONNQ与面向q接兌的套接字未被连接上</p><p style="box-sizing: border-box; margin: 0px; padding: 0px; word-wrap: break-word; word-break: normal; color: #454545; font-family: "PingFang SC", "Microsoft YaHei", SimHei, Arial, SimSun; font-size: 16px; background-color: #ffffff;">ENOTSOCKQsock索引的不是套接字</p><img src ="http://www.shnenglu.com/API/aggbug/215420.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.shnenglu.com/API/" target="_blank">C++技术中?/a> 2017-12-12 10:32 <a href="http://www.shnenglu.com/API/archive/2017/12/12/215420.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>关于linux信号ȝhttp://www.shnenglu.com/API/archive/2017/09/27/215270.htmlC++技术中?/dc:creator>C++技术中?/author>Wed, 27 Sep 2017 09:51:00 GMThttp://www.shnenglu.com/API/archive/2017/09/27/215270.htmlhttp://www.shnenglu.com/API/comments/215270.htmlhttp://www.shnenglu.com/API/archive/2017/09/27/215270.html#Feedback0http://www.shnenglu.com/API/comments/commentRss/215270.htmlhttp://www.shnenglu.com/API/services/trackbacks/215270.html
$ kill -l
1) SIGHUP       2) SIGINT       3) SIGQUIT      4) SIGILL
 5) SIGTRAP      6) SIGABRT      7) SIGBUS       8) SIGFPE
 9) SIGKILL     10) SIGUSR1     11) SIGSEGV     12) SIGUSR2
13) SIGPIPE     14) SIGALRM     15) SIGTERM     17) SIGCHLD
18) SIGCONT     19) SIGSTOP     20) SIGTSTP     21) SIGTTIN
22) SIGTTOU     23) SIGURG      24) SIGXCPU     25) SIGXFSZ
26) SIGVTALRM   27) SIGPROF     28) SIGWINCH    29) SIGIO
30) SIGPWR      31) SIGSYS      34) SIGRTMIN    35) SIGRTMIN+1
36) SIGRTMIN+2  37) SIGRTMIN+3  38) SIGRTMIN+4  39) SIGRTMIN+5
40) SIGRTMIN+6  41) SIGRTMIN+7  42) SIGRTMIN+8  43) SIGRTMIN+9
44) SIGRTMIN+10 45) SIGRTMIN+11 46) SIGRTMIN+12 47) SIGRTMIN+13
48) SIGRTMIN+14 49) SIGRTMIN+15 50) SIGRTMAX-14 51) SIGRTMAX-13
52) SIGRTMAX-12 53) SIGRTMAX-11 54) SIGRTMAX-10 55) SIGRTMAX-9
56) SIGRTMAX-8  57) SIGRTMAX-7  58) SIGRTMAX-6  59) SIGRTMAX-5
60) SIGRTMAX-4  61) SIGRTMAX-3  62) SIGRTMAX-2  63) SIGRTMAX-1
64) SIGRTMAX

(1)1 ~ 31的信号ؓ传统UNIX支持的信P是不可靠信号(非实时的)
(2)32 ~ 63的信h后来扩充的,U做可靠信号(实时信号)。不可靠信号和可靠信L区别在于前者不支持排队Q可能会造成信号丢失Q而后者不会?/span>

?具体每个信号的生原?br />1) SIGHUP:当用户退出shellӞpshell启动的所有进E将收到q个信号Q默认动作ؓl止q程

本信号在用户l端q接(正常或非正常)l束时发? 通常是在l端的控制进E结束时, 通知同一session内的各个作业, q时它们与控制终端不再关联?/p>

dLinuxӞpȝ会分配给d用户一个终?Session)。在q个l端q行的所有程序,包括前台q程l和后台q程l,一般都属于q个Session。当用户退出LinuxdӞ前台q程l和后台有对l端输出的进E将会收到SIGHUP信号。这个信L默认操作为终止进E,因此前台q程l和后台有终端输出的q程׃中止。不q可以捕莯个信P比如wget能捕获SIGHUP信号Qƈ忽略它,q样q退ZLinuxdQwget也能l箋下蝲?/p>

此外Q对于与l端q关系的守护进E,q个信号用于通知它重新读取配|文件?/p>
2QSIGINTQ当用户按下?lt;Ctrl+C>l合键时Q用L端向正在q行中的pl端启动的程序发出此信号。默认动
作ؓl止里程?br />

3QSIGQUITQ当用户按下<ctrl+\>l合键时产生该信P用户l端向正在运行中的由该终端启动的E序发出些信
受默认动作ؓl止q程?/span>q程在因收到SIGQUIT退出时会生core文g, 在这个意义上cM于一个程序错误信受?br />
4QSIGILLQCPU到某进E执行了非法指o。默认动作ؓl止q程q生core文g
执行了非法指? 通常是因为可执行文g本n出现错误, 或者试图执行数据段. 堆栈溢出时也有可能生这个信受?br />
5QSIGTRAPQ该信号由断Ҏ令或其他 trap指o产生。默认动作ؓl止里程 q生core文g?br />由断Ҏ令或其它trap指o产生. 由debugger使用?br />
6 ) SIGABRT:调用abort函数时生该信号。默认动作ؓl止q程q生core文g?br />
7QSIGBUSQ非法访问内存地址Q包括内存对齐出错,默认动作为终止进Eƈ产生core文g?br />
非法地址, 包括内存地址寚w(alignment)出错。比如访问一个四个字长的整数, 但其地址不是4的倍数。它与SIGSEGV的区别在于后者是׃对合法存储地址的非法访问触发的(如访问不属于自己存储I间或只d储空??br />
8QSIGFPEQ在发生致命的运错误时发出。不仅包括Q点运错误,q包括溢出及除数?{所有的法错误。默
认动作ؓl止q程q生core文g?br />

9QSIGKILLQ无条gl止q程。本信号不能被忽略,处理和阻塞。默认动作ؓl止q程。它向系l管理员提供了可
以杀MQ何进E的Ҏ?br />
10QSIGUSE1Q用户定?的信受即E序员可以在E序中定义ƈ使用该信受默认动作ؓl止q程?br />
11QSIGSEGVQ指CE进行了无效内存讉K。默认动作ؓl止q程q生core文g?br />
试图讉K未分配给自己的内? 或试囑־没有写权限的内存地址写数?br />
12QSIGUSR2Q这是另外一个用戯定义信号 Q程序员可以在程序中定义 q用该信号。默认动作ؓl止q程?br />
13QSIGPIPEQBroken pipe向一个没有读端的道写数据。默认动作ؓl止q程?br />
14) SIGALRM:定时器超Ӟ时的时?ql调用alarm讄。默认动作ؓl止q程?br />
旉定时信号, 计算的是实际的时间或旉旉. alarm函数使用该信?

15QSIGTERMQ程序结束信P与SIGKILL不同的是Q该信号可以被阻塞和l止。通常用来要示E序正常退出。执?br />shell命oKillӞ~省产生q个信号。默认动作ؓl止q程?br />E序l束(terminate)信号, 与SIGKILL不同的是该信号可以被d和处理。通常用来要求E序自己正常退出,shell命okill~省产生q个信号。如果进E终止不了,我们才会试SIGKILL?br />
17QSIGCHLDQ子q程l束Ӟ父进E会收到q个信号。默认动作ؓ忽略q个信号?br />

子进E结束时, 父进E会收到q个信号?/p>

如果父进E没有处理这个信P也没有等?wait)子进E,子进E虽然终止,但是q会在内核进E表中占有表,q时的子q程UCؓ僵尸q程。这U情冉|们应该避?父进E或者忽略SIGCHILD信号Q或者捕捉它Q或者wait它派生的子进E,或者父q程先终止,q时子进E的l止自动由initq程来接??/p>
18QSIGCONTQ停止进E的执行。信号不能被忽略Q处理和d。默认动作ؓl止q程?br />
让一个停?stopped)的进El执? 本信号不能被d. 可以用一个handler来让E序在由stopped状态变为l执行时完成特定的工? 例如, 重新昄提示W?br />
19)SIGSTOP
停止(stopped)q程的执? 注意它和terminate以及interrupt的区?该进E还未结? 只是暂停执行. 本信号不能被d, 处理或忽?

20QSIGTSTPQ停止进E的q行。按?lt;ctrl+z>l合键时发出q个信号。默认动作ؓ暂停q程?br />
停止q程的运? 但该信号可以被处理和忽略. 用户键入SUSP字符?通常是Ctrl-Z)发出q个信号

21QSIGTTINQ后台进E读l端控制台。默认动作ؓ暂停q程?br />当后C业要从用L端读数据? 该作业中的所有进E会收到SIGTTIN信号. ~省时这些进E会停止执行.

22QSIGTTOU:该信LgSIGTTINQ在后台q程要向l端输出数据时发生。默认动作ؓ暂停q程?br />cM于SIGTTIN, 但在写终?或修改终端模?时收?

23QSIGURGQ套接字上有紧急数据时Q向当前正在q行的进E发Z信号Q报告有紧急数据到达。如|络带外数据
到达Q默认动作ؓ忽略该信受?br />
?紧?数据或out-of-band数据到达socket时?

24QSIGXCPUQ进E执行时间超q了分配l该q程的CPU旉 Q系l生该信号q发送给该进E。默认动作ؓl止
q程?br />
过CPU旉资源限制. q个限制可以由getrlimit/setrlimit来读?改变

25QSIGXFSZQ超q文件的最大长度设|。默认动作ؓl止q程?br />
当进E企图扩大文件以至于过文g大小资源限制?/span>

26QSIGVTALRMQ虚拟时钟超时时产生该信受类gSIGALRMQ但是该信号只计该q程占用CPU的用时间。默
认动作ؓl止q程?br />
虚拟旉信号. cM于SIGALRM, 但是计算的是该进E占用的CPU旉.

27QSGIPROFQ类gSIGVTALRMQ它不公包括该进E占用CPU旉q包括执行系l调用时间。默认动作ؓl止q?br />E?br />
包括该进E用的CPU旉以及pȝ调用的时?/span>

28QSIGWINCHQ窗口变化大时发出。默认动作ؓ忽略该信受?br />
29QSIGIOQ此信号向进E指C发Z一个异步IO事g。默认动作ؓ忽略?br />
文g描述W准备就l? 可以开始进行输?输出操作

30QSIGPWRQ关机。默认动作ؓl止q程?br />
31QSIGSYSQ无效的pȝ调用。默认动作ؓl止q程q生core文g?br />
32QSIGRTMIN~(64QSIGRTMAXQLINUX的实时信P它们没有固定的含义(可以q戯定义Q?br />
所有的实时?/span>L默认动作都ؓl止q程?/span> 

在以上列出的信号中,E序不可捕获、阻塞或忽略的信hQSIGKILL,SIGSTOP
不能恢复至默认动作的信号有:SIGILL,SIGTRAP
默认会导致进E流产的信号有:SIGABRT,SIGBUS,SIGFPE,SIGILL,SIGIOT,SIGQUIT,SIGSEGV,SIGTRAP,SIGXCPU,SIGXFSZ
默认会导致进E退出的信号有:SIGALRM,SIGHUP,SIGINT,SIGKILL,SIGPIPE,SIGPOLL,SIGPROF,SIGSYS,SIGTERM,SIGUSR1,SIGUSR2,SIGVTALRM
默认会导致进E停止的信号有:SIGSTOP,SIGTSTP,SIGTTIN,SIGTTOU
默认q程忽略的信hQSIGCHLD,SIGPWR,SIGURG,SIGWINCH

此外QSIGIO在SVR4是退出,?.3BSD中是忽略QSIGCONT在进E挂h是l,否则是忽略,不能被阻塞?nbsp;



]]>
A*法实现http://www.shnenglu.com/API/archive/2017/08/17/215164.htmlC++技术中?/dc:creator>C++技术中?/author>Thu, 17 Aug 2017 06:43:00 GMThttp://www.shnenglu.com/API/archive/2017/08/17/215164.htmlhttp://www.shnenglu.com/API/comments/215164.htmlhttp://www.shnenglu.com/API/archive/2017/08/17/215164.html#Feedback1http://www.shnenglu.com/API/comments/commentRss/215164.htmlhttp://www.shnenglu.com/API/services/trackbacks/215164.html#ifndef _ASTAR_FLY_H__
#define _ASTAR_FLY_H__
#include "Coordinate.h"
#include <map>
#include <set>

using namespace std;



typedef struct 
{
    int x;
    int y;
    int f; // f = g + h
    int g;
    int h;

    void Init(int _x,int _y)
    {
        f = 0;
        g = 0;
        h = 0;
        x = _x;
        y = _y;
    }
} APoint, *PAPoint;



class CAStarFly
{
public:
    typedef std::list<CIntPoint> PosList;


    CAStarFly();
    ~CAStarFly();

    bool CalcPath(PosList& pathList, const CIntPoint& from, const CIntPoint& to);
protected:
    virtual int GetMapWidth();
    virtual int GetMapHeight();
    virtual CIntSize GetRoleSize();
    virtual bool GetNearPos(int curX,int curY,PosList &nearPos);
    virtual bool IsCanFly(int x,int y);
    virtual void OnAddBestPoint(int x, int y);
    virtual void OnUnAddBestPoint(int x, int y);
private:
    PAPoint CalcNextPoint(PosList& pathList, PAPoint ptCalc); // 应用递归的办法进行查?/span>

    void SetStartPoint(int x, int y);
    void SetEndPoint(int x, int y);
    void SetOpened(int x, int y);
    void SetClosed(int x, int y);
    void SetCurrent(int x, int y);

    int32 GetPosIndex(int x, int y);
    int32 GetFValue(const CIntPoint &pos);
    bool IsNotClosePos(int x, int y);
private:
    APoint m_startPoint; //起始?/span>
    APoint m_endPoint;   //l束?/span>
    APoint m_curPoint;   //当前Ud?/span>
    set<int32> m_setClosePos;
    int m_curGValue;     //当前G?/span>
};

#endif/*_ASTAR_FLY_H__*/



#include "stdafx.h"
#include "AStarFly.h"


CAStarFly::CAStarFly()
{
}

CAStarFly::~CAStarFly()
{
}

bool CAStarFly::CalcPath(PosList& pathList, const CIntPoint& from, const CIntPoint& to)
{
    //1.先设|开始与目标
    SetStartPoint(from.x, from.y);
    SetEndPoint(to.x, to.y);
    m_curPoint = m_startPoint;
    SetClosed(m_startPoint.x, m_startPoint.y); //路径搜烦不再l过
    m_curGValue = 0;

    //从v点开始计\径点
    return CalcNextPoint(pathList, nullptr)!=nullptr;
}

PAPoint CAStarFly::CalcNextPoint(PosList& pathList, PAPoint ptCalc)
{
    if (nullptr == ptCalc)
    {
        ptCalc = &m_startPoint;
    }
    int curX = ptCalc->x;
    int curY = ptCalc->y;
    int destX = m_endPoint.x;
    int destY = m_endPoint.y;

    //判断是否已经C最l位|?/span>
    if ((curX == destX && abs(curY - destY) == 1) || (curY == destY && abs(curX - destX) == 1))
    {
        pathList.push_back(CIntPoint(m_endPoint.x, m_endPoint.y));
        OnAddBestPoint(m_endPoint.x, m_endPoint.y);
        return &m_endPoint;
    }

    // 最优步骤的坐标和?/span>
    int xmin = curX;
    int ymin = curY;
    int fmin = 0;

    //获得当前周边点的的坐?/span>
    PosList nearPos;
    if (GetNearPos(curX,curY,nearPos) == false)
    {
        return nullptr;
    }

    //删除不能飞的区块
    
//扑և最优f?/span>
    for (auto itPos : nearPos)
    {
        int fValue = GetFValue(itPos);
        if (fmin == 0 || fValue<fmin)
        {
            fmin = fValue;
            xmin = itPos.x;
            ymin = itPos.y;
        }
    }

    if (fmin > 0)
    {
        SetCurrent(xmin, ymin);
        SetClosed(xmin, ymin); //路径搜烦不再l过
        pathList.push_back(CIntPoint(xmin,ymin));
        OnAddBestPoint(xmin, ymin);
        PAPoint pAPoint= CalcNextPoint(pathList,&m_curPoint);
        if (nullptr == pAPoint)
        {
            SetCurrent(curX, curY);
            SetClosed(xmin, ymin); //路径搜烦不再l过

            
//最后一ơ入的队删除
            pathList.pop_back();
            OnUnAddBestPoint(xmin, ymin);
            return CalcNextPoint(pathList, &m_curPoint);
        }

        return pAPoint;
    }
    

    return nullptr;
}

CIntSize CAStarFly::GetRoleSize()
{
    return CIntSize(1,1);
}

int CAStarFly::GetMapWidth()
{
    return -1;
}

int CAStarFly::GetMapHeight()
{
    return -1;
}

void CAStarFly::SetStartPoint(int x, int y)
{
    m_startPoint.Init( x,y);
}

void CAStarFly::SetEndPoint(int x, int y)
{
    m_endPoint.Init(x,y);
}


void CAStarFly::SetClosed(int x, int y)
{
    m_setClosePos.insert(GetPosIndex(x,y));
}

void CAStarFly::SetCurrent(int x, int y)
{
    m_curPoint.Init( x, y);
}

int32 CAStarFly::GetPosIndex(int x,int y)
{
    return  y * GetMapWidth() + x;
}


bool CAStarFly::GetNearPos(int curX, int curY, PosList &nearPos)
{
    int newX;
    int newY;
    //1.?/span>
    if (curY > 0)
    {
        newX = curX;
        newY = curY - 1;

        if (IsNotClosePos(newX, newY) && IsCanFly(newX, newY))
        {
            nearPos.push_back(CIntPoint(newX, newY));
        }
    }
    
     
    


    //3.?/span>
    if (curX > 0)
    {
        newX = curX - 1;
        newY = curY;

        if (IsNotClosePos(newX, newY) && IsCanFly(newX, newY))
        {
            nearPos.push_back(CIntPoint(newX, newY));
        }
    }
    

    //4.?/span>
    if (curX < GetMapWidth())
    {
        newX = curX + 1;
        newY = curY;

        if (IsNotClosePos(newX, newY) && IsCanFly(newX, newY))
        {
            nearPos.push_back(CIntPoint(newX, newY));
        }
    }
    
    //2.?/span>
    if (curY < GetMapHeight())
    {
        newX = curX;
        newY = curY + 1;

        if (IsNotClosePos(newX, newY) && IsCanFly(newX, newY))
        {
            nearPos.push_back(CIntPoint(newX, newY));
        }
    }
    return nearPos.size()>0;
}

bool CAStarFly::IsCanFly(int x, int y)
{
    return true;
}

int32 CAStarFly::GetFValue(const CIntPoint &pos)
{
    int xDis = abs(pos.x - m_endPoint.x)*100;
    int yDis = abs(pos.y - m_endPoint.y)*100;
    return m_curGValue +  (int32)sqrt(xDis*xDis + yDis*yDis);
}



bool CAStarFly::IsNotClosePos(int x, int y)
{
    return m_setClosePos.find(GetPosIndex(x, y)) == m_setClosePos.end();
}

void CAStarFly::OnAddBestPoint(int x, int y)
{
    m_curGValue += 100;
}

void CAStarFly::OnUnAddBestPoint(int x, int y)
{
    m_curGValue -= 100;
}


#include "AStarFly.h"
#include <memory>

class CNewMap;

class CMobAStarFly : public CAStarFly
{
public:
    static CMobAStarFly& GetInstance();
    bool FindPath(const std::shared_ptr<CNewMap>& pMap, const CIntPoint& from, const CIntPoint& to, CAStarFly::PosList& pathList, const CIntSize& roleSize = CIntSize(1, 1));
    void setpath(int x, int y);
    void show();

    virtual int GetMapWidth() override;
    virtual int GetMapHeight() override;
    virtual CIntSize GetRoleSize() override;
    virtual bool IsCanFly(int x, int y) override;
    void OnAddBestPoint(int x, int y) override;
    void OnUnAddBestPoint(int x, int y) override;
private:
    weak_ptr<CNewMap> m_pNewMap;
    CIntSize m_roleSize;
};



#include "stdafx.h"
#include "MobFlyAStar.h"
#include "NewMap.h"

CMobAStarFly& CMobAStarFly::GetInstance()
{
    static CMobAStarFly s_Instance;
    return s_Instance;
}

bool CMobAStarFly::FindPath(const std::shared_ptr<CNewMap>& pMap, const CIntPoint& from, const CIntPoint& to, CAStarFly::PosList& pathList, const CIntSize& roleSize)
{
    m_pNewMap = pMap;
    m_roleSize = roleSize;

    return CalcPath(pathList, from, to);
}

int CMobAStarFly::GetMapWidth()
{
    if (m_pNewMap.lock() == nullptr)
        return 0;

    return m_pNewMap.lock()->GetWidth();
}

int CMobAStarFly::GetMapHeight()
{
    if (m_pNewMap.lock() == nullptr)
        return 0;

    return m_pNewMap.lock()->GetHeight();
}

CIntSize CMobAStarFly::GetRoleSize()
{
    return m_roleSize;
}

bool CMobAStarFly::IsCanFly(int x, int y)
{
    for (int w = 0; w < m_roleSize.width; ++w)
    {
        for (int h = 0; h < m_roleSize.height; ++h)
        {
            if (m_pNewMap.lock()->IsPosBlock(m_pNewMap.lock()->PosToWorldX(x + w), m_pNewMap.lock()->PosToWorldY(y - h)))    {
                return false;
            }
        }
    }

    return true;
}

void CMobAStarFly::setpath(int x, int y)
{
    
}

void CMobAStarFly::show()
{

}

void CMobAStarFly::OnAddBestPoint(int x, int y)
{

}

void CMobAStarFly::OnUnAddBestPoint(int x, int y)
{

}


]]>
vc内存地址填充http://www.shnenglu.com/API/archive/2017/07/06/215056.htmlC++技术中?/dc:creator>C++技术中?/author>Thu, 06 Jul 2017 03:33:00 GMThttp://www.shnenglu.com/API/archive/2017/07/06/215056.htmlhttp://www.shnenglu.com/API/comments/215056.htmlhttp://www.shnenglu.com/API/archive/2017/07/06/215056.html#Feedback1http://www.shnenglu.com/API/comments/commentRss/215056.htmlhttp://www.shnenglu.com/API/services/trackbacks/215056.html0xcdcdcdcd - Created but not initialised

0xdddddddd - Deleted
0xfeeefeee - Freed memory set by NT's heap manager
0xcccccccc - Uninitialized locals in VC6 when you compile w/ /GZ
0xabababab - Memory following a block allocated by LocalAlloc()


VC++在Debug~译方式~译的程序中Q会跟踪用new分配的内存。新分配的内存会?xcd(助记词ؓCleared Data)填充Q防止未初始化;当它被delete后,又会?xdd(Dead   Data)填充Q防止再ơ被使用。这h利于调试内存错误。之所以选这L填充模式Q是因ؓQ?/span>

1.大数Q若被当成指针就会越?nbsp;

2.奇数Q指针通常指向偶数地址  

3.?,q样不会?  NULL   h?nbsp;


在Release版中不会有这些字节填充?/span>



]]>
c++函数throw()http://www.shnenglu.com/API/archive/2017/06/30/215041.htmlC++技术中?/dc:creator>C++技术中?/author>Fri, 30 Jun 2017 08:28:00 GMThttp://www.shnenglu.com/API/archive/2017/06/30/215041.htmlhttp://www.shnenglu.com/API/comments/215041.htmlhttp://www.shnenglu.com/API/archive/2017/06/30/215041.html#Feedback0http://www.shnenglu.com/API/comments/commentRss/215041.htmlhttp://www.shnenglu.com/API/services/trackbacks/215041.html#define _NOEXCEPT throw ()
shared_ptr<_Ty> lock() const _NOEXCEPT

它是函数提供者和使用者的一U君子协定,标明该函C抛出M异常?/p>

之所以说是君子协定,是因为实际上内部实现是需要h肉确保?nbsp;

如果一个标明throw()的函数内部发生了throwQ?/p>

1Q如果内部直接throw somethingQ编译器会发现ƈ指出Q?/p>

2. 如果是内部调用了一个可能throw something的函敎ͼ~译器无法发玎ͼq行时一旦这个内部的函数throwQ程序会abort?/p>

 

**** 

func() throw(type) ,会抛出某U异?/p>

func() throw(),不会抛出

func() throw(...)Q可能是Mcd的异?/p>

]]>
EAcȝ关系http://www.shnenglu.com/API/archive/2017/06/19/215008.htmlC++技术中?/dc:creator>C++技术中?/author>Mon, 19 Jun 2017 02:34:00 GMThttp://www.shnenglu.com/API/archive/2017/06/19/215008.htmlhttp://www.shnenglu.com/API/comments/215008.htmlhttp://www.shnenglu.com/API/archive/2017/06/19/215008.html#Feedback0http://www.shnenglu.com/API/comments/commentRss/215008.htmlhttp://www.shnenglu.com/API/services/trackbacks/215008.htmlEnterprise Architect中定义的关系主要有一下几U:

●AssociateQ关联)Q类之间有关联,通常是作为变量存在;

●Aggregate(聚合)Q类A包含cB或由cBl成Q?/p>

●ComposeQ组合)Q类A是由其他cȝ成;

●DependencyQ依赖)Q类A需要类B的协助,cB变化会媄响类A,反过来不成立Q?/p>

●GeneralizeQ泛化)Q一般到具体的关p;

●RealizeQ实玎ͼQ类A实现cBQ?/p>

 注意Q其中,聚合Q组成属于关联关p,泛化关系表现为承或实现关系(is a)Q关联关p表Cؓ变量(has a )Q依赖关p表Cؓ函数中的参数(use a)?/p>

 

1.兌(Associate)

表示ҎQ?头Q实U,头指向被用的c;

pȝ图标Q?img src="http://www.shnenglu.com/images/cppblog_com/api/a.gif" border="0" alt="" width="131" height="18" />

使用说明Q类与类之间的联接,它一个类知道另一个类的属性和ҎQ如下图所C:


 

2. 聚合关系QAggregationQ?/p>

表示ҎQ空心菱形+实线Q空心菱形指向整?/p>

pȝ图标Q?img src="http://pic002.cnblogs.com/img/allanbolt/200912/2009122416523521.gif" alt="" style="border: 0px; max-width: 100%; margin: 0px; padding: 0px;" />

使用说明Q聚合关pL整体和个体的关系。下囑ֺ用程序聚合功能模块,但是功能模块可以d应用E序而独立存在,如下图所C:

 

 

3. l合关系QCompositionQ?/p>

表示ҎQ实心菱形+实线 实心菱Ş指向整体

pȝ图标Q?img src="http://pic002.cnblogs.com/img/allanbolt/200912/2009122416530521.gif" alt="" style="border: 0px; max-width: 100%; margin: 0px; padding: 0px;" />

使用说明Q是兌关系的一U,是比聚合关系强的关系。它要求普通的聚合关系中代表整体的对象负责代表部分的对象的生命周期Q下囑֊能模块组合操作方法,q个操作Ҏ不能q功能模块单独的存在,功能模块消失后操作方法也随即消失Q?/p>

 

4. 依赖(Dependency)

表示ҎQ虚U+头 头指向被依赖类Q?/p>

pȝ图标Q?img src="http://pic002.cnblogs.com/img/allanbolt/200912/2009122416534398.gif" alt="" style="border: 0px; max-width: 100%; margin: 0px; padding: 0px;" />

使用说明Q如果类A讉KcB的属性或者方法,或者类A负责实例化类BQ那么可以说cA依赖cB。和兌关系不同Q无dcA中定义类Bcd的属性:

 

 

5. 泛化(Generalization)

表示ҎQ实U+三角头 三角头指向一般类Q?/p>

pȝ图标Q?img src="http://pic002.cnblogs.com/img/allanbolt/200912/2009122416540576.gif" alt="" style="border: 0px; max-width: 100%; margin: 0px; padding: 0px;" />

使用说明Q两个类存在泛化的关pL׃用此关系Q例如父和子Q动物和老虎Q植物和qQ在面向对象中,我们一般称之ؓl承关系Q?/p>

 

 

6. 实现(Realization)

表示ҎQ虚U+三角头 三角头指向一般类Q?/p>

pȝ图标Q?img src="http://pic002.cnblogs.com/img/allanbolt/200912/2009122416544554.gif" alt="" style="border: 0px; max-width: 100%; margin: 0px; padding: 0px;" />

使用说明Q类实现了另一个类的功能,一般表现在cȝ承接口上Q如下图Q?/p>

 



]]>
游戏服务器相兌??http://www.shnenglu.com/API/archive/2017/06/02/214972.htmlC++技术中?/dc:creator>C++技术中?/author>Fri, 02 Jun 2017 06:08:00 GMThttp://www.shnenglu.com/API/archive/2017/06/02/214972.htmlhttp://www.shnenglu.com/API/comments/214972.htmlhttp://www.shnenglu.com/API/archive/2017/06/02/214972.html#Feedback0http://www.shnenglu.com/API/comments/commentRss/214972.htmlhttp://www.shnenglu.com/API/services/trackbacks/214972.html服务器结构探?-- 最单的l构 

  所谓服务器l构Q也是如何服务器各部分合理地安排Q以实现最初的功能需求。所以,l构本无所谓正与错误Q当Ӟ优秀的结构更有助于系l的搭徏Q对pȝ的可扩展性及可维护性也有更大的帮助?nbsp;

  好的l构不是一y而就的,而且每个设计者心中的那把都不相同,所以这个优Ul构的定义也没有定论。在q里Q我们不打算对现有游戏结构做评hQ而是试着从头开始搭Z个我们需要的MMOGl构?nbsp;

  对于一个最单的游戏服务器来_它只需要能够接受来自客L的连接请求,然后处理客户端在游戏世界中的Ud及交互,也即游戏逻辑处理卛_。如果我们把q两功能集成到一个服务进E中Q则最l的l构很简单: 

  client ----- server 

  嗯,太简单了点,q样也敢叫服务器l构Q好吧,现在我们来往里面E稍加点东西Q让它看h更像是服务器l构一些?nbsp;

  一般来_我们在接入游戏服务器的时候都会要提供一个帐号和密码Q验证通过后才能进入。关于ؓ什么要提供用户名和密码才能q入的问题我们这里不打算做过多讨论,云风曑֯此也提出q类似的疑问Qƈl出了只用一个标识串pq入的设惻I有兴的可以ȝ看他们的讨论。但不管是采用何U方式进入,照目前看来我们的服务器v码得提供一个帐号验证的功能?nbsp;

  我们把观察点先集中在一个大区内。在大多数情况下Q一个大区内都会有多l游戏服Q也是多个游戏世界可供选择。简单点来实玎ͼ我们完全可以抛弃q个大区的概念,认ؓ一个大Z是攑֜同一个机房的多台服务器组Q各服务器组间没有什么关pR这P我们可ؓ每组服务器单独配备一台登录服。最后的l构囑ֺ该像q样Q?nbsp;

  loginServer   gameServer 
     |           / 
     |         / 
     client 

  该结构下的玩家操作流EؓQ先选择大区Q再选择大区下的某台服务器,x个游戏世界,点击q入后开始帐号验证过E,验证成功则进入了该游戏世界。但是,如果玩家惌切换游戏世界Q他只能先退出当前游戏世界,然后q入新的游戏世界重新q行帐号验证?nbsp;

  早期的游戏大都采用的是这U结构,有些游戏在实现时采用了一些技术手D得在切换游戏服时不需要再ơ验证帐P但整体结构还是未做改变?nbsp;

  该结构存在一个服务器资源配置的问题。因为登录服处理的逻辑相对来说比较单,是玩家提交的帐号和密码送到数据?/a>q行验证Q和生成会话密钥发送给游戏服和客户端,操作完成后连接就会立x开Q而且玩家在以后的游戏q程中不会再与登录服打Q何交道。这样处理短q接的过E得系l在大多数情况下都是比较I闲的,但是在某些时候,׃h比较密集Q比如开新服的时候,d服的负蝲又会比较大,甚至会处理不q来?nbsp;

  另外在实际的游戏q营中,有些游戏世界很火爆,而有些游戏世界却非常hQ甚x有多h玩的情况也是很常见的。所以,我们能否更合理地配置d服资源,使得整个大区内的d服可以共享就成了下一步改q的目标?nbsp;

服务器结构探?-- d服的负蝲均衡 

  回想一下我们在玩wow时的操作程Q运行wow.exeq入游戏后,首先׃要求我们输入用户名和密码q行验证Q验证成功后才会出来游戏世界列表Q之后是排队q入游戏世界Q开始游?.. 

  可以看到跟前面的描述有个很明昄不同Q那是要先验证帐号再选择游戏世界。这U结构也׃得登录服不是固定配备l个游戏世界Q而是全区共有的?nbsp;

  我们可以试着从实际需求的角度来考虑一下这个问题。正如我们之前所描述q的那样Q登录服在大多数情况下都是比较空闲的Q也许我们的一个拥?0个游戏世界的大区仅仅使用10台或更少的登录服卛_满需求。而当在开新区的时候,或许要配?0台登录服才能应付那如潮水般涌入的玩家dh。所以,d服在设计上应该能满q种动态增删的需求,我们可以在Q何时候ؓ大区增加或减登录服的部|Ӏ?nbsp;

  当然Q在q里也不会存在要求添加太多登录服的情c还是拿开新区的情冉|_即新增加登录服满了玩家登录的hQ游戏世界服的承载能力依然有限,玩家一样只能在排队pȝ中等待,或者是q入到游戏世界中D大家都卡?nbsp;

  另外Q当我们在增加或U除d服的时候不应该需要对游戏世界服有所改动Q也不会要求重启世界服,当然也不应该要求客户端有什么更新或者修改,一切都是在背后自动完成?nbsp;

  最后,有关数据持久化的问题也在q里考虑一下。一般来_使用现有的商业数据库pȝ比自己手工技术先q要明智得多。我们需要持久化的数据有玩家的帐号及密码Q玩家创建的角色相关信息Q另外还有一些游戏世界全局共有数据也需要持久化?nbsp;

  好了Q需求已l提出来了,现在来考虑如何其实现?nbsp;

  对于负蝲均衡来说Q已有了成熟的解x案。一般最常用Q也最单部|的应该是基于DNS的负载均衡系l了Q其通过在DNS中ؓ一个域名配|多个IP地址来实现。最新的DNS服务已实CҎ服务器系l状态来实现的动态负载均衡,也就是实C真正意义上的负蝲均衡Q这样也有效地解决了当某台d服当机后QDNS服务器不能立卛_出反应的问题。当Ӟ如果找不到这L解决ҎQ自׃头打造一个也q不难。而且Q通过DNS来实现的负蝲均衡已经包含了所做的修改对登录服及客L的透明?nbsp;

  而对于数据库的应用,在这U结构下Q登录服及游戏世界服都会需要连接数据库。从数据库服务器的部|上来说Q可以将帐号和角色数据都攑֜一个中心数据库中,也可分ؓ两个不同的库分别来处理,基到从物理上分到两台不同的服务器上去也行?nbsp;

  但是对于不同的游戏世界来_其角色及游戏内数据都是互相独立的Q所以一般情况下也就为每个游戏世界单独配备一台数据库服务器,以减L据库的压力。所以,整体的服务器l构应该是一个大区有一台帐h据库服务器,所有的d服都q接到这里。而每个游戏世界都有自q游戏数据库服务器Q只允许本游戏世界内的服务器q接?nbsp;

  最后,我们的服务器l构像q样Q?nbsp;

               大区服务?nbsp;       
          /     |       \ 
            /       |        \ 
            d?   d?   世界?   世界? 
         \         |         |       |   
          \       |         |         | 
          帐号数据?nbsp;        DBS     DBS 

  q里既然讨论C大区及帐h据库Q所以顺带也说一下关于激zd区的概念。wow中一共有八个大区Q我们想要进入某个大区游戏之前,必须到官|上Ȁz这个区Q这是ؓ什么呢Q?nbsp;

  一般来_在各个大区帐h据库之上q有一个ȝ帐号数据库,我们可以U它Z心数据库。比如我们在官网上注册了一个帐Pq时帐号数据是只保存在中心数据库上的。而当我们要到一区去创徏角色开始游戏的时候,在一区的帐号数据库中q没有我们的帐号数据Q所以,我们必须先到官网上做一ơ激zL作。这个激zȝq程也就是从中心库上把我们的帐号数据拯到所要到的大区帐h据库中?nbsp;

服务器结构探?-- 单的世界服实?/strong> 

  讨论了这么久我们一直都q没有进入游戏世界服务器内部Q现在就让我们来H探一下里面的l构吧?nbsp;

  对于现在大多数MMORPG来说Q游戏服务器要处理的基本逻辑有移动、聊天、技能、物品、Q务和生物{,另外q有地图理与消息广播来对其他高U功能做支撑。如U队、好友、公会、战场和副本{,q些都是通过基本逻辑功能l合或扩展而成?nbsp;

  在所有这些基逻辑中,与我们要讨论的服务器l构关系最紧密的当属地囄理方式。决定了地图的管理方式也决定了我们的服务器l构Q我们仍然先从最单的实现方式开始说赗?nbsp;

  回想一下我们曾战斗q无C夜晚的暗黑破坏神Q整个暗黑的世界被分Z若干个独立的地图,当我们在地图间穿时Q一般都要经q一个叫做传送门的装|。世界中有些地图间虽然在地理上是直接相连的,但我们发现其游戏内部的逻辑却是完全隔离的。可以这栯为,一块地囑ְ是一个独立的数据处理单元?nbsp;

  既然如此Q我们就把每块地N当作是一台独立的服务器,他提供了在这块地图上游戏时的所有逻辑功能Q至于内部结构如何划分我们暂不理会,先把他当作一个黑盒子吧?nbsp;

  当两个h合作做一件事Ӟ我们可以以对{的关系怺协商着来做Q而且一般也都不会有什么问题。当人数增加C个时Q我们对{的合作关系可能会有些复杂,因ؓ我们每个人都同时要与另两个h合作协商。正如俗语所说的那样Q三个和可能会到没水喝的情况。当人数l箋增加Q情况就变得不那么简单了Q我们得需要一个管理者来Ҏ们的工作q行分工、协调。游戏的地图服务器之间也是这么回事?nbsp;

  一般来_我们的游戏世界不可能会只有一块或者两块小地图Q那理成章的,也就需要一个地囄理者。先U它为游戏世界的中心服务器吧Q毕竟是理者嘛Q大安以它Z心?nbsp;

  中心服务器主要维护一张地图ID到地图服务器地址的映表。当我们要进入某张地图时Q会从中心服上取得该地图的IP和port告诉客户端,客户端主动去q接Q这栯入他惌ȝ游戏地图。在整个游戏q程中,客户端始l只会与一台地图服务器保持q接Q当要切换地囄时候,在获取到新地囄地址后,会先与当前地图断开q接Q再q入新的地图Q这样保证玩家数据在服务器上只有一份?nbsp;

  我们来看看结构图是怎样的: 

              中心服务?nbsp;
           /       \         \ 
         /         \         \ 
       d?nbsp;    地图1     地图2   地图n 
         \         |         /       / 
           \       |         /       / 
                客户?nbsp;

  很简单,不是吗。但是简单ƈ不表C功能上会有什么损失,单也更不能表C游戏不能赚钱。早期不游戏也实采用的就是这U简单结构?nbsp;

服务器结构探?-- l箋世界?/strong> 

  都已l看出来了,q种每切换一ơ地囑ְ要重新连接服务器的方式实在是不够优雅Q而且在实际游戏运营中也发玎ͼ地图切换D的卡P复制装备{问题非常多Q这里完全就是一个事故多发地D,如何避免q种频繁的连接操作呢Q?nbsp;

  最直接的方法就是把那个囑ր{q来p了。客L只需要连接到中心服上Q所有到地图服务器的数据都由中心服来转发。很完美的解x案,不是吗? 

  q种l构在实际的部v中也遇到了一些挑战。对于一般的MMORPG服务器来_单台服务器的承蝲量^均在2000左右Q如果你的服务器很不q地只能?000人,没关p,不少游戏都是如此Q如果你的服务器上跑?000多玩家依然比较流畅,那你可以自豪地告诉你的策划,多设计些大量消耗服务器资源的玩法吧Q比如大型国战、公会战争等?nbsp;

  2000人,g我们的策划朋友们不大愿意接受q个数字。我们将地图服务器分开来原来也是想负载分开Q以多带些客LQ现在要所有的q接都从中心服上转发Q那q接数又遇到单台服务器的可最大承载量的瓶颈了?nbsp;

  q里有必要再解释下这个数字。我知道Q有Z定会_才带2000人,那是你水q不行,我随便写个TCP服务器都可带个五六千q接。问题恰恰在于你是随便写的,而MMORPG的服务器是复杂设计的。如果一个演Csocket API用的echo服务器就能满MMOG服务器的需求,那写服务器该是g多么惬意的事啊?nbsp;

  但我们所遇到的事实是Q服务器收到一个移动包后,要向周围所有hq播Q而不是echo服务器那L单的回应Q服务器在收C个连接断开通知时要向很多h通知玩家退ZӞq将该玩家的资料写入数据库,而不是echo服务器那样什么都不需要做Q服务器在收C个物品用请求包后要做一pd的逻辑判断以检查玩家有没有作弊Q服务器上还启动着很多定时器用来更新游戏世界的各种状?..... 

  其实q么一比较Q我们也看出资源消耗的所在了Q服务器上大量的复杂的逻辑处理。再回过头来看看我们惌实现的结构,我们既想要有一个唯一的入口,使得客户端不用频J改变连接,又希望这个唯一入口的负载不会太大,以致于接受不了多连接?nbsp;

  仔细看一看这个需求,我们惌的仅仅只是一台管理连接的服务器,q不打算让他承担太多的游戏逻辑。既然如此,那五六千个连接也q有满我们的要求。至在现在来说Q一个游戏世界内Q也是一l服务器内同时有五六千个在线的玩家还是g让h很兴奋的事。事实上Q在大多数游戏的大部分时间里Q这个数字也是很让h眼红的?nbsp;

  什么?你说梦、魔兽还有史先生的那个什么征途远不止q么点h了!噢,我说的是大多敎ͼ是大多数Q不包括那些明星。你知道大陆现在有多游戏在q营吗?或许你又该说Q我们不该在一开始就把自q目标定的太低Q好吧,我们q是先不谈这个?nbsp;

  l箋我们的结构讨论。一般来_我们把这台负责连接管理的服务器称为网x务器Q因为内部的数据都要通过q个|关才能出去Q不q从q台服务器提供的功能来看Q称其ؓ反向代理服务器可能更合适。我们也不在q个名字上纠~了Q就按大安用的叫法,q是UC为网x务器吧?nbsp;

  |关之后的结构我们依然可以采用之前描q的ҎQ只是,gq没有必要ؓ每一个地N开一个独立的监听端口了。我们可以试着对地图进行一些划分,׃个Master Server来管理一些更的Zone ServerQ玩安过|关q接到Master Server上,而实际与地图有关的逻辑是分z更小的Zone Serverd理?nbsp;

  最后的l构看v来大概是q样的: 

         Zone Server         Zone Server 
                 \             / 
                 \           / 
                 Master Server           Master Server 
                     /       \                   / 
                   /         \                 / 
         Gateway Server         \               / 
             |         \         \             / 
             |         \         \           / 
             |               Center Server 
             | 
             | 
           Client 

服务器结构探?-- 最l的l构 

  如果我们此打住Q可能马上就会有嗤之以E了,p点古董的技术也敢出来现。好吧,我们q是把之前留下的问题拿出来解x吧?nbsp;

  一般来_当某一部分能力达不到我们的要求Ӟ最单的解决Ҏ是在此多投入一点资源。既然想要更多的q接敎ͼ那就再加一台网x务器吧。新增加了网x后需要在大区服上做相应的支持Q或者再单点Q有一C要的|关服,当其负蝲较高Ӟd新到达的连接重定向到其他网x上?nbsp;

  而对于游戏服来说Q有一台还是多台网x是没有什么区别的。每个代表客L玩家的对象内部都保留一个代表其q接的对象,消息q播时要求每个玩家对象用自qq接对象发送数据即可,至于q接是在什么地方,那是完全透明的。当Ӟq只是一U简单的实现Q也是普通用的一U方案,如果后期惛_消息q播做一些优化的话,那可能才需要多考虑一下?nbsp;

  既然说到了优化,我们也稍E考虑一下现在结构下可能采用的优化方案?nbsp;

  首先是当前的Zone Server要做的事情太多了Q以至于他都处理不了多少q接。这其中最消耗系l资源的当属生物的AI处理了,其是那些复杂的寻\
Q所以我们可以考虑把这部分AI逻辑独立出来Q由一台单独的AI服务器来承担?nbsp;

  然后Q我们可以试着把一些与地图数据无关的公共逻辑攑ֈMaster Server上去实现Q这样Zone Server上只保留了与地图数据紧密相关的逻辑Q如生物理Q玩家移动和状态更新等?nbsp;

  q有聊天处理逻辑Q这部分与游戏逻辑没有M兌Q我们也完全可以其独立出来Q放C台单独的聊天服务器上d现?nbsp;

  最后是数据库了Qؓ了减L据库的压力,提高数据h的响应速度Q我们可以在数据库之前徏立一个数据库~存服务器,一些常用数据缓存在此,服务器与数据库的通信都要通过q台服务器进行代理。缓存的数据会定时的写入到后台数据库中?nbsp;

  好了Q做完这些优化我们的服务器结构大体也定的差不多了,暂且也不再l深入,更细化的内容{到各个部分实现的时候再探讨?nbsp;

  好比我们ȝ一场晚会,舞台上演员们按着预定的节目单有序C演着Q但q就是整场晚会的全部吗?昄不止Q在q后q有太多太多的h在忙着Q甚臛_晚会前和晚会后都有。我们的游戏服务器也如此?nbsp;

  在之前描q的部分如同舞C的演员,是我们能直接看到的,q后的工作h员我们也来认识一下?nbsp;

  现实中有警察来维护秩序,游戏中也如此Q这是我们常说的GM。GM可以采用跟普通玩家一L拉入方式来进入游戏,当然权限会比普通玩安一些,也可以提供一台GM服务器专门用来处理GM命oQ这样可以有更高的安全性,GM服一般接在中心服务器上?nbsp;

  在以旉收费的游戏中Q我们还需要一台计费的服务器,q台服务器一般接在网x务器上,注册玩家d和退Z件以记录玩家的游戏时间?nbsp;

  M为用h供服务的地方都会有日志记录,游戏服务器当然也不例外。从记录玩家d的时_地址Q机器信息到游戏q程中的每一Ҏ作都可以作ؓ日志记录下来Q以备查错及数据挖掘用。至于搜集玩家机器资料所涉及到的法律问题不是我们该考虑的?nbsp;

  差不多就q么多了吧,接下来我们会按照q个大致的结构来详细讨论各部分的实现?nbsp;

服务器结构探?-- 一Ҏ?/strong> 

  再强调一下,服务器结构本无所谓好坏,只有是否适合自己。我们在前面探讨了一些在现在的游戏中见到q的l构Qƈ我所知地分析了各自存在的一些问题和可以做的一些改q,希望其中没有谬误Q如果能l大家也带来些启发那自然更好?nbsp;

  H然发现自己一旦罗嗦v来还真是没完没了。接下来先说说我在开发中遇到q的一些困惑和一基础问题探讨吧,q些问题可能有h与我一P也曾遇到q,或者正在被困扰中,而所要探讨的q些基础问题向来也是争论比较多的Q我们也不评价其中的好与坏,只做单的描述?nbsp;

  首先是服务器操作pȝQ?a title="Linux知识? target="_blank" style="color: #df3434; text-decoration: none; font-weight: bold;">Linux与windows之争随处可见Q其实在大多数情况下q不是我们所能决定的Q似乎各大公怹基本都有了自q传统Q如|易的freebsdQ腾讯的linux{。如果真有权利去选择的话Q选自己最熟悉的吧?nbsp;

  军_了OS也就基本上确定了|络IO模型Qwindows上的IOCP和linux下的epoolQ或者直接用现有的|络框架Q如ACE和asio{,其他q有些商业的|络库在国内的用好像没有见刎ͼ不符合中国国情嘛?) 

  然后是网l协议的选择Q以前的选择大多們֐于UDPQؓ了可靠传输一般自己都会在上面实现一层封装,而现在更普通的是直接采用本w就很可靠的TCPQ或者TCP与UDP的؜用。早期选择UDP的主要原因还是带宽限Ӟ现在宽带普通的情况下TCP比UDP多出来的一点点开销与开发的便利性相比已l不什么了。当Ӟ如果已有了成熟的可靠UDP库,那也可以l箋使用着?nbsp;

  q有消息包格式的定义Q这个曾在云风的blog上展开q激烈的争论。消息包格式定义包括三段Q包ѝ消息码和包体,争论的焦点在于应该是消息码在前还是包长在前,我们也把q个当作是信仰问题吧Q有兴趣的去云风的blog上看看,?nbsp;

  另外早期有些游戏的包格式定义是以Ҏ字符作分隔的Q这样一个好处是其中某个包出现错误后我们的游戏还能l。但实际上,我觉得这是完全没有必要的Q真要出现这L错误Q直接断开q个客户端的q接可能更安全。而且Q以Ҏ字符做分隔的消息包定义还加大了一点点|络数据量?nbsp;

  最后是一个纯技术问题,有关socketq接数的最大限制。开始学习网l编E的时候我犯过q样的错误,以ؓport的定义ؓunsigned shortQ所以想当然的认为服务器的最大连接数?5535Q这会是一个硬性的限制。而实际上Q一个socket描述W在windows上的定义是unsigned intQ因此要有限刉也是四十多亿Q放心好了?nbsp;

  在服务器上port是监听用的,惌q样一U情况,web server?0端口上监听,当一个连接到来时Q系l会个连接分配一个socket句柄Q同时与其在80端口上进行通讯Q当另一个连接到来时Q服务器仍然?0端口与之通信Q只是分配的socket句柄不一栗这个socket句柄才是描述每个q接的唯一标识。按windows|络~程W二版上的说法,q个上限值配|媄响?nbsp;

  好了Q废话说完了Q下一,我们开始进入登录服的设计吧?nbsp;

d服的设计 -- 功能需?/strong> 

  正如我们在前面曾讨论q的Q登录服要实现的功能相当单,是帐号验证。ؓ了便于描qͼ我们暂不引入那些讨论q的优化手段Q先以最单的方式实现Q另外也基本以mangos的代码作为参考来q行描述?nbsp;

  惌一下帐号验证的实现ҎQ最Ҏ的那是把用戯入的明文用帐号和密码直接发给d服,服务器根据帐号从数据库中取出密码Q与用户输入的密码相比较?nbsp;

  q个Ҏ存在的安全隐患实在太大,明文的密码传输太Ҏ被截获了。那我们试着在传输之前先加一下密Qؓ了服务器能进行密码比较,我们应该采用一个可逆的加密法Q在服务器端把这个加密后的字串还原ؓ原始的明文密码,然后与数据库密码q行比较。既然是一个可逆的q程Q那外挂制作者L办法知道我们的加密过E,所以,q个Ҏ仍不够安全?nbsp;

  哦,如果我们只是希望密码不可能被q原出来Q那q不Ҏ吗,使用一个不可逆的散列法p了。用户在d时发送给服务器的是明文的帐号和经散列后的不可逆密码串Q服务器取出密码后也用同L法q行散列后再q行比较。比如,我们q使用最q泛的md5法吧。噢Q不要管那个王小云的什么论文,如果我真有那么好的运气,早中500w了,q用在这考虑该死的服务器设计吗? 

  g是一个很完美的方案,外挂制作者再也偷不到我们的密码了。慢着Q外挂偷密码的目的是什么?是ؓ了能用我们的帐号q游戏!如果我们L用一U固定的法来对密码做散列,那外挂只需要记住这个散列后的字串就行了Q用q个做密码就可以成功d?nbsp;

  嗯,q个问题好解冻I我们不要用固定的法q行散列是了。只是,问题在于服务器与客户端采用的散列法得出的字串必L相同的,或者是可验证其是否匚w的。很q运的是Q伟大的数学字们早就为我们准备好了很多优U的这cȝ法,而且l理论和实践都证明他们也实是够安全的?nbsp;

  q其中之一是一个叫做SRP的算法,全称叫做Secure Remote PasswordQ即安全q程密码。wow使用的是W?版,也就是SRP6法。有兛_中的数学证明Q如果有向我解释清楚Qƈ能让我真正弄明白的话Q我非常感Ȁ。不q其代码实现步骤倒是q不复杂Qmangos中的代码也还清晎ͼ我们也不再赘q?nbsp;

  d服除了帐号验证外q得提供另一功能,是在玩家的帐号验证成功后返回给他一个服务器列表让他去选择。这个列表的状态要定时hQ可能有新的游戏世界开放了Q也可能有些游戏世界非常不幸地停止运转了Q这些状态的变化都要可能及时地让玩家知道。不发生了什么事Q用户都有权利知道,特别是对于付q费的用h_我们不该藏着掖着Q不是吗Q?nbsp;

  q个游戏世界列表的功能将由大区服来提供,具体的结构我们在之前也描q过Q这里暂不做讨论。登录服从大区服上获取到的游戏世界列表发给已验证通过的客L卛_。好了,d服要实现的功能就q些Q很单,是吧?nbsp;

  实是太单了Q不q简单的l构正好更适合我们来看一看游戏服务器内部的模块结构,以及一些服务器共有lg的实现方法。这q作下一吧?nbsp;

服务器公q件实?-- mangos的游戏主循环 

  当阅M工E的源码Ӟ我们大概会选择从main函数开始,而当开始一Ҏ的工E时Q第一个写下的函数大多也是main。那我们先来看看,游戏服务器代码实CQmain函数都做了些什么?nbsp;

  ׃我在L术文章时最不喜看到的就是大D大D늚代码Q特别是那些直接Ctrl+C再Ctrl+V后未做Q何修改的代码Q用句时髦的话说Q一Ҏ术含量都没有Q所以在我们今后所要讨论的内容中,量会避免出现直接的代码Q在有些地方实需要代码来表述Ӟ也将会选择使用伪码?nbsp;

  先从mangos的登录服代码开始。mangos的登录服是一个单U程的结构,虽然在数据库q接中可以开启一个独立的U程Q但q个U程也只是对无返回结果的执行cSQL做缓Ԍ而对需要有q回l果的查询类SQLq是在主逻辑U程中阻塞调用的?nbsp;

  d服中唯一的这一个线E,也就是主循环U程对监听的socket做select操作Qؓ每个q接q来的客Ld其上的数据ƈ立即q行处理Q直到服务器收到SIGABRT或SIGBREAK信号时结束?nbsp;

  所以,mangosd服主循环的逻辑Q也包括后面游戏服的逻辑Q主循环的关键代码其实是在SocketHandler中,也就是那个Select函数中。检查所有的q接Q对新到来的q接调用OnAcceptҎQ有数据到来的连接则调用OnReadҎQ然后socket处理器自己定义对接收到的数据如何处理?nbsp;

  很简单的l构Q也比较Ҏ理解?nbsp;


  只是Q在Ҏ能要求比较高的服务器上Qselect一般不会是最好的选择。如果我们用windowsq_Q那IOCP是首选;如果是linuxQepool是不二选择。我们也不打讨论基于IOCP或是Zepool的服务器实现Q如果仅仅只是要实现服务器功能,很简单的几个API调用卛_Q而且|上已有很多好的教程Q如果是要做一个成熟的|络服务器品,不是我几简单的技术介l文章所能达到?nbsp;

  另外Q在服务器实CQ网lIO与逻辑处理一般会攑֜不同的线E中Q以免耗时较长的IOq程d住了需要立卛_应的游戏逻辑?nbsp;

  数据库的处理也类|会用异步的方式Q也是避免耗时的查询过E将游戏服务器主循环d住。想象一下,因某个玩家上U而发L一ơ数据库查询操作D服务器内所有在U玩安卡住不动是多么恐怖的一件事Q?nbsp;

  另外q有一些如事g、脚本、消息队列、状态机、日志和异常处理{公qӞ我们也会在接下来的时间里q行探讨?nbsp;

服务器公q件实?-- l箋来说d@?nbsp;

  前面我们只简单了解了下mangosd服的E序l构Q也发现了一些不之处,现在我们来看看如何提供一个更好的Ҏ?nbsp;

  正如我们曾讨的,Z游戏主逻辑循环的流畅运行,所有比较耗时的IO操作都会分n到单独的U程中去做,如网lIOQ数据库IO和日志IO{。当Ӟ也有把这些分享到单独的进E中d的?nbsp;

  另外对于大多数服务器E序来说Q在q行旉是作为精灵进E或服务q程的,所以我们ƈ不需要服务器能够处理控制台用戯入,我们所要处理的数据来源都来自网l?nbsp;

  q样Q主逻辑循环所要做的就是不停要取消息包来处理,当然q些消息包不仅有来自客户端的玩家操作数据包,也有来自GM服务器的理命oQ还包括来自数据库查询线E的q回l果消息包。这个@环将一直持l,直到收到一个通知服务器关闭的消息包?nbsp;

  主逻辑循环的结构还是很单的Q复杂的部分都在如何处理q些消息包的逻辑上。我们可以用一D늮单的伪码来描q这个@环过E: 

    while (Message* msg = getMessage()) 
    { 
      if (msg为服务器关闭消息) 
        break; 
      处理msg消息; 
    } 

  q里有一个问题需要探讨了Q在getMessage()的时候,我们应该d里取消息Q前面我们考虑q,臛_会有三个消息来源Q而我们还讨论q,q些消息源的IO操作都是在独立的U程中进行的Q我们这里的ȝE不应该直接去那几处消息源进行阻塞式的IO操作?nbsp;

  很简单,让那些独立的IOU程在接收完数据后自己送过来就是了。好比是Q我q里提供了一个仓库,有很多的供货商,他们有货要给我的时候只需要交C库,然后我再C库去取就是了Q这个仓库也是消息队列。消息队列是一个普通的队列实现Q当然必要提供多线E互斥访问的安全性支持,其基本的接口定义大概cMq样Q?nbsp;

    IMessageQueue 
    { 
      void putMessage(Message*); 
      Message* getMessage(); 
    } 

  |络IOQ数据库IOU程把整理好的消息包都加入到主逻辑循环U程的这个消息队列中便返回。有x息队列的实现和线E间消息的传递在ACE中有比较完全的代码实现及描述Q还有一些用示例,是个很好的参考?nbsp;

  q样的话Q我们的d@环就很清CQ从ȝE的消息队列中取消息Q处理消息,再取下一条消?..... 

服务器公q件实?-- 消息队列 

  既然说到了消息队列,那我们l来E微多聊一点吧?nbsp;

  我们所能想到的最单的消息队列可能是使用stl的list来实CQ即消息队列内部l护一个list和一个互斥锁QputMessage时将message加入到队列尾QgetMessage时从队列头取一个messageq回Q同时在getMessage和putMessage之前都要求先获取锁资源?nbsp;

  实现虽然单,但功能是l对满需求的Q只是性能上可能稍E有些不如人意。其最大的问题在频J的锁竞争上?nbsp;

  对于如何减少锁竞争次数的优化ҎQGhost Cheng提出了一U。提供一个队列容器,里面有多个队列,每个队列都可固定存放一定数量的消息。网lIOU程要给逻辑U程投递消息时Q会从队列容器中取一个空队列来用,直到该队列填满后再攑֛容器中换另一个空队列。而逻辑U程取消息时是从队列容器中取一个有消息的队列来dQ处理完后清I队列再攑֛到容器中?nbsp;

  q样便得只有在寚w列容器进行操作时才需要加锁,而IOU程和逻辑U程在操作自己当前用的队列旉不需要加锁,所以锁竞争的机会大大减了?nbsp;

  q里为每个队列设了个最大消息数Q看来好像是打算只有当IOU程写满队列时才会将其放回到容器中换另一个队列。那q样有时也会出现IOU程未写满一个队列,而逻辑U程又没有数据可处理的情况,特别是当数据量很时可能会很Ҏ出现。Ghost Cheng在他的描qC没有讲到如何解决q种问题Q但我们可以先来看看另一个方案?nbsp;

  q个Ҏ与上一个方案基本类|只是不再提供队列容器Q因为在q个Ҏ中只使用了两个队列,arthur在他的一邮件中描述了这个方案的实现及部分代码。两个队列,一个给逻辑U程读,一个给IOU程用来写,当逻辑U程d队列后会自q队列与IOU程的队列相调换。所以,q种Ҏ下加锁的ơ数会比较多一些,IOU程每次写队列时都要加锁Q逻辑U程在调换队列时也需要加锁,但逻辑U程在读队列时是不需要加锁的?nbsp;

  虽然看v来锁的调用次数是比前一U方案要多很多,但实际上大部分锁调用都是不会引vd的,只有在逻辑U程调换队列的那一瞬间可能会得某个线E阻塞一下。另外对于锁调用q程本n来说Q其开销是完全可以忽略的Q我们所不能忍受的仅仅是因ؓ锁调用而引Ld而已?nbsp;

  两种Ҏ都是很优U的优化方案,但也都是有其适用范围的。Ghost Cheng的方案因为提供了多个队列Q可以得多个IOU程可以dE师的,互不q扰的用自q队列Q只是还有一个遗留问题我们还不了解其解决Ҏ。arthur的方案很好的解决了上一个方案遗留的问题Q但因ؓ只有一个写队列Q所以当惌提供多个IOU程ӞU程间互斥地写入数据可能会增大竞争的ZQ当Ӟ如果只有一个IOU程那将是非常完的?nbsp;

服务器公q件实?-- 环Ş~冲?/strong> 

  消息队列锁调用太频繁的问题算是解决了Q另一个让人有些苦恼的大概是这太多的内存分配和释放操作了。频J的内存分配不但增加了系l开销Q更使得内存片不断增多Q非怸利于我们的服务器长期E_q行。也许我们可以用内存池Q比如SGI STL中附带的内存分配器。但是对于这U按照严格的先进先出序处理的,块大ƈ不算的Q而且块大也q不l一的内存分配情冉|_更多使用的是一U叫做环形缓冲区的方案,mangos的网l代码中也有q么一个东西,其原理也是比较简单的?nbsp;

  好比两个h围着一张圆形的桌子在追逐,跑的|络IOU程所控制Q当写入数据Ӟq个人就往前跑Q追的h是逻辑U程Q会一直往前追直到q上跑的人。如果追上了怎么办?那就是没有数据可MQ先{会儿呗Q等跑的人向前跑几步了再q,M能让游戏没得玩了吧。那要是q的的太慢,跑的{了一圈过来反q上q的Z呢?那您也先歇会儿吧。要是一直这么反着q,估计您就只能换一个跑的更快的q逐者了Q要不这游戏q真没法玩下厅R?nbsp;

  前面我们特别了,按照严格的先q先出顺序进行处理,q是环Ş~冲区的使用必须遵守的一要求。也是Q大安得遵守规定,q的Z能从桌子上跨q去Q跑的h当然也不允许反过来跑。至于ؓ什么,不需要多做解释了吧?nbsp;

  环Ş~冲区是一很好的技术,不用频繁的分配内存,而且在大多数情况下,内存的反复用也使得我们能用更少的内存块做更多的事?nbsp;

  在网lIOU程中,我们会ؓ每一个连接都准备一个环形缓冲区Q用于时存放接收到的数据,以应付半包及_包的情c在解包及解密完成后Q我们会这个数据包复制到逻辑U程消息队列中,如果我们只用一个队列,那这里也会是个环Ş~冲区,IOU程往里写Q逻辑U程在后面读Q互相追逐。可要是我们使用了前面介l的优化Ҏ后,可能q里便不再需要环形缓冲区了,臛_我们q不再需要他们是环Ş的了。因为我们对同一个队列不再会出现同时d写的情况Q每个队列在写满后交l逻辑U程去读Q逻辑U程d后清I队列再交给IOU程dQ一D固定大的~冲区即可。没关系Q这么好的技术,在别的地方一定也会用到的?nbsp;

服务器公q件实?-- 发包的方?/strong> 

  前面一直都在说接收数据时的处理ҎQ我们应该用专门的IOU程Q接收到完整的消息包后加入到ȝE的消息队列Q但是主U程如何发送数据还没有探讨q?nbsp;

  一般来说最直接的方法就是逻辑U程什么时候想发数据了q接调用相关的socket API发送,q要求服务器的玩家对象中保存其连接的socket句柄。但是直接send调用有时候有会存在一些问题,比如遇到pȝ的发送缓冲区满而阻塞住的情况,或者只发送了一部分数据的情况也时有发生。我们可以将要发送的数据先缓存一下,q样遇到未发送完的,在逻辑U程的下一ơ处理时可以接着再发送?nbsp;

  考虑数据~存的话Q那q里q可以有两种实现方式了,一是ؓ每个玩家准备一个缓冲区Q另外就是只有一个全局的缓冲区Q要发送的数据加入到全局~冲区的时候同时要指明q个数据是发到哪个socket的。如果用全局~冲区的话,那我们可以再q一步,使用一个独立的U程来处理数据发送,cM于逻辑U程Ҏ据的处理方式Q这个独立发送线E也l护一个消息队列,逻辑U程要发数据时也只是把数据加入到q个队列中,发送线E@环取包来执行send调用Q这时的d也就不会寚w辑U程有Q何媄响了?nbsp;

  采用W二U方式还可以附带一个优化方案。一般对于广播消息而言Q发送给周围玩家的数据都是完全相同的Q我们如果采用给每个玩家一个缓冲队列的方式Q这个数据包需要拷贝多份,而采用一个全局发送队列时Q我们只需要把q个消息入队一ơ,同时指明该消息包是要发送给哪些socket的即可。有兌优化的说明在云风描述其连接服务器实现的blog文章中也有讲刎ͼ有兴的可以去阅M下?nbsp;

服务器公q件实?-- 状态机 

  有关State模式的设计意囑֏实现׃从设计模式中摘抄了,我们只来看看游戏服务器编E中如何使用State设计模式?nbsp;

  首先q是从mangos的代码开始看P我们注意到登录服在处理客L发来的消息时用到了这样一个结构体Q?nbsp;

  struct AuthHandler 
  { 
    eAuthCmd cmd; 
    uint32 status; 
    bool (AuthSocket::*handler)(void); 
  }; 

  该结构体定义了每个消息码的处理函数及需要的状态标识,只有当前状态满求时才会调用指定的处理函敎ͼ否则q个消息码的出现是不合法的。这个status状态标识的定义是一个宏Q有两种有效的标识,STATUS_CONNECTED和STATUS_AUTHEDQ也是未认证通过和已认证通过。而这个状态标识的改变是在q行时进行的Q确切的说是在收到某个消息ƈ正确处理完后改变的?nbsp;

  我们再来看看设计模式中对State模式的说明,其中关于State模式适用情况里有一条,当操作中含有庞大的多分支的条件语句,且这些分支依赖于该对象的状态,q个状态通常用一个或多个枚D变量表示?nbsp;

  描述的情况与我们q里所要处理的情况是如此的怼Q也许我们可以试一试。那再看看State模式提供的解x案是怎样的,State模式每一个条件分支放入一个独立的cM?nbsp;

  ׃q里的两个状态标识只区分Z两种状态,所以,我们仅需要两个独立的c,用以表示两种状态即可。然后,按照State模式的描qͼ我们q需要一个Contextc,也就是状态机理c,用以理当前的状态类。稍作整理,大概的代码会cMq样Q?nbsp;

  状态基cL口: 
  StateBase 
  { 
    void Enter() = 0; 
    void Leave() = 0; 
    void Process(Message* msg) = 0; 
  }; 

  状态机基类接口Q?nbsp;
  MachineBase 
  { 
    void ChangeState(StateBase* state) = 0; 

    StateBase* m_curState; 
  }; 

  我们的逻辑处理cM从MachineBasezQ当取出数据包后交给当前状态处理,前面描述的两个状态类从StateBasezQ每个状态类只处理该状态标识下需要处理的消息。当要进行状态{换时Q调用MachineBase的ChangeState()ҎQ显C地告诉状态机理c自p转到哪一个状态。所以,状态类内部需要保存状态机理cȝ指针Q这个可以在状态类初始化时传入。具体的实现l节׃做过多描qC?nbsp;

  使用状态机虽然避免了复杂的判断语句Q但也引入了新的ȝ。当我们在进行状态{换时Q可能会需要将一些现场数据从老状态对象{Ud新状态对象,q需要在定义接口时做一下考虑。如果不希望执行拯Q那么这里公有的现场数据也可攑ֈ状态机cMQ只是这样在使用时可能就不那么优雅了?nbsp;

  正如同在设计模式中所描述的,所有的模式都是已有问题的另一U解x案,也就是说qƈ不是唯一的解x案。放到我们今天讨论的State模式中,拿d服所处理的两个状态来_也许用mangos所采用的遍历处理函数的Ҏ可能更简单,但当pȝ中的状态数量增多,状态标识也变多的时候,State模式显得尤光要了?nbsp;

  比如在游戏服务器上玩家的状态管理,q有在实现NPC人工时的各种状态管理,q些q作以后的专题吧?nbsp;

服务器公q?-- 事g与信?/strong> 

关于q一节,q几天已l打了好几遍草稿Q总觉得说不清楚,也不好组l这些内容,但是打铁要趁热,为避免热情消退Q先整理一点东西放q,好l下面的主题Q以后如果有Z再回来完善吧。本节内Ҏ考虑Q希望大家多l点意见?nbsp;

有些cM于QT中的event与signalQ我一些动作请求消息定义ؓ事gQ而将状态改变消息定义ؓ信号。比如在QT应用E序中,用户的一ơ鼠标点M产生一个鼠标点M件加入到事g队列中,当处理此事g时可能会D某个按钮控g产生一个clicked()信号?nbsp;

对应到我们的服务器上的一个例子,玩家d时会发给服务器一个请求登录的数据包,服务器可其当作一个用L录事Ӟ该事件处理完后可能会产生一个用户已d信号?nbsp;

q样Q与QTcMQ对于事件我们可以重定义其处理方法,甚至qo掉某些事件其不被处理,但对于信h们只是收C一个通知Q有些类gObserve模式中的观察者,当收到更新通知Ӟ我们只能更新自己的状态,对刚刚发生的事g我不已不能做M影响?nbsp;

仔细来看Q事件与信号其实q无多大差别Q从我们对其需求上来说Q都只要能注册事件或信号响应函数Q在事g或信号生时能够被通知到即可。但有一区别在于,事g处理函数的返回值是有意义的Q我们要Ҏq个q回值来定是否q要l箋事g的处理,比如在QT中,事g处理函数如果q回trueQ则q个事g处理已完成,QApplication会接着处理下一个事Ӟ而如果返回falseQ那么事件分zևCl箋向上L下一个可以处理该事g的注册方法。信号处理函数的q回值对信号分派器来说是无意义的?nbsp;

单点_是我们可以Z件定义过滤器Q得事件可以被qo。这一功能需求在游戏服务器上是到处存在的?nbsp;

关于事g和信h制的实现Q网l上的开源训也比较多Q比如FastDelegateQsigslotQboost::signal{,其中sigslotq被Google采用Q在libjingle的代码中我们可以看到他是如何被用的?nbsp;

在实C件和信号机制时或许可以考虑用同一套实玎ͼ在前面我们就分析q,两者唯一的区别仅在于q回值的处理上?nbsp;

另外q有一个需要我们关注的问题是事件和信号处理时的优先U问题。在QT中,事g因ؓ都是与窗口相关的Q所以事件回调时都是从当前窗口开始,一U一U向上派发,直到有一个窗口返回trueQ截断了事g的处理ؓ止。对于信L处理则比较简单,默认是没有顺序的Q如果需要明的序Q可以在信号注册时显C地指明槽的位置?nbsp;

在我们的需求中Q因为没有窗口的概念Q事件的处理也与信号cMQ对注册q的处理器要按某个顺序依ơ回调,所以优先的设|功能是需要的?nbsp;

最后需要我们考虑的是事g和信L处理方式。在QT中,事g使用了一个事仉列来l护Q如果事件的处理中又产生了新的事Ӟ那么新的事g会加入到队列,直到当前事g处理完毕后,QApplication再去队列头取下一个事件来处理。而信L处理方式有些不同Q信号处理是立即回调的,也就是一个信号生后Q他上面所注册的所有槽都会立即被回调。这样就会生一个递归调用的问题,比如某个信号处理器中又生了一个信P会得信L处理像一|一L展开。我们需要注意的一个很重要的问题是会不会引起@环调用?nbsp;

关于事g机制的考虑其实q很多,但都是一些不成熟的想法。在上面的文字中同时出C消息、事件和信号三个相近的概念,而在实际处理中,l常发现三者不知道如何界定的情况,实际的情冉|我在q里描述的要混ؕ的多?nbsp;

q里也就当是挖下一个坑Q希望能够有所交流?nbsp;

再谈d服的实现 

    L们的d服实现已l太q了Q先拉回来一下?nbsp;
    
    关于d服、大区服及游戏世界服的结构之前已做过探讨Q这里再把各自的职责和关pd一下?nbsp;

        GateWay/WorldServer   GateWay/WodlServer LoginServer LoginServer DNSServer WorldServerMgr 
                |                     |                     |                 |            | 
      --------------------------------------------------------------------------------------------- 
                                             | | | 
                                             internet 
                                                | 
                                              clients 

    其中DNSServer负责带负载均衡的域名解析服务Q返回LoginServer的IP地址l客L。WorldServerMgrl护当前大区内的世界服列表,LoginServer会从q里取世界列表发l客L。LoginServer处理玩家的登录及世界服选择h。GateWay/WorldServer为各个独立的世界服或者通过|关q接到后面的世界服?nbsp;

    在mangos的代码中Q我们注意到d服是从数据库中取的世界列表,而在wow官方服务器中Q我们却会注意到Q这个世界服列表q不是一开始就固定Q而是动态生成的。当每周一ơ的l护完成之后Q我们可以很明显的看到这个列表生成的q程。刚开始时Q世界列表是I的Q慢慢的Q世界服会一个个加入q来Q而这里如果有世界服当机,他会昄为离U,不会从列表中删除。但是当下一ơ服务器再维护后Q所有的世界服都不存在了Q全部重新开始添加?nbsp;

    从上面的q程描述中,我们很容易想到利用一个时的列表来保存世界服信息Q这也是我们增加WorldServerMgr服务器的目的所在。GateWay/WorldServer在启动时会自动向WorldServerMgr注册自己Q这样就把自己所代表的游戏世界添加到世界列表中了。类似的Q如果DNSServer也可以让LoginServer自己L册,q样在时LoginServer时就不需要去改动DNSServer的配|文件了?nbsp;

    WorldServerMgr内部的实现很单,监听一个固定的端口Q接受来自WorldServer的主动连接,q检其状态。这里可以用一个心跛_来实现其状态的,如果WorldServer的连接断开或者在规定旉内未收到心蟩包,则将其状态更Cؓȝ。另外WorldServerMgrq处理来自LoginServer的列表请求。由于世界列表ƈ不常变化Q所以LoginServer没有必要每次发送世界列表时都到WorldServerMgr上去取,LoginServer完全可以自己l护一个列表,当WorldServerMgr上的列表发生变化ӞWorldServerMgr会主动通知所有的LoginServer也更C下自q列表。这个或许就可以用前面描q过的事件方式,或者就是观察者模式了?nbsp;

    WorldServerMgr实现所要考虑的内容就q些Q我们再来看看LoginServerQ这才是我们今天要重点讨论的对象?nbsp;

    前面探讨一些服务器公共lgQ那我们q里也应该试用一下,不能只是停留在理Z。先从状态机开始,前面也说q了Q登录服上的q接会有两种状态,一是帐号密码验证状态,一是服务器列表选择状态,其实q有另外一个状态我们未曾讨Q因为它与我们的dq程q无多大关系Q这是升包发送状态。三个状态的转换程大致为: 

        LogonState -- 验证成功 -- 版本?-- 版本低于最新?-- 转到UpdateState 
                                          | 
                                           -- 版本{于最新?-- 转到WorldState 

    q个版本查的和决定下一个状态的q程是在LogonState中进行的Q下一个状态的选择是由当前状态来军_。密码验证的q程使用了SRP6协议Q具体过E就不多做描qͼ每个游戏使用的方式也都不大一栗而版本检查的q程更无值得探讨的东西,一个if-else卛_?br />
    升状态其实就是文件传输过E,文g发送完毕后通知客户端开始执行升U文件ƈ关闭q接。世界选择状态则提供了一个列表给客户端,其中包括了所有游戏世界网x务器的IP、PORT和当前负载情c如果客L一直连接着Q则该状态会以每5U一ơ的频率不停h列表l客LQ当然是否值得q样做还是有待商榗?nbsp;

    整个q程g都没有值得探讨的内容,但是Q还没有完。当客户端选择了一个世界之后该怎么办?wow的做法是Q当客户端选择一个游戏世界时Q客L会主动去q接该世界服的IP和PORTQ然后进入这个游戏世界。与此同Ӟ与登录服的连接还没有断开Q直到客L实q接上了选定的世界服q且走完了排队过Eؓ止。这是一个很必要的设计,保证了我们在因意外情况连接不上世界服或者发C界服正在排队而想换另外一个试试时不会需要重新进行密码验证?nbsp;

    但是我们所要关注的q不是这些,而是客户端去q接游戏世界的网x时服务器该如何识别我们。打个比方,有个不自觉的玩家不遵守游戏规则,没有去验证帐号密码就直接跑去q接世界服了Q就如同一个不自觉的乘客没有换L牌就直接跑到L口一栗这Ӟ乘务员会客气地告诉你要先换登机牌Q那L牌又从哪来?口换的Qh家会先验明你的n份,认后才会发l你L牌。一L处理q程Q我们的d服在验明客户端n份后Q也会发l客L一个登机牌Q这个登机牌q有一个学名,叫做session key?nbsp;

    客户端拿着q个session keyM界服|关处就可正登录了吗?gq是有个疑问Q他怎么知道我这个key是不是造假的?没办法,中国的假货太多,我们不得不到处都考虑假货的问题。方法很单,Ll他L牌的那个员问一下,q张牌是不是他发的不得了。可是,那么多的LoginServerQ要一个个问下来,q效率也太低了,后面排的镉K一定会开始叫唤了。那么,LoginServer这个key存到数据库中Q让|关服自己去数据库验证?g也是个可行的Ҏ?nbsp;

    如果觉得q样l数据库带来了太大的压力的话Q也可以考虑cMWorldServerMgr的做法,用一个时的列表来保存,甚至可以这个列表就保存到WorldServerMgr上,他正好是全区唯一的。这两种Ҏ的本质ƈ无差别,只是看你愿意负载放在哪里。而不在哪里Q这个查询的压力都是有点大的Q想惻I全区所有玩家呢。所以,我们也可以试着考虑一U新的方案,一U不需要去全区唯一一个入口查询的Ҏ?nbsp;

    那我们将q些session key分开存储不就得了。一个可行的Ҏ是,让Q意时d有一个地方保存一个客L的session keyQ这个地方可能是客户端当前正q接着的服务器Q也可以是它正要去连接的服务器。让我们来详l描qC下这个过E,客户端在LoginServer上验证通过ӞLoginServer为其生成了本ơ会话的session keyQ但只是保存在当前的LoginServer上,不会存数据库Q也不会发送给WorldServerMgr。如果客Lq时惌L个游戏世界,那么他必d通知当前q接的LoginServer要去的服务器地址QLoginServersession key安全转移l目标服务器Q{Uȝ意思是要确保目标服务器收到了session keyQ本C存的要删除掉。{UL功后LoginServer通知客户端再去连接目标服务器Q这时目标服务器在验证session key合法性的时候就不需要去别处查询了,只在本地保存的session key列表中查询即可?nbsp;

    当然了,Zsession key的安全,所有的服务器在收到一个新的session key后都会ؓ其设一个有效期Q在有效期过后还没来认证的,则该session key会被自动删除。同Ӟ所有服务器上的session key在连接关闭后一定会被删除,保证一个session key真正只ؓ一ơ连接会话服务?nbsp;

    但是Q很昄的,wowq没有采用这U方案,因ؓ客户端在选择世界服时q没有向服务器发送要求确认的消息。wow中的session key应该是保存在一个类gWorldServerMgr的地方,或者如mangos一P是保存在了数据库中。不是怎样一U方式,了解了其q程Q代码实现都是比较简单的Q我们就不再赘述了?/span>


]]>
C/C++中volatile?mutable,explicit 关键字详?/title><link>http://www.shnenglu.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.shnenglu.com/API/archive/2017/04/13/214839.html</guid><wfw:comment>http://www.shnenglu.com/API/comments/214839.html</wfw:comment><comments>http://www.shnenglu.com/API/archive/2017/04/13/214839.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.shnenglu.com/API/comments/commentRss/214839.html</wfw:commentRss><trackback:ping>http://www.shnenglu.com/API/services/trackbacks/214839.html</trackback:ping><description><![CDATA[<div><font face="verdana">  C/C++ 中的 volatile 关键字和 const 对应Q用来修饰变量,通常用于建立语言U别?memory barrier。这?BS ?"The C++ Programming Language" ?volatile 修饰词的说明Q?/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>      volatile 关键字是一U类型修饰符Q用它声明的cd变量表示可以被某些编译器未知的因素更改,比如Q操作系l、硬件或者其它线E等。遇到这个关键字声明的变量,~译器对讉K该变量的代码׃再进行优化,从而可以提供对Ҏ地址的稳定访问。声明时语法Qint volatile vInt; 当要求?volatile 声明的变量的值的时候,pȝL重新从它所在的内存d数据Q即使它前面的指令刚刚从该处dq数据。而且d的数据立刻被保存。例如:</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>// 其他代码Qƈ未明告诉编译器Q对 i q行q操?/div><div>5<span style="white-space:pre"> </span>int b = i;</div><div>    volatile 指出 i 是随时可能发生变化的Q每ơ用它的时候必M i的地址中读取,因而编译器生成的汇~代码会重新从i的地址d数据攑֜ b 中。而优化做法是Q由于编译器发现两次?iL据的代码之间的代码没有对 i q行q操作,它会自动把上ơ读的数据放?b 中。而不是重C i 里面诅R这样以来,如果 i是一个寄存器变量或者表CZ个端口数据就Ҏ出错Q所以说 volatile 可以保证对特D地址的稳定访问。注意,?VC 6 中,一般调试模式没有进行代码优化,所以这个关键字的作用看不出来。下面通过插入汇编代码Q测试有?volatile 关键字,对程序最l代码的影响Q?/div><div></div><div>输入下面的代码:</div><div></div><div>01<span style="white-space:pre"> </span>#include <stdio.h></div><div>02<span style="white-space:pre"> </span> </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>    int i = 10;</div><div>06<span style="white-space:pre"> </span>    int a = i;</div><div>07<span style="white-space:pre"> </span> </div><div>08<span style="white-space:pre"> </span>    printf("i = %d", a);</div><div>09<span style="white-space:pre"> </span> </div><div>10<span style="white-space:pre"> </span>    // 下面汇编语句的作用就是改变内存中 i 的?/div><div>11<span style="white-space:pre"> </span>    // 但是又不让编译器知道</div><div>12<span style="white-space:pre"> </span>    __asm {</div><div>13<span style="white-space:pre"> </span>        mov dword ptr [ebp-4], 20h</div><div>14<span style="white-space:pre"> </span>    }</div><div>15<span style="white-space:pre"> </span> </div><div>16<span style="white-space:pre"> </span>    int b = i;</div><div>17<span style="white-space:pre"> </span>    printf("i = %d", b);</div><div>18<span style="white-space:pre"> </span>}</div><div>    然后Q在 Debug 版本模式q行E序Q输出结果如下:</div><div></div><div>i = 10</div><div>i = 32</div><div>    然后Q在 Release 版本模式q行E序Q输出结果如下:</div><div></div><div>i = 10</div><div>i = 10</div><div>    输出的结果明显表明,Release 模式下,~译器对代码q行了优化,W二ơ没有输出正的 i 倹{下面,我们?i 的声明加?volatile 关键字,看看有什么变化:</div><div></div><div>01<span style="white-space:pre"> </span>#include <stdio.h></div><div>02<span style="white-space:pre"> </span> </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>    volatile int i = 10;</div><div>06<span style="white-space:pre"> </span>    int a = i;</div><div>07<span style="white-space:pre"> </span> </div><div>08<span style="white-space:pre"> </span>    printf("i = %d", a);</div><div>09<span style="white-space:pre"> </span>    __asm {</div><div>10<span style="white-space:pre"> </span>        mov dword ptr [ebp-4], 20h</div><div>11<span style="white-space:pre"> </span>    }</div><div>12<span style="white-space:pre"> </span> </div><div>13<span style="white-space:pre"> </span>    int b = i;</div><div>14<span style="white-space:pre"> </span>    printf("i = %d", b);</div><div>15<span style="white-space:pre"> </span>}</div><div>    分别?Debug ?Release 版本q行E序Q输出都是:</div><div></div><div>i = 10</div><div>i = 32</div><div>    q说明这?volatile 关键字发挥了它的作用。其实不只是“内嵌汇编操纵?#8221;q种方式属于~译无法识别的变量改变,另外更多的可能是多线Eƈ发访问共享变量时Q一个线E改变了变量的|怎样让改变后的值对其它U程 visible。一般说来,volatile用在如下的几个地方: </div><div>1) 中断服务E序中修改的供其它程序检的变量需要加volatileQ?nbsp;</div><div>2) 多Q务环境下各Q务间׃n的标志应该加volatileQ?nbsp;</div><div>3) 存储器映的g寄存器通常也要加volatile说明Q因为每ơ对它的d都可能由不同意义Q?/div><div></div><div>2.volatile 指针</div><div></div><div>    ?const 修饰词类|const 有常量指针和指针帔R的说法,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>注意Q对?VCQ这个特性实现在 VC 8 之后才是安全的?/div><div></div><div>指针自n的?#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>    注意Q?1) 可以把一个非volatile int赋给volatile intQ但是不能把非volatile对象赋给一个volatile对象?/div><div></div><div>          (2) 除了基本cd外,对用户定义类型也可以用volatilecdq行修饰?/div><div>              (3) C++中一个有volatile标识W的cd能访问它接口的子集,一个由cȝ实现者控制的子集。用户只能用const_cast来获得对cd接口的完全访问。此外,volatile向const一样会从类传递到它的成员?/div><div></div><div>3. 多线E下的volatile   </div><div></div><div>    有些变量是用volatile关键字声明的。当两个U程都要用到某一个变量且该变量的g被改变时Q应该用volatile声明Q该关键字的作用是防止优化编译器把变量从内存装入CPU寄存器中。如果变量被装入寄存器,那么两个U程有可能一个用内存中的变量,一个用寄存器中的变量Q这会造成E序的错误执行。volatile的意思是让编译器每次操作该变量时一定要从内存中真正取出Q而不是用已l存在寄存器中的|如下Q?nbsp;</div><div></div><div>  volatile  BOOL  bStop  =  FALSE;  </div><div>   (1) 在一个线E中Q? </div><div>  while(  !bStop  )  {  ...  }  </div><div>  bStop  =  FALSE;  </div><div>  return;    </div><div>   (2) 在另外一个线E中Q要l止上面的线E@环:  </div><div>  bStop  =  TRUE;  </div><div>  while(  bStop  );  //{待上面的线E终止,如果bStop不用volatilexQ那么这个@环将是一个死循环Q因为bStop已经dC寄存器中Q寄存器中bStop的值永q不会变成FALSEQ加上volatileQ程序在执行Ӟ每次均从内存中读出bStop的|׃会死循环了?/div><div>    q个关键字是用来讑֮某个对象的存储位|在内存中,而不是寄存器中。因Z般的对象~译器可能会其的拷贝放在寄存器中用以加快指令的执行速度Q例如下D代码中Q? </div><div>  ...  </div><div>  int  nMyCounter  =  0;  </div><div>  for(;  nMyCounter<100;nMyCounter++)  </div><div>  {  </div><div>  ...  </div><div>  }  </div><div>  ...  </div><div>   在此D代码中QnMyCounter的拷贝可能存攑ֈ某个寄存器中Q@环中Q对nMyCounter的测试及操作LҎ寄存器中的D行)Q但是另外又有段代码执行了这L操作QnMyCounter  -=  1;q个操作中,对nMyCounter的改变是对内存中的nMyCounterq行操作Q于是出Cq样一个现象:nMyCounter的改变不同步?/div><img src ="http://www.shnenglu.com/API/aggbug/214839.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.shnenglu.com/API/" target="_blank">C++技术中?/a> 2017-04-13 13:39 <a href="http://www.shnenglu.com/API/archive/2017/04/13/214839.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item></channel></rss> <footer> <div class="friendship-link"> <p>лǵվܻԴȤ</p> <a href="http://www.shnenglu.com/" title="精品视频久久久久">精品视频久久久久</a> <div class="friend-links"> </div> </div> </footer> <a href="http://www.fylmbd.cn" target="_blank">þseֻоƷ</a>| <a href="http://www.santai58.cn" target="_blank">Ʒþþ99</a>| <a href="http://www.thegraces.com.cn" target="_blank">ձþþþþþþþ</a>| <a href="http://www.taozhenyuan.cn" target="_blank">þþƷAV</a>| <a href="http://www.sanda8.com.cn" target="_blank">ɫۺϾþҹɫƷ</a>| <a href="http://www.gmxd.net.cn" target="_blank">þþþùպƷվ</a>| <a href="http://www.hnzzwl.cn" target="_blank">91Ʒþþþþ91</a>| <a href="http://www.yndi.com.cn" target="_blank">avھƷþþþӰԺ</a>| <a href="http://www.denlight.com.cn" target="_blank">vaĻþ</a>| <a href="http://www.ebankon.com.cn" target="_blank">þùɫavѿ</a>| <a href="http://www.mingfeiyaye.cn" target="_blank">þþùҺ</a>| <a href="http://www.um258.cn" target="_blank">þۺɫHEZYO</a>| <a href="http://www.myth9.cn" target="_blank">þݺҹҹ2020һ </a>| <a href="http://www.52888666.cn" target="_blank">ھƷ˾þþþavһ</a>| <a href="http://www.zhhhtch.cn" target="_blank">þˬ˰</a>| <a href="http://www.panxl.cn" target="_blank">2021ƷþþƷ</a>| <a href="http://www.yayalove.cn" target="_blank">þù׽</a>| <a href="http://www.xfb55.cn" target="_blank">ɫþþþþۺ</a>| <a href="http://www.byhyri.cn" target="_blank">뾫Ʒþþþþ</a>| <a href="http://www.cd-hk.cn" target="_blank">ݺɫþۺ</a>| <a href="http://www.941ad.cn" target="_blank">ޡvþþ뾫Ʒ</a>| <a href="http://www.huacai0019.cn" target="_blank">þAvԴվ</a>| <a href="http://www.jvfl.cn" target="_blank">þ99Ʒþþþþö̬ͼ</a>| <a href="http://www.spdx.com.cn" target="_blank">avttþþƷ</a>| <a href="http://www.phpluck.cn" target="_blank">󽶾þĻ</a>| <a href="http://www.semtmtw.cn" target="_blank">þоƷƵ</a>| <a href="http://www.01010101.com.cn" target="_blank">ѾƷþ</a>| <a href="http://www.vf369.cn" target="_blank">91Ʒ91þþþø</a>| <a href="http://www.9405.com.cn" target="_blank">þۺŷ</a>| <a href="http://www.fengdingjun.cn" target="_blank">ŷƷþø</a>| <a href="http://www.jlife-pal.cn" target="_blank">þþþþùƷŮ</a>| <a href="http://www.zhoushandk.cn" target="_blank">WWWAVþþӰƬ</a>| <a href="http://www.csmfy.cn" target="_blank">ھƷþþþþòӰԺ</a>| <a href="http://www.gzlinquan.cn" target="_blank">þۺɫɫ</a>| <a href="http://www.blog060422.cn" target="_blank">þ99Ʒþþþþ</a>| <a href="http://www.033009.cn" target="_blank">þþ91뾫ƷHD</a>| <a href="http://www.ttprinting.cn" target="_blank">þþƷŷպ99</a>| <a href="http://www.huating.net.cn" target="_blank">ɫݺݾþAVۺ</a>| <a href="http://www.aystone.cn" target="_blank">þerƷѹۿ8</a>| <a href="http://www.csmfy.cn" target="_blank">Ʒþþþ</a>| <a href="http://www.abcvi.cn" target="_blank">2021þþƷ99Ʒ</a>| <script> (function(){ var bp = document.createElement('script'); var curProtocol = window.location.protocol.split(':')[0]; if (curProtocol === 'https') { bp.src = 'https://zz.bdstatic.com/linksubmit/push.js'; } else { bp.src = 'http://push.zhanzhang.baidu.com/push.js'; } var s = document.getElementsByTagName("script")[0]; s.parentNode.insertBefore(bp, s); })(); </script> </body>