﻿<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:trackback="http://madskills.com/public/xml/rss/module/trackback/" xmlns:wfw="http://wellformedweb.org/CommentAPI/" xmlns:slash="http://purl.org/rss/1.0/modules/slash/"><channel><title>C++博客-jack-wang-随笔分类-游戏服务器端开发</title><link>http://www.cppblog.com/jack-wang/category/10930.html</link><description>小王</description><language>zh-cn</language><lastBuildDate>Wed, 19 Oct 2016 20:32:03 GMT</lastBuildDate><pubDate>Wed, 19 Oct 2016 20:32:03 GMT</pubDate><ttl>60</ttl><item><title>C++中使用Lua脚本 和lua中调用c的方法 </title><link>http://www.cppblog.com/jack-wang/archive/2011/09/29/157115.html</link><dc:creator>小王</dc:creator><author>小王</author><pubDate>Wed, 28 Sep 2011 18:34:00 GMT</pubDate><guid>http://www.cppblog.com/jack-wang/archive/2011/09/29/157115.html</guid><wfw:comment>http://www.cppblog.com/jack-wang/comments/157115.html</wfw:comment><comments>http://www.cppblog.com/jack-wang/archive/2011/09/29/157115.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cppblog.com/jack-wang/comments/commentRss/157115.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/jack-wang/services/trackbacks/157115.html</trackback:ping><description><![CDATA[<p class="postbody"><font size="3">转：<a href="http://www.cnweblog.com/fly2700/archive/2010/02/09/282920.html">http://www.cnweblog.com/fly2700/archive/2010/02/09/282920.html</a><br /><br /><span style="font-size: 10pt">参考</span><a href="http://blog.csdn.net/kun1234567/archive/2007/12/11/1929815.aspx"><span style="font-size: 10pt">http://blog.csdn.net/kun1234567/archive/2007/12/11/1929815.aspx</span></a><br /></font><strong>第1</strong><strong style="font-size: 10pt">步：下载<br /></strong><span style="font-size: 10pt">从官方主页</span><a style="font-size: 10pt" href="http://www.lua.org/">www.lua.org</a><span style="font-size: 10pt">下载Lua源代码，最新版本为5.1.3。（jack-wang注：现在已经是5.1.4了）</span><br style="font-size: 10pt" /><span style="font-size: 10pt">解压之后找到&#8220;[</span><font style="font-size: 10pt" size="3">Lua]/</font><span style="font-size: 10pt">src&#8221;文件夹，这里面就是Lua了，不过还不能直</span>接使用。</p>
<p class="postbody"><strong>第2步：编译lua</strong><br /><span style="font-size: 10pt">使用任意ANSI C编译器，在这里使用VS2005编译LUA。具体步骤如下：</span><br /><span style="font-size: 10pt">a.打开vs的命令行工具，工具--&gt;visual studio 2005 command prompt</span><br /><span style="font-size: 10pt">b.跳转到[Lua]目录，例如：cd&nbsp; </span><font size="3"><span style="font-size: 10pt">D:/Program Files/Lua</span></font><br /><span style="font-size: 10pt">c.执行：etc\luavs.bat（ 注意，是 \ 不是 /,写错了不能执行编译 )</span><br /><span style="font-size: 10pt">d.然后lua51.dll, lua51.lib, lua.exe, and luac.exe就生成在 src 路径下了</span><br /><span style="font-size: 10pt">e.在windows环境变量中把[Lua]\src添加到系统Path中去,</span><br /></p>
<p class="postbody"><strong style="font-size: 10pt">第3步：创建lua脚本</strong><br style="font-size: 10pt" /><font style="font-size: 10pt" size="3"><a href="http://luaforge.net/frs/download.php/1037/LuaEdit_2_5.zip"><span style="font-size: 10pt">下载LuaEdit http://luaforge.net/frs/download.php/1037/LuaEdit_2_5.zip</span></a><span style="font-size: 10pt">&nbsp;他是Lua脚本的编辑工具，还可以对Lua脚本进行语法检测和调试。</span></font><span style="font-size: 10pt">你也可以其他的纯文本编辑</span><span style="font-size: 10pt">工具写一个文件test.lua，注意后面没有&#8220;;&#8221;</span></p>
<div class="postbody">
<div style="border-bottom: #cccccc 1px solid; border-left: #cccccc 1px solid; padding-bottom: 4px; background-color: #eeeeee; padding-left: 4px; width: 98%; padding-right: 5px; font-size: 13px; word-break: break-all; border-top: #cccccc 1px solid; border-right: #cccccc 1px solid; padding-top: 4px" twffan="done"><img alt="" align="top" src="http://www.cnweblog.com/Images/OutliningIndicators/None.gif" twffan="done" /><span style="color: #000000" twffan="done">function&nbsp;f&nbsp;(&nbsp;x,&nbsp;y)<br /><img alt="" align="top" src="http://www.cnweblog.com/Images/OutliningIndicators/None.gif" twffan="done" />&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="color: #0000ff" twffan="done">return</span><span style="color: #000000" twffan="done">&nbsp;x&nbsp;</span><span style="color: #000000" twffan="done">+</span><span style="color: #000000" twffan="done">&nbsp;y<br /><img alt="" align="top" src="http://www.cnweblog.com/Images/OutliningIndicators/None.gif" twffan="done" />end</span></div></div>
<div class="postbody"><br /><strong>第4步：在C++ 中调用Lua脚本<br /></strong><span style="font-size: 10pt">开启VC++6.0环境创建一个新文件main.cpp</span><br /></div>
<div class="postbody">
<div style="border-bottom: #cccccc 1px solid; border-left: #cccccc 1px solid; padding-bottom: 4px; background-color: #eeeeee; padding-left: 4px; width: 98%; padding-right: 5px; font-size: 13px; word-break: break-all; border-top: #cccccc 1px solid; border-right: #cccccc 1px solid; padding-top: 4px" twffan="done"><img alt="" align="top" src="http://www.cnweblog.com/Images/OutliningIndicators/None.gif" twffan="done" /><span style="color: #008000" twffan="done">//</span><span style="color: #008000" twffan="done">&nbsp;Win32Console.cpp&nbsp;:&nbsp;Defines&nbsp;the&nbsp;entry&nbsp;point&nbsp;for&nbsp;the&nbsp;console&nbsp;application.<br /><img alt="" align="top" src="http://www.cnweblog.com/Images/OutliningIndicators/None.gif" twffan="done" /></span><span style="color: #008000" twffan="done">//<br /><img alt="" align="top" src="http://www.cnweblog.com/Images/OutliningIndicators/None.gif" twffan="done" /></span><span style="color: #000000" twffan="done"><br /><img alt="" align="top" src="http://www.cnweblog.com/Images/OutliningIndicators/None.gif" twffan="done" />#include&nbsp;</span><span style="color: #000000" twffan="done">"</span><span style="color: #000000" twffan="done">stdafx.h</span><span style="color: #000000" twffan="done">"</span><span style="color: #000000" twffan="done"><br /><img alt="" align="top" src="http://www.cnweblog.com/Images/OutliningIndicators/None.gif" twffan="done" /><br /><img alt="" align="top" src="http://www.cnweblog.com/Images/OutliningIndicators/None.gif" twffan="done" /></span><span style="color: #0000ff" twffan="done">extern</span><span style="color: #000000" twffan="done">&nbsp;</span><span style="color: #000000" twffan="done">"</span><span style="color: #000000" twffan="done">C</span><span style="color: #000000" twffan="done">"</span><span style="color: #000000" twffan="done"><br /><img id="Codehighlighter1_111_204_Open_Image" alt="" align="top" src="http://www.cnweblog.com/Images/OutliningIndicators/ExpandedBlockStart.gif" twffan="done" /><img style="display: none" id="Codehighlighter1_111_204_Closed_Image" alt="" align="top" src="http://www.cnweblog.com/Images/OutliningIndicators/ContractedBlock.gif" twffan="done" /></span><span style="border-bottom: #808080 1px solid; border-left: #808080 1px solid; background-color: #ffffff; display: none; border-top: #808080 1px solid; border-right: #808080 1px solid" id="Codehighlighter1_111_204_Closed_Text" twffan="done"><img alt="" src="http://www.cnweblog.com/Images/dot.gif" twffan="done" /></span><span id="Codehighlighter1_111_204_Open_Text" twffan="done"><span style="color: #000000" twffan="done">{<br /><img alt="" align="top" src="http://www.cnweblog.com/Images/OutliningIndicators/InBlock.gif" twffan="done" />#include&nbsp;</span><span style="color: #000000" twffan="done">"</span><span style="color: #000000" twffan="done">D:/Lib/Lua/lua.h</span><span style="color: #000000" twffan="done">"</span><span style="color: #000000" twffan="done"><br /><img alt="" align="top" src="http://www.cnweblog.com/Images/OutliningIndicators/InBlock.gif" twffan="done" />#include&nbsp;</span><span style="color: #000000" twffan="done">"</span><span style="color: #000000" twffan="done">D:/Lib/Lua/lualib.h</span><span style="color: #000000" twffan="done">"</span><span style="color: #000000" twffan="done"><br /><img alt="" align="top" src="http://www.cnweblog.com/Images/OutliningIndicators/InBlock.gif" twffan="done" />#include&nbsp;</span><span style="color: #000000" twffan="done">"</span><span style="color: #000000" twffan="done">D:/Lib/Lua/lauxlib.h</span><span style="color: #000000" twffan="done">"</span><span style="color: #000000" twffan="done"><br /><img alt="" align="top" src="http://www.cnweblog.com/Images/OutliningIndicators/ExpandedBlockEnd.gif" twffan="done" />}</span></span><span style="color: #000000" twffan="done"><br /><img alt="" align="top" src="http://www.cnweblog.com/Images/OutliningIndicators/None.gif" twffan="done" />#pragma&nbsp;comment(&nbsp;lib&nbsp;,</span><span style="color: #000000" twffan="done">"</span><span style="color: #000000" twffan="done">D:/Lib/Lua/lua51.lib</span><span style="color: #000000" twffan="done">"</span><span style="color: #000000" twffan="done">)<br /><img alt="" align="top" src="http://www.cnweblog.com/Images/OutliningIndicators/None.gif" twffan="done" />lua_State&nbsp;</span><span style="color: #000000" twffan="done">*</span><span style="color: #000000" twffan="done">L;<br /><img alt="" align="top" src="http://www.cnweblog.com/Images/OutliningIndicators/None.gif" twffan="done" /><br /><img alt="" align="top" src="http://www.cnweblog.com/Images/OutliningIndicators/None.gif" twffan="done" /></span><span style="color: #008000" twffan="done">//</span><span style="color: #008000" twffan="done">调用lua</span><span style="color: #008000" twffan="done"><br /><img alt="" align="top" src="http://www.cnweblog.com/Images/OutliningIndicators/None.gif" twffan="done" /></span><span style="color: #0000ff" twffan="done">double</span><span style="color: #000000" twffan="done">&nbsp;fun(&nbsp;</span><span style="color: #0000ff" twffan="done">double</span><span style="color: #000000" twffan="done">&nbsp;x,&nbsp;</span><span style="color: #0000ff" twffan="done">double</span><span style="color: #000000" twffan="done">&nbsp;y&nbsp;)<br /><img id="Codehighlighter1_308_668_Open_Image" alt="" align="top" src="http://www.cnweblog.com/Images/OutliningIndicators/ExpandedBlockStart.gif" twffan="done" /><img style="display: none" id="Codehighlighter1_308_668_Closed_Image" alt="" align="top" src="http://www.cnweblog.com/Images/OutliningIndicators/ContractedBlock.gif" twffan="done" /></span><span style="border-bottom: #808080 1px solid; border-left: #808080 1px solid; background-color: #ffffff; display: none; border-top: #808080 1px solid; border-right: #808080 1px solid" id="Codehighlighter1_308_668_Closed_Text" twffan="done"><img alt="" src="http://www.cnweblog.com/Images/dot.gif" twffan="done" /></span><span id="Codehighlighter1_308_668_Open_Text" twffan="done"><span style="color: #000000" twffan="done">{<br /><img alt="" align="top" src="http://www.cnweblog.com/Images/OutliningIndicators/InBlock.gif" twffan="done" />&nbsp;</span><span style="color: #0000ff" twffan="done">double</span><span style="color: #000000" twffan="done">&nbsp;ret;<br /><img alt="" align="top" src="http://www.cnweblog.com/Images/OutliningIndicators/InBlock.gif" twffan="done" />&nbsp;lua_getglobal(&nbsp;L,&nbsp;</span><span style="color: #000000" twffan="done">"</span><span style="color: #000000" twffan="done">add</span><span style="color: #000000" twffan="done">"</span><span style="color: #000000" twffan="done">);&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="color: #008000" twffan="done">//</span><span style="color: #008000" twffan="done">&nbsp;获取全局变量f</span><span style="color: #008000" twffan="done"><br /><img alt="" align="top" src="http://www.cnweblog.com/Images/OutliningIndicators/InBlock.gif" twffan="done" /></span><span style="color: #000000" twffan="done">&nbsp;lua_pushnumber(&nbsp;L,x);&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="color: #008000" twffan="done">//</span><span style="color: #008000" twffan="done">&nbsp;操作数压栈</span><span style="color: #008000" twffan="done"><br /><img alt="" align="top" src="http://www.cnweblog.com/Images/OutliningIndicators/InBlock.gif" twffan="done" /></span><span style="color: #000000" twffan="done">&nbsp;lua_pushnumber(&nbsp;L,y);&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="color: #008000" twffan="done">//</span><span style="color: #008000" twffan="done">&nbsp;操作数压栈</span><span style="color: #008000" twffan="done"><br /><img alt="" align="top" src="http://www.cnweblog.com/Images/OutliningIndicators/InBlock.gif" twffan="done" /></span><span style="color: #000000" twffan="done">&nbsp;lua_call(&nbsp;L,&nbsp;</span><span style="color: #000000" twffan="done">2</span><span style="color: #000000" twffan="done">,&nbsp;</span><span style="color: #000000" twffan="done">1</span><span style="color: #000000" twffan="done">);&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="color: #008000" twffan="done">//</span><span style="color: #008000" twffan="done">&nbsp;执行：2个操作数，1个返回值<br /><img alt="" align="top" src="http://www.cnweblog.com/Images/OutliningIndicators/InBlock.gif" twffan="done" />&nbsp;</span><span style="color: #008000" twffan="done">//</span><span style="color: #008000" twffan="done">lua_pcall(&nbsp;L,&nbsp;2,&nbsp;1,&nbsp;0);&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="color: #008000" twffan="done">//</span><span style="color: #008000" twffan="done">&nbsp;保护模式的lua_call,0为错误处理码。具体应用暂时不明，在使用手册中有粗略介绍</span><span style="color: #008000" twffan="done"><br /><img alt="" align="top" src="http://www.cnweblog.com/Images/OutliningIndicators/InBlock.gif" twffan="done" /></span><span style="color: #000000" twffan="done">&nbsp;ret&nbsp;</span><span style="color: #000000" twffan="done">=</span><span style="color: #000000" twffan="done">&nbsp;lua_tonumber(&nbsp;L,&nbsp;</span><span style="color: #000000" twffan="done">-</span><span style="color: #000000" twffan="done">1</span><span style="color: #000000" twffan="done">);&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="color: #008000" twffan="done">//</span><span style="color: #008000" twffan="done">&nbsp;将栈顶元素转换成数字并赋值给ret</span><span style="color: #008000" twffan="done"><br /><img alt="" align="top" src="http://www.cnweblog.com/Images/OutliningIndicators/InBlock.gif" twffan="done" /></span><span style="color: #000000" twffan="done">&nbsp;lua_pop(&nbsp;L,&nbsp;</span><span style="color: #000000" twffan="done">1</span><span style="color: #000000" twffan="done">);&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="color: #008000" twffan="done">//</span><span style="color: #008000" twffan="done">&nbsp;从栈中弹出一个元素</span><span style="color: #008000" twffan="done"><br /><img alt="" align="top" src="http://www.cnweblog.com/Images/OutliningIndicators/InBlock.gif" twffan="done" /></span><span style="color: #000000" twffan="done">&nbsp;</span><span style="color: #0000ff" twffan="done">return</span><span style="color: #000000" twffan="done">&nbsp;ret;<br /><img alt="" align="top" src="http://www.cnweblog.com/Images/OutliningIndicators/ExpandedBlockEnd.gif" twffan="done" />}</span></span><span style="color: #000000" twffan="done"><br /><img alt="" align="top" src="http://www.cnweblog.com/Images/OutliningIndicators/None.gif" twffan="done" /><br /><img alt="" align="top" src="http://www.cnweblog.com/Images/OutliningIndicators/None.gif" twffan="done" /></span><span style="color: #008000" twffan="done">//</span><span style="color: #008000" twffan="done">被lua调用的方法</span><span style="color: #008000" twffan="done"><br /><img alt="" align="top" src="http://www.cnweblog.com/Images/OutliningIndicators/None.gif" twffan="done" /></span><span style="color: #0000ff" twffan="done">static</span><span style="color: #000000" twffan="done">&nbsp;</span><span style="color: #0000ff" twffan="done">int</span><span style="color: #000000" twffan="done">&nbsp;average(lua_State&nbsp;</span><span style="color: #000000" twffan="done">*</span><span style="color: #000000" twffan="done">L2)<br /><img id="Codehighlighter1_717_1079_Open_Image" alt="" align="top" src="http://www.cnweblog.com/Images/OutliningIndicators/ExpandedBlockStart.gif" twffan="done" /><img style="display: none" id="Codehighlighter1_717_1079_Closed_Image" alt="" align="top" src="http://www.cnweblog.com/Images/OutliningIndicators/ContractedBlock.gif" twffan="done" /></span><span style="border-bottom: #808080 1px solid; border-left: #808080 1px solid; background-color: #ffffff; display: none; border-top: #808080 1px solid; border-right: #808080 1px solid" id="Codehighlighter1_717_1079_Closed_Text" twffan="done"><img alt="" src="http://www.cnweblog.com/Images/dot.gif" twffan="done" /></span><span id="Codehighlighter1_717_1079_Open_Text" twffan="done"><span style="color: #000000" twffan="done">{<br /><img id="Codehighlighter1_720_748_Open_Image" alt="" align="top" src="http://www.cnweblog.com/Images/OutliningIndicators/ExpandedSubBlockStart.gif" twffan="done" /><img style="display: none" id="Codehighlighter1_720_748_Closed_Image" alt="" align="top" src="http://www.cnweblog.com/Images/OutliningIndicators/ContractedSubBlock.gif" twffan="done" />&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="border-bottom: #808080 1px solid; border-left: #808080 1px solid; background-color: #ffffff; display: none; border-top: #808080 1px solid; border-right: #808080 1px solid" id="Codehighlighter1_720_748_Closed_Text" twffan="done">/**/</span><span id="Codehighlighter1_720_748_Open_Text" twffan="done"><span style="color: #008000" twffan="done">/*</span><span style="color: #008000" twffan="done">&nbsp;get&nbsp;number&nbsp;of&nbsp;arguments&nbsp;</span><span style="color: #008000" twffan="done">*/</span></span><span style="color: #000000" twffan="done"><br /><img alt="" align="top" src="http://www.cnweblog.com/Images/OutliningIndicators/InBlock.gif" twffan="done" />&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="color: #0000ff" twffan="done">int</span><span style="color: #000000" twffan="done">&nbsp;n&nbsp;</span><span style="color: #000000" twffan="done">=</span><span style="color: #000000" twffan="done">&nbsp;lua_gettop(L2);<br /><img alt="" align="top" src="http://www.cnweblog.com/Images/OutliningIndicators/InBlock.gif" twffan="done" />&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="color: #0000ff" twffan="done">double</span><span style="color: #000000" twffan="done">&nbsp;sum&nbsp;</span><span style="color: #000000" twffan="done">=</span><span style="color: #000000" twffan="done">&nbsp;</span><span style="color: #000000" twffan="done">0</span><span style="color: #000000" twffan="done">;<br /><img alt="" align="top" src="http://www.cnweblog.com/Images/OutliningIndicators/InBlock.gif" twffan="done" />&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="color: #0000ff" twffan="done">int</span><span style="color: #000000" twffan="done">&nbsp;i;<br /><img id="Codehighlighter1_801_832_Open_Image" alt="" align="top" src="http://www.cnweblog.com/Images/OutliningIndicators/ExpandedSubBlockStart.gif" twffan="done" /><img style="display: none" id="Codehighlighter1_801_832_Closed_Image" alt="" align="top" src="http://www.cnweblog.com/Images/OutliningIndicators/ContractedSubBlock.gif" twffan="done" />&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="border-bottom: #808080 1px solid; border-left: #808080 1px solid; background-color: #ffffff; display: none; border-top: #808080 1px solid; border-right: #808080 1px solid" id="Codehighlighter1_801_832_Closed_Text" twffan="done">/**/</span><span id="Codehighlighter1_801_832_Open_Text" twffan="done"><span style="color: #008000" twffan="done">/*</span><span style="color: #008000" twffan="done">&nbsp;loop&nbsp;through&nbsp;each&nbsp;argument&nbsp;</span><span style="color: #008000" twffan="done">*/</span></span><span style="color: #000000" twffan="done"><br /><img alt="" align="top" src="http://www.cnweblog.com/Images/OutliningIndicators/InBlock.gif" twffan="done" />&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="color: #0000ff" twffan="done">for</span><span style="color: #000000" twffan="done">&nbsp;(i&nbsp;</span><span style="color: #000000" twffan="done">=</span><span style="color: #000000" twffan="done">&nbsp;</span><span style="color: #000000" twffan="done">1</span><span style="color: #000000" twffan="done">;&nbsp;i&nbsp;</span><span style="color: #000000" twffan="done">&lt;=</span><span style="color: #000000" twffan="done">&nbsp;n;&nbsp;i</span><span style="color: #000000" twffan="done">++</span><span style="color: #000000" twffan="done">)<br /><img id="Codehighlighter1_861_920_Open_Image" alt="" align="top" src="http://www.cnweblog.com/Images/OutliningIndicators/ExpandedSubBlockStart.gif" twffan="done" /><img style="display: none" id="Codehighlighter1_861_920_Closed_Image" alt="" align="top" src="http://www.cnweblog.com/Images/OutliningIndicators/ContractedSubBlock.gif" twffan="done" />&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="border-bottom: #808080 1px solid; border-left: #808080 1px solid; background-color: #ffffff; display: none; border-top: #808080 1px solid; border-right: #808080 1px solid" id="Codehighlighter1_861_920_Closed_Text" twffan="done"><img alt="" src="http://www.cnweblog.com/Images/dot.gif" twffan="done" /></span><span id="Codehighlighter1_861_920_Open_Text" twffan="done"><span style="color: #000000" twffan="done">{<br /><img id="Codehighlighter1_864_888_Open_Image" alt="" align="top" src="http://www.cnweblog.com/Images/OutliningIndicators/ExpandedSubBlockStart.gif" twffan="done" /><img style="display: none" id="Codehighlighter1_864_888_Closed_Image" alt="" align="top" src="http://www.cnweblog.com/Images/OutliningIndicators/ContractedSubBlock.gif" twffan="done" />&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="border-bottom: #808080 1px solid; border-left: #808080 1px solid; background-color: #ffffff; display: none; border-top: #808080 1px solid; border-right: #808080 1px solid" id="Codehighlighter1_864_888_Closed_Text" twffan="done">/**/</span><span id="Codehighlighter1_864_888_Open_Text" twffan="done"><span style="color: #008000" twffan="done">/*</span><span style="color: #008000" twffan="done">&nbsp;total&nbsp;the&nbsp;arguments&nbsp;</span><span style="color: #008000" twffan="done">*/</span></span><span style="color: #000000" twffan="done"><br /><img alt="" align="top" src="http://www.cnweblog.com/Images/OutliningIndicators/InBlock.gif" twffan="done" />&nbsp;&nbsp;&nbsp;&nbsp;sum&nbsp;</span><span style="color: #000000" twffan="done">+=</span><span style="color: #000000" twffan="done">&nbsp;lua_tonumber(L2,&nbsp;i);<br /><img alt="" align="top" src="http://www.cnweblog.com/Images/OutliningIndicators/ExpandedSubBlockEnd.gif" twffan="done" />&nbsp;&nbsp;&nbsp;&nbsp;}</span></span><span style="color: #000000" twffan="done"><br /><img alt="" align="top" src="http://www.cnweblog.com/Images/OutliningIndicators/InBlock.gif" twffan="done" />&nbsp;&nbsp;&nbsp;&nbsp;lua_pushnumber(L,&nbsp;sum&nbsp;</span><span style="color: #000000" twffan="done">/</span><span style="color: #000000" twffan="done">&nbsp;n);<br /><img id="Codehighlighter1_952_969_Open_Image" alt="" align="top" src="http://www.cnweblog.com/Images/OutliningIndicators/ExpandedSubBlockStart.gif" twffan="done" /><img style="display: none" id="Codehighlighter1_952_969_Closed_Image" alt="" align="top" src="http://www.cnweblog.com/Images/OutliningIndicators/ContractedSubBlock.gif" twffan="done" />&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="border-bottom: #808080 1px solid; border-left: #808080 1px solid; background-color: #ffffff; display: none; border-top: #808080 1px solid; border-right: #808080 1px solid" id="Codehighlighter1_952_969_Closed_Text" twffan="done">/**/</span><span id="Codehighlighter1_952_969_Open_Text" twffan="done"><span style="color: #008000" twffan="done">/*</span><span style="color: #008000" twffan="done">&nbsp;push&nbsp;the&nbsp;sum&nbsp;</span><span style="color: #008000" twffan="done">*/</span></span><span style="color: #000000" twffan="done"><br /><img alt="" align="top" src="http://www.cnweblog.com/Images/OutliningIndicators/InBlock.gif" twffan="done" />&nbsp;&nbsp;&nbsp;&nbsp;lua_pushnumber(L,&nbsp;sum);<br /><img id="Codehighlighter1_997_1030_Open_Image" alt="" align="top" src="http://www.cnweblog.com/Images/OutliningIndicators/ExpandedSubBlockStart.gif" twffan="done" /><img style="display: none" id="Codehighlighter1_997_1030_Closed_Image" alt="" align="top" src="http://www.cnweblog.com/Images/OutliningIndicators/ContractedSubBlock.gif" twffan="done" />&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="border-bottom: #808080 1px solid; border-left: #808080 1px solid; background-color: #ffffff; display: none; border-top: #808080 1px solid; border-right: #808080 1px solid" id="Codehighlighter1_997_1030_Closed_Text" twffan="done">/**/</span><span id="Codehighlighter1_997_1030_Open_Text" twffan="done"><span style="color: #008000" twffan="done">/*</span><span style="color: #008000" twffan="done">&nbsp;return&nbsp;the&nbsp;number&nbsp;of&nbsp;results&nbsp;</span><span style="color: #008000" twffan="done">*/</span></span><span style="color: #000000" twffan="done"><br /><img alt="" align="top" src="http://www.cnweblog.com/Images/OutliningIndicators/InBlock.gif" twffan="done" />&nbsp;&nbsp;&nbsp;&nbsp;printf(</span><span style="color: #000000" twffan="done">"</span><span style="color: #000000" twffan="done">average&nbsp;called.&nbsp;[ok]\n</span><span style="color: #000000" twffan="done">"</span><span style="color: #000000" twffan="done">);<br /><img alt="" align="top" src="http://www.cnweblog.com/Images/OutliningIndicators/InBlock.gif" twffan="done" />&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="color: #0000ff" twffan="done">return</span><span style="color: #000000" twffan="done">&nbsp;</span><span style="color: #000000" twffan="done">2</span><span style="color: #000000" twffan="done">;<br /><img alt="" align="top" src="http://www.cnweblog.com/Images/OutliningIndicators/ExpandedBlockEnd.gif" twffan="done" />}</span></span><span style="color: #000000" twffan="done"><br /><img alt="" align="top" src="http://www.cnweblog.com/Images/OutliningIndicators/None.gif" twffan="done" /></span><span style="color: #008000" twffan="done">//</span><span style="color: #008000" twffan="done">==============================================<br /><img alt="" align="top" src="http://www.cnweblog.com/Images/OutliningIndicators/None.gif" twffan="done" /></span><span style="color: #008000" twffan="done">//</span><span style="color: #008000" twffan="done">&nbsp;Main&nbsp;Functions<br /><img alt="" align="top" src="http://www.cnweblog.com/Images/OutliningIndicators/None.gif" twffan="done" /></span><span style="color: #008000" twffan="done">//</span><span style="color: #008000" twffan="done">==============================================</span><span style="color: #008000" twffan="done"><br /><img alt="" align="top" src="http://www.cnweblog.com/Images/OutliningIndicators/None.gif" twffan="done" /></span><span style="color: #0000ff" twffan="done">int</span><span style="color: #000000" twffan="done">&nbsp;main(&nbsp;</span><span style="color: #0000ff" twffan="done">void</span><span style="color: #000000" twffan="done">)<br /><img id="Codehighlighter1_1213_1869_Open_Image" alt="" align="top" src="http://www.cnweblog.com/Images/OutliningIndicators/ExpandedBlockStart.gif" twffan="done" /><img style="display: none" id="Codehighlighter1_1213_1869_Closed_Image" alt="" align="top" src="http://www.cnweblog.com/Images/OutliningIndicators/ContractedBlock.gif" twffan="done" /></span><span style="border-bottom: #808080 1px solid; border-left: #808080 1px solid; background-color: #ffffff; display: none; border-top: #808080 1px solid; border-right: #808080 1px solid" id="Codehighlighter1_1213_1869_Closed_Text" twffan="done"><img alt="" src="http://www.cnweblog.com/Images/dot.gif" twffan="done" /></span><span id="Codehighlighter1_1213_1869_Open_Text" twffan="done"><span style="color: #000000" twffan="done">{<br /><img alt="" align="top" src="http://www.cnweblog.com/Images/OutliningIndicators/InBlock.gif" twffan="done" />&nbsp;</span><span style="color: #0000ff" twffan="done">int</span><span style="color: #000000" twffan="done">&nbsp;error;<br /><img alt="" align="top" src="http://www.cnweblog.com/Images/OutliningIndicators/InBlock.gif" twffan="done" />&nbsp;L&nbsp;</span><span style="color: #000000" twffan="done">=</span><span style="color: #000000" twffan="done">&nbsp;lua_open();&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="color: #008000" twffan="done">//</span><span style="color: #008000" twffan="done">&nbsp;创建Lua接口指针（借用DX的术语，本质是个堆栈指针）</span><span style="color: #008000" twffan="done"><br /><img alt="" align="top" src="http://www.cnweblog.com/Images/OutliningIndicators/InBlock.gif" twffan="done" /></span><span style="color: #000000" twffan="done">&nbsp;luaopen_base(L);&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="color: #008000" twffan="done">//</span><span style="color: #008000" twffan="done">&nbsp;加载Lua基本库</span><span style="color: #008000" twffan="done"><br /><img alt="" align="top" src="http://www.cnweblog.com/Images/OutliningIndicators/InBlock.gif" twffan="done" /></span><span style="color: #000000" twffan="done">&nbsp;luaL_openlibs(L);&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="color: #008000" twffan="done">//</span><span style="color: #008000" twffan="done">&nbsp;加载Lua通用扩展库</span><span style="color: #008000" twffan="done"><br /><img id="Codehighlighter1_1355_1595_Open_Image" alt="" align="top" src="http://www.cnweblog.com/Images/OutliningIndicators/ExpandedSubBlockStart.gif" twffan="done" /><img style="display: none" id="Codehighlighter1_1355_1595_Closed_Image" alt="" align="top" src="http://www.cnweblog.com/Images/OutliningIndicators/ContractedSubBlock.gif" twffan="done" /></span><span style="border-bottom: #808080 1px solid; border-left: #808080 1px solid; background-color: #ffffff; display: none; border-top: #808080 1px solid; border-right: #808080 1px solid" id="Codehighlighter1_1355_1595_Closed_Text" twffan="done">/**/</span><span id="Codehighlighter1_1355_1595_Open_Text" twffan="done"><span style="color: #008000" twffan="done">/*</span><span style="color: #008000" twffan="done"><br /><img alt="" align="top" src="http://www.cnweblog.com/Images/OutliningIndicators/InBlock.gif" twffan="done" />&nbsp;可能有的文章会采用以下写法，手工控制加载哪些库：<br /><img alt="" align="top" src="http://www.cnweblog.com/Images/OutliningIndicators/InBlock.gif" twffan="done" />&nbsp;luaopen_table(L);&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;//&nbsp;加载table库<br /><img alt="" align="top" src="http://www.cnweblog.com/Images/OutliningIndicators/InBlock.gif" twffan="done" />&nbsp;luaopen_io(L);&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;//&nbsp;加载IO库<br /><img alt="" align="top" src="http://www.cnweblog.com/Images/OutliningIndicators/InBlock.gif" twffan="done" />&nbsp;luaopen_string(L);&nbsp;&nbsp;&nbsp;&nbsp;//&nbsp;加载string库<br /><img alt="" align="top" src="http://www.cnweblog.com/Images/OutliningIndicators/InBlock.gif" twffan="done" />&nbsp;luaopen_math(L);&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;//&nbsp;加载math库<br /><img alt="" align="top" src="http://www.cnweblog.com/Images/OutliningIndicators/InBlock.gif" twffan="done" />&nbsp;经过测试，luaopen_io(L);该句执行异常，可能跟Lua的IO库有关系。具体原因暂时没有追究，将来如果有机会弄清楚，再回头来阐述。<br /><img alt="" align="top" src="http://www.cnweblog.com/Images/OutliningIndicators/ExpandedSubBlockEnd.gif" twffan="done" /></span><span style="color: #008000" twffan="done">*/</span></span><span style="color: #000000" twffan="done"><br /><img alt="" align="top" src="http://www.cnweblog.com/Images/OutliningIndicators/InBlock.gif" twffan="done" />&nbsp;<br /><img id="Codehighlighter1_1600_1620_Open_Image" alt="" align="top" src="http://www.cnweblog.com/Images/OutliningIndicators/ExpandedSubBlockStart.gif" twffan="done" /><img style="display: none" id="Codehighlighter1_1600_1620_Closed_Image" alt="" align="top" src="http://www.cnweblog.com/Images/OutliningIndicators/ContractedSubBlock.gif" twffan="done" />&nbsp;</span><span style="border-bottom: #808080 1px solid; border-left: #808080 1px solid; background-color: #ffffff; display: none; border-top: #808080 1px solid; border-right: #808080 1px solid" id="Codehighlighter1_1600_1620_Closed_Text" twffan="done">/**/</span><span id="Codehighlighter1_1600_1620_Open_Text" twffan="done"><span style="color: #008000" twffan="done">/*</span><span style="color: #008000" twffan="done">&nbsp;load&nbsp;the&nbsp;script&nbsp;</span><span style="color: #008000" twffan="done">*/</span></span><span style="color: #000000" twffan="done"><br /><img alt="" align="top" src="http://www.cnweblog.com/Images/OutliningIndicators/InBlock.gif" twffan="done" />&nbsp;lua_register(L,&nbsp;</span><span style="color: #000000" twffan="done">"</span><span style="color: #000000" twffan="done">average</span><span style="color: #000000" twffan="done">"</span><span style="color: #000000" twffan="done">,&nbsp;average);<br /><img alt="" align="top" src="http://www.cnweblog.com/Images/OutliningIndicators/InBlock.gif" twffan="done" />&nbsp;error&nbsp;</span><span style="color: #000000" twffan="done">=</span><span style="color: #000000" twffan="done">&nbsp;luaL_dofile(L,&nbsp;</span><span style="color: #000000" twffan="done">"</span><span style="color: #000000" twffan="done">hellow.lua</span><span style="color: #000000" twffan="done">"</span><span style="color: #000000" twffan="done">);&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="color: #008000" twffan="done">//</span><span style="color: #008000" twffan="done">&nbsp;读取Lua源文件到内存编译</span><span style="color: #008000" twffan="done"><br /><img alt="" align="top" src="http://www.cnweblog.com/Images/OutliningIndicators/InBlock.gif" twffan="done" /></span><span style="color: #000000" twffan="done"><br /><img alt="" align="top" src="http://www.cnweblog.com/Images/OutliningIndicators/InBlock.gif" twffan="done" />&nbsp;</span><span style="color: #0000ff" twffan="done">double</span><span style="color: #000000" twffan="done">&nbsp;ret&nbsp;</span><span style="color: #000000" twffan="done">=</span><span style="color: #000000" twffan="done">&nbsp;fun(&nbsp;</span><span style="color: #000000" twffan="done">10</span><span style="color: #000000" twffan="done">,&nbsp;</span><span style="color: #000000" twffan="done">3.4</span><span style="color: #000000" twffan="done">);&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="color: #008000" twffan="done">//</span><span style="color: #008000" twffan="done">&nbsp;调用模版函数f</span><span style="color: #008000" twffan="done"><br /><img alt="" align="top" src="http://www.cnweblog.com/Images/OutliningIndicators/InBlock.gif" twffan="done" /></span><span style="color: #000000" twffan="done">&nbsp;printf(&nbsp;</span><span style="color: #000000" twffan="done">"</span><span style="color: #000000" twffan="done">ret&nbsp;=&nbsp;%f\n</span><span style="color: #000000" twffan="done">"</span><span style="color: #000000" twffan="done">,&nbsp;ret);&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="color: #008000" twffan="done">//</span><span style="color: #008000" twffan="done">&nbsp;输出结果，C语言的东西，跟Lua无关</span><span style="color: #008000" twffan="done"><br /><img alt="" align="top" src="http://www.cnweblog.com/Images/OutliningIndicators/InBlock.gif" twffan="done" /></span><span style="color: #000000" twffan="done">&nbsp;lua_close(&nbsp;L);<br /><img alt="" align="top" src="http://www.cnweblog.com/Images/OutliningIndicators/InBlock.gif" twffan="done" />&nbsp;</span><span style="color: #0000ff" twffan="done">return</span><span style="color: #000000" twffan="done">&nbsp;</span><span style="color: #000000" twffan="done">1</span><span style="color: #000000" twffan="done">;<br /><img alt="" align="top" src="http://www.cnweblog.com/Images/OutliningIndicators/ExpandedBlockEnd.gif" twffan="done" />}</span></span><span style="color: #000000" twffan="done"><br /><img alt="" align="top" src="http://www.cnweblog.com/Images/OutliningIndicators/None.gif" twffan="done" /></span></div></div>
<div class="postbody"><br />创建一个hellow.lua文件和main.cpp放在一起,写入以下内容<br /></div>
<div class="postbody">
<div style="border-bottom: #cccccc 1px solid; border-left: #cccccc 1px solid; padding-bottom: 4px; background-color: #eeeeee; padding-left: 4px; width: 98%; padding-right: 5px; font-size: 13px; word-break: break-all; border-top: #cccccc 1px solid; border-right: #cccccc 1px solid; padding-top: 4px" twffan="done"><img alt="" align="top" src="http://www.cnweblog.com/Images/OutliningIndicators/None.gif" twffan="done" /><span style="color: #000000" twffan="done">function&nbsp;add&nbsp;(&nbsp;x,&nbsp;y)&nbsp;&nbsp;&nbsp;&nbsp;<br /><img alt="" align="top" src="http://www.cnweblog.com/Images/OutliningIndicators/None.gif" twffan="done" />&nbsp;&nbsp;&nbsp;&nbsp;file&nbsp;</span><span style="color: #000000" twffan="done">=</span><span style="color: #000000" twffan="done">&nbsp;assert(io.open(</span><span style="color: #000000" twffan="done">"</span><span style="color: #000000" twffan="done">data.txt</span><span style="color: #000000" twffan="done">"</span><span style="color: #000000" twffan="done">,&nbsp;</span><span style="color: #000000" twffan="done">"</span><span style="color: #000000" twffan="done">w</span><span style="color: #000000" twffan="done">"</span><span style="color: #000000" twffan="done">))<br /><img alt="" align="top" src="http://www.cnweblog.com/Images/OutliningIndicators/None.gif" twffan="done" />&nbsp;&nbsp;&nbsp;&nbsp;file:write(</span><span style="color: #000000" twffan="done">"</span><span style="color: #000000" twffan="done">abcde\n</span><span style="color: #000000" twffan="done">"</span><span style="color: #000000" twffan="done">)<br /><img alt="" align="top" src="http://www.cnweblog.com/Images/OutliningIndicators/None.gif" twffan="done" />&nbsp;&nbsp;&nbsp;&nbsp;file:write(</span><span style="color: #000000" twffan="done">"</span><span style="color: #000000" twffan="done">ok!\n</span><span style="color: #000000" twffan="done">"</span><span style="color: #000000" twffan="done">)<br /><img alt="" align="top" src="http://www.cnweblog.com/Images/OutliningIndicators/None.gif" twffan="done" />&nbsp;&nbsp;&nbsp;&nbsp;file:close()<br /><img alt="" align="top" src="http://www.cnweblog.com/Images/OutliningIndicators/None.gif" twffan="done" /><br /><img alt="" align="top" src="http://www.cnweblog.com/Images/OutliningIndicators/None.gif" twffan="done" />&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="color: #000000" twffan="done">--</span><span style="color: #000000" twffan="done">DataDumper(</span><span style="color: #000000" twffan="done">1</span><span style="color: #000000" twffan="done">,</span><span style="color: #000000" twffan="done">2</span><span style="color: #000000" twffan="done">,</span><span style="color: #000000" twffan="done">3</span><span style="color: #000000" twffan="done">,</span><span style="color: #000000" twffan="done">4</span><span style="color: #000000" twffan="done">)<br /><img alt="" align="top" src="http://www.cnweblog.com/Images/OutliningIndicators/None.gif" twffan="done" />&nbsp;&nbsp;&nbsp;&nbsp;file&nbsp;</span><span style="color: #000000" twffan="done">=</span><span style="color: #000000" twffan="done">&nbsp;assert(io.open(</span><span style="color: #000000" twffan="done">"</span><span style="color: #000000" twffan="done">data.txt</span><span style="color: #000000" twffan="done">"</span><span style="color: #000000" twffan="done">,&nbsp;</span><span style="color: #000000" twffan="done">"</span><span style="color: #000000" twffan="done">r</span><span style="color: #000000" twffan="done">"</span><span style="color: #000000" twffan="done">))<br /><img alt="" align="top" src="http://www.cnweblog.com/Images/OutliningIndicators/None.gif" twffan="done" />&nbsp;&nbsp;&nbsp;&nbsp;str&nbsp;</span><span style="color: #000000" twffan="done">=</span><span style="color: #000000" twffan="done">&nbsp;file:read(</span><span style="color: #000000" twffan="done">"</span><span style="color: #000000" twffan="done">*a</span><span style="color: #000000" twffan="done">"</span><span style="color: #000000" twffan="done">)<br /><img alt="" align="top" src="http://www.cnweblog.com/Images/OutliningIndicators/None.gif" twffan="done" />&nbsp;&nbsp;&nbsp;&nbsp;io.write(str)<br /><img alt="" align="top" src="http://www.cnweblog.com/Images/OutliningIndicators/None.gif" twffan="done" />&nbsp;&nbsp;&nbsp;&nbsp;io.write(</span><span style="color: #000000" twffan="done">"</span><span style="color: #000000" twffan="done">\n</span><span style="color: #000000" twffan="done">"</span><span style="color: #000000" twffan="done">)<br /><img alt="" align="top" src="http://www.cnweblog.com/Images/OutliningIndicators/None.gif" twffan="done" />&nbsp;&nbsp;&nbsp;&nbsp;<br /><img alt="" align="top" src="http://www.cnweblog.com/Images/OutliningIndicators/None.gif" twffan="done" />&nbsp;&nbsp;&nbsp;&nbsp;avg,&nbsp;sum&nbsp;</span><span style="color: #000000" twffan="done">=</span><span style="color: #000000" twffan="done">&nbsp;average(</span><span style="color: #000000" twffan="done">10</span><span style="color: #000000" twffan="done">,&nbsp;</span><span style="color: #000000" twffan="done">200</span><span style="color: #000000" twffan="done">,&nbsp;</span><span style="color: #000000" twffan="done">3000</span><span style="color: #000000" twffan="done">)<br /><img alt="" align="top" src="http://www.cnweblog.com/Images/OutliningIndicators/None.gif" twffan="done" />&nbsp;&nbsp;&nbsp;&nbsp;print(</span><span style="color: #000000" twffan="done">"</span><span style="color: #000000" twffan="done">The&nbsp;average&nbsp;is&nbsp;</span><span style="color: #000000" twffan="done">"</span><span style="color: #000000" twffan="done">,&nbsp;avg)<br /><img alt="" align="top" src="http://www.cnweblog.com/Images/OutliningIndicators/None.gif" twffan="done" />&nbsp;&nbsp;&nbsp;&nbsp;print(</span><span style="color: #000000" twffan="done">"</span><span style="color: #000000" twffan="done">The&nbsp;sum&nbsp;is&nbsp;</span><span style="color: #000000" twffan="done">"</span><span style="color: #000000" twffan="done">,&nbsp;sum)<br /><img alt="" align="top" src="http://www.cnweblog.com/Images/OutliningIndicators/None.gif" twffan="done" />&nbsp;&nbsp;&nbsp;&nbsp;<br /><img alt="" align="top" src="http://www.cnweblog.com/Images/OutliningIndicators/None.gif" twffan="done" />&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="color: #0000ff" twffan="done">return</span><span style="color: #000000" twffan="done">&nbsp;x&nbsp;</span><span style="color: #000000" twffan="done">+</span><span style="color: #000000" twffan="done">&nbsp;y<br /><img alt="" align="top" src="http://www.cnweblog.com/Images/OutliningIndicators/None.gif" twffan="done" />end<br /></span></div></div><img src ="http://www.cppblog.com/jack-wang/aggbug/157115.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/jack-wang/" target="_blank">小王</a> 2011-09-29 02:34 <a href="http://www.cppblog.com/jack-wang/archive/2011/09/29/157115.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>一种高效的寻路算法 - B*寻路算法</title><link>http://www.cppblog.com/jack-wang/archive/2011/04/07/143569.html</link><dc:creator>小王</dc:creator><author>小王</author><pubDate>Wed, 06 Apr 2011 16:07:00 GMT</pubDate><guid>http://www.cppblog.com/jack-wang/archive/2011/04/07/143569.html</guid><wfw:comment>http://www.cppblog.com/jack-wang/comments/143569.html</wfw:comment><comments>http://www.cppblog.com/jack-wang/archive/2011/04/07/143569.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cppblog.com/jack-wang/comments/commentRss/143569.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/jack-wang/services/trackbacks/143569.html</trackback:ping><description><![CDATA[<span style="FONT-SIZE: medium"><font size=3>转：<a href="http://qinysong.iteye.com/blog/678941">http://qinysong.iteye.com/blog/678941</a><br><br><br>在此把这个算法称作B* 寻路算法（Branch Star 分支寻路算法，且与A*对应），本算法适用于游戏中怪物的自动寻路，其效率远远超过A*算法，经过测试，效率是普通A*算法的几十上百倍。 <br><br>通过引入该算法，一定程度上解决了游戏服务器端无法进行常规寻路的效率问题，除非服务器端有独立的AI处理线程，否则在服务器端无法允许可能消耗大量时间的寻路搜索，即使是业界普遍公认的最佳的A*，所以普遍的折中做法是服务器端只做近距离的寻路，或通过导航站点缩短A*的范围。 <br><br>算法原理 <br>本算法启发于自然界中真实动物的寻路过程，并加以改善以解决各种阻挡问题。 <br>前置定义： <br>1、探索节点： <br>为了叙述方便，我们定义在寻路过程中向前探索的节点（地图格子）称为探索节点，起始探索节点即为原点。（探索节点可以对应为A*中的开放节点） <br><br>2、自由的探索节点： <br>探索节点朝着目标前进，如果前方不是阻挡，探索节点可以继续向前进入下一个地图格子，这种探索节点我们称为自由探索节点； <br><br>3、绕爬的探索节点： <br>探索节点朝着目标前进，如果前方是阻挡，探索节点将试图绕过阻挡，绕行中的探索节点我们成为绕爬的探索节点； <br>算法过程 <br>1、起始，探索节点为自由节点，从原点出发，向目标前进； <br>2、自由节点前进过程中判断前面是否为障碍， <br>&nbsp;&nbsp;&nbsp;&nbsp; a、不是障碍，向目标前进一步，仍为自由节点； <br>&nbsp;&nbsp;&nbsp;&nbsp; b、是障碍，以前方障碍为界，分出左右两个分支，分别试图绕过障碍，这两个分支节点即成为两个绕爬的探索节点； <br>3、绕爬的探索节点绕过障碍后，又成为自由节点，回到2）； <br>4、探索节点前进后，判断当前地图格子是否为目标格子，如果是则寻路成功，根据寻路过程构造完整路径； <br>5、寻路过程中，如果探索节点没有了，则寻路结束，表明没有目标格子不可达； <br><br><br>演示如下：&nbsp; <br><img alt="" src="http://dl.javaeye.com/upload/picture/pic/63917/9f8aebdc-1d29-3b24-8503-0a4c2f55df77.gif" width=279 height=164>&nbsp;&nbsp;&nbsp;&nbsp; <img alt="" src="http://dl.javaeye.com/upload/picture/pic/63919/f1b052c3-e811-3020-8963-40a86c34ecd2.gif" width=267 height=160><br><img alt="" src="http://dl.javaeye.com/upload/picture/pic/63925/abdfd7cb-5bad-3a63-9c33-e6e9a9b667c8.gif" width=283 height=168>&nbsp;&nbsp;&nbsp; <img alt="" src="http://dl.javaeye.com/upload/picture/pic/63923/8d7ccf80-c4c0-3fd3-bc49-69b1aea86dbe.gif" width=281 height=173><br><img alt="" src="http://dl.javaeye.com/upload/picture/pic/63921/75f501d8-6dec-3266-922b-514790a91437.gif" width=282 height=198><br><br><br>B*与A*算法的性能比较 <br><br>寻路次数比较（5秒钟寻路次数）&nbsp;<br><img alt="" src="http://dl.javaeye.com/upload/picture/pic/70724/0e1c8a2f-273a-3dd0-a148-e0bedf7f96ca.gif" width=495 height=268><br><br>&nbsp; <br>B*与A*性能比较实例 <br>1、 无障碍情况 <br>此种情况，根据以上测试数据，B*算法效率是普通A*的44倍（左为A*，右为B*) <br><br><img alt="" src="http://dl.javaeye.com/upload/picture/pic/63935/211d83ed-1c79-3208-a171-0dd076c0a302.gif" width=280 height=153>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <img alt="" src="http://dl.javaeye.com/upload/picture/pic/63927/2e342093-bbf8-384d-b2a6-8c4883a1a5c0.gif" width=286 height=164><br>&nbsp; <br><br>2、线形障碍 <br>此种情况，根据以上测试数据，B*算法效率是普通A*的28倍（左为A*，右为B*) <br><br><img alt="" src="http://dl.javaeye.com/upload/picture/pic/63941/5a1ca015-d81f-3283-89d3-b512d0c94f8c.gif" width=301 height=184>&nbsp;&nbsp;&nbsp; <img alt="" src="http://dl.javaeye.com/upload/picture/pic/63933/bc007ab8-291f-3e03-b79d-0491656dd857.gif" width=303 height=183><br><br>&nbsp;&nbsp; <br>3、环形障碍 <br>此种情况，根据以上测试数据，B*算法效率是普通A*的132倍（左为A*，右为B*) <br><br><img alt="" src="http://dl.javaeye.com/upload/picture/pic/63931/1a47e78a-15e0-3d1c-af26-a37837119ef2.gif" width=296 height=173>&nbsp;&nbsp;&nbsp;&nbsp; <img alt="" src="http://dl.javaeye.com/upload/picture/pic/63939/94a906fa-70ca-32e5-aed8-e48cfff5e4c0.gif" width=290 height=180>&nbsp;<br><br><br>4、封闭障碍（目标不可达） <br>此种情况，根据以上测试数据，B*算法效率是普通A*的581倍（左为A*，右为B*) <br><img alt="" src="http://dl.javaeye.com/upload/picture/pic/63937/75004f42-0c05-39c0-96f4-47e88619b88b.gif" width=281 height=169>&nbsp;&nbsp;&nbsp;&nbsp; <img alt="" src="http://dl.javaeye.com/upload/picture/pic/63929/1991d318-e47c-3953-8449-e953b7942eff.gif" width=292 height=161><br><br>衍生算法 <br>通过以上封闭障碍，可以看出，这个方法在判断地图上的两个点是否可达上，也是非常高效的，在不可达情况下，时间复杂度与封闭障碍的周长相当，而不是整个地图的面积。</font></span>
<img src ="http://www.cppblog.com/jack-wang/aggbug/143569.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/jack-wang/" target="_blank">小王</a> 2011-04-07 00:07 <a href="http://www.cppblog.com/jack-wang/archive/2011/04/07/143569.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>魔兽世界私服trinitycore2的架构——世界对象</title><link>http://www.cppblog.com/jack-wang/archive/2010/07/11/120057.html</link><dc:creator>小王</dc:creator><author>小王</author><pubDate>Sat, 10 Jul 2010 17:53:00 GMT</pubDate><guid>http://www.cppblog.com/jack-wang/archive/2010/07/11/120057.html</guid><wfw:comment>http://www.cppblog.com/jack-wang/comments/120057.html</wfw:comment><comments>http://www.cppblog.com/jack-wang/archive/2010/07/11/120057.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cppblog.com/jack-wang/comments/commentRss/120057.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/jack-wang/services/trackbacks/120057.html</trackback:ping><description><![CDATA[<p style="FONT-SIZE: 10pt">转自：<a href="http://blog.csdn.net/romandion/archive/2009/10/27/4733596.aspx">http://blog.csdn.net/romandion/archive/2009/10/27/4733596.aspx</a><br><br>假设世界突然静止，你能够从中单独去掉的物体就是构成这个世界的对象，包括玩家、怪物、武器、矿石等。魔兽世界是个大型的网游，里面的种类十分丰富，更不用说对象了。当我看着trinity-core2的源码中，game目录的时候，有种傻眼的感觉，实在太多了。为了理解方便，我们抽丝剥茧，分析出类的继承关系。</p>
<p style="FONT-SIZE: 10pt">在tc2中，所有的对象都被认为是object，以object作为基类，以1.2.3格式表示继承关系，如：1 --&gt; object&nbsp; ；1.2 --&gt; worldobject。1.2表示worldobject继承自1（object）。下面列出完整的对象继承族谱。</p>
<p style="FONT-SIZE: 10pt">1、&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; Object</p>
<p style="FONT-SIZE: 10pt">1.1&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; Item&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 【物品，比如武器】</p>
<p style="FONT-SIZE: 10pt">1.1.1&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; Bag&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 【包裹，特殊的物品，可以容纳其他物品】</p>
<p style="FONT-SIZE: 10pt">1.2&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; WorldObject&nbsp;&nbsp;&nbsp;&nbsp; 【显示在地图上的物体】</p>
<p style="FONT-SIZE: 10pt">1.2.1&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; Corpse&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 【尸体】</p>
<p style="FONT-SIZE: 10pt">1.2.2&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; DynamicObject 【动态生成的对象，比如魔法，生命周期比较短】</p>
<p style="FONT-SIZE: 10pt">1.2.3&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; GameObject&nbsp;&nbsp;&nbsp;&nbsp; 【游戏物品，这个分类很难从字面上理解，后面会比较详细的介绍】</p>
<p style="FONT-SIZE: 10pt">1.2.3.1&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; Transport&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 【传送点】</p>
<p style="FONT-SIZE: 10pt">1.2.4&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; Unit&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 【游戏中有生命的单位】</p>
<p style="FONT-SIZE: 10pt">1.2.4.1&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; Creature&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 【生物，非玩家】</p>
<p style="FONT-SIZE: 10pt">1.2.4.2&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; TempSummon&nbsp;&nbsp; 【临时召唤物】</p>
<p style="FONT-SIZE: 10pt">1.2.4.2.1&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; Minion&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 【宠物】</p>
<p style="FONT-SIZE: 10pt">1.2.4.2.1.1&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; Guardian&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 【守卫】</p>
<p style="FONT-SIZE: 10pt">1.2.4.2.1.1.1&nbsp;&nbsp; Pet&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 【玩家的宠物，比如猎人的宠物，或者术士的恶魔，除非主人解散，否则一直存在】</p>
<p style="FONT-SIZE: 10pt">1.2.4.2.1.2&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; Puppet&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 【傀儡，有一定的存在时间。比如德鲁依的树人】</p>
<p style="FONT-SIZE: 10pt">1.2.4.2.1.3&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; Totem&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 【图腾，萨满的图腾】</p>
<p style="FONT-SIZE: 10pt">1.2.4.2&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; Player&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 【玩家，就不用多解释了】</p>
<p style="FONT-SIZE: 10pt">将整个魔兽世界分解静态物体和静态物体的行为，就比较好理解了。事实上，现实世界也是如此。将世界划分为时间和空间2个维度，现实物体就是空间的概念，行为就是空间物体在时间维度上的变化。象光或者其他不可见或者没有空间占位的物品也可以看作特殊的空间物体。我在这里先将空间维度将分离出来分析。</p>
<p style="FONT-SIZE: 10pt">差点忘了介绍GameObject这个另类。在GameObject.h中，有个struct GameObjectInfo定义，里面有个union能大概看出GameObject究竟指的是哪些东西。</p>
<p style="FONT-SIZE: 10pt">0、GAMEOBJECT_TYPE_DOOR，指的是门</p>
<p style="FONT-SIZE: 10pt">1、GAMEOBJECT_TYPE_BUTTON，应该是按钮，锁之类的。</p>
<p style="FONT-SIZE: 10pt">2、GAMEOBJECT_TYPE_QUESTGIVER任务物品</p>
<p style="FONT-SIZE: 10pt">3、GAMEOBJECT_TYPE_CHEST箱子</p>
<p style="FONT-SIZE: 10pt">4、GAMEOBJECT_TYPE_BINDER没定义，估计没有用上。</p>
<p style="FONT-SIZE: 10pt">5、GAMEOBJECT_TYPE_GENERIC通用的，跟任务相关的东西</p>
<p style="FONT-SIZE: 10pt">6、GAMEOBJECT_TYPE_TRAP陷阱</p>
<p style="FONT-SIZE: 10pt">7、GAMEOBJECT_TYPE_CHAIR椅子</p>
<p style="FONT-SIZE: 10pt">8、GAMEOBJECT_TYPE_SPELL_FOCUS没搞懂啥玩意儿。</p>
<p style="FONT-SIZE: 10pt">9、GAMEOBJECT_TYPE_TEXT文本</p>
<p style="FONT-SIZE: 10pt">10、GAMEOBJECT_TYPE_GOOBER应该是果实一类的吧。</p>
<p style="FONT-SIZE: 10pt">11、GAMEOBJECT_TYPE_TRANSPORT传送点</p>
<p style="FONT-SIZE: 10pt">12、GAMEOBJECT_TYPE_AREADAMAGE区域性伤害，没搞懂啥玩意儿。</p>
<p style="FONT-SIZE: 10pt">13、GAMEOBJECT_TYPE_CAMERA照相机，再研究下</p>
<p style="FONT-SIZE: 10pt">14、GAMEOBJECT_TYPE_MAPOBJECT地图对象，空着</p>
<p style="FONT-SIZE: 10pt">15、GAMEOBJECT_TYPE_MO_TRANSPORT地图对象的传送，不知道啥玩意儿。</p>
<p style="FONT-SIZE: 10pt">16、GAMEOBJECT_TYPE_DUELFLAG决斗标志，估计就是PK，插旗吧。空着没用</p>
<p style="FONT-SIZE: 10pt">17、GAMEOBJECT_TYPE_FISHINGNODE钓鱼点</p>
<p style="FONT-SIZE: 10pt">18、GAMEOBJECT_TYPE_SUMMONING_RITUAL仪式，不知道干啥。估计跟术士拉人那东西有关。</p>
<p style="FONT-SIZE: 10pt">19、GAMEOBJECT_TYPE_MAILBOX邮箱</p>
<p style="FONT-SIZE: 10pt">20、GAMEOBJECT_TYPE_DONOTUSE顾名思义，没用</p>
<p style="FONT-SIZE: 10pt">21、GAMEOBJECT_TYPE_GUARDPOST岗哨，是某种生物</p>
<p style="FONT-SIZE: 10pt">22、GAMEOBJECT_TYPE_SPELLCASTER魔法施放。</p>
<p style="FONT-SIZE: 10pt">23、GAMEOBJECT_TYPE_MEETINGSTONE集合石</p>
<p style="FONT-SIZE: 10pt">24、GAMEOBJECT_TYPE_FLAGSTAND不知道啥玩意儿。</p>
<p style="FONT-SIZE: 10pt">25、GAMEOBJECT_TYPE_FISHINGHOLE估计也是钓鱼点</p>
<p style="FONT-SIZE: 10pt">26、GAMEOBJECT_TYPE_FLAGDROP掉落标记</p>
<p style="FONT-SIZE: 10pt">27、GAMEOBJECT_TYPE_MINI_GAME也没懂是啥玩意儿。</p>
<p style="FONT-SIZE: 10pt">28、GAMEOBJECT_TYPE_CAPTURE_POINT应该是部落和联盟争夺地区</p>
<p style="FONT-SIZE: 10pt">29、GAMEOBJECT_TYPE_AURA_GENERATOR灵气</p>
<p style="FONT-SIZE: 10pt">30、GAMEOBJECT_TYPE_DUNGEON_DIFFICULTY地下城难度</p>
<p style="FONT-SIZE: 10pt">31、GAMEOBJECT_TYPE_BARBER_CHAIR理发师的椅子，唉，都啥玩意儿啊。</p>
<p style="FONT-SIZE: 10pt">32、GAMEOBJECT_TYPE_DESTRUCTIBLE_BUILDING可摧毁的建筑</p>
<p style="FONT-SIZE: 10pt">33、GAMEOBJECT_TYPE_GUILDBANK工会银行，空，不可见。</p>
<p style="FONT-SIZE: 10pt">34、GAMEOBJECT_TYPE_TRAPDOOR陷阱的门。</p>
<p style="FONT-SIZE: 10pt">应该说，GameObject是描述游戏中，除装备、生物、魔法外，无法准确归类的东西，比较杂，因此只能笼统归结为游戏对象</p>
<p style="FONT-SIZE: 10pt"><br>本文来自CSDN博客，转载请标明出处：<a href="http://blog.csdn.net/romandion/archive/2009/10/27/4733596.aspx">http://blog.csdn.net/romandion/archive/2009/10/27/4733596.aspx</a></p>
<img src ="http://www.cppblog.com/jack-wang/aggbug/120057.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/jack-wang/" target="_blank">小王</a> 2010-07-11 01:53 <a href="http://www.cppblog.com/jack-wang/archive/2010/07/11/120057.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>游戏对象的实现</title><link>http://www.cppblog.com/jack-wang/archive/2010/07/10/120049.html</link><dc:creator>小王</dc:creator><author>小王</author><pubDate>Sat, 10 Jul 2010 14:54:00 GMT</pubDate><guid>http://www.cppblog.com/jack-wang/archive/2010/07/10/120049.html</guid><wfw:comment>http://www.cppblog.com/jack-wang/comments/120049.html</wfw:comment><comments>http://www.cppblog.com/jack-wang/archive/2010/07/10/120049.html#Feedback</comments><slash:comments>1</slash:comments><wfw:commentRss>http://www.cppblog.com/jack-wang/comments/commentRss/120049.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/jack-wang/services/trackbacks/120049.html</trackback:ping><description><![CDATA[狭义的游戏对象是指游戏世界中所能看到及可交互的对象，如玩家、怪物、物品等，我们这里也主要讨论这类对象在服务器上的组织及实现。
<p style="FONT-SIZE: 10pt"></p>
<p style="FONT-SIZE: 10pt">　　在大部分的MMOG中，游戏对象的类型都大同小异，主要有物品、生物、玩家等。比如在wow中，通过服务器发下来的GUID我们可以了解到，游戏中有9大类对象，包括物品(Item)、背包(Container)、生物(Unit)、玩家(Player)、游戏对象(GameObject)、动态对象(DynamicObject)、尸体(Corpse)等。</p>
<p style="FONT-SIZE: 10pt">　　在mangos的实现中，对象使用类继承的方式，由Object基类定义游戏对象的公有接口及属性，包括GUID的生成及管理、构造及更新UpdateData数据的虚接口、设置及获取对象属性集的方法等。然后分出了两类派生对象，一是Item，另一是WorldObject。Item即物品对象，WorldObject顾名思义，为世界对象，即可添加到游戏世界场景中的对象，该对象类型定义了纯虚接口，也就是不可被实例化，主要是在Object对象的基础上又添加了坐标设置或获取的相关接口。</p>
<p style="FONT-SIZE: 10pt">　　Item类型又派兵出了一类Bag对象，这是一种特殊的物品对象，其本身具有物品的所有属性及方法，但又可作为新的容器类型，并具有自己特有的属性和方法，所以实现上采用了派生。mangos在实现时对Bag的类型定义做了点小技巧，Item的类型为2，Bag的类型为6，这样在通过位的方式来表示类型时，Bag类型也就同时属于Item类型了。虽然只是很小的一个技巧，但在很多地方却带来了极大的便利。</p>
<p style="FONT-SIZE: 10pt">　　从WorldObject派生出的类型就有好几种了，Unit、GameObject、DynamicObject和Corpse。<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;Unit为所有生物类型的基类，同WorldObject一样，也不可被实例化。它定义了生物类型的公有属性，如种族、职业、性别、生命、魔法等，另外还提供了相关的一些操作接口。游戏中实际的生物对象类型为Creature，从Unit派生，另外还有一类派生对象Player为玩家对象。Player与Creature在实现上最大的区别是玩家的操作由客户端发来的消息驱动，而Creature的控制是由自己定义的AI对象来驱动，另外Player内部还包括了很多的逻辑系统实现。</p>
<p style="FONT-SIZE: 10pt">　　另外还有两类特殊的Creature，Pet和Totem，其对象类型仍然还是生物类，只是实现上与会有些特殊的东西需要处理，所以在mangos中将其作为独立的派生类，只是实现上的一点处理。另外在GameObject中也实现有派生对象，最终的继承关系图比较简单，就不麻烦地去画图了。</p>
<p style="FONT-SIZE: 10pt">　　从我所了解的早期游戏实现来看，大部分的游戏对象结构都是采用的类似这种方式。可能与早期对面向对象的理解有关，当面向对象的概念刚出来时，大家认为继承就是面向对象的全部，所以处处皆对象，处处皆继承。</p>
<p style="FONT-SIZE: 10pt">　　类实现的是一种封装，虽然从云风那里出来的弃C++而转投C的声音可能会影响一部分人，但是，使用什么语言本身就是个人喜好及团队整体情况决定的。我们所要的也是最终的实现结果，至于中间的步骤，完全看个人。还是用云风的话说，这只是一种信仰问题，我依然采用我所熟悉的C++，下面的描述也是如此。</p>
<p style="FONT-SIZE: 10pt">　　随着面向对象技术的深入，以及泛型等概念的相继提出，软件程序结构方面的趋势也有了很大改变。C++大师们常说的话中有一句是这样说的，尽是采用组合而不是继承。游戏对象的实现也有类似的转变，趋向于以组合的方式来实现游戏对象类型，也就是实现一个通用的entity类型，然后以脚本定义的方式组合出不同的实际游戏对象类型。</p>
<p style="FONT-SIZE: 10pt">　　<br><br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;在游戏编程精粹四有三篇文章讲到了实体以及实体管理的实现方案，其中一篇文章说到了实体管理系统的四大要素：定义实体怎样沟通的实体消息，实现一实体类代码和数据的实体代码，维护已经注册在案的实体类列表，和用来创建、管理、发送消息的实体管理器。</p>
<p style="FONT-SIZE: 10pt">　　关于实体消息的内容之前讨论事件机制的时候做过一点说明，其实这也就是按接口调用和按消息驱动的区别，现在mangos的做法是完全的接口调用，所以引擎内部就没有任何的实体消息。实体代码实现和实体管理器是我们重点要讨论的内容。</p>
<p style="FONT-SIZE: 10pt">　　另有一篇文章也提到了使用类继续的方式实现游戏对象的两大问题，一是它要求系统中的所有对象都必须从一个起点衍生而成，也就是说所有对象类在编译的时候已经确定，这可能是一个不受欢迎的限制，如果开发者决定添加新的对象类，则必须要对基类有所了解，方能支持新类。另一个问题在于所有的对象类都必须实现同样的一些底层函数。</p>
<p style="FONT-SIZE: 10pt">　　对于第二个问题，可以通过接口继承的方式来避免基类的方法太多。在mangos的实现中就采用了类似的方法，从Object虚基类派生的Unit和WorldObject仍然还是不可实例化的类，这两种对象定义了不同的属性和方法，分来实现不同类型的对象。在游戏内部可以根据对象的实际类型来Object指针向下转型为Unit或WorldObject，以调用需要的接口。方法虽然不够OO，也还能解决问题。但是第一个问题是始终无法避免的。</p>
<p style="FONT-SIZE: 10pt">　　所以我们便有了通用实体这么一个概念，其主要方法是将原来基类的接口进行分类，分到一个个不同的子类中，然后以对象组合的方式来生成我们所需要的实际游戏对象类型。这个组合的过程可以通过脚本定义的方式，这样便可以在运行时生成为同的对象类型，也就解决了上面提到的第一个问题。</p>
<p style="FONT-SIZE: 10pt">　　通用实体的实现方法在目前的游戏引擎及开源代码中也可以看到影子。一个是BigWorld，从提供的资料来看，其引擎只提供了一个entity游戏对象，然后由游戏内容实现者通过xml和python脚本来自由定义不同类型的entity类型，每种类型可有不同的property和不同的方法。这样原来由基类定义的接口完全转移到脚本定义，具有非常强的灵活性。</p>
<p style="FONT-SIZE: 10pt">　　另外还有一个是CEL中的entity实现。按照CEL的描述，entity可以是游戏中的任意对象，包括玩家可交互的对象，如钥匙、武器等，也可以包括不能直接交互的对象，如游戏世界，甚至任务链中的一部分等。entity本身并没有任何特性，具体的功能实现需要靠附加property来完成。简单来说，property才定义了entity可以做什么，至于该怎么做，那又是依靠behavior来定义。所以，最终在CEL中一个游戏对象其实是由entity组合了多个property及多个behavior而生成的。</p>
<p style="FONT-SIZE: 10pt">　　但是CEL中的property与BigWorld中的property意义不大一样，在CEL中可定义的property其实是引擎内部要先提供的，比如其示例中所举的pcobject.mesh、pcmove.linear、pctools.inventory等，而BigWorld中的property是完全的自由定制。从这个角度来讲，其实可以把CEL中的property看作是游戏的逻辑系统，也就是我们上面所描述的，接口分类后所定义的子类。</p>
<p style="FONT-SIZE: 10pt">　　由引擎内部提供可选择的property与BigWorld所采用的完全自由定制property其实本质上是相同的。在用BigWorld实现的游戏中，也不可能为每种游戏对象类型都完全从头定义property，基于代码利用的原则，也会先定义一些小类，然后在entity类型定义时以自定义property的方式来包含这些小类。当然，我没有使用过BigWorld，上面的描述也只是基于游戏实现的大原则所做出来的。</p>
<p style="FONT-SIZE: 10pt">　　描述的依然有些抽象，我们可以用wow及mangos代码来说明一下。mangos中为object定义了一个属性集合，根据对象类型的不同，这个属性集的大小及保存数据也会有差异，另外游戏对象内部含有处理不同游戏逻辑的系统，如RestSystem、FloodFilterSystem、VariousSystem等等，在player.h中以接口组的方式进行定义。</p>
<p style="FONT-SIZE: 10pt">　　如果要将这种结构改为我们描述的通用entity系统，可以让object只提供property注册和删除的接口，这里的property定义与CEL中的相同，放在mangos中也就是上面说的RestSystem、FloodFilterSystem、VariousSystem这些。然后也通过xml文件的方式定义我们所需要的游戏对象类型，如player,creature,item等，不同的对象类型可以选择加载不同的property，加载的原则是需要哪些功能就加载哪些property。</p>
<p style="FONT-SIZE: 10pt">　　对象的定义按上面的方法完成后，对象的实现也需要做一些修改。以前客户端的消息是直接交由player来处理，AI也是直接调用creature的接口来完成一些功能，现在通用的entity内部已经没有任何可用的方法，所有的实现都转到了property中，所以需要由各个property实现自己来注册感兴趣的事件及消息，entity实现一个消息的转发，转给对此感兴趣的property来处理。其余的实现就没有什么不同了。</p>
<p style="FONT-SIZE: 10pt">　　当然，我们再做一点扩展，让property不光由引擎来提供，用脚本本身也能定义property，并且可以通过xml来注册这些property，这样便实现了与BigWorld一样的完全自由特性。这其实也就是将很多用C++实现的功能转移到了python中，这种做法可作为参考，但不一定对所有人合适，至少在我看来，这样的实现也基本只能由程序员来做，所以让程序员选择自己最擅长的语言可能会更易于开发和调试。<br><br><br><br><br>有关游戏对象实现的描述，前面两篇文章中说的不甚清楚，主要是一直都要引用网上能够找到的资料来进行描述，以避免与公司引起不必要的麻烦。所以语言有些拼凑的感觉，举的例子也很不恰当，今天正好看到了游戏编程精粹五和六上的两篇文章，内容都差不多，&lt;&lt;基于组件的对象管理&gt;&gt;和&lt;&lt;基于组件的游戏对象系统&gt;&gt;，说的也是我上两篇文章想要描述的内容，所以再补一篇，引用其中的部分文字进行明确的说明。 <br></p>
<p style="FONT-SIZE: 10pt" align=left>　　传统的游戏对象管理系统采用继承的方式来实现，例如，所有的子类都从CObject派生。大多数情况下，直接派生的也是抽象类，其中带一些功能而另一些子类则不带这些功能，比如可控制/不可控制，可动画/不可动画等。mangos的实现中基本就是这种情况，从Object直接派生的Unit和WorldObject都是不可直接实例化的类。</p>
<p style="FONT-SIZE: 10pt"><br>&nbsp;</p>
<p style="FONT-SIZE: 10pt" align=left>　　传统方法的问题在于无法应对需求的变化，如要求武器也有动画效果时就无法处理了。如果硬要是这样做，那随着需求的啬，很多的方法会被放到基类中，最终的结果是继承树变得越来越头重脚轻，这样的类会丧失它的内聚性，因为它们试图为所有对象完成所有的事。</p>
<p style="FONT-SIZE: 10pt">&nbsp;</p>
<p style="FONT-SIZE: 10pt" align=left>　　就是说到最后，基类会有一个很长的接口列表，而很多的游戏对象类型根本不需要实现其中的一些甚至大部分接口，但是按照这种结构却又必须去实现。以至于于实现一个非常庞大的对象，而且想要修改一点功能会导致系统的大调整。</p>
<p style="FONT-SIZE: 10pt">&nbsp;</p>
<p style="FONT-SIZE: 10pt" align=left>　　我们希望的系统是可以将现有的功能组合到新的对象中，并且在将新的功能添加到现有的对象中时不需要重构大量的代码和调整继承树的结构。</p>
<p style="FONT-SIZE: 10pt">&nbsp;</p>
<p style="FONT-SIZE: 10pt" align=left>　　实现的方法就是从组件来创建一个对象。组件是一个包含所有相关数据成员和方法的类，它完成某个特定的任务。把几个组件组合在一起就可以创建一个新的对象。如把Entity组件、Render组件和Collectable组件组合在一起生成了一个Spoon对象。Entity组件让我们可以把对象放到游戏世界中，Render组件让我们可以为对象指定一个模型进行渲染，而Collectable组件让我们可以拾取这个对象。</p>
<p style="FONT-SIZE: 10pt">&nbsp;</p>
<p style="FONT-SIZE: 10pt" align=left>　　关于组件的实现，所有的组件都从一个基础组件接口派生，可称其为IComponent。每个组件也有自己的接口定义，并且这个接口也需要从IComponent派生，类似于这样：IComponent -- ICmpRender -- CCmpRender&nbsp;M<br></p>
<p style="FONT-SIZE: 10pt" align=left>　　这里的每个组件也就是我在上一篇中所说的由引擎提供的属性，或者说在BigWorld中自己实现然后定义的属性，或者使用mangos中的定义，就是一个个的System，虽然mangos并没有将其完全做成组件，但是通过其代码注释可以看到，接口也是按功能组进行了分类，如果要拆分成组件也是比较方便的。</p>
<p style="FONT-SIZE: 10pt" align=left>　　组件之间的通信有两种方法，一是用组件ID查询到组件接口指针，然后调用接口方法；二是使用消息的方式，向对象中所有组件发消息。在初始化的时候，每一个组件类型都会告诉对象管理器应该接收什么样的消息。<br></p>
<p style="FONT-SIZE: 10pt" align=left>　　查询接口的方法也就是直接的方法调用，只不过接口不是全部在基类中，所以必须先查询到指定的组件然后才能调用其接口。消息的使用前面已经说过多次，其实现方案也有过说明。<br></p>
<p style="FONT-SIZE: 10pt" align=left>　　最后是关于游戏对象功能的扩展和游戏对象的定义。需要扩展功能也就是需要实现一个新的组件，或者修改现在组件。在大多数情况下，扩展都不会引起结构的很大调整，受影响的最多只是使用到该组件的部分代码。<br></p>
<p style="FONT-SIZE: 10pt" align=left>　　游戏对象定义可采用完全数据驱动的方式，使用xml或者脚本语言来定义对象类型，以及每个类型需要加载的组件。对象类型注册到对象管理器后，由管理器提供创建指定类型的对象的方法。数据驱动的方式能够让策划自由定义游戏对象类型，并且随时可自由创建新的对象类型。</p>
<img src ="http://www.cppblog.com/jack-wang/aggbug/120049.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/jack-wang/" target="_blank">小王</a> 2010-07-10 22:54 <a href="http://www.cppblog.com/jack-wang/archive/2010/07/10/120049.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>号称是目标软件的服务器架构（转载的，不关我事哦，图片就不发了，懒）</title><link>http://www.cppblog.com/jack-wang/archive/2010/01/16/105817.html</link><dc:creator>小王</dc:creator><author>小王</author><pubDate>Sat, 16 Jan 2010 07:38:00 GMT</pubDate><guid>http://www.cppblog.com/jack-wang/archive/2010/01/16/105817.html</guid><wfw:comment>http://www.cppblog.com/jack-wang/comments/105817.html</wfw:comment><comments>http://www.cppblog.com/jack-wang/archive/2010/01/16/105817.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cppblog.com/jack-wang/comments/commentRss/105817.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/jack-wang/services/trackbacks/105817.html</trackback:ping><description><![CDATA[&nbsp;&nbsp;&nbsp;&nbsp; 摘要: &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;计算机研究生开放研究&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;...&nbsp;&nbsp;<a href='http://www.cppblog.com/jack-wang/archive/2010/01/16/105817.html'>阅读全文</a><img src ="http://www.cppblog.com/jack-wang/aggbug/105817.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/jack-wang/" target="_blank">小王</a> 2010-01-16 15:38 <a href="http://www.cppblog.com/jack-wang/archive/2010/01/16/105817.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>网络游戏中的同步问题</title><link>http://www.cppblog.com/jack-wang/archive/2010/01/10/105312.html</link><dc:creator>小王</dc:creator><author>小王</author><pubDate>Sat, 09 Jan 2010 17:22:00 GMT</pubDate><guid>http://www.cppblog.com/jack-wang/archive/2010/01/10/105312.html</guid><wfw:comment>http://www.cppblog.com/jack-wang/comments/105312.html</wfw:comment><comments>http://www.cppblog.com/jack-wang/archive/2010/01/10/105312.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cppblog.com/jack-wang/comments/commentRss/105312.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/jack-wang/services/trackbacks/105312.html</trackback:ping><description><![CDATA[<p style="FONT-SIZE: 10pt">同步在网络游戏中是非常重要的，它保证了每个玩家在屏幕上看到的东西大体是一样的。其实呢，解决同步问题的最简单的方法就是把每个玩家的动作都向其他玩家 广播一遍，这里其实就存在两个问题：1，向哪些玩家广播，广播哪些消息。2，如果网络延迟怎么办。</p>
<p style="FONT-SIZE: 10pt">事实上呢，第一个问题是个非常简单的问题，不过之所以我 提出这个问题来，是提醒大家在设计自己的消息结构的时候，需要把这个因素考虑进去。而对于第二个问题，则是一个挺麻烦的问题，大家可以来看这么个例子： 比如有一个玩家A向服务器发了条指令，说我现在在P1点，要去P2点。指令发出的时间是T0，服务器收到指令的时间是T1，然后向周围的玩家 广播这条消息，消息的内容是&#8220;玩家A从P1到P2&#8221;有一个在A附近的玩家B，收到服务器的这则广播的消息的时间是T2，然后开始在客户端上画图，A从P1 到P2点。这个时候就存在一个不同步的问题，玩家A和玩家B的屏幕上显示的画面相差了T2-T1的时间。这个时候怎么办呢？ 有个解决 方案，我给它取名叫 预测拉扯，虽然有些怪异了点，不过基本上大家也能从字面上来理解它的意思。</p>
<p style="FONT-SIZE: 10pt">要解决这个问题，首先要定义一个值叫：预测误差。然后需要在服务器端每个玩家连 接的类里面加一项属性，叫TimeModified，然后在玩家登陆的时候，对客户端的时间和服务器的时间进行比较，得出来的差值保存在 TimeModified里面。还是上面的那个例子，服务器广播消息的时候，就根据要广播对象的TimeModified，计算出一个客户端的 CurrentTime，然后在消息头里面包含这个CurrentTime，然后再进行广播。并且同时在玩家A的客户端本地建立一个队列，保存该条消息， 只到获得服务器验证就从未被验证的消息队列里面将该消息删除，如果验证失败，则会被拉扯回P1点。然后当玩家B收到了服务器发过来的消息&#8220;玩家A从P1到 P2&#8221;这个时候就检查消息里面服务器发出的时间和本地时间做比较，如果大于定义的预测误差，就算出在T2这个时间，玩家A的屏幕上走到的地点P3，然后把 玩家B屏幕上的玩家A直接拉扯到P3，再继续走下去，这样就能保证同步。</p>
<p style="FONT-SIZE: 10pt">更进一步，为了保证客户端运行起来更加smooth，我并不推荐直接把玩家拉扯过 去，而是算出P3偏后的一点P4，然后用(P4-P1)/T(P4-P3)来算出一个很快的速度S，然后让玩家A用速度S快速移动到P4，这样的处理方法 是比较合理的，这种解决方案的原形在国际上被称为（Full plesiochronous），当然，该原形被我篡改了很多来适应网络游戏的同步，所以而变成所谓的：预测拉扯。 另外一个解决方 案，我给它取名叫 验证同步，听名字也知道，大体的意思就是每条指令在经过服务器验证通过了以后再执行动作。具体的思路如下：首先也需要在每个玩家连接类型里面定义一个 TimeModified，然后在客户端响应玩家鼠标行走的同时，客户端并不会先行走动，而是发一条走路的指令给服务器，然后等待服务器的验证。服务器接 受到这条消息以后，进行逻辑层的验证，然后计算出需要广播的范围，包括玩家A在内，根据各个客户端不同的TimeModified生成不同的消息头，开始 广播，这个时候这个玩家的走路信息就是完全同步的了。</p>
<p style="FONT-SIZE: 10pt">这个方法的优点是能保证各个客户端之间绝对的同步，缺点是当网络延迟比较大的时候，玩家的客户端的行 为会变得比较不流畅，给玩家带来很不爽的感觉。该种解决方案的原形在国际上被称为（Hierarchical master-slave synchronization），80年代以后被广泛应用于网络的各个领域。 </p>
<p style="FONT-SIZE: 10pt">最后一种解决方案是一种理想化的解决方案，在国际上被 称为Mutual synchronization，是一种对未来网络的前景的良好预测出来的解决方案。这里之所以要提这个方案，并不是说我们已经完全的实现了这种方案，而 只是在网络游戏领域的某些方面应用到这种方案的某些思想。我对该种方案取名为：半服务器同步。大体的设计思路如下： 首先客户端需要在 登陆世界的时候建立很多张广播列表，这些列表在客户端后台和服务器要进行不及时同步，之所以要建立多张列表，是因为要广播的类型是不止一种的，比如说有 local message,有remote message,还有global message 等等，这些列表都需要在客户端登陆的时候根据服务器发过来的消息建立好。在建立列表的同时，还需要获得每个列表中广播对象的TimeModified，并 且要维护一张完整的用户状态列表在后台，也是不及时的和服务器进行同步，根据本地的用户状态表，可以做到一部分决策由客户端自己来决定，当客户端发送这部 分决策的时候，则直接将最终决策发送到各个广播列表里面的客户端，并对其时间进行校对，保证每个客户端在收到的消息的时间是和根据本地时间进行校对过的。 那么再采用预测拉扯中提到过的计算提前量，提高速度行走过去的方法，将会使同步变得非常的smooth。该方案的优点是不通过服务器，客户端自己之间进行 同步，大大的降低了由于网络延迟而带来的误差，并且由于大部分决策都可以由客户端来做，也大大的降低了服务器的资源。由此带来的弊端就是由于消息和决策权 都放在客户端本地，所以给外挂提供了很大的可乘之机。 </p>
<p style="FONT-SIZE: 10pt">综合以上三种关于网络同步派系的优缺点，综合出一套关于网络游戏传输同步的较完整的解决方案，我称它为综合同步法（colligate synchronization）。大体设计思路如下： 首先将服务器需要同步的所有消息从划分一个优先等级，然后按照3/4的比例划分出重要消息和非重要消息，对于非重要消息，把决策权放在客户端，在客户端逻辑上建立相关的决策机构和各种消息缓存区，以及相关的消息缓存区管理机构，如下图所示： 上图简单说明了对于非重要消息，客户端的大体处理流程，其中有一个客户端被动行为值得大家注意，其中包括对服务器发过来的某些验证代码做返回，来确保消 息缓存中的消息和服务器端是一致的，从而有效的防止外挂来篡改本地消息缓存。其中的消息来源是包括本地的客户端响应玩家的消息以及远程服务器传递过来的消 息。 </p>
<p style="FONT-SIZE: 10pt">对于重要消息，比如说战斗或者是某些牵扯到玩家一些比较敏感数据的操作，则采用另外一套方案，该方案首先需要在服务器和客户端之 间建立一套Ping System，然后服务器保存和用户的及时的ping值，当ping比较小的时候，响应玩家消息的同时先不进行动作，而是先把该消息反馈给服务器，并且阻 塞，服务器收到该消息，进行逻辑验证之后向所有该详细广播的有效对象进行广播（包括消息发起者），然后客户端收到该消息的验证，才开始执行动作。而当 ping比较大的时候，客户端响应玩家消息的同时立刻进行动作，并且同时把该消息反馈给服务器，值得注意的是这个时候还需要在本地建立一个无验证消息的队 列，把该消息入队，执行动作的同时等待服务器的验证，还需要保存当前状态。服务器收到客户端的请求后，进行逻辑验证，并把消息反馈到各个客户端，带上各个 客户端校对过的本地时间。如果验证通过不过，则通知消息发起者，该消息验证失败，然后客户端自动把已经在进行中的动作取消，恢复原来状态。如果验证通过， 则广播到的各个客户端根据从服务器获得校对时间进行对其进行拉扯，保证在该行为完成之前完成同步。 至此，一个比较成熟的网络游戏的同步机制已经初步建立起来了，接下来的逻辑代码就根据各自不同的游戏风格以及侧重点来写了。 同步是网络游戏最重要的问题，如何同步也牵扯到各个方面的问题，比如说游戏的规模，游戏的类型以及各种各样的方面，对于规模比较大的游戏，在同步方面可 以下很多的工夫，把消息分得十分的细腻，对于不同的消息采用不同的同步机制，而对于规模比较小的游戏，则可以采用大体上一样的同步机制，究竟怎么样同步， 没有个定式，是需要根据自己的不同情况来做出不同的同步决策的 网游同步算法之导航推测（Dead Reckoning）算法： 在了解该算法前，我们先来谈谈该算法的一些背景资料。</p>
<p style="FONT-SIZE: 10pt">大家都知道，在网络传输的时候，延迟现象是很普遍的，而在基于Server/Client结构下的 网络游戏的同步也就成了很头疼的问题，在保证客户端响应用户本地指令流畅的情况下，没法有效的保证的同步的及时性。同样，在军方也有类似的事情发生，即使 是同一LAN里面的机器，也会因为传输的延迟，导致一些运算的失误，介于此，美国国防部投入了大量的资金用于研究一种比较的好的方案来解决分布式系统中的 延迟问题，特别是一个叫分布式模拟运动（Distributed Interactive Simulation）的系统，这套系统呢，其中就提出了一套号称是Latency Hiding &amp; Bandwidth Reduction的方案，命名为Dead Reckoning。呵呵，来头很大吧，恩，那么我们下面就来看看这套系统的一些观点，以及我们如何把它运用到我们的网络游戏的同步中。 首先，这套同步方案是基于我那篇《网络游戏的同步》一文中的Mutual Synchronization同步方案的，也就是说，它并不是Server/Client结构的，而是基于客户端之间的同步的。</p>
<p style="FONT-SIZE: 10pt">下面我们先来说一些本文中将用到的名词概念：网状网络：客户端之间构成的网络节点：网状网络中的每个客户端极限误差：进行同步的时候可能产生的误差的极值 恩，在探讨其原理的之前，我们先来看看我们需要一个什么样的环境。首先，需要一个网状网络，网状网络如何构成呢？当有新节点进入的时候，通知该网络里面 的所有节点，各节点为该客户端在本地创建一个副本，登出的时候，则通知所有节点销毁本地关于该节点的副本。然后每个节点该保存一些什么数据呢？首先有一个 很重要的包需要保存，叫做协议数据包（PDU Protocol Data Unit），PDU包含节点的一些相关的运动信息，比如当前位置，速度，运动方向，或者还有加速度等一些信息。除PDU之外，还有其他信息需要保存，比如 说节点客户端人物的HP，MP之类的。然后，保证每个节点在最少8秒之内要向其它节点广播一次PDU信息。最后，设置一个极限误差值。到此，其环境就算搭 建完成了。下面，我们就来看看相关的具体算法： 假设在节点A有一个小人（路人甲），开始跑路了，这个时候，就像所有的节点广播一次他 的PDU信息，包括：速度（S），方向（O），加速度（A）。那么所有的节点就开始模拟路人甲的运动轨迹和路线，包括节点A本身（这点很重要），同时，路 人甲在某某玩家的控制下，会不时的改变一下方向，让其跑路的路线变得不是那么正规。在跑路的过程中，节点A有一个值在不停的记录着其真实坐标和在后台模拟 运动的坐标的差值，当差值大于极限误差的时候，则计算出当前的速度S，方向O和速度A（算法将在后面介绍），并广播给网络中其他所有节点。其他节点在收到 这条消息之后呢，就可以用一些很平滑的移动把路人甲拉扯过去，然后重新调整模拟跑路的数据，让其继续在后台模拟跑路。 很显然，如果极 限误差定义得大了，其他节点看到的偏差就会过大，如果极限偏差定义得小了，网络带宽就会增大。如果定义这个极限误差，就该根据各种数据的重要性来设计了。 如果是回合制的网络游戏，那么在走路上把极限误差定义得大些无所谓，可以减少带宽。但是如果是及时打斗的网络游戏，那么就得把极限误差定义得小一些，否则 会出现某人看到某人老远把自己给砍死的情况。 Dead Reckoning的主要算法有9种，但是只有两种是解决主要问题的，其他的基本上只是针对不同的坐标系的一些不同的算法，这里就不一一介绍了。好，那么我们下面来看传说中的最主要的两种算法：第一：目标点 = 原点 + 速度 * 时间差第二：目标点 = 原点 + 速度 * 时间差 + 1/2 * 加速度 * 时间差呵呵，传说中的算法都是很经典的，虽然我们早在初中物理的时候就学过。 该算法的好处呢，正如它开始所说的，Latency Hiding &amp; Bandwidth Reduction，从原则上解决了网络延迟导致的不同步的问题，并且有效的减少了带宽，不好的地方就是该算法基本上只能使用于移动中的同步，当然，移动 的同步是网络游戏中同步的最大的问题。 该方法结合我在《网络游戏的同步》一文中提出的综合同步法的构架可以基本上解决掉网络游戏中走路同步的问题。相关问题欢迎大家一起讨论。 </p>
<p style="FONT-SIZE: 10pt">有关导航推测算法（Dead Reckoning）中的平滑处理： 根据我上篇文章所介绍的，在节点A收到节点B新的PDU包时，如果和A本地的关于B的模拟运动的坐标不一致时，怎么样在A的屏幕上把B拽到新的PDU包 所描叙的点上面去呢，上文中只提了用&#8220;很平滑的移动&#8221;把B&#8220;拉扯&#8221;过去，那么实际中应该怎么操作呢？这里介绍四种方法。 第一种方法，我取名叫直接拉扯法，大家听名字也知道，就是直接把B硬生生的拽到新的PDU包所描叙的坐标上去，该方法的好处是：简单。坏处是：看了以下三种方法之后你就不会用这种方法了。 第二种方法，叫直线行走（Linear），即让B从它的当前坐标走直线到新的PDU包所描叙的坐标，行走速度用上文中所介绍的经典算法：目标点 = 原点 + 速度 * 时间差 + 1/2 * 加速度 * 时间差算出：首先算出从当前坐标到PDU包中描叙的坐标所需要的时间： T = Dest( TargetB &#8211; OriginB ) / Speed 然后根据新PDU包中所描叙的坐标信息模拟计算出在时间T之后，按照新的PDU包中的运动信息所应该达到的位置： _TargetB = NewPDU.Speed * T 然后根据当前模拟行动中的B和_TargetB的距离配合时间T算出一个修正过的速度_S： _S = Dest( _TargetB &#8211; OriginB ) / T 然后在画面上让B以速度_S走直线到Target_B，并且在走到之后调整其速度，方向，加速度等信息为新的PDU包中所描叙的。 这种方法呢，非常的土，会让物体在画面上移动起来变得非常的不现实，经常会出现很生硬的拐角，而且对于经常要修改的速度_S，在玩家A的画面上，玩家B的行动会变得非常的诡异。其好处是：比第一种方法要好。 第三种方法，叫二次方程行走（Quadratic），该方法的原理呢，就是在直线行走的过程中，加入二次方程来计算一条曲线路径，让Dest( _TargetB &#8211; OriginB )的过程是一条曲线，而不是一条直线，恩，具体的实现方法，就是在Linear方法的计算中，设定一个二次方程，在Dest函数计算距离的时候根据设定的 二次方程来计算，这样一来，可以使B在玩家A屏幕上的移动变得比较的有人性化一些。但是该方法的考虑也是不周全的，仅仅只考虑了TargetB到 _TargetB的方向，而没有考虑新的PDU包中的方向描叙，那么从_TargetB开始模拟行走的时候，仍然是会出现比较生硬的拐角，那么下面提出的 最终解决方案，将彻底解决这个问题。 </p>
<p style="FONT-SIZE: 10pt">最后一种方法叫：立方体抖动（Cubic Splines），这个东东比较复杂，它需要四个坐标信息作为它的参数来进行运算，第一个参数Pos1是OriginB，第二个参数Pos2是 OriginB在模拟运行一秒以后的位置，第三个参数Pos3是到达_TargetB前一秒的位置，第四个参数pos4是_TargetB的位置。 Struct pos { Coordinate X; Coordinate Y; } Pos1 = OriginB Pos2 = OriginB + V Pos3 = _TargetB &#8211; V Pos4 = _TargetB 运动轨迹中(x, y)的坐标。 x = At^3 + Bt^2 + Ct + D y = Et^3 + Ft^2 + Gt + H （其中时间t的取值范围为0-1，在Pos1的时候为0，在Pos4的时候为1） x(0-3)代表Pos1-Pos4中x的值，y(0-3)代表Pos1-Pos4中y的值 A = x3 &#8211; 3 * x2 +3 * x1 &#8211; x0 B = 3 * x2 &#8211; 6 * x1 + 3 * x0 C = 3 * x1 &#8211; 3 * x0 D = x0 E = y3 &#8211; 3 * y2 +3 * y1 &#8211; y0 F = 3 * y2 &#8211; 6 * y1 + 3 * y0 G = 3 * y1 &#8211; 3 * y0 H = y0 上面是公式，那么下面我们来看看如何获得Pos1-Pos4：首先，Pos1和 Pos2的取值会比较容易获得，根据OriginB配合当前的速度和方向可以获得，然而Pos3和Pos4呢，怎么获得呢？如果在从Pos1到Pos4的 过程中有新的PDU到达，那么我们定义它为NewPackage。 Pos3 = NewPackage.X + NewPackage.Y * t + 1/2 * NewPackage.a * t^2 Pos4 = Pos3 &#8211; (NewPackage.V + NewPackage.a * t) 如果没有NewPackage的情况下,则Pos3和Pos4按照开始所规定的方法获得。</p>
<p style="FONT-SIZE: 10pt"><br>本文来自CSDN博客，转载请标明出处：<a href="http://blog.csdn.net/wolfcoder/archive/2009/02/16/3897672.aspx">http://blog.csdn.net/wolfcoder/archive/2009/02/16/3897672.aspx</a></p>
<img src ="http://www.cppblog.com/jack-wang/aggbug/105312.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/jack-wang/" target="_blank">小王</a> 2010-01-10 01:22 <a href="http://www.cppblog.com/jack-wang/archive/2010/01/10/105312.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>一种经典的服务器架构（和我的体会太接近了，不得不转）</title><link>http://www.cppblog.com/jack-wang/archive/2010/01/10/105310.html</link><dc:creator>小王</dc:creator><author>小王</author><pubDate>Sat, 09 Jan 2010 17:07:00 GMT</pubDate><guid>http://www.cppblog.com/jack-wang/archive/2010/01/10/105310.html</guid><wfw:comment>http://www.cppblog.com/jack-wang/comments/105310.html</wfw:comment><comments>http://www.cppblog.com/jack-wang/archive/2010/01/10/105310.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cppblog.com/jack-wang/comments/commentRss/105310.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/jack-wang/services/trackbacks/105310.html</trackback:ping><description><![CDATA[<a id=viewpost1_TitleUrl href="http://www.cppblog.com/johndragon/archive/2008/04/10/46768.html"><font color=#000080>一种经典的网络游戏服务器架构</font></a> <br><a href="http://www.cppblog.com/johndragon/archive/2009/06/19/46768.html">http://www.cppblog.com/johndragon/archive/2009/06/19/46768.html</a><br><br>首先，二话不说，上图（用Windows画图画的。。。）<br><br><img border=0 alt="" src="http://www.cppblog.com/images/cppblog_com/johndragon/servergraph.jpg" width=988 height=768><br>这个图是一个区的架构图，所有区的架构是一样的。上面虚线框的ServerGroup和旁边方框内的架构一样。图上的所有x N的服务器，都是多台一起的。红线，绿线，和蓝线图上也有图示，这里就不多介绍了。关于Agent Server大家也能看出来，其实就是Gate。<br>这里主要介绍下图上的标记了号码的位置的数据连接的内容和意义。<br><br>1-&nbsp;&nbsp;&nbsp;这是一条WebService的管道，在用户激活该区帐号，或者修改帐号密码的时候，通过这条通道来插入和更新用户的帐号信息。<br>2-&nbsp;&nbsp;&nbsp;这也是一条WebService管道，用来获取和控制用户该该组内的角色信息，以及进行付费商城代币之类的更新操作。<br>3-&nbsp;&nbsp;&nbsp;这是一条本地的TCP/IP连接，这条连接主要用来进行服务器组在登陆服务器的注册，以及登陆服务器验证帐户后，向用户服务器注册帐户登陆信息，以及进行对已经登陆的帐户角色信息进行操作（比如踢掉当前登陆的角色），还有服务器组的信息更新（当前在线玩家数量等）。<br>4-&nbsp;&nbsp;&nbsp;这也是一条本地TCP/IP连接，这条连接用来对连接到GameServer的客户端进行验证，以及获取角色数据信息，还有传回GameServer上角色的数据信息改变。<br>5-&nbsp;&nbsp;&nbsp;这条连接也是一条本地的TCP/IP连接，它用来进行公共信息服务器和数个游戏服务器间的交互，用来交换一些游戏世界级的信息（比如公会信息，跨服组队信息，跨服聊天频道等）。<br>6-&nbsp;&nbsp;&nbsp;这里的两条连接，想表达的意思是，UserServer和GameServer的Agent是可以互换使用的，也就是玩家进入组内之后，就不需要再切换Agent。如果不怕乱套，也可以把登陆服务器的Agent也算上，这样用户整个过程里就不需要再更换Agent，减少重复连接的次数，也提高了稳定性。（毕竟连接次数少了，也降低了连不上服务器的出现几率）<br><br>在这个架构里面，GameServer实际上是一个游戏逻辑的综合体，里面可以再去扩展成几个不同的逻辑服务器，通过PublicServer进行公共数据交换。<br>UserServer实际上扮演了一个ServerGroup的领头羊的角色，它负责向LoginServer注册和更新服务器组的信息（名字，当前人数），并且对Agent进行调度，对选择了该组的玩家提供一个用户量最少的Agent。同时，它也兼了一个角色管理服务器的功能，发送给客户端当前的角色列表，角色的创建，删除，选择等管理操作，都是在这里进行的。而且，它还是一个用户信息的验证服务器，GameServer需要通过它来进行客户端的合法性验证，以及获取玩家选择的角色数据信息。<br><br><br>采用这种架构的游戏，通常有以下表现。<br>1- 用户必须激活一个大区，才能在大区内登陆自己的帐号。<br>2- 用户启动客户端的时候，弹出一个登陆器，选择大区。<br>3- 用户启动真正的客户端的时候，一开始就是输入帐号密码。<br>4- 帐号验证完成之后，进行区内的服务器选择。<br>5- 服务器选择完成之后，进入角色管理。同时，角色在不同的服务器里不能共享。<br><br>市面上符合上面几个表现特征的游戏相当的多，而且也不乏旷世巨作。这个架构不是一个新的架构，但是它足够经典和完善，并且逻辑简单而清晰，用来做MMORPG，或者其它网络游戏的服务器架构，是一种不错的选择。<br>
<img src ="http://www.cppblog.com/jack-wang/aggbug/105310.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/jack-wang/" target="_blank">小王</a> 2010-01-10 01:07 <a href="http://www.cppblog.com/jack-wang/archive/2010/01/10/105310.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>从腾讯QQgame高性能服务器集群架构看“分而治之”与“自治”等分布式架构设计原则</title><link>http://www.cppblog.com/jack-wang/archive/2010/01/05/104793.html</link><dc:creator>小王</dc:creator><author>小王</author><pubDate>Mon, 04 Jan 2010 18:37:00 GMT</pubDate><guid>http://www.cppblog.com/jack-wang/archive/2010/01/05/104793.html</guid><wfw:comment>http://www.cppblog.com/jack-wang/comments/104793.html</wfw:comment><comments>http://www.cppblog.com/jack-wang/archive/2010/01/05/104793.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cppblog.com/jack-wang/comments/commentRss/104793.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/jack-wang/services/trackbacks/104793.html</trackback:ping><description><![CDATA[<h1>从腾讯QQgame高性能服务器集群架构看&#8220;分而治之&#8221;与&#8220;自治&#8221;等分布式架构设计原则</h1>
<p style="FONT-SIZE: 10pt"><a href="http://space.itpub.net/17007506/viewspace-616852">http://space.itpub.net/17007506/viewspace-616852</a></p>
<p style="FONT-SIZE: 10pt"><a href="http://www.cppblog.com/batch.common.php?action=viewspace&amp;op=up&amp;itemid=616852&amp;uid=17007506">上一篇</a> / <a href="http://www.cppblog.com/batch.common.php?action=viewspace&amp;op=next&amp;itemid=616852&amp;uid=17007506">下一篇</a> &nbsp;2009-10-19 13:54:17 / 个人分类：<a href="http://www.cppblog.com/17007506/spacelist-blog-itemtypeid-79388">服务器构架</a> </p>
<div style="FONT-SIZE: 10pt"><a href="#xspace-tracks">查看( 1313 )</a> / <a href="#xspace-itemreply">评论( 9 )</a> / <a href="#xspace-itemform">评分( 20 / 10 )</a> </div>
<div style="FONT-SIZE: 10pt" id=xspace-showmessage>
<p style="FONT-SIZE: 10pt">&nbsp;&nbsp;<a href="http://space.itpub.net/17007506/viewspace-615570" target=_blank>【上篇：从腾讯QQ升级游戏之&#8220;快速加入游戏&#8221;功能的实现缺陷看C/S之间如何正确分配相关协作】</a></p>
&nbsp; &nbsp; 腾讯QQGame游戏同时在线的玩家数量极其庞大，为了方便组织玩家组队游戏，腾讯设置了大量游戏室（房间），玩家可以选择进入属意的房间，并在此房间内找到可以加入的游戏组（牌桌、棋盘等）。玩家选择进入某个房间时，必须确保此房间当前人数未满（通常上限为400），否则进入步骤将会失败。玩家在登入QQGame后，会从<a title=服务器 href="http://server.it168.com/" target=_blank>服务器</a>端获取某类游戏下所有房间的当前人数数据，玩家可以据此找到未满的房间以便进入。
<p style="FONT-SIZE: 10pt">&nbsp;&nbsp;&nbsp; 如上篇所述的原因，如果待进入房间的人数接近上限时，玩家的进入请求可能失败，这是因为<a title=服务器 href="http://product.it168.com/files/0402search.shtml" target=_blank>服务器</a>在收到此进入请求之前可能有若干其他玩家也请求进入这个房间，造成房间人数达到上限。这一问题是无法通过上篇所述调整协作分配的方法来解决的，这是因为：要进入的房间是由玩家来指定的，无法在服务器端完成此项工作，游戏<a title=软件 href="http://software.it168.com/" target=_blank><a onclick="javascript:tagshow(event, '%C8%ED%BC%FE');" href="javascript:;" target=_self>软件</a></a>必须将服务器端所维护的所有房间人数数据复制到玩家的客户端，并让玩家在界面上看到这些数据，以便进行选择。这样，上篇所述的客户端与服务器端协作分配原则（谁掌握数据，谁干活），还得加上一些限制条件，并让位于另一个所谓"用户<a title=驱动 href="http://driver.it168.com/" target=_blank>驱动</a>客户端行为"原则--如果某个功能的执行是由用户来推动的，则这个功能的实现应当放在客户端（或者至少由客户端来控制整个协作），并且客户端必须持有此功能所依赖相关数据的副本，这个副本应当尽量与服务器端的源保持同步。</p>
<p style="FONT-SIZE: 10pt"><a href="http://space.itpub.net/batch.download.php?aid=18802" target=_blank><img border=0 src="http://space.itpub.net/attachments/2009/10/17007506_200910191415261.jpg"></a><br>&nbsp;&nbsp;图一"进入房间"失败示意</p>
<p style="FONT-SIZE: 10pt">&nbsp;&nbsp;&nbsp; 注意：点击图片可以放大观看</p>
<p style="FONT-SIZE: 10pt">&nbsp;&nbsp;&nbsp; QQGame还存在一个明显的不足，就是：玩家如果在游戏一段时间后，离开了某个房间，并且想进入其它房间，这时QQGame并不会刷新所有房间的当前人数，造成玩家据此信息所选的待进入房间往往实际上人数已满，使得进入步骤失败。笔者碰到的最糟情形是重复3、4次以上，才最后成功进入另外某个房间。此缺陷其实质是完全放弃了客户端数据副本与服务器端的源保持同步的原则。</p>
<p style="FONT-SIZE: 10pt">&nbsp;&nbsp;&nbsp; 实际上，QQGame的开发者有非常充分的理由来为此缺陷的存在进行辩护：QQGame同时在线的用户数超过百万甚至千万数量级，如果所有客户端要实时（所谓实时，就玩家的体验容忍度而言，可以定为不超过1秒的延迟）地从服务器端获取更新数据，那么最终只有一个结果--系统彻底崩溃。设想一下每秒千万次请求的吞吐量，以普通服务器每秒上百个请求的处理能力（这个数据是根据服务请求处理过程可能涉及到I/O操作来估值的，纯<a title=内存 href="http://product.it168.com/list/b/0205_1.shtml" target=_blank>内存</a>处理的情形可能提高若干数量级），需要成千上万台服务器组成集群方能承受（高可用性挑战）；而随着玩家不断地进入或退出游戏房间，相关数据一直在快速变化中，正向来看，假设有一台中心服务器持有这些数据，那么需要让成千上万台服务器与中心保持这些动态数据的实时同步（数据一致性挑战）；相对应的，逆向来看，玩家进入房间等请求被分配给不同的服务器来处理，一旦玩家进入房间成功则对应服务器内的相关数据被改变，那么假定中的中心服务器就需要实时汇集所有工作服务器内发生的数据变动（数据完整性挑战）。同时处理上万台服务器的数据同步，这需要什么样的中心服务器呢？即使有这样的超级服务器存在，那么Internet网较大的（而且不稳定的）<a title=网络 href="http://net.it168.com/" target=_blank>网络</a>通讯延迟又怎么解决呢？</p>
<p style="FONT-SIZE: 10pt">&nbsp;&nbsp;&nbsp; 对于软件缺陷而言，可以在不同的层面来加以解决--从设计、到需求、甚至是直接在业务层面来解决（例如，08年北京奥运会网上购票系统，为了解决订票请求拥塞而至系统崩溃的缺陷，最后放弃了原先"先到先得"的购票业务流程，改为：用户先向系统发订票申请，系统只是记录下来而不进行处理，而到了空闲时，在后台随机抽选幸运者，为他们一一完成订票业务）。当然解决方案所处的层面越高，可能就越让人不满意。就上述进入房间可能遭遇失败的缺陷而言，最简便的解决方案就是：在需求层面调整系统的操作方式，即增加一个类似上篇所述"自动快速加入游戏"的功能--"自动进入房间"功能。系统在服务器端为玩家找到一个人数较多又未满的房间，并尝试进入（注意，软件需求是由用户的操作目标所驱动的，玩家在此的目标就是尽快加入一个满意的游戏组，因此由系统来替代玩家选择目标房间同样符合相关目标）。而为了方便玩家手工选择要进入的房间，则应当增加一个"刷新当前各房间人数"的功能。另外，还可以调整房间的组织模式，例如以地域为单位来划分房间，像深圳（长城宽带）区房间1、四川（电信）房间3、北美区房间1等，在深圳上网的玩家将被系统引导而优先进入深圳区的房间。</p>
<p style="FONT-SIZE: 10pt">&nbsp;&nbsp;&nbsp; 不管怎样，解决软件缺陷的王道还是在设计层面。要解决上述缺陷，架构设计师就必须同时面对高可用、数据一致性、完整性等方面的严峻挑战。</p>
<p style="FONT-SIZE: 10pt">&nbsp;&nbsp;&nbsp; 在思考相关解决方案时，我们将应用若干与高性能服务器集群架构设计相关的一些重要原则。首先是"分而治之"原则，即将大量客户端发出的服务请求进行适当的划分（例如，所有从深圳长城宽带上网的玩家所发出的服务请求分为一组），分别分配给不同的服务器（例如，将前述服务请求分组分配给放置于深圳<a onclick="javascript:tagshow(event, '%CA%FD%BE%DD%D6%D0%D0%C4');" href="javascript:;" target=_self>数据中心</a>的服务器）来加以处理。对于QQGame千万级的并发服务请求数而言，采用Scale Up向上扩展，即升级单个服务器处理能力的方式基本上不予考虑（没有常规的主机能处理每秒上千万的请求）。唯一可行的，只有Scale Out向外扩展，即利用大量服务器集群做<a title=负载均衡 href="http://product.it168.com/list/b/0462_1.shtml" target=_blank>负载均衡</a>的方式，这实质上就是"分而治之"原则的具体应用。</p>
<p style="FONT-SIZE: 10pt"><a href="http://space.itpub.net/batch.download.php?aid=18803" target=_blank><img border=0 src="http://space.itpub.net/attachments/2009/10/17007506_200910191415571.jpg"></a></p>
<div style="FONT-SIZE: 10pt" id=div6078909><br>&nbsp;&nbsp;&nbsp; 图二 分而治之"下的QQGame游戏服务集群部署</div>
<p style="FONT-SIZE: 10pt">&nbsp;&nbsp;&nbsp; 然而，要应用"分而治之"原则进行Scale Out向外扩展，还依赖于其它的条件。如果各服务器在处理被分配的服务请求时，其行为与其它服务器的行为结果产生交叉（循环）依赖，换句话讲就是共享了某些数据（例如，服务器A处理客户端a发来的进入房间#n请求，而同时，服务器B也在处理客户端b发来的进入房间#n请求，此时服务器A与B的行为存在循环依赖--因为两者要同时访问房间#n的数据，这一共享数据会造成两者间的循环依赖），则各服务器之间必须确保这些共享数据的一致完整性，否则就可能发生逻辑错误（例如，假定房间#n的人数差一个就满了，服务器A与B在独自处理的情况下，将同时让客户端a与b的进入请求成功，于是房间#n的最终人数将超出上限）。而要做到此点，各服务器的处理进程之间就必须保持同步（实际上就是排队按先后顺序访问共享数据，例如服务器A先处理，让客户端a进入房间成功，此时房间#n满员；此后服务器B更新到房间#n满的数据，于是客户端b的进入请求处理结果失败），这样，原来将海量请求做负载均衡的意图就彻底失败了，多台服务器的并发处理能力在此与一台实质上并没有区别。由此，我们导出了另外一个所谓"处理自治"（或称"行为独立"）的原则，即所有参与负载均衡的服务器，其处理对应服务请求的行为应当不循环依赖于其它服务器，换句话讲，就是各服务器的行为相对独立（注意，在这里，非循环依赖是允许的，下文中我们来分析为什么）。</p>
<p style="FONT-SIZE: 10pt">&nbsp;&nbsp;&nbsp; 由此可见，简单的负载均衡策略对于QQGame而言是解决不了问题的。我们必须找到一种途径，使得在使用大量服务器进行"分而治之"的同时，同时有确保各个服务器"处理自治"。此间的关键就在于"分而治之"的"分"字上。前述将某个地域网段内上网的玩家所发出的服务请求分到一组，并分配给同一服务器的做法，其目的不外乎是尽可能地减少网络通讯延迟带来的负面影响。但它不能满足"处理自治"的要求，为了确保自治，应当让同一台服务器所处理的请求本身是"自治"（准确的说法是"自闭包"Closure）的。同一台服务器所处理的所有请求组成一个服务请求集合，这个集合如果与其它任何与其无交集的（请求）集合（包含此集合的父集合除外）不循环依赖，则此服务请求集合是"自闭包"的，而处理此请求集合的服务器，其"行为独立"。我们可以将针对同一房间的进入请求划分到同一服务请求分组，这些请求相互之间当然是存在循环依赖的，但与其它分组中的请求却不存在循环依赖（本房间内人数的变化不会影响到其它房间），而将它们都分配给同一服务器（不妨命名为"房间<a onclick="javascript:tagshow(event, '%B9%DC%C0%ED');" href="javascript:;" target=_self>管理</a>服务器"，简称"房间服务器"）后，那个服务器将是"处理自治"的。</p>
<p style="FONT-SIZE: 10pt"><a href="http://space.itpub.net/batch.download.php?aid=18804" target=_blank><img border=0 src="http://space.itpub.net/attachments/2009/10/17007506_200910191416231.jpg"></a></p>
<div style="FONT-SIZE: 10pt" id=div8703545><br>&nbsp;&nbsp;&nbsp; 图三 满足"处理自治"条件的QQ游戏区域"房间管理"服务部署</div>
<p style="FONT-SIZE: 10pt">&nbsp;&nbsp;&nbsp; 那么接下来要解决的问题，就是玩家所关注的某个游戏区内，所有房间当前人数数据的实时更新问题。其解决途径与上述的方法类似，我们还是将所有获取同一区内房间数据的服务请求归为一组，并交给同一服务器处理。与上文所述场景不同的是，这个服务器需要实时汇集本区内所有房间服务器的房间人数数据。我们可以让每个房间服务器一旦发生数据变更时，就向此服务器（不妨命名为"游戏区域管理服务器"，简称"区服务器"）推送一个变更数据记录，而推送的数据只需包含房间Id和所有进入的玩家Id（房间服务器还包含其它细节数据，例如牌桌占位数据）便可。</p>
<p style="FONT-SIZE: 10pt">&nbsp;&nbsp;&nbsp; 另外，由于一个区内的玩家数可能是上十万数量级，一个服务器根本承担不了此种负荷，那么怎么解决这一矛盾呢？如果深入分析，我们会发现，更新区内房间数据的请求是一种数据只读类请求，它不会对服务器状态造成变更影响，因此这些请求相互间不存在依赖关系；这样，我们可以将它们再任意划分为更小的分组，而同时这些分组仍然保持"自闭包"特性，然后分配给不同的区服务器。多台区服务器来负责同一区的数据更新请求，负载瓶颈被解决。当然，此前，还需将这些区服务器分为1台主区服务器和n台从属区服务器；主区服务器负责汇集本区内所有房间服务器的房间人数数据，从属区服务器则从主区服务器实时同步区房间数据副本。更好的做法，则是如『图五』所示，由房间服务器来充当从属区服务器的角色，玩家进入某个房间后，在玩家进入另外一个房间之前，其客户端都将从此房间对应的房间服务器来更新区内房间数据。要注意的是，图中房间服务器的数据更新利用了所谓的"分布式对象缓存服务"。</p>
<p style="FONT-SIZE: 10pt">&nbsp;&nbsp;&nbsp; 玩家进入某个房间后，还要加入某个游戏组才能玩游戏。上篇所述的方案，是让第一个加入某个牌桌的用户，其主机自动充当本牌桌的游戏服务器；而其它玩家要加入此牌桌，其加入请求应当发往第一个加入的用户主机；此后开始游戏，其对弈过程将由第一个加入用户的主机来主导执行。</p>
<p style="FONT-SIZE: 10pt">&nbsp;&nbsp;&nbsp; 那么此途径是否同样也符合上述的前两个设计原则呢？游戏在执行的过程中，根据输赢结果，玩家要加分或减分，同时还要记录胜负场数。这些数据必须被持久化（比如在<a onclick="javascript:tagshow(event, '%CA%FD%BE%DD%BF%E2');" href="javascript:;" target=_self>数据库</a>中保存下来），因此游戏服务器（『图六』中的设计，是由4个部署于QQ客户端的"升级"游戏前台逻辑执行服务，加上1个"升级"游戏后台逻辑执行服务，共同组成一个牌桌的"升级"游戏服务）在处理相关游戏执行请求时，将依赖于玩家游戏账户数据服务（『图六』中的所谓"QQGame会话服务"）；不过这种依赖是非循环的，即玩家游戏账户数据服务器的行为反过来并不依赖于游戏服务器。上文中曾提到，"处理自治"原则中非循环依赖是允许的。这里游戏服务器在处理游戏收盘请求时，要调用玩家游戏账户数据服务器来更新相关数据；因为不同玩家的游戏账户数据是相互独立的，此游戏服务器在调用游戏账户数据服务器时，逻辑上不受其它游戏服务器调用游戏账户数据服务器的影响，不存在同步等待问题；所以，游戏服务器在此能够达成负载均衡的意图。</p>
<p style="FONT-SIZE: 10pt">&nbsp;</p>
<div style="FONT-SIZE: 10pt" id=div2960811><a href="http://space.itpub.net/batch.download.php?aid=18805" target=_blank><img border=0 src="http://space.itpub.net/attachments/2009/10/17007506_200910191417001.jpg"></a><br>&nbsp;&nbsp;&nbsp; 图四 存在"非循环依赖"的QQ游戏客户端P2P服务与交互逻辑部署</div>
<p style="FONT-SIZE: 10pt">&nbsp;&nbsp;&nbsp; 不过，在上述场景中，虽然不存在同步依赖，但是性能依赖还是存在的，游戏账户数据服务器的处理性能不够时，会造成游戏服务器长时间等待。为此，我们可以应用分布式数据库表水平分割的技术，将QQ玩家用户以其登记的行政区来加以分组，并部署于对应区域的数据库中（例如，深圳的玩家数据都在深圳的游戏账户数据库中）。</p>
<p style="FONT-SIZE: 10pt"><a href="http://space.itpub.net/batch.download.php?aid=18806" target=_blank><img border=0 src="http://space.itpub.net/attachments/2009/10/17007506_200910191417311.jpg"></a></p>
<div style="FONT-SIZE: 10pt" id=div6663285><br>&nbsp;&nbsp;&nbsp; 图五 满足"自闭包"条件的QQ分布式数据库（集群）部署</div>
<p style="FONT-SIZE: 10pt">&nbsp;&nbsp;&nbsp; 实际上，我们由此还可以推论出一个数据库表水平分割的原则--任何数据库表水平分割的方式，必须确保同一数据库实例中的数据记录是"自闭包"的，即不同数据库实例中的数据记录相互间不存在循环依赖。</p>
<p style="FONT-SIZE: 10pt">&nbsp;&nbsp;&nbsp; 总之，初步满足QQGame之苛刻性能要求的分布式架构现在已经是初具雏形了，但仍然有很多涉及性能方面的细节问题有待解决。例如，Internet网络通讯延迟的问题、服务器之间协作产生的性能瓶颈问题等等。笔者将在下篇中继续深入探讨这些话题。<br></p>
</div>
<img src ="http://www.cppblog.com/jack-wang/aggbug/104793.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/jack-wang/" target="_blank">小王</a> 2010-01-05 02:37 <a href="http://www.cppblog.com/jack-wang/archive/2010/01/05/104793.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>无缝世界网游服务器架构的设计思路（转）</title><link>http://www.cppblog.com/jack-wang/archive/2009/11/18/101271.html</link><dc:creator>小王</dc:creator><author>小王</author><pubDate>Wed, 18 Nov 2009 00:20:00 GMT</pubDate><guid>http://www.cppblog.com/jack-wang/archive/2009/11/18/101271.html</guid><wfw:comment>http://www.cppblog.com/jack-wang/comments/101271.html</wfw:comment><comments>http://www.cppblog.com/jack-wang/archive/2009/11/18/101271.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cppblog.com/jack-wang/comments/commentRss/101271.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/jack-wang/services/trackbacks/101271.html</trackback:ping><description><![CDATA[<h2 class=entry-title>无缝世界网游服务器架构的设计思路</h2>
<div class=entry-content>
<p>原文：<a href="http://canremember.com/?p=8">http://canremember.com/?p=8</a><br><br>过去一年中，花了很多时间在考虑服务器架构设计方面的问题。看了大量文章、也研究了不少开源项目，眼界倒是开阔了不少，不过回过头来看，对网游架构设计方面的帮助却是不多。老外还是玩儿console game的多，MMO Games方面涉及的还是不如国内广泛。看看 <a title=http://www.charlesriver.com/Books/BookDetail.aspx?productID=62407 onclick="javascript:pageTracker._trackPageview('/outgoing/www.charlesriver.com/Books/BookDetail.aspx?productID=62407');" href="http://www.charlesriver.com/Books/BookDetail.aspx?productID=62407" target=_blank><u><font color=#0000ff>Massively Multiplayer Games Development</font></u></a> 1 &amp; 2 这两本书吧，质量说实话很一般，帮助自然也很有限。当然这也是好事，对国内的研发公司/团队来说，在网游服务器技术方面当然就存在超越老外的可能性，而且在这方面技术超越的机会更大，当然前提是要有积累、要舍得投入，研发人员更要耐得住寂寞、经得起诱惑，在平均每天收到超过3个猎头电话的时候——依然不动心。</p>
<p>上面有点儿扯远了，下面聊聊无缝世界架构（Seamless world server architecture）设计方面的一点儿看法。</p>
<p>先说架构设计的目标——我的看法，服务器组架构设计的目标就是确定各服务器拓补关系和主要的业务逻辑处理方法。主要要解决的问题就是在满足游戏内容设计需要的前提下，如何提高带负载能力的问题。<br><span id=more-8></span><br>最简单的架构就是基本的C/S架构，一台Server直接构成一个Cluster，所有Client直接连接这个Server，这个Server完成所有逻辑和数据处理。这架构其实很好，最大的好处就是它架构上的 Simplicity ，Cluster内部的跨进程交互完全被排除，复杂度立刻就降下来了，而且——完全可以实现一个无缝（Seamless world）的游戏世界。但是即使我不说，大家也知道这种单Server架构会有什么问题。不过我们不妨以另外一个角度来看这个Server——一个黑盒子。从系统外部的角度来看，什么样的系统都可以看成一个整体、一个黑盒，而不管系统内部的拓补关系和实现复杂度方面的问题。在不考虑这个系统的实现的前提下，理论上Cluster的处理能力就是由硬件的数量和能力决定的，也就是说一个Server Cluster内包含越多的服务器、服务器越&#8216;快&#8217;，那么这个Cluster的处理能力越好、带负载能力越好。那么我们要面对的带负载能力的问题，就是如何高效的利用这些Server的问题，基本上也可以理解为<strong>如何提高玩家请求的并发处理能力</strong>的问题。</p>
<p>CPU厂商在很久以前就在考虑这方面的问题了，CPU其实也可以看成个黑盒。看看他们用过的技术——<a title=http://en.wikipedia.org/wiki/Pipeline_(computer) href="https://canremember.com/redirect/go.php?q=http%3A%2F%2Fen.wikipedia.org%2Fwiki%2FPipeline_%28computer%29&amp;hl=c1" target=_blank><u><font color=#0000ff>流水线（pipeline）</font></u></a>技术、<a title=http://en.wikipedia.org/wiki/Multi-core_(computing) href="https://canremember.com/redirect/go.php?q=http%3A%2F%2Fen.wikipedia.org%2Fwiki%2FMulti-core_%28computing%29&amp;hl=c1" target=_blank><u><font color=#0000ff>多CPU/多核（multicore）</font></u></a>技术，以及这些技术的衍生技术。我想了很久让 Server Cluster 内部处理并行的方法、并且有了比较清晰的思路之后，才发现其实早就可以参照CPU厂商的方法。流水线的方法就是把一个指令处理拆分成很多个步骤，这样指令的处理被分解之后就可以部分重叠（相当于变成并发的了）执行。我们的Server Cluster一样可以用这种方法来拆分，我想了个名字——</p>
<p><strong>Services-based Architecture</strong>——基于服务的架构。在这种架构内部，我们根据处理数据、逻辑的相关性来划分组内各个服务器的工作任务。例如：位置服务提供物体可见性信息、物品服务处理所有物品相关的逻辑、社会关系服务提供行会家族等等方面的逻辑、战斗服务器只处理战斗相关的逻辑，等等。这样划分的话、逻辑处理的并发就有了可能性。举例来说：A砍B一刀这件事情与C从奸商手里买到一件武器这个事情是完全不相干的，而且这2个请求本来就在不同的服务器上被处理，他们是被不同的Service Server并发处理的。这就是 Services-based Architecture 的并发方法。</p>
<p>基本上，把游戏逻辑的处理拆分成一个个的service，就和设计cpu的时候把机器指令的具体处理拆分，然后设计出一个个流水线单元是一个道理。</p>
<p><strong>Cells-based Architecture</strong>——基于cell的架构。每个cell都在不同的物理server上面运行着完全一样的应用程序服务器，但是他们负责承载不同的游戏场景区域的游戏逻辑。和 services-based arch. 明显不同的就是，每个cell都是个&#8216;在逻辑上完整的&#8217;服务器。它得处理物品操作、人物移动、战斗计算等等几乎所有的游戏逻辑。尽管这么做会带来一些（可能是很复杂）的问题，但是它完全是可行的。举例来说：在吴国A砍B一刀显然地和千里之外在越国的C砍D一刀不搭界，他们完全可以被不同的Cell并发地处理。</p>
<p>基本上，这就相当于一个主板上面插多个CPU或者一个CPU但是有多个内核，每个CPU能做的事情都是一样的，而且能一起做。</p>
<p>关于这两种 seamless world 架构的基本分析和需要解决的一些主要问题，下次再写。</p>
<h4>Related posts</h4>
<ul class=st-related-posts>
    <li><a title="技术主义者的网游观点 (2008.03月.23)" href="http://canremember.com/?p=16"><u><font color=#0000ff>技术主义者的网游观点</font></u></a> (11)</li>
    <li><a title="无缝世界网游服务器架构的设计思路（续） (2008.02月.15)" href="http://canremember.com/?p=10"><u><font color=#0000ff>无缝世界网游服务器架构的设计思路（续）</font></u></a> (3)</li>
    <li><a title="服务器组架构设计需要考虑的一些关键因素 (2008.02月.5)" href="http://canremember.com/?p=4"><u><font color=#0000ff>服务器组架构设计需要考虑的一些关键因素</font></u></a> (6)</li>
    <li><a title="关于我 (2008.02月.3)" href="http://canremember.com/?page_id=2"><u><font color=#0000ff>关于我</font></u></a> (0)</li>
    <li><a title="Trap of C++ (2008.02月.18)" href="http://canremember.com/?p=15"><u><font color=#0000ff>Trap of C++</font></u></a> (0)</li>
</ul>
</div>
<img src ="http://www.cppblog.com/jack-wang/aggbug/101271.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/jack-wang/" target="_blank">小王</a> 2009-11-18 08:20 <a href="http://www.cppblog.com/jack-wang/archive/2009/11/18/101271.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>网游服务器架构的设计</title><link>http://www.cppblog.com/jack-wang/archive/2009/06/18/88027.html</link><dc:creator>小王</dc:creator><author>小王</author><pubDate>Thu, 18 Jun 2009 12:47:00 GMT</pubDate><guid>http://www.cppblog.com/jack-wang/archive/2009/06/18/88027.html</guid><wfw:comment>http://www.cppblog.com/jack-wang/comments/88027.html</wfw:comment><comments>http://www.cppblog.com/jack-wang/archive/2009/06/18/88027.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cppblog.com/jack-wang/comments/commentRss/88027.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/jack-wang/services/trackbacks/88027.html</trackback:ping><description><![CDATA[由于网游服务器的设计牵涉到太多内容，比如：网络通信方面、人工智能、数据库设计等等，所以本文将重点从网络通信方面的内容展开论述。谈到网络通信，就不能不涉及如下五个问题：<br>1、 常见的网游服务通信器架构概述<br>2、 网游服务器设计的基本原则<br>3、 网游服务器通信架构设计所需的基本技术<br>4、 网游服务器通信架构的测试<br>5、 网游服务器通信架构设计的常见问题<br><br>下面我们就从第一个问题说起：<br><br>常见的网游服务器通信架构概述<br>　　目前，国内的网游市场中大体存在两种类型的网游游戏：MMORPG（如：魔兽世界）和休闲网游（如：QQ休闲游戏和联众游戏，而如泡泡堂一类的游戏与QQ休闲游戏有很多相同点，因此也归为此类）。由于二者在游戏风格上的截然不同，导致了他们在通信架构设计思路上的较大差别。下面笔者将分别描述这两种网游的通信架构。<br><br>1．MMORPG类网游的通信架构<br>　　网游的通信架构，通常是根据几个方面来确定的：游戏的功能组成、游戏的预计上线人数以及游戏的可扩展性。<br>　　目前比较通用的MMORPG游戏流程是这样的：<br><br>a. 玩家到游戏官方网站注册用户名和密码。<br>b. 注册完成后，玩家选择在某一个区激活游戏账号。<br>c. 玩家在游戏客户端中登录进入已经被激活的游戏分区，建立游戏角色进行游戏。<br><br>　　通常，在这样的模式下，玩家的角色数据是不能跨区使用的，即：在A区建立的游戏角色在B区是无法使用的，各区之间的数据保持各自独立性。我们将这样独立的A区或B区称为一个独立的服务器组，一个独立的服务器组就是一个相对完整的游戏世界。而网游服务器的通信架构设计，则包括了基于服务器组之上的整个游戏世界的通信架构，以及在一个服务器组之内的服务器通信架构。<br><br>　　我们先来看看单独的服务器组内部的通信是如何设计的。<br>　　一个服务器组内的各服务器组成，要依据游戏功能进行划分。不同的游戏内容策划会对服务器的组成造成不同的影响。一般地，我们可以将一个组内的服务器简单地分成两类：场景相关的（如：行走、战斗等）以及场景不相关的（如：公会聊天、不受区域限制的贸易等）。为了保证游戏的流畅性，可以将这两类不同的功能分别交由不同的服务器去各自完成。另外，对于那些在服务器运行中进行的比较耗时的计算，一般也会将其单独提炼出来，交由单独的线程或单独的进程去完成。<br><br>　　各个网游项目会根据游戏特点的不同，而灵活选择自己的服务器组成方案。经常可以见到的一种方案是：场景服务器、非场景服务器、服务器管理器、AI服务器以及数据库代理服务器。<br>　　以上各服务器的主要功能是：<br><br>　　场景服务器：它负责完成主要的游戏逻辑，这些逻辑包括：角色在游戏场景中的进入与退出、角色的行走与跑动、角色战斗（包括打怪）、任务的认领等。场景服务器设计的好坏是整个游戏世界服务器性能差异的主要体现，它的设计难度不仅仅在于通信模型方面，更主要的是整个服务器的体系架构和同步机制的设计。<br><br>　　非场景服务器：它主要负责完成与游戏场景不相关的游戏逻辑，这些逻辑不依靠游戏的地图系统也能正常进行，比如公会聊天或世界聊天，之所以把它从场景服务器中独立出来，是为了节省场景服务器的CPU和带宽资源，让场景服务器能够尽可能快地处理那些对游戏流畅性影响较大的游戏逻辑。<br><br>　　服务器管理器：为了实现众多的场景服务器之间以及场景服务器与非场景服务器之间的数据同步，我们必须建立一个统一的管理者，这个管理者就是服务器组中的服务器管理器。它的任务主要是在各服务器之间作数据同步，比如玩家上下线信息的同步。其最主要的功能还是完成场景切换时的数据同步。当玩家需要从一个场景A切换到另一个场景B时，服务器管理器负责将玩家的数据从场景A转移到场景B，并通过协议通知这两个场景数据同步的开始与结束。所以，为了实现这些内容繁杂的数据同步任务，服务器管理器通常会与所有的场景服务器和非场景服务器保持socket连接。<br><br>　　AI（人工智能）服务器：由于怪物的人工智能计算非常消耗系统资源，所以我们把它独立成单独的服务器。AI服务器的主要作用是负责计算怪物的AI，并将计算结果返回给场景服务器，也就是说，AI服务器是单独为场景服务器服务的，它完成从场景服务器交过来的计算任务，并将计算结果返回给场景服务器。所以，从网络通信方面来说，AI服务器只与众多场景服务器保持socket连接。<br><br>　　数据库代理服务器：在网游的数据库读写方面，通常有两种作法，一种是在应用服务器中直接加进数据库访问的代码进行数据库访问，还有一种方式是将数据库读写独立出来，单独作成数据库代理，由它统一进行数据库访问并返回访问结果。<br><br>　　其中，非场景服务器在不同的游戏项目中可能会被设计成不同的功能，比如以组队、公会或全频道聊天为特色的游戏，很可能为了满足玩家的聊天需求而设立单独的聊天服务器；而如果是以物品贸易（如拍卖等）为特色的游戏，很可能为了满足拍卖的需求而单独设立拍卖服务器。到底是不是有必要将某一项游戏功能独立处理成一个服务器，要视该功能对游戏的主场景逻辑（指行走、战斗等玩家日常游戏行为）的影响程度而定。如果该功能对主场景逻辑的影响比较大，可能对主场景逻辑的运行造成比较严重的性能和效率损失，那么应考虑将其从主场景逻辑中剥离，但能否剥离还有另一个前提：此功能是否与游戏场景（即地图坐标系统）相关。如果此功能与场景相关又确实影响到了主场景逻辑的执行效率，则可能需要在场景服务器上设立专门的线程来处理而不是将它独立成一个单独的服务器。<br><br>　　以上是一个服务器组内的各服务器组成情况介绍，那么，各服务器之间是如何通信的呢？它的基本通信构架有哪些呢？<br>　　MMORPG的单组服务器架构通常可以分为两种：第一种是带网关的服务器架构；第二种是不带网关的服务器架构。两种方案各有利弊。<br><br>　　就带网关的服务器架构而言，由于它对外只向玩家提供唯一的一个通信端口，所以在玩家一侧会有比较流畅的游戏体验，这通常也是那些超大规模无缝地图网游所采用的方案，但这种方案的缺点是服务器组内的通信架构设计相对复杂、调试不方便、网关的通信压力过大、对网关的通信模型设计要求较高等。第二种方案会同时向玩家开放多个游戏服务器端口，除了游戏场景服务器的通信端口外，同时还可能提供诸如聊天服务器等的通信端口。这种方案的主要缺点是在进行场景服务器的切换时，玩家客户端的表现中通常会有一个诸如场景调入的界面出现，影响了游戏的流畅感。基于这种方案的游戏在客户端的界面处理方面，比较典型的表现是：当要进行场景切换时，只能通过相应的&#8220;传送功能&#8221;传送到另外的场景去，或者需要进入新的场景时，客户端会有比较长时间的等待进入新场景的等待界面(Loading界面)。<br><br>　　从技术角度而言，笔者更倾向于将独立的服务器组设计成带网关的模型，虽然这加大了服务器的设计难度，但却增强了游戏的流畅感和安全性，这种花费还是值得的。<br>　　笔者在下面附上了带网关的MMORPG通信架构图，希望能给业内的朋友们一点有益的启迪。 
<img src ="http://www.cppblog.com/jack-wang/aggbug/88027.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/jack-wang/" target="_blank">小王</a> 2009-06-18 20:47 <a href="http://www.cppblog.com/jack-wang/archive/2009/06/18/88027.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>目前我们的游戏服务器逻辑层设计草案（转帖于：云风的BLOG)</title><link>http://www.cppblog.com/jack-wang/archive/2009/02/12/73515.html</link><dc:creator>小王</dc:creator><author>小王</author><pubDate>Wed, 11 Feb 2009 18:50:00 GMT</pubDate><guid>http://www.cppblog.com/jack-wang/archive/2009/02/12/73515.html</guid><wfw:comment>http://www.cppblog.com/jack-wang/comments/73515.html</wfw:comment><comments>http://www.cppblog.com/jack-wang/archive/2009/02/12/73515.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cppblog.com/jack-wang/comments/commentRss/73515.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/jack-wang/services/trackbacks/73515.html</trackback:ping><description><![CDATA[<div class=entry-body style="FONT-SIZE: 10pt">
<p style="FONT-SIZE: 10pt">我们一开始的游戏逻辑层是基于网络包驱动的，也就是将 client 消息定义好结构打包发送出去，然后再 server 解析这些数据包，做相应的处理。</p>
<p style="FONT-SIZE: 10pt">写了一段时间后，觉得这种方案杂乱不利于复杂的项目。跟同事商量以后，改成了非阻塞的 RPC 模式。</p>
<p style="FONT-SIZE: 10pt">首先由处理逻辑的 server 调用 client 的远程方法在 client 创建出只用于显示表现的影子对象；然后 server 对逻辑对象的需要client 做出相应表现的操作，变成调用 client 端影子对象的远程方法来实现。</p>
<p style="FONT-SIZE: 10pt">这使得游戏逻辑编写变的清晰了很多，基本可以无视网络层的存在，和单机游戏的编写一样简单。</p>
</div>
<div class=entry-more id=more style="FONT-SIZE: 10pt">
<p style="FONT-SIZE: 10pt">本质上，这样一个系统跟网络包驱动的方式没有区别；但是从编码表现形式上要自然很多。正如 C 语言也可以实现面向对象，但却没有 C++ 实现的自然一样。在这个系统中，引擎封装了对象管理的部分，使得逻辑编写的时候不再需要处理讨厌的对象数字 id ；还隐藏了消息发送或广播的问题。</p>
<p style="FONT-SIZE: 10pt">我把玩家控制的角色，和服务器上你的角色分做两个东西。即，你控制的你，和服务器认为的你就分开了。服务器认为的你，你看见的服务器上的其他人是一类东西。操作自己的角色行动时，你通过 client 上的控制器的远程方法向服务器发送指令；而服务器通过远程调用每个角色的远程方法让 client 可以收到感兴趣的所有角色的行为。</p>
<p style="FONT-SIZE: 10pt">这样，client 永远都是通过一个控制器调用其远程方法来告诉服务器"我要干什么"，而服务器的逻辑层则通过调用其上所有逻辑对象的远程方法来改变每个对象的状态。而引擎就根据每个链接的需要，广播这些消息，使得每个 client 上对应的影子对象可以收到状态改变的消息。</p>
<p style="FONT-SIZE: 10pt">这些，就是半个月来我跟同事一起做的工作。当然，由于我们用脚本编写逻辑层，这样，脚本接口可以比 C 接口实现的漂亮的多。</p>
<p style="FONT-SIZE: 10pt">首先是自定义格式的接口描述文件，用自编写的工具自动编译成对应脚本代码。我们只需要在脚本中编写对应的类，就可以自动响应远端调用的方法了。而调用远程方法，也跟本地方法保持同样的形式，写起来跟本地函数调用没有区别。这在以前用 C/C++ 编写逻辑的时候是很难做到的。</p>
<p style="FONT-SIZE: 10pt">其次，引擎内部做好对象的管理工作，负责把通讯协议上的 id 转换成逻辑层中的对象传递给逻辑层使用。</p>
<p style="FONT-SIZE: 10pt">再次，enum 这样的类型再也不需要用一些数字的常数了，也不需要在脚本额外的定义出来。可以在接口文件中定义好，经过引擎的处理后，逻辑层可以直接用更为友好的字符串代替，而不失去效率。</p>
<p style="FONT-SIZE: 10pt">编写逻辑的程序员不再需要关心网络的问题后，就可以把心思放在细节上。</p>
<p style="FONT-SIZE: 10pt">最后，对于实现行为预测来补偿网络延迟的特性上。在先前的版本中，我们为了实现这个，花了不少的气力。主要是将时间戳信息放在基础通讯协议中来辅助实现。具体的消息包收到后，再计算延迟时间来推算当前的状态。现在，可以把时间信息封装到 RPC 中，让每个远程方法自动带有延迟时间，方便计算。按模拟程序的实际效果上看，单单位置同步的预测策略，可以让延迟在 8 秒之内的玩家可以忍受；而延迟小于 1 秒的时候，几乎不会受到滞后的影响了。</p>
<p style="FONT-SIZE: 10pt">关于每个链接感兴趣的信息的问题，决定了每个逻辑对象的状态改变要通知哪些人。目前的想法是独立到单独进程去处理，我们在处理连接的服务器和处理逻辑的服务器之间设置单独的服务器来管理每个链接感兴趣的对象，这个任务相对单一且责任重大，独立出来可以大大减轻逻辑服务器的复杂度。</p>
</div>
<img src ="http://www.cppblog.com/jack-wang/aggbug/73515.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/jack-wang/" target="_blank">小王</a> 2009-02-12 02:50 <a href="http://www.cppblog.com/jack-wang/archive/2009/02/12/73515.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>高性能的网络游戏服务器的设计。。。作者谈了QQGAME SERVER。。。 （转）</title><link>http://www.cppblog.com/jack-wang/archive/2009/02/12/73514.html</link><dc:creator>小王</dc:creator><author>小王</author><pubDate>Wed, 11 Feb 2009 18:41:00 GMT</pubDate><guid>http://www.cppblog.com/jack-wang/archive/2009/02/12/73514.html</guid><wfw:comment>http://www.cppblog.com/jack-wang/comments/73514.html</wfw:comment><comments>http://www.cppblog.com/jack-wang/archive/2009/02/12/73514.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cppblog.com/jack-wang/comments/commentRss/73514.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/jack-wang/services/trackbacks/73514.html</trackback:ping><description><![CDATA[<span style="FONT-SIZE: 10pt">高性能的网络游戏服务器的设计[转]<br minmax_bound="true">2007年10月05日 星期五 18:08<br minmax_bound="true">&nbsp; &nbsp; 说起高性能的网络游戏，有2个典范，1个是暴雪的WOW，另外一个要数腾讯的QQGame了，因为对于MMPRPG的体系接触不深，几乎属于文盲，没有太多的发言权，而自己又是搞休闲游戏开发的所以本文就主要谈谈QQGame了。 <br minmax_bound="true"><br minmax_bound="true">&nbsp; &nbsp; 前些天通过朋友得到了QQGame的一个系统分析的文档，看完后很是震惊，彻底被QQ的设计所折服了，到底是千万人在线系统经验的拥有者，牛！ <br minmax_bound="true"><br minmax_bound="true">&nbsp; &nbsp; 通过资料了解到QQGame目前有以下让我欣赏的特性： <br minmax_bound="true"><br minmax_bound="true">1.单机最高容纳35,000人同时在线，对没有看错是这么多，由于它适用了Linux下高性能的网络处理模型ePoll技术，并且一系列高超的优化技术轻松破万人，当然为了稳定性考虑单机保持了2万人的容量，此时的带宽消耗为近30M； <br minmax_bound="true">2.采用共享内存方式高速完成进程间高速通讯； <br minmax_bound="true">3.服务器的扩充方式不是平面的方式，而是裂变式的扩充方式，形成负载均衡阵列树状结构； <br minmax_bound="true">4.所有的游戏服务器不是直接和数据库联系，而是和数据proxy(qq管叫数据交换机和路由器)进行联系，由此带来的就是游戏用户数据的分布存储，我分析着应该是proxy上记录着这个用户数据所在的实际的dbserver的信息，然后定时的将最新的用户信息写回到db中去，这样就大大缓解了数据库服务器的压力，而且可以非常平滑的将数据分裂开来，数据库服务器也就可以无限的扩充，当然我觉得肯定有个数据库信息索引了用户的id和对应的存储地点的关联关系，这点就类似于google的原理了，所以对于数据库的硬件要求也就不是那么高了，qqgame的一组服务器通常是7台服务器，可以容纳5万人，其中就包含了数据库服务器，这点就不是棋牌游戏所常使用的数据集中存储了； <br minmax_bound="true">5.游戏服务器的网络和逻辑分开，不仅仅是层次上的分开，而是在进程上分开，然后中间通过共享通道进行管理和协调，并且增加了辅助线程，在主线程处理大压力的异步的操作的时候直接交给辅助线程处理，保障了游戏服务器的高效性运转。 <br minmax_bound="true">&nbsp; &nbsp; 作为游戏服务器的结构方面(以下只讨论休闲游戏部分,MMORPG服务器不属于讨论范围)的设计已经相对于成熟并且统一,结构方面和3年前我所接触的中游系的那套平台没有太大的差异,无非是服务器群采用星状的结构,以1个中心节点作为核心,然后向四周扩散出一些应用服务器,如负责登陆的LoginServer,负责具体游戏逻辑的GameServer等等,当然最精简的结构是这样的,这样的结构可以满足50万以下同时在线的容量,如果为了满足更大的容量,如QQGAME这样的目前已经有200万以上同时在线的超大容量的应用则需要额外的优化,从这个结构中分离出一些子应用独立开发出一些服务器端来处理,一方面降低偶合度,另外一方面作为高可用性系统为负载均衡提供条件. <br minmax_bound="true"><br minmax_bound="true">关于负载均衡,作为整个游戏平台的所有服务器.我觉得除了具体的游戏逻辑服务器以外都是可以采用负载均衡,多点分担的方式来处理,惟独逻辑服务器不可以,因为休闲游戏,都是分层次的,不管泡泡堂也好,QQGAME也好这些游戏其实在客户端的表现形式都是分层次的,如QQGAME就是LOBBY-HALL-ROOM这样的结构,LOBBY这层就是游戏广场了,可以看到所有的游戏类别,游戏服务器和具体的游戏大厅,比如:牌类&#8211;斗地主&#8211;新手场&#8211;新手场1 这样的顺序,传统的设计中新手场1就是属于一个独立的游戏逻辑服务器拥有一个独立的IP以及侦听端口,在服务器端通常也是一个独立的进程.一般的游戏服务器允许的连接数通常都是300-600人之间超过的就提示服务器已满了，这样做的原因并不是因为进程的限制因为一个进程完全可以做到同时让3000以上的玩家同时游戏，而是人为设计的考虑，因为在游戏逻辑服务器中有很多需要广播的消息，如游戏玩家的聊天信息，某个房间的开始信息，结束信息，某人进出的信息等，而对于广播来说，给300个人广播和给3000个人广播所消耗的资源是绝对没有可比性的。但是通过从进程上独立来处理这个传统的方式也有个缺陷，比如通过开10个进程来达到3000人和1个进程达到3000人，如果不考虑广播的因素在内的话前者的资源是要高与后者的资源的，并且进程间的通讯也要比进程内的通讯要耗费资源和复杂度方面要高很多，比如说如果要实现一个需求让玩家可以在同一类游戏中可以使用小喇叭类或者跨游戏服务器找人之类的功能的话，同一个进程的优势就显示出来了，为此QQGAME所使用的是Channel(频道)的概念，即一个游戏逻辑服务器的进程可以容纳5000人左右，然后服务器端通过设置分割出很多的Channel如新手1，新手2，新手3之类的传统意义上的游戏大厅，将消息的分发范围进行隔离，节约了资源。这一点可以通过查看连接属性看到，连接QQGAME的同类型靠近的几个游戏大厅其实端口和IP都是一致的。 <br minmax_bound="true"><br minmax_bound="true">我们目前的游戏服务器类型主要有3类：CenterServer，管理着所有的服务器连接，LoginServer 负责处理用户的登陆、注册，并且用来给用户传递游戏逻辑服务器列表等功能， GameServer具体逻辑服务器，根据不同的逻辑来实现不同的游戏需求。 <br minmax_bound="true"><br minmax_bound="true">当然，根据业务的发展需要，我们正准备把用户的状态在服务器端保存，并且增加一个类似IM服务器的功能来满足玩家跨服务器聊天和查询其他玩家状态的需要。以及GM服务器实现多功能的网管室的需求，这些将在以后慢慢写。 <br minmax_bound="true"><br minmax_bound="true">今天开始写些技术的题材,一方面记录一些自己的本分工作的东西,另外一方面也是充实一下BLOG,工作太忙也没有什么太多的思绪来一直写其他的题材,所以就拿工作来填充了.同时如果有人有幸看到了这些文章,并且也有兴趣的话也希望多多探讨. <br minmax_bound="true"><br minmax_bound="true">先简单介绍一下,由于本人的工作就是游戏开发公司的,一直与游戏开发打交道,主要做休闲类的游戏,目前又以棋牌游戏为主,在这个行业中摸爬滚打了整3年了,从运营开始做起,运营过当时国内一流的游戏平台中游系列的产品,然后由于合作方面的原因又自主开发过一套游戏平台,然后由于发展方向上的分歧出来单独做,目前在开发并运维一套全新的产品. <br minmax_bound="true"><br minmax_bound="true">这篇主要是对于游戏服务器的一些想法,结合目前自身的产品的一些问题延伸开来的. <br minmax_bound="true"><br minmax_bound="true">目前我们的服务器还是属于Windows平台的架构,暂时还没有考虑到跨平台,主要原因有2:<br minmax_bound="true">1.成本:作为公司来说首先得考虑的就是成本了,虽然说Linux类的平台存在着操作系统成本低廉,性能优异,稳定性高这几个特色,但是作为全局考虑来说,一个Linux的系统管理员,以及开发人员的成本要比Windows平台要高很多,并且作为操作系统方面是免费的,但是支持几乎是没有的只有一些不适合商业应用的开源社区作为支撑,要得到更加好的服务或者额外的支持还是得通过大厂商的高额的产品或服务来实现,而Windows平台则不同,虽然操作系统貌似价格不扉,但是本身就带了很多的模块,比如组件服务,日志服务等等,加上平台上各种软件的数量也和Linux下的不是同一量级的,可选择性要大的多,而让Linuxer所诟病的Win32的安全性其实完全可以通过另外独立的安全模块,如硬件防火墙等来完善,毕竟操作系统不是专门做安全防护的Linux也存在很多的漏洞,并且随着Linux发行版的日益增多,用户的逐渐了解,漏洞也日益的增多.<br minmax_bound="true">2.开发效率.无疑Visual Studio系列开发套件是目前开发领域最方便最强大的IDE环境,这已是不争的事实,而如果用VI + GCC的模式去开发LINUX下面的服务器其效率和质量至少从我目前的水平(可以部分代表国内目前中小游戏软件开发商实际现状)要彻底的领悟并转换需要一定的时间,并且还没有成熟的开发平台作为保证, JAVA好象有 但是C++还没有听说有超过VC++的. </span><!-- google_ad_section_end -->
<img src ="http://www.cppblog.com/jack-wang/aggbug/73514.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/jack-wang/" target="_blank">小王</a> 2009-02-12 02:41 <a href="http://www.cppblog.com/jack-wang/archive/2009/02/12/73514.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>MMORPG开发入门</title><link>http://www.cppblog.com/jack-wang/archive/2009/01/23/72506.html</link><dc:creator>小王</dc:creator><author>小王</author><pubDate>Fri, 23 Jan 2009 05:36:00 GMT</pubDate><guid>http://www.cppblog.com/jack-wang/archive/2009/01/23/72506.html</guid><wfw:comment>http://www.cppblog.com/jack-wang/comments/72506.html</wfw:comment><comments>http://www.cppblog.com/jack-wang/archive/2009/01/23/72506.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cppblog.com/jack-wang/comments/commentRss/72506.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/jack-wang/services/trackbacks/72506.html</trackback:ping><description><![CDATA[<table class=t_table cellSpacing=0 width="98%" align=center>
    <tbody>
        <tr>
            <td width="80%"><font size=3>作者： Radu Privantu <br>译者：<a href="http://blog.vckbase.com/panic" target=_blank><font color=#0000ff><u>pAnic</u></font></a> 2005年5月11日<br>原文链接：<a href="http://www.devmaster.net/articles/building-mmorpg/" target=_blank><font color=#0000ff><u>http://www.devmaster.net/articles/building-mmorpg/</u></font></a><br>译者序：这是一篇讲解如何开发一款MMORPG的入门文章，作者本人也是一款游戏的开发者，文中的内容源于实践，有很高的参考价值。很多人都想拥有自己的游戏，这篇文章对那些想自己开发游戏的人来说可能是一纸福音，也可能是一盆冷水。无论如何，开发游戏都不是一件简单的事情。<br>以下是翻译正文：<br></font></td>
            <td width="2%">[/td]
            <tr>
                <td>文章的中心是如何起步开发你自己的 大型多人在线角色扮演游戏( 原文：Massive Multiplayer Online Role Playing Games) (MMORPG)(译者注：俗称：网络游戏，网游)。 针对的读者是经验和资源有限的开发者。 读完文章之后，你应该懂得如何起步， 还有一些关于什么是应该做的和不应该做的忠告。第一步是评估你的能力和资源。你必须对自己诚实，因为做你力不从心的事情会浪费你的时间并让你心灰意冷。<br><br>第一步：评估你的能力必须的技能:
                <ul type=1>
                    <li>懂至少一种编程语言。 迄今为止， C++因为性能和效率的优越性成为游戏开发者的首选。 Visual Basic, Java 或者 C# 可能也是不错的选择。<br>
                    <li>熟悉一种图形库。通常的选择是SDL, OpenGL, 或者DX/D3D。(<font color=#ff0000>译者注：网上也有很多免费/付费引擎下载和出售</font>)
                    <li>选择一种网络通讯库。 你可以从WinSock, SDL_net, 或DirectPlay中选择。(<font color=#ff0000>译者注：很多人喜欢开发自己独特的网络库，这并不复杂，似乎ACE也是一种选择</font>)<br>
                    <li>对游戏开发有大体的经验。 例如，事件循环， 多线程， GUI 设计，等等。</li>
                </ul>
                强烈推荐的技能:
                <ul type=1>
                    <li>C/S结构通讯。
                    <li>多平台开发。 你可能希望设计一个MMORPG, 尤其是服务器能运行在多种操作系统。为此，我推荐使用SDL, OpenGL 和SDL_net。<br>
                    <li>网站开发。如果你想让用户通过网站查看玩家统计，服务器信息和其他信息，这是必须的。(<font color=#ff0000>译者注：其实网站可以交给其他人开发，如果有必要的话</font>)。
                    <li>安全管理。 你当然不想因为有人攻击你的服务器而浪费时间！
                    <li>团队组织能力。 你需要一个你能成功领导和管理的团队。<br></li>
                </ul>
                第二步：初步规划我注意到很多人在不同的论坛发帖子寻找团队开发MMORPG。他们中的大部分是这样：&#8220;我们成立了一个公司/游戏工作室，需要3个美工，两个程序，1个音乐制作，等等。为了创新，不要参考过去的MMORPG，你有全部的自由用来创造你想要的世界，等等。 我们会在项目完成并赚到钱的时候付给你酬劳，等等&#8221;。 不幸的是，以现有的技术和带宽，你无法拥有一个动态的世界。 朝向无法到达的目标前进只会导致失败。正确的做法是拿出一些小规模的，功能性强的，可扩展的设计和构架。<br>基本软件构架首先，尝试创建一个简单的C/S模型，有如下功能：<br>
                <ul type=1>
                    <li>创建一个新角色
                    <li>保存那个角色(服务器端)
                    <li>用那个角色登陆
                    <li>能够和其他人交谈
                    <li>能在3D空间游览</li>
                </ul>
                保存角色看起来简单，其实不然。 例如，有两种方式保存角色：使用数据库服务或者使用文件。两者有各自的优缺点： <br><br>
                <table class=t_table cellSpacing=0 width="98%" align=center>
                    <tbody>
                        <tr>
                            <td width="10%"></td>
                            <td width="45%"><strong>数据库</strong></td>
                            <td width="45%"><strong>文件</strong></td>
                        </tr>
                        <tr>
                            <td><strong>优点</strong></td>
                            <td>
                            <ul>
                                <li>添加新域或者修改现有的都很简单。
                                <li>更新玩家统计数据非常简单(从游戏外)。
                                <li>你可以通过SQL查询方便的获取不同种类的统计结果。
                                <li>无需自行完成I/O操作，数据库会替你做好。 <br>
                                <li>易于更新/恢复。</li>
                            </ul>
                            </td>
                            <td>
                            <ul>
                                <li>高速操作(读/写)。
                                <li>实现简单。
                                <li>无需额外的库。
                                <li>不依赖数据库服务器。因此你不必担心数据库升级或安全问题。 <br></li>
                            </ul>
                            </td>
                        </tr>
                        <tr>
                            <td><strong>缺点</strong></td>
                            <td>
                            <ul>
                                <li>容易出错。 例如，做一个更新查询的时候遗漏了'where'子句。会导致惨痛的损失，尤其是你没有备份的时候。
                                <li>数据库会比打开/写入一个玩家档案文件慢。你查询一些数据的时候会耗费几个毫秒，尤其是大量玩家同时登入/登出的时候。
                                <li>需要额外的代码进行游戏和数据库间的数据转换。
                                <li>需要操作数据库和SQL的经验。并且需要一个程序和数据库之间的接口库。
                                <li>如果因为某些原因数据库文件损坏，那算你倒霉，你可能会丢失所有的玩家数据(尤其是短期内没有备份的时候)。</li>
                            </ul>
                            </td>
                            <td>
                            <ul>
                                <li>很难添加新的域，除非一开始就很小心的设计了文件的格式/结构。
                                <li>没法做全体玩家的查询。(这可以通过每天晚上用程序把重要字段添加进一个数据库间接实现)。
                                <li>如果你想更新/检查玩家状态，你必须额外写代码。 <br>
                                <li>更新和还原比较复杂。</li>
                            </ul>
                            </td>
                        </tr>
                    </tbody>
                </table>
                现在你决定了如何存储角色，你还得选择C/S通讯的网络协议：TCP 还是 UDP？,我们都知道TCP速度慢，但是更准确，并且需要额外带宽。我实际使用TCP并没有遇到什么问题。 如果你有充足的带宽，TCP是个好选择，至少对初学者是这样。&nbsp;&nbsp;UDP 会很麻烦，尤其是对新手。 记住，游戏或引擎的初步测试会在你的局域网进行，所有的包都会按顺序依次抵达。在Internet上无法保证这一点。虽然包会按顺序到达，但是有时候会丢包，这通常是个麻烦事。 当然，你可以设计你的协议使得C/S能够从丢包中恢复。但这对初学者来说很痛苦，不值得推荐。 <br>第三步：选择数据传输协议又是看起来很简单，其实不然。你不能只是发送'\0'结尾的串。因为你需要一个通用的协议，能同时适用字符串和二进制数据。用0(或其他字符)做结束符是不明智的，因为那个结束符可能是你要发送的数据的一部分。此外，如果你发送20字节，然后再20字节，服务器极有可能收不到两个20字节的包。取而代之的是，它会一次性收到40字节，这是为了避免浪费带宽在不必要的头上。 而且，你可以发送1KB的包，但服务器会以两个小包的形式收到它。所以你必须知道哪里是一个包的开始，哪里是结束。在 &#8220;永恒大陆&#8221;(<font color=#ff0000>译者注：原文： Eternal Lands，本文的作者正在开发的一款MMORPG</font>)中，我们用如下的方法：<br>
                <ul>
                    <li><strong>Offset 0</strong>: 1 字节 表示传输的命令。
                    <li><strong>Offset 1</strong>: 2 字节，传输的数据长度。
                    <li><strong>Offset 3</strong>: 变长，消息内容。</li>
                </ul>
                这种方法有一致的优点：所有的数据传输有统一的标准。缺点是有些命令有固定已知的长度，浪费了一些带宽。以后我们会改成混合的方法。<br>下一件事是决定服务器模型： &#8220;非阻塞soket,不使用线程&#8221;，或者&#8220;阻塞soket，使用线程&#8221;。两种方法(使用线程 vs 不使用线程)各有优缺点。<br><strong>线程:</strong><br>
                <ul type=1>
                    <li>服务器响应会更加平滑，因为如果一个玩家需要大量时间(例如从数据库中读取数据)，这会在它自己的线程中完成，不会影响其他人。(<font color=#ff0000>译者注：也许作者的意思是每个玩家都有独立的线程，但这对MMORPG不太现实</font>)
                    <li>难以恰当的实现和调试：你可能需要大量同步，并且一个小疏忽就会导致灾难性的后果( 服务器瘫痪，物品复制，等等)。
                    <li>可以利用多处理器。</li>
                </ul>
                <strong>无线程:</strong>
                <ul type=1>
                    <li>实现和调试更简单。
                    <li>响应速度慢。</li>
                </ul>
                在我的公司，我们使用无线程的方法，因为我没有足够的资源和人力处理线程模式。<br>第四步：客户端你打算做2D还是3D游戏？有些人认为2D游戏做起来简单。我两者都做过，并且我倾向于3D游戏更简单。容我解释。<br>2D下，你通常有一个帧缓冲，也就是一个巨大的象素点数组。象素点的格式会因显卡的不同而不同。 有些是RGB模式，另一些是BGR模式，等等。每种颜色的bit数也会不同。只有在16bpp模式才有这个问题。8-bit和24-bit模式简单一些，但有他们各自的问题(8-bit颜色数太少(256)，而24-bit速度更慢)。同时，你需要制作你的精灵动画程序，不得不自己排序所有对象，以便他们以正确的顺序绘制。 当然，你可以用OpenGL或者D3D制作2D游戏，但通常这并不值得。并不是所有人都有3D加速卡，所以使用3D库开发2D游戏一般会带给你两者的缺点：不是所有人都能玩，你也不能旋转摄像机，拥有漂亮的阴影，和3D游戏炫目的效果。(<font color=#ff0000>译者注，目前绝大部分显卡都支持565的16bpp格式，这个也成为目前16位色的业界通用格式，有不少文章和代码都是讲述这一格式下图像处理的，尤其是使用MMX技术</font>) <br>3D的途径，正如我所说，更简单。但是需要一些数学(尤其是三角)的知识。现代的图形库很强大，免费提供了基本的操作(你不需要从后到前排列对象，改变物体的色彩和/或帖图都十分简单，对象的光照会按照光源和它的位置计算(只要你为它们计算了法向量)，还有更多)。并且。3D给了你的创作和运动更多的自由度，缺点就是不是所有人都能玩你的游戏(没有3D卡的人数可能会让你大吃一惊的)，并且，预渲染的图片总是比实时渲染的更漂亮。(<font color=#ff0000>译者注：市面上想买不支持3D的显卡目前很困难，只是高性能的3D卡价格也不低</font>)<br>第五步：安全显然，不能相信用户。任何时候都不能假设用户无法破解你精巧的加密算法(如果你使用了的话)或者协议，用户发送的任何信息都要通过验证。极有可能，在你的服务器上，你有固定的缓冲区。例如，通常有一个小(可能是4k)缓冲区用来接收数据(从soket)。恶意用户会发送超长数据。如果不检查，这会导致缓冲区溢出，引起服务器瘫痪，或者更坏的，这个用户可以hack你的服务器，执行非法代码。每个单独的消息都必须检查：缓冲区是否溢出，数据是否合法(例如用户发送&#8220;进入那扇门&#8221;，即使门在地图的另一端，或者&#8220;使用治疗药水&#8221;尽管用户没有那种药水，等等)。 我再次强调，验证所有数据非常重要。一旦有非法数据，把它和用户名，IP，时间和日期，和非法的原因记录下来。偶尔检查一下那个记录。如果你发现少量的非法数据，并且来自于大量用户，这通常是客户端的bug或者网络问题。然而，如果你发现从一个用户或者IP发现大量非法数据，这是明显的迹象表明有人正在欺骗服务器，试图hack服务器，或者运行宏/脚本。同时，决不要在客户端存储数据。客户端应该从服务器接收数据。换句话说，不能发送这样的消息&#8220;OK，这是我得物品列表&#8221;或者&#8220;我的力量是10，魔法是200，生命值是2000/2000&#8221;。 而且，客户端不应收到它不需要的数据。例如：客户端不应该知道其他玩家的位置，除非他们在附近。 这是常识，给每个人发送所有玩家会占用大量带宽，并且有些玩家会破解客户端从中获取不公平的利益(像在地图上显示特定玩家的位置)(<font color=#ff0000>译者注：就像传奇的免蜡烛外挂</font>)。所有这些似乎都是常识，但，再次，你会惊奇的发现有多少人不知道这些我们认为的常识。 <br>另一个要考虑的问题，当涉及到安全：玩家走动的速度必须在服务器计算，而不是客户端。(<font color=#ff0000>译者注：这是重要的原则，但是会耗费大量服务器资源。魔兽世界没有这样做，它采用类似其他玩家揭发的形式掩盖这个事实，导致加速外挂可以用，但是在有其他玩家的时候会暴露</font>)。服务器应该跟踪时间(以ms为单位)当客户最后一次移动的时候，并且，移动的请求如果比通常的极限更快到来，这个请求应该被抛弃。不要记录这类虚假请求，因为这可能是因为网络延迟(也就是玩家延迟，过去的10秒内发送的数据同时到达了)。<br>检查距离。如果一个玩家试图和100亿公里以外的玩家交易(或者甚至在另一张地图上)，记录下来。如果一个玩家试图查看，或者使用一个遥远的地图对象，记录它。小心假的ID。例如，正常情况下每个玩家都会分配一个ID(ID在登陆的时候分配，可以是持久的(唯一ID)。 如果ID在玩家登陆的时候赋予9或怪物被创建的时候)，显然可以用玩家数组(保存玩家)的位置(索引)作为ID。<br>所以第一个登陆的玩家ID是0，第二个是1，依此类推。现在，通常你会有一个限制，比如说2000个索引在玩家列表里。所以如果一个客户端发送一条命令类似：&#8220;查看ID200000的角色&#8221;，这会使服务器当机，如果没有防备的话，因为服务器会访问非法的内存区域。所以，一定要检查，就像这样： "if actor id&lt;0 or if actor id&gt; max players 然后记录非法操作并且断开玩家。如果你使用C或者C++，注意或者定义索引为'unsigned int' 并且检查上限，或因为某些原因定义为int(int,默认是有符号的)，记得检查 &lt;0 and &gt;max 。没有做这些会严重挫伤你和其他用户。类似的，要检查超出地图坐标。如果你的服务器有某种寻路算法，并且客户端通过点击地面来移动，确保他们不要点击在地图外部。 <br>第六步：获得一个团队制作游戏需要大量的工作(除非是个Pong and Tetris游戏)。尤其是MMORPG。你无法单靠自己。理论上，一个完整的团队组成是这样：<br>
                <ul>
                    <li>至少<strong>3 个程序员：</strong> 1 个做服务器，两个客户端(或者一个客户端，一个负责工具，例如美术插件，世界编辑器，等等)。有6个程序员是最好的，更多就没必要了。这取决于你的领导能力。最少一个美工，2到3个更合适。如果这是个3D游戏，你需要一个3D美工，一个2D美工(制作帖图，界面，等等)，一个动画师，和一个美术部负责人。美术部应该由有经验的人组织和安排，除非你就是个艺术家。,
                    <li><strong>少数世界构建者:</strong>创建所有地图是个漫长的过程， 并且直接关系到游戏的成败。再次，你需要一个世界构建部的负责人。你的世界需要协调一致，所以不能只有一个意气用事的人。
                    <li>一个 <strong>网站管理员</strong>是必须的，除非你精通网站设计，并且愿意花时间做网站。音效和音乐不是必须的，但是有音效和音乐的游戏比没有的会更吸引人。
                    <li>一个游戏经济系统 <strong>设计师</strong>.。你也许觉得那很简单，可以自己来做，但事实上那是最复杂的工作之一。如果经济系统设计不良(比如物品没有平衡，资源在地图上随意放置，等等。)玩家会觉得无聊并且退出游戏。我们早期的进展存在很大的问题，尤其是因为经济系统主要是由我(一个程序员)设计的，它没有被恰当的计划。 于是，我们花费了两个月来重新思考和建立一整个新的经济系统。这需要一次完全的物品清除。我告诉你，玩家会很不乐意你删除他们的物品。幸运的是，大部分玩家赞同这个想法，但是这么多小时的争论，妥协，解释和时间的浪费还是让我们丧气。以后会更多。 <br></li>
                </ul>
                如前所说，你需要一个10~15人的团队，不包括协调员和管理者。这10~15人必须是有经验的。如果都是新手就不值得，因为你需要花大量时间解释要做什么，怎样做，为什么他现在的做法不好，等等。<br>一开始就凑齐10~15人几乎是不可能的。不管你在不同的论坛发多少帖，你也无法找到合适的团队成员。毕竟，如果一个人在他/她的领域得心应手，为什么在你无法拿出任何东西的时候他/她要加入你的团队？很多人有远大的想法，但是实现它们需要大量时间和努力，所以他们宁可从事自己的工作也不会加入你。那如果你需要10~15人，但是无法让他们加入你的团队，你如何才能制作一款MMORPG呢？ 好，事实上，你一开始不需要所有人都到位。你真正需要的是一个程序员和一个美工。如果你是个程序员，只要找个美工就可以了。请求懂美术的朋友帮忙，花钱请大学生/朋友做一些美术或者其他工作。<br>现在你有了一个美工，你期待的游戏的样子，现在可以开始实现了。一旦你有了可以运行的C/S引擎，一些用来展示的截图(或者更好，玩家可以登陆你的世界，四处走动，聊天)，更多的人会愿意加入你的团队。更恰当的是，除非你使用独有的技术，否则你的客户端可以开源。许多程序员会加入(作为志愿者)一个开源工程而不是非开源项目。而服务器不应该开源(除非你打算做一款完全开源的MMORPG)。<br>其他一些忠告：在有东西可展示之前，不要夸大你的游戏。最惹人烦的事情之一就是一个新手发一个&#8220;需要帮助&#8221;的请求，并且解释这个游戏到底有多酷，最后要求一个巨大的团队加入他的游戏制作。一旦你拥有了网站广告(通常是在一个免费主机)，你会看到一个吸引人的导航条，包含&#8220;下载&#8221;，&#8220;截图&#8221;，&#8220; 原画&#8221;(译者注，原文：Concept art，概念艺术，在游戏应该指美工的原始设计)，&#8220;论坛&#8221;。你点击下载链接，然后看到美妙的&#8220;建设中&#8221;页面(或者更糟糕，一个404错误)。然后你点击截图，得到同样的结果。如果你没有东西给人下载，就不要放下载链接。如果没有截图展示，不要放截图链接。然而更好的是，在工程进展10%(程序和美工)之前，不要浪费时间在网站上。 <br>第七步：打破某些神话
                <ul type=1>
                    <li><strong>你无法制作MMORPG, 只有大公司才可以。</strong><br>我不同意。虽然制作一款像魔兽世界(World of Warcraft)，无尽任务2(Ever Quest 2)，亚瑟王的召唤2(Asheron's Call 2)，血统2(Lineage 2)，和其他一些游戏对一个小型的自发团队是不可能的，但是做一款像样的游戏还是可以的，只要你有经验，动机，和时间。,你需要1000小时的编程来制作一个可运行的测试版，大概10~15k小时完成几乎完整的客户端和服务器。但是作为团队领导者，你不能只编程。保持团队团结，解决争执，维护公共关系(PR)，技术支持，架设服务器，惩罚捣乱分子，自由讨论，等等都是你的职责。你可能会被非编程的任务淹没。你很可能需要上班/上学，这减少了你花费在项目上的时间。我们很幸运，没有成员离开团队，但是如果这种事情发生，那的确是大问题。假设你的美工半途离开。或者更糟糕，他/她没有给你使用他/她作品的许可。当然这可以通过和他们签订合同来解决，但找另外一个美工仍然很麻烦。一个工程中有两种不同的美术风格也是问题。
                    <li><strong>需要大笔金钱(通常 4-6 位数) 用来架设一个 MMORPG 服务器.<br></strong>当然，这不是真的。我见过专业服务器，1000GB/月，不到100美元/月(2~300美元的初装费)。除非你的数据传输协议设计非常不合理，1000GB/月对一个1000玩家在线(平均)的服务器来说足够了。当然，你还需要另一个服务器做网站和客户端下载(客户端下载会占用大量流量，当游戏变得流行的时候)。我们的客户端有22MB，有时候会有400GB/月的传输量。而我们还没有很流行(仍然)。另一件事，我们不需要另一台专用服务器开启这个工程。ADSL/cable服务器可以胜任，直到你的同时在线人数达到20~30。然后要么找一个友好的主机公司，用广告交换免费主机，要么就只能自己掏腰包了。&nbsp;&nbsp;<br>
                    <li><strong>制作一个MMORPG很有趣。<br></strong>这不是真的。你可能认为每个人都会赏识你，玩家会支持你，你标新立异，并且，当然，很多玩家都玩你的游戏。玩家可能让人讨厌。即使是完全免费的游戏，他们也能找到理由抱怨。更糟糕的是人们经常会抱怨矛盾的事。战士会抱怨升级太难，商人会对战士掠夺大量钱财很失望。如果你减少怪物掉落物品，有些玩家就会威胁说要退出游戏。如果你增加，同样的一群人会不满新手能更简单赚钱的事实。 真是左右为难。改革和改进是必须的。如果你决定改变某些东西，例如给加工物品增加挑战性，有些人会说太难了。如果你不做，他们又会说太简单无味。你会发现满意的玩家通常不会说什么并且感到满意，同时破坏者会怨声载道。<br></li>
                </ul>
                MMORPG的经济比单机版难以平衡的多。在单机游戏，你可以逐渐改良武器，只要玩家进展，他/她可以使用更好的装备，丢弃(或者卖掉)旧的。另一方面，在多人游戏里，这种观点不成立，因为每个人都试图得到最好的武器，而跳过低等级武器。大部分玩家宁可空手省钱，直到他们能买游戏中最好的武器。经济系统设计要参考相关的文章。<br>迄今为止我列举的所有事情，加上额外的工作和挑战，足以让你在决定涉足这个工程之前三思而行。你必须知道你的决定意味着什么。<br>总结希望这篇文章能给你足够的知识。我的下一篇文章将介绍如何建立一个经济系统(更明确的，要避免哪些错误)，还有一些调试服务器和客户端的信息。<br>关于作者这篇文章作者是 Radu Privantu, 永恒大陆(Eternal Lands) <a href="http://www.eternal-lands.com/" target=_blank><font color=#0000ff><u>www.eternal-lands.com</u></font></a>的主程序和项目规划, 永恒大陆是一款免费，客户端开源的MMORPG。作者可以通过 chaos_rift at yahoo dot com 联系。</td>
            </tr>
        </tbody>
    </table>
<img src ="http://www.cppblog.com/jack-wang/aggbug/72506.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/jack-wang/" target="_blank">小王</a> 2009-01-23 13:36 <a href="http://www.cppblog.com/jack-wang/archive/2009/01/23/72506.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>服务器宕机</title><link>http://www.cppblog.com/jack-wang/archive/2009/01/23/72505.html</link><dc:creator>小王</dc:creator><author>小王</author><pubDate>Fri, 23 Jan 2009 05:34:00 GMT</pubDate><guid>http://www.cppblog.com/jack-wang/archive/2009/01/23/72505.html</guid><wfw:comment>http://www.cppblog.com/jack-wang/comments/72505.html</wfw:comment><comments>http://www.cppblog.com/jack-wang/archive/2009/01/23/72505.html#Feedback</comments><slash:comments>2</slash:comments><wfw:commentRss>http://www.cppblog.com/jack-wang/comments/commentRss/72505.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/jack-wang/services/trackbacks/72505.html</trackback:ping><description><![CDATA[<p>我不得不承认，我的能力不足以写出一个100%不会宕机的游戏服务器程序，这也不能全怪我的能力太弱，谁让咱国内网游玩家数量庞大，哪个游戏刚上线时没有挤的爆满过？还有些或是猎奇，或是谋私的个人和组织，在制造着千奇百怪，匪夷所思的数据包及操作流程来试探你的服务器。这些都曾是我在服务器宕机后向老板开脱的理由。</p>
<p>&nbsp;</p>
<p>当WOW终于来到中国时，我一边欣喜着终于可以在艾泽拉斯的大陆上自由翱翔，一边却咒骂着9C的破服务器，动不动就宕机。当然，身为游戏程序设计师的我明知道，这大部分的错误都不应归罪于代理商9C，但是，谁让blizzard是我心目中的神，谁又让WOW成为我游戏制作的教科书呢。好吧，我知道上面这段极力追捧blizzard跟WOW的话可能早已让你恶心连连，不堪入目了，对不起，忘了这一节，让我们继续。</p>
<p>&nbsp;</p>
<p>&nbsp;</p>
<p><strong>服务器宕机后都发生了些什么？</strong></p>
<p>&nbsp;</p>
<p>显然的，宕机后玩家会骂，就像我在玩WOW时那样，骂游戏公司，骂老板，骂GM。非常抱歉，我们可爱的玩家们似乎并不清楚，这个时候最该骂的其实是我们这些程序员们。长久的遗忘被我们当成了包容，以至于游戏程序员在公司里都养成了趾高气扬，不可一世的坏毛病：看吧，策划们，你们做的太烂了，数值不平衡，玩法没新意，只会照抄WOW跟大菠萝，能怪玩家骂你们吗？运营不得力，买服务器的钱不知道去了哪里，游戏里卡的要死，偶尔办个活动还没半点吸引力，能不被玩家骂你是无良运营商吗？GM们能不天天被骂家指着骂吗？&#8230;&#8230;呃，又扯远了。</p>
<p>&nbsp;</p>
<p>赶紧先把服务器重启吧。老板正站在你的身后，一脸愁容，虽然暂时还没有发作，但看得出来：老板很生气，后果很严重！</p>
<p>玩家们很快又回来了，不得不为玩家们的毅力和执着精神而感动，更为自己的错误而愧疚，凌晨时分，服务器启了又宕，宕了又启，如此反复，可热情的玩家们依然陪着我在折腾。哦，当年安其拉开门的时候，我也曾这样折腾过。</p>
<p>这个时候不是你一个人在战斗。GM们在忙碌地处理着玩家不断打来的投诉电话：刚买的装备在宕机后消失了；花光了身上所有材料合成的武器回档了，但材料却没有还给我&#8230;&#8230;数据库维护组的同事们也在紧张的恢复着数据，尽可能的将玩家的损失减到最少。</p>
<p>&nbsp;</p>
<p>真是一件令人沮丧的事。</p>
<p>&nbsp;</p>
<p>&nbsp;</p>
<p><strong>真的该试着做点什么了吧！</strong></p>
<p>&nbsp;</p>
<p>既然我们非常不愿意看到宕机的情况发生，但又无法100%保证写出来的服务器程序一定不会出错，那我们就在当机发生后的抢救措施上花点功夫，让玩家的损失不至于太大，也让我们的维护人员少些压力吧。</p>
<p>&nbsp;</p>
<p>一个最简单也最有效的做法是为每一台服务器都配备物理冗余，同步更新冗余服务器上的状态，当宕机发生时，立即将处理切换到后备服务器上。只是，物理冗余的代价太大，从成本方面考虑，老板可能不大愿意点头。</p>
<p>&nbsp;</p>
<p>既然不能做硬冗余，那就再来考虑软的吧。</p>
<p>如果只是简单的启动冗余进程，其实是换汤不换药的做法。原来能跑1000人的服务器，由于同时运行了两个相同的进程，使得CPU和内存开销都翻了倍，结果是只能跑500人了。还是要加服务器。</p>
<p>&nbsp;</p>
<p>看来只能更深一层，从架构设计上来动手了。</p>
<p>假设我们的游戏世界是由多个独立场景构成的，那么在实现上我们可以让这些场景在进程上也独立，这样做的好处是可以使得一个场景的宕机不会影响到其他场景的正常运行。如果我们的游戏世界物理上没有分隔，是一个无缝的大世界，我们也可以人为的将其分成多个独立区域，所需要做的额外工作是处理好那些站在区域边界附近的对象。事实上，现在的无缝大世界也都是这样实现的。</p>
<p>&nbsp;</p>
<p>有了这样一个前提，我们再来看这个已宕掉的场景该如何处理。</p>
<p>还是老办法，赶紧先把它拉起来吧。一个具体可行的方案是，由场景管理器，或者你也有可能叫它世界服务器，来监视各个场景进程的运行状态，当某个场景异常失去联系时，由管理器来将其重新启动。这里需要再花点心思的是，如何让玩家数据正常地发送到新启动的场景进程中，而且这个过程对于客户端来说是透明的。</p>
<p>&nbsp;</p>
<p>这个方案听起来似乎不错，只是，如果宕掉的是场景管理器进程，那该怎么办呢？</p>
<p>按照前面的描述，场景管理器可以看作是整个游戏世界的中心，它以一个指挥者的身份维护着游戏世界的有序运行，所以它的宕机对整个游戏世界的影响也将会是巨大的。</p>
<p>有没有什么办法能够使得场景管理器进程再次启动后能够恢复先前的状态呢？</p>
<p>我们可以为管理器和场景进程定义一套协议，使得管理器不仅能够创建并恢复一个已有场景，而且场景管理器还能通过现有的场景进程数据恢复出自己。</p>
<p>一个理论上可行的方案是，场景管理器与场景进程间保持TCP长连接，并以一定频率进行心跳联系，任意一方发现联系中断或长时间未收到心跳包后都会立即做出处理。</p>
<p>如果是管理器发现场景进程失去联系，那就启动新的场景，如前面所描述的那样。如果是场景进程发现管理器失去联系，那就立即启动重连过程，直接再次连接上管理器，然后立即将自己当前的状态和负责的场景ID报告给管理器。管理器通过这些上报的数据就能恢复出游戏世界内的场景对应关系表，也就是恢复出了自己原来的状态。</p>
<p>&nbsp;</p>
<p>进程是恢复出来了，可我们忘了最重要的内容：数据。当场景进程宕机后，上面保存的玩家属性数据也随之丢失了，虽然我们能够再次将这个场景创建出来，并把原来在这个场景内的客户端数据重新定向过来，但这些客户端对应的玩家对象的数据却没有了，游戏仍然无法继续。</p>
<p>&nbsp;</p>
<p>也许我们可以再做一点修改，把场景内的玩家数据分离出来，保存到一个独立的进程上，比如，我们可以把这个进程叫做数据服务器，或者数据中心之类的。一个隐含的要求是，数据服务器的逻辑实现非常简单，简单到你可以认为它是绝对安全的，不会宕机。所以，保存在这里的玩家数据也就是绝对安全的。</p>
<p>&nbsp;</p>
<p>让我们在这个问题上稍微再深入一点。</p>
<p>场景进程上每次执行玩家的游戏逻辑时都要异步地到数据服务器上来存取数据，这个开销可能太大，而且会使得一些游戏逻辑的实现变的很复杂，那么，把一些会频繁使用到的数据直接保存在场景进程中，当数据发生改变时同步更新到数据服务器上，这样可能会比较容易接受。</p>
<p>&nbsp;</p>
<p>&nbsp;</p>
<p><strong>老板全都满意了吗？</strong></p>
<p>&nbsp;</p>
<p>从理论上来说，我们已经解决了场景进程宕机和管理器宕机后的状态恢复问题，并且在场景恢复后也不会因为丢失了玩家数据而无法继续进行游戏，而且，只要处理得当，这个过程对客户端来说可以是完全透明的，也就是玩家根本不知道服务器上有个进程意外结束，我们做了这么多的工作来将它恢复了。</p>
<p>&nbsp;</p>
<p>事实上，这个过程的透明也是必须的，我们并不需要嚷嚷着告诉我们的用户，也就是玩家，我们做了多少多少事情来让你玩的更顺畅，又花了多少多少精力来解决因为服务器宕机而引起的麻烦，对于最终的用户来说，他只需要享受最好的服务。闲话少说，让我们继续。</p>
<p>&nbsp;</p>
<p><strong>真的已经完全解决了所有问题吗？</strong></p>
<p>想象这样一个场景：我带着几个刚刚降临到艾泽拉斯大陆的伙伴冲向了艾尔文森林，去开荒霍格！正在霍格只剩下一丝血的时候，服务器稍稍卡了一下，等我缓过神来，面前的霍格骤然消失，地上也不见尸体。找了一圈，它正在出生点摇头晃脑，也在四处张望，但头顶上的血条分明是，满血！</p>
<p>怎么回事？</p>
<p>处理这张地图的场景进程意外结束了，服务器的宕机处理机制很快地恢复了这个场景进程，并且把我的客户端数据重新定向到了新场景。只是，事情并不是一切都完美。因为这个场景是完全重新创建的，这意味着所有的怪物也是重新创建并被摆放到了初始位置，所以，只剩下一丝血的霍格碰上了好运气&#8230;&#8230;</p>
<p>&nbsp;</p>
<p>类似的还有，正在护送NPC返回营地，在稍微停顿了一会儿之后，NPC又重新回到了原来的地方，等等。</p>
<p>&nbsp;</p>
<p>虽然这比起最初的&#8220;客户端被迫断开连接，服务器端数据丢失&#8221;要进步了许多，但会给我工资的老板仍然可能不太满意，他希望，霍格应该还在我的面前，而且只有一丝血，那个跟着我的NPC也应该还在我旁边&#8230;&#8230;</p>
<p>&nbsp;</p>
<p>我要是不能说服老板，这是&#8220;根本不可能完成的任务！&#8221;，那也就只能坐下来再试一试。</p>
<p>也许，可以考虑将所有对象的数据都保存到数据服务器上，当然，这要求每个怪物都跟玩家一样，有一个唯一ID，这一点实现起来可能会有些麻烦。</p>
<p>再不然，为对象提供一个从已有的内存数据构造的方法，这样便可以使用共享内存来保存现场数据，再从共享内存中恢复出原来的对象。理论上来说，这个方法是可行的，只是，这三十多个字的文字描述要用C++来实现也可能将会是一项极大的挑战，所以，这也仅只是可供参考的一个尝试方案。</p>
<p>&nbsp;</p>
<p>&nbsp;</p>
<p><strong>我想，我们走的够远了</strong></p>
<p>&nbsp;</p>
<p>让我们先暂停一会儿，回过头来看一看最初的目的。其实我们想要的只是尽可能的让服务器进程不要宕机，如果实在是没有办法，就尽可能的让宕机后的玩家损失比较小，不需要我们做大量的工作去做善后处理。</p>
<p>很简单的需求，似乎我们纠缠的有些过头了。</p>
<p>&nbsp;</p>
<p>写出能够稳定运行的程序是对程序员的最基本要求，如果一个程序连稳定性都不具备，那根本都不用再去考虑功能啊、扩展啊等其他标准了。但是，正如我最开始所说的，没有一个人能够100%保证他写出来的服务器程序是绝对不会崩溃的。我们所能要求的只是尽可能的仔细，尽可能的多一些必要的测试，离安全尽可能的更近一步。</p>
<p>&nbsp;</p>
<p>剩下的就是在宕机后如何降低损失的问题了。</p>
<p>对于一般的MMOG来说，玩家在进入游戏时会从数据库中将该玩家的所有相关数据读到内存，以便快速的进行游戏逻辑的处理，而在玩家下线时再将数据的改动存回数据库。</p>
<p>显然的，当服务器进程出现意外宕机时，内存中所有的数据都丢失了，这也就造成了玩家数据的回档，而且玩家在游戏中呆的时间越长，回档的损失就越大。所以，一个被广泛采用的做法是为玩家数据实现一种定时存盘的机制，就像现在大多数的单机游戏一样，AutoSave。比如，每5分钟自动为玩家存一次盘，这样就可以使得回档的最大损失控制在5分钟以内。</p>
<p>另外，对于一些重要数据的变动，比如玩家花大量游戏货币购买了一件贵重的武器装备，这时可将玩家数据立即做一次存盘操作，这也将有效的减少玩家的重大损失。</p>
<p>&nbsp;</p>
<p>听起来这是一项不错的技术，在意外宕机的时候最多只回档5分钟，而且还没有贵重物品的损失，玩家应该是可以接受的吧。</p>
<p>&nbsp;</p>
<p>&nbsp;</p>
<p><strong>我已经听到了数据库维护员的咆哮</strong></p>
<p>&nbsp;</p>
<p>&#8220;数据库已经快要崩溃了，你就不能让每秒需要执行的SQL语句少一点吗？&#8221;</p>
<p>&#8220;呃&#8230;&#8230;&#8230;&#8221;</p>
<p>&nbsp;</p>
<p>我一直以为我们的数据库非常强大，可以处理任何的数据，唯一的缺点就是查询速度比直接内存读取要慢很多。所以我使用了异步数据存取的方法，并且开启了多个数据库操作线程来并行的执行我的请求，运行的效果看起来还不错。</p>
<p>&nbsp;</p>
<p>也许，我应该来算一算，每秒种究竟丢了多少条操作请求给数据库。</p>
<p>&nbsp;</p>
<p>请允许我再自私一回，我已经很久没有提到WOW了&#8230;&#8230;</p>
<p>大概可信的数字是，WOW一组服务器的玩家数量在3000到5000之间，去掉最大的数，再去掉最小的数，最后的平均值是，4000吧，就算4000。</p>
<p>4000人在线，假设也是每5分钟定时存盘一次，再假设所有玩家的存盘时间是平均分布的。这样算下来，每秒种就会有67个玩家向数据库发出存盘请求操作。</p>
<p>&nbsp;</p>
<p>才67个，数据库维护组的同事就跟我说不堪重负了？笑话，这数据库服务器是谁买的？</p>
<p>先别急，67是玩家数，但是每个玩家的存盘请求不会只有一条SQL语句。</p>
<p>&nbsp;</p>
<p>虽然每个游戏的内容都各有差别，但是一款MMOG需要存入数据库的数据少不了会有技能、物品、任务、宠物、好友、公会这些东西。取决于游戏的类型差异，每个游戏都会有自己的存盘方式，比如我可能会把所有的技能ID作为一条数据来存储，但是我也有可能把每个技能作为一条单独的记录来存储，这样可以方便对技能附加数据的扩展，等等。</p>
<p>&nbsp;</p>
<p>但是，游戏中的物品存储大概都是相同的，只能是一件(组)物品作为一条记录来存储。</p>
<p>而且，可以说游戏中存储量最大的就是物品数据。算一算你的角色背包有多大，50格？&nbsp;100格？还是200格？不要忘了银行、摆摊位、装备拦、宠物背包和邮箱这些地方也能放物品。并且，在游戏进行过程中，玩家背包中物品数据的变动也是相当的频繁，不断的有药品被用掉，不断地又有些小玩意儿被捡起来，不久后，它们又被卖给了NPC。</p>
<p>&nbsp;</p>
<p>虽然你可以使用一些巧妙的比较算法来过滤掉那些实际上没有发生变动的物品更新，另外也不是所有的玩家物品数据变动都很频繁，但在实际运营中，尤其是当玩家的背包格数都很多的时候，物品数据的存盘的确会成为一个很大的问题。</p>
<p>&nbsp;</p>
<p>除了物品，还有玩家的基本属性存盘，社会关系存盘等等，再加上全局公共数据的存盘，如公会数据，拍卖行物品数据，如果老板也要我在游戏中开上一家拍卖行的话。</p>
<p>&nbsp;</p>
<p>这么一算下来，似乎是有些多了。</p>
<p>&nbsp;</p>
<p>&nbsp;</p>
<p><strong>再一次的挑战</strong></p>
<p>&nbsp;</p>
<p>具体的数字将取决于游戏的类型和设计的数据表结构。</p>
<p>而数据库服务器能承担的每秒查询数则取决于数据库服务器的软硬件配置情况。</p>
<p>但是一般来说，数据库维护人员可能会告诉我，当每秒执行的SQL语句数达到1000条时，数据库服务器将会感受到明显的压力，我可能也会看到数据库执行队列中的请求数一直在增长，也可能会看到数据库服务器间歇性地拒绝响应，等等。</p>
<p>&nbsp;</p>
<p>看起来我们又一次的面对到了巨大的打冷战。</p>
<p>这个问题的起因是什么？我们不希望服务器进程宕机时回档太久，所以我们增加了一个玩家数据定时存盘的机制，结果却导致了数据库请求的骤然增多。</p>
<p>那再退回到这个起点处，将定时存盘的时间间隔延长点，比如10分钟才存一次？数据库的压力会有缓解，但最初的问题却又会有所暴露。真是个两难的问题。</p>
<p>&nbsp;</p>
<p>既想要玩家数据存盘间隔时间短一点，又不想给数据库造成的压力太大。</p>
<p>同样的需求似乎出现过很多回了：在中间加一层代理做缓冲。我们姑且称这一层代理为数据库代理服务器，它所要完成的工作是从场景进程收集玩家的定时存盘请求数据，再以一个低一点的频率写入到数据库。</p>
<p>&nbsp;</p>
<p>听起来这又像是一个换汤不换药的做法，写入数据库的时间间隔还是变长了。但实际上在前面我们就曾经描述过，如果服务器进程不会出现意外的宕机，玩家数据只需要在他上线时读取，在他下线时写入即可，中间添加的这些定时存盘过程完全只是为了防范宕机回档所造成的巨大损失。</p>
<p>&nbsp;</p>
<p>因为这个中间代理层的加入，我们把场景进程宕机的隐患与数据丢失的后果隔离开来了，现在即使场景进程宕机，数据还在数据库代理服务器上，当然这里又隐含了一个条件：数据库代理服务器需要足够稳定，不会在场景进程之前先宕掉。事实上，因为这个代理进程的工作是，我们完全有理由相信，这个进程是非常稳定的，那样的话，多久时间才把缓存的数据真正写入数据库，就看你自己的喜好了。</p>
<p>&nbsp;</p>
<p>&nbsp;</p>
<p><strong>该结束了吧</strong></p>
<p>&nbsp;</p>
<p>是否有些似曾相识的感觉？</p>
<p>没错，前面我们曾经描述过一个数据服务器，也是这样说的。</p>
<p>&nbsp;</p>
<p>所以，数据服务器，数据库代理服务器可以合并到一起，来共同保证数据的安全。</p>
<p>再加上场景进程与管理器进程的恢复协议，让服务器的重启对玩家保持透明。</p>
<p>看起来这个晚上可以睡个安稳觉。</p>
<img src ="http://www.cppblog.com/jack-wang/aggbug/72505.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/jack-wang/" target="_blank">小王</a> 2009-01-23 13:34 <a href="http://www.cppblog.com/jack-wang/archive/2009/01/23/72505.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>超大地图MMORPG的场景管理    </title><link>http://www.cppblog.com/jack-wang/archive/2009/01/02/70969.html</link><dc:creator>小王</dc:creator><author>小王</author><pubDate>Thu, 01 Jan 2009 20:09:00 GMT</pubDate><guid>http://www.cppblog.com/jack-wang/archive/2009/01/02/70969.html</guid><wfw:comment>http://www.cppblog.com/jack-wang/comments/70969.html</wfw:comment><comments>http://www.cppblog.com/jack-wang/archive/2009/01/02/70969.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cppblog.com/jack-wang/comments/commentRss/70969.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/jack-wang/services/trackbacks/70969.html</trackback:ping><description><![CDATA[<h2 class=diaryTitle>超大地图MMORPG的场景管理- -</h2>
<strong>Tag</strong>： <a href="http://tag.bokee.com/SearchTag.b?span=1&amp;wd=%B3%AC%B4%F3%B5%D8%CD%BCMMORPG%B5%C4%B3%A1%BE%B0%B9%DC%C0%ED" target=_blank><u><font color=#0000ff>超大地图MMORPG的场景管理</font></u></a> &nbsp;&nbsp; &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;
<p>
<p>目前在做一个超大地图MMORPG的场景管理部分，客户端通过动态预读解决了超大图量的动态加载，但是在做人物行走的时候遇到了一些问题：<br>　　一张地图上的PLAYER和NPC等是存放在一个list中的，地图超大那么上面的PLAYER就可能超多（预计大于２００），这样的话每个行走动作都要发送２００条以上的消息，这对于服务器是一种很大的负担，而且这种负担是呈级数增长（10个玩家都走一步服务器将发送10*10=100条消息，而200个的话就是200*200=40000条消息！），可能任何服务器都无法负担。<br>　　肯定有很多朋友都遇到了类似的问题，很想知道大家是怎么解决的？<br>　　</p>
<p>方案一：<br>　&#183;服务器上每个场景用一个list来保存上面的player和NPC<br>　&#183;玩家行走、进入和离开等事件发给list中的所有player<br>　&#183;客户端的list保有该场景上的所有player和npc<br>　优点：处理起来简单直接<br>　缺点：发送的消息会随玩家数量的增加而暴增、客户端负担很重<br><br>方案二：<br>　&#183;服务器上每个场景用一个list来保存上面的player和NPC<br>　&#183;玩家行走、进入和离开等事件只发给该玩家一定范围内的Player<br>　&#183;客户端的list只保有本玩家附近的player和npc<br>　&#183;在服务器可以考虑使用hash表来优化查询速度<br>　优点：减少了服务器发送消息的数量、减轻了客户端的负担<br>　缺点：实现相对复杂，服务器负担大大加重（因为要不断判断玩家间的位置关系）<br><br>方案三：<br>　&#183;在服务器上把场景划分为小区域（大于屏幕大小）。每个区域对应一个list，场景中的所有对象按他们的位置加入到对应区域的list中，那么每次行走只需要把消息发送给最多４个相临区域的Player<br>　&#183;客户端的list只保有本玩家附近的player和npc<br>　优点：大大减轻了服务器遍历list的负担、减少了发送消息的数量、减轻了客户端的负担<br>　缺点：实现非常复杂、而且在服务器需要不断判断玩家是否跨越区域<br><br>方案四：<br>　&#183;服务器上场景的每个TILE保存一个Object指针用来绑定该格子上的player或NPC<br>　&#183;玩家行走、进入和离开等事件发给玩家周围一定范围内的player<br>　&#183;客户端保有该player周围一定范围内的player和npc<br>　优点：处理起来极为直接、避免了耗时链表遍历（典型的以空间换时间）<br>　缺点：地图每个TILE都要加入一个指针变量（管理不善容易出错）、每次发送场景广播要遍历所有TILE<br><br>方案五：<br>　&#183;服务器上每个场景用一个list来保存上面的player和NPC<br>　&#183;不使用事件通知，而使用状态位置通知的方式通过定时发送状态来更新客户端的player和npc状态<br>　&#183;客户端保有该player周围一定范围内的player和npc<br>　优点：处理比较简单<br>　缺点：实时性太低，对于要求同步比较精确的ＡＲＰＧ不太适合</p>
<img src ="http://www.cppblog.com/jack-wang/aggbug/70969.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/jack-wang/" target="_blank">小王</a> 2009-01-02 04:09 <a href="http://www.cppblog.com/jack-wang/archive/2009/01/02/70969.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>MMORPG中游戏世界的构建 </title><link>http://www.cppblog.com/jack-wang/archive/2009/01/02/70965.html</link><dc:creator>小王</dc:creator><author>小王</author><pubDate>Thu, 01 Jan 2009 19:39:00 GMT</pubDate><guid>http://www.cppblog.com/jack-wang/archive/2009/01/02/70965.html</guid><wfw:comment>http://www.cppblog.com/jack-wang/comments/70965.html</wfw:comment><comments>http://www.cppblog.com/jack-wang/archive/2009/01/02/70965.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cppblog.com/jack-wang/comments/commentRss/70965.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/jack-wang/services/trackbacks/70965.html</trackback:ping><description><![CDATA[<p>Author: Fox<br>原文地址：<a href="http://www.cppblog.com/Fox/archive/2007/12/16/game_world_architecture.html">http://www.cppblog.com/Fox/archive/2007/12/16/game_world_architecture.html</a><br><span><br>一个</span><span>MMORPG</span><span>（</span><span>Massively Multiplayer Online Role Playing Game</span><span>）的架构包含客户端和服务器两部分。客户端主要涉及计算机图形学、物理学、多媒体技术等，服务器主要涉及网络通信技术、数据库技术，而人工智能、操作系统等计算机基础学科知识的应用体现在</span><span>MMORPG</span><span>开发过程中的方方面面。</span></p>
<p><span>一、游戏世界的划分</span> </p>
<p><span>理想状态的游戏世界仅由一个完整的场景组成，在《魔兽争霸</span> <span>III</span> <span>》、《</span> <span>CS</span> <span>》这样的单机游戏中，所有玩家位于该场景中，在理论上，位于该场景中的任意玩家都可以看到游戏中所有玩家并与之交互，出于公平性和游戏性（而不是技术上）的考虑，游戏中并不会这样做。</span> </p>
<p><span>然而，目前的</span> <span>MMORPG</span> <span>中，几乎没有任何一款可以做到整个游戏世界只包含一个场景，因为在一款</span> <span>MMORPG</span> <span>中，同时在线的玩家数量成百上千，甚至是数万人同时在一个游戏世界中交互。以现在的网络技术和计算机系统，还无法为这么多玩家的交互提供即时处理。因此，</span> <span>MMORPG</span> <span>的游戏世界被划分为大小不等、数量众多的场景，游戏服务器对于这些场景的处理分为分区和无缝两种。</span> </p>
<p><span>在分区式服务器中，一个场景中的玩家无法看到另一个场景中的玩家，当玩家从一个场景到另外一个场景跨越时，都有一个数据转移和加载的过程（尤其是从一个分区服务器跨越到另外一个服务器时），玩家都有一个等待的时间，在这段时间内，服务器的主要工作是实现跨越玩家数据的转移和加载以及后一个场景中玩家、</span> <span>NPC</span> <span>等数据的传输，客户端的主要工作是实现新场景资源的加载和服务器通信。主要时间的长短主要取决于后一个场景中资源数据的大小。分区式服务器的优点主要是各分区服务器保持相对独立，缺点是游戏空间不够大，而且，一旦某个分区服务器中止服务，位于该服务器上的所有玩家将失去连接。</span> </p>
<p><span>所谓无缝服务器，玩家几乎察觉不到场景之间的这种切换，在场景间没有物理上的屏障，对于玩家而言，众多场景构成了一个巨大的游戏世界。场景之间，甚至服务器之间&#8220;没有了&#8221;明确的界线。因此，无缝服务器为玩家提供了更大的游戏空间和更友好的交互，实现了动态边界的无缝服务器甚至可以在某个服务器中止服务时，按一定策略将负载动态分散到其他服务器。因此，无缝服务器在技术上要比分区服务器更加复杂。</span> </p>
<p><span>目前国内上市的</span> <span>MMORPG</span> <span>，大多采用分区式服务器，做到无缝世界的主要有《完美世界》和《天下贰》等，国外的</span> <span>MMORPG</span> <span>中，像《魔兽世界》、《</span> <span>EVE</span> <span>》等，都实现了无缝世界。</span> </p>
<p><span>无缝服务器与分区式服务器在技术上的主要区别是，当位于场景</span> <span>S1</span> <span>中的玩家</span> <span>P1</span> <span>处于两个（甚至更多）场景</span> <span>S1</span> <span>、</span> <span>S2</span> <span>的边界区域内时，要保证</span> <span>P1</span> <span>能够看到场景</span> <span>S2</span> <span>中建筑、玩家、</span> <span>NPC</span> <span>等可感知对象。而且边界区域的大小要大于等于</span> <span>P1</span> <span>可感知的范围，否则就可能发生</span> <span>S2</span> <span>中的可感知对象突然闪现在</span> <span>P1</span> <span>视野中的异常。</span> </p>
<p><span>无疑，无缝世界为玩家提供了更人性化和更具魅力的用户体验。</span> </p>
<p><span>二、无缝世界游戏服务器的整体架构</span> </p>
<p><span>MMORPG</span> <span>的服务器架构从功能上主要划分为三种：</span> </p>
<p><span><span>1、</span> </span><span>登录服务器（</span> <span>Login Server</span> <span>）</span> </p>
<p><span>登录服务器用于玩家验证登录，并根据系统记录玩家信息得到其所在节点服务器，并通过世界服务器为登录玩家和对应节点服务器建立连接。</span> </p>
<p><span><span>2、</span> </span><span>世界服务器（</span> <span>World Server</span> <span>）</span> </p>
<p><span>世界服务器将整个游戏世界划分成不同场景，将所有场景按一定策略分配给节点服务器，并对节点服务器进行管理。世界服务器的另一功能是与登录服务器交互。因此，世界服务器是登录服务器、节点服务器的沟通桥梁，当然，一旦玩家登录成功，世界服务器将主要处理节点服务器间的通信。因此，世界服务器对于玩家是透明的。</span> </p>
<p><span><span>3、</span> </span><span>节点服务器（</span> <span>Node Server</span> <span>）</span> </p>
<p><span>节点服务器负责管理位于该节点的所有玩家、</span> <span>NPC</span> <span>的所有交互，在无缝世界游戏中，由于边界区域的存在，一个节点服务器甚至要处理相邻节点上位于边界区域的玩家和</span> <span>NPC</span> <span>的信息。</span> </p>
<p><span>在具体实现上，不同的</span> <span>MMORPG</span> <span>为了便于管理，可能还会具有</span> <span>AI</span> <span>服务器、日志服务器、数据库缓存服务器、代理服务器等。</span> </p>
<p><span><span>三、</span> </span><span>无缝世界游戏服务器的主要技术需求</span> </p>
<p><span><span>1、</span> </span><span>编程语言（</span> <span>C/C++</span> <span>、</span> <span>SQL</span> <span>、</span> <span>Lua</span> <span>、</span> <span>Python</span> <span>）</span> </p>
<p><span><span>2、</span> </span><span>图形库（</span> <span>Direct 3D</span> <span>、</span> <span>OpenGL</span> <span>）</span> </p>
<p><span><span>3、</span> </span><span>网络通信（</span> <span>WinSock</span> <span>、</span> <span>BSD Socket</span> <span>，或者</span> <span>ACE</span> <span>）</span> </p>
<p><span><span>4、</span> </span><span>消息、事件、多线程、</span> <span>GUI</span> </p>
<p><span><span>5、</span> </span><span>OS</span> </p>
<p><span>三、无缝世界游戏服务器需要解决的主要问题</span> </p>
<p><span><span>1、</span> </span><span>资源管理</span> </p>
<p><span>无论是服务器还是客户端，都涉及到大量资源：玩家数据、</span> <span>NPC</span> <span>数据、战斗公式、模型资源、通信资源等。当这些资源达到一定规模，其管理的难度不可忽视。而且，资源管理的好坏，直接关系到游戏的安全和生命。</span> </p>
<p><span><span>2、</span> </span><span>网络安全</span> </p>
<p><span>安全永远是第一位的，我们无法指望所有的玩家及其所持的客户端永远是友好的。事实上，威胁到游戏的公平性和安全性的大多数问题，归根结底，都是由于网络通信中存在的欺骗和攻击造成的，这些问题包含但不限于交易欺骗、物品复制。</span> </p>
<p><span><span>3、</span> </span><span>逻辑安全</span> </p>
<p><span>逻辑安全按理说应该是游戏中最基本的考虑，覆盖的范围也最广最杂。随机数系统是一个非常值得重视的问题，随机数不仅仅用于玩家可见的一些任务系统、战斗公式、人工智能、物品得失等，还可用于网络报文加密等。因此，随机数系统本身的安全不容忽视。另外一个常见的逻辑安全是玩家的移动，最主要的就是防止加速齿轮这样的变态操作。</span> </p>
<p><span><span>4、</span> </span><span>负载均衡</span> </p>
<p><span>MMORPG</span> <span>中的负载均衡包括客户端及服务器资源管理和逻辑处理的负载均衡，其中最难预知的是网络通信的负载均衡，正常情况下的网络通信数量是可以在游戏设计时做出评估的，但因恶意攻击造成的网络负载是无法预测的。因此，负载均衡所要处理的主要是实时动态负载均衡和灾难恢复。负载均衡需要解决的问题包括负载监控、负载分析、负载分发和灾难恢复。</span> </p>
<p><span><span>5、</span> </span><span>录像系统</span> </p>
<p><span>录像系统的构建，主要用于重现关键数据的输入输出，如玩家交易、玩家充值，或者当</span> <span>bug</span> <span>出现后，为逻辑服务器（泛指上文提到的所有类型服务器，主要是节点服务器）相应部分启动录像系统。待收集到足够数据后，通过录像系统重现</span> <span>bug</span> <span>。为了使逻辑服务器不受自身时间（如中断调试等）的影响，还可以专门设计心跳服务器来控制数据传输。</span> </p>
<p><span>四、总结</span> </p>
<p><span>在</span> <span>MMORPG</span> <span>中，真正的</span> <span>bug</span> <span>永远存在于将来。从这一点出发，关于</span> <span>MMORPG</span> <span>中游戏世界的构建，怎样苛刻的思考都不为过。<br><br>参考资料：</span> </p>
<p><span>1、 [美] Kim Pallister编, 孟宪武 等译. 游戏编程精粹5, P467-474, P516. 人民邮电出版社, 2007年9月. 北京.<br>2、 [美] Thor Alexander编, 史晓明 译. 大型多人在线游戏开发, P174-185. 人民邮电出版社, 2006年12月. 北京.<br>3、 [美] Dante Treglia编, 张磊 译. 游戏编程精粹3, P117-122. 人民邮电出版社, 2003年7月. 北京.<br>4、 [美] Mark DeLoura编, 王淑礼 等译. 游戏编程精粹1, P90-93. 人民邮电出版社, 2004年10月. 北京.<br>5、 [美] Douglas 等著, 於春景 译. C++网络编程 卷1. 中国电力出版社, 2004年11月. 北京.<br>6、 [美] Stephen D. Huston 等著, 马维达 译. ACE程序员指南. 中国电力出版社, 2004年11月. 北京.<br>7、 [美] Erich Gamma等著, 李英军 等译. 设计模式. 机械工业出版社, 2000年6月. 北京.<br>8、 游戏引擎全剖析. <a href="http://bbs.gameres.com/showthread.asp?threadid=101293"><u><font color=#0000ff>http://bbs.gameres.com/showthread.asp?threadid=101293</font></u></a>.<br>9、 服务器结构探讨：登录服的负载均衡. <a href="http://gamedev.csdn.net/page/351491d0-05ad-48a4-85e1-77870bc1eef3"><u><font color=#0000ff>http://gamedev.csdn.net/page/351491d0-05ad-48a4-85e1-77870bc1eef3</font></u></a>.<br>10、服务器结构探讨：最终的结构. <a href="http://gamedev.csdn.net/page/28695655-974c-4291-8ac4-2589c4e770d3"><u><font color=#0000ff>http://gamedev.csdn.net/page/28695655-974c-4291-8ac4-2589c4e770d3</font></u></a>.<br></span></p>
<img src ="http://www.cppblog.com/jack-wang/aggbug/70965.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/jack-wang/" target="_blank">小王</a> 2009-01-02 03:39 <a href="http://www.cppblog.com/jack-wang/archive/2009/01/02/70965.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>负载均衡－－大型在线系统实现的关键(下篇)</title><link>http://www.cppblog.com/jack-wang/archive/2009/01/02/70962.html</link><dc:creator>小王</dc:creator><author>小王</author><pubDate>Thu, 01 Jan 2009 18:19:00 GMT</pubDate><guid>http://www.cppblog.com/jack-wang/archive/2009/01/02/70962.html</guid><wfw:comment>http://www.cppblog.com/jack-wang/comments/70962.html</wfw:comment><comments>http://www.cppblog.com/jack-wang/archive/2009/01/02/70962.html#Feedback</comments><slash:comments>1</slash:comments><wfw:commentRss>http://www.cppblog.com/jack-wang/comments/commentRss/70962.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/jack-wang/services/trackbacks/70962.html</trackback:ping><description><![CDATA[在网络应用中，&#8220;负载均衡&#8221;已经不能算是什么新鲜话题了，从硬件到软件，也都有了很多的方法来实现负载均衡。我们这里讨论的负载均衡，并不是指依靠DNS转向或其它硬件设备等所作的负载均衡，而是指在应用层所作的负载均衡。<br>　　一般而言，只有在大型在线系统当中才有必要引入负载均衡，那么，多大的系统才能被称为大型系统呢？比如动辄同时在线数十万的网络游戏，比如同时在线数在10万以上的WEB应用，这些我们都可以理解为大型系统，这本身就是一个宽泛的概念。<br>　<br>　设计再好的服务器程序，其单个程序所能承载的同时访问量也是有限的，面对一个庞大且日益增长的网络用户群，如何让我们的架构能适应未来海量用户访问，这<br>自然就牵涉到了负载均衡问题。支持百万级以上的大型在线系统，它的架构核心就是如何将&#8220;百万&#8221;这么大的一个同时在线量分摊到每个单独的服务器程序上去。真<br>正的逻辑处理应该是在这最终的底层的服务器程序（如QQ游戏平台的游戏房间服务器）上的，而在此之前所存在的那些服务器，都可以被称为&#8220;引路者&#8221;，它们的<br>作用就是将客户端一步步引导到这最终的负责真正逻辑的底层服务器上去，我们计算&#8220;百万级在线&#8221;所需要的服务器数量，也是首先考虑这底层的逻辑服务器单个可<br>承载的客户端连接量。<br>　　比如：按上篇我们所分析QQ游戏架构而言，假设每个服务器程序最高支持2W的用户在线（假设一台机子只运行一个<br>服务器程序），那么实现150万的在线量至少需要多少台服务器呢？如果算得简单一点的话，就应该是：150/2=75台。当然，这样算起来，可能并不能代<br>表真正的服务器数量，因为除了这底层的服务器外，还要包括登录/账号服务器以及大厅服务器。但是，由于登录/账号服务器和大厅服务器，它们与客户端的连接<br>都属于短连接（即：取得所需要信息后，客户端与服务器即断开连接），所以，客户端给这两类服务器带来的压力相比于长连接（即：客户端与服务器始终保持连<br>接）而言就要轻得多，它们的压力主要在处理瞬间的并发访问上。<br>　　&#8220;短连接&#8221;，是实现应用层负载均衡的基本手段！！！如果客户端要始终与登录/账号服务器以及大厅服务器保持连接，那么这样作的分层架构将是无意义的，这也没有办法从根本上解决用户量不断增长与服务器数量有限之间的矛盾。<br>　<br>　当然，短连接之所以可以被使用并能维护正常的游戏逻辑，是因为在玩家看不到的地方，服务器与服务器之间进行了大量的数据同步操作。如果一个玩家没有登录<br>到登录服务器上去而是直接连接进了游戏房间服务器并试图进行游戏，那么，由于游戏房间服务器与大厅服务器和登录/账号服务器之间都会有针对于玩家登录的逻<br>辑维护，游戏房间服务器会检测出来该玩家之前并没有到登录服务器进行必要的账号验证工作，它便会将玩家踢下线。由此看来，各服务器之间的数据同步，又是实<br>现负载均衡的又一必要条件了。<br>　　服务器之间的数据同步问题，依据应用的不同，也会呈现不同的实现方案。比如，我们在处理玩家登录这个问<br>题上。我们首先可以向玩家开放一些默认的登录服务器（服务器的IP及PORT信息），当玩家连接到当前的登录服务器后，由该服务器首先判断自己同时连接的<br>玩家是不是超过了自定义的上限，如果是，由向与该服务器连接着的&#8220;登录服务器管理者&#8221;（一般是一个内部的服务器，不直接向玩家开放）申请仲裁，由&#8220;登录服<br>务器管理者&#8221;根据当前各登录服务器的负载情况选择一个新的服务器IP和PORT信息传给客户端，客户端收到这个IP和PORT信息之后重定向连接到这个新<br>的登录服务器上去，完成后续的登录验证过程。<br>　　这种方案的一个特点是，在面向玩家的一侧，会提供一个外部访问接口，而在服务器集群的内部，会提供一个&#8220;服务器管理者&#8221;及时记录各登录服务器的负载情况以便客户端需要重定向时根据策略选择一个新的登录接口给客户端。<br>　<br>　采用分布式结构的好处是可以有效分摊整个系统的压力，但是，不足点就是对于全局信息的索引将会变得比较困难，因为每个单独的底层逻辑服务器上都只是存放<br>了自己这一个服务器上的用户数据，它没有办法查找到其它服务器上的用户数据。解决这个问题，简单一点的作法，就是在集群内部，由一个中介者，提供一个全局<br>的玩家列表。这个全局列表，根据需要，可以直接放在&#8220;服务器管理者&#8221;上，也可以存放在数据库中。<br>　　对于逻辑相对独立的应用，全局列表的<br>使用机会其实并不多，最主要的作用就是用来检测玩家是不是重复登录了。但如果有其它的某些应用，要使用这样的全局列表，就会使数据同步显得比较复杂。比<br>如，我们在超大无缝地图的MMORPG里，如果允许跨服操作(如跨服战斗、跨服交易等)的话，这时的数据同步将会变得异常复杂，也容易使处理逻辑出现不可<br>预测性。<br>　　我认为，对于休闲平台而言，QQ游戏的架构已经是比较合理的，也可以称之为休闲平台的标准架构了。那么，MMORPG一般的架构是什么样的呢？<br>　<br>　MMORPG一般是把整个游戏分成若干个游戏世界组，每个组内其实就是一个单独的游戏世界。而不同的组之间，其数据皆是相互独立的，并不象QQ休闲平台<br>一样所有的用户都会有一个集中的数据存放点，MMORPG的游戏数据是按服务器组的不同而各自存放的。玩家在登录QQ游戏时，QQ游戏相关的服务器会自动<br>为玩家的登录进行负载均衡，选择相对不忙的服务器为其执行用户验证并最终让用户选择进入哪一个游戏房间。但是，玩家在登录MMORPG时，却没有这样的自<br>动负载均衡，一般是由玩家人为地去选择要进入哪一个服务器组，之所以这样，是因为各服务器组之间的数据是不相通的。其实，细致想来，MMORPG的服务器<br>架构思想与休闲平台的架构思想有异曲同工之妙，MMORPG的思想是：可以为玩家无限地开独立的游戏世界（即服务器组），以满足大型玩家在线；而休闲平台<br>的思想则是：可以为玩家无限地开游戏房间以满足大量玩家在线。这两种应用，可以无限开的都是&#8220;具有完整游戏性的游戏世界&#8221;，对于MMORPG而言，它的一<br>个完整的游戏地图就是一个整体的&#8220;游戏世界&#8221;，而对于休闲平台，它的一个游戏房间就可以描述为一个&#8220;游戏世界&#8221;。如果MMORPG作成了休闲平台那样的全<br>服皆通，也不是不可以，但随之而来的，就是要解决众多跨服问题，比如：好友、组队、帮派等等的问题，所有在传统MMORPG里所定义的这些玩家组织形式的<br>规则可能都会因为&#8220;全服皆通&#8221;而改变。<br>　　架构的选择是多样性的，确实没有一种可以称得上是最好的所谓的架构，适合于当前项目的，不一定就适合于另一个项目。针对于特定的应用，会灵活选用不同的架构。但有一点，是可以说的：不管你如何架构，你所要作的就是－－要以尽可能简单的方案实现尽可能的稳定、高效！
<img src ="http://www.cppblog.com/jack-wang/aggbug/70962.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/jack-wang/" target="_blank">小王</a> 2009-01-02 02:19 <a href="http://www.cppblog.com/jack-wang/archive/2009/01/02/70962.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item></channel></rss>