cpp的ai文件如何应用到mangos编译教程服务端中,是否需要用到其他软件导入?

当前访客身份:游客 [
这个人很懒,啥也没写
:这是2d啊,不是2dx啊
:写得不错 现在游戏服务器 特别在跨区的时候 要对...
:逻辑用脚本应对的是多变的策划需求而不是为了要高...
:没看完,顶一下,虽然我是javaer,但我是多年的w...
:通俗易懂,很好。~
:可以给个权限不?
:谢谢分享了!
今日访问:11
昨日访问:0
本周访问:17
本月访问:148
所有访问:4515
列表模式: |
统计: 0评/37阅/0赞
统计: 0评/374阅/0赞
[此文章被主人设置为私密,不可见]
统计: 0评/2阅/0赞
配置ANDROID_HOME环境变量很简单的ant打包我们只需要利用eclipse自带的ant插件即可实现,首先配置ANDROID_HOME,右键 计算机-&属性-&高级-&环境变量,如下图,在“系统变量”下新建一个变量,变量名为ANDROID_HOME,变量值为android_sdk的存放目录然后,在系统变量中的Path变量里最前面添加“%ANDROID_HOME%/”;如下图(注意有个后面有个分号)好了,确定配置完毕,这样完了之后我们就可以在cmd窗口中执行android命令了,如果显示android不是系统命令,那么检查自己在配置ANDROID_HOME过程中出了什么问题吧,其实咱们执行的android命令就是tools目录下的android.bat,所以才在Path中添加tools的路径。ant打包前面我们已经配置好ANDROID_HOME了,下面我们进入cmd,并进入需要ant打包的工程根目录,执行命令“android update project -p ./”,参数p表示路径,用./表示当前路径,执行结果如下图:然后我们在eclipse中刷新项目,可以看到多了以下两个文件building.xml和local.properties我们先不要看里面是什么,现在我们已经可以使用ant进行打包了,在build.xml文件上右键-&运行方式,由于是第一次执行ant构建,我们选择第二个“ant 构建”配置执行的选项target这里我选的release,这些target都是sdk自带的ant写好的,在“xxx\android-sdk\tools\ant\build.xml”中有定义执行顺序可以自己调整,“help”可以在目标中把勾去掉。点击运行,开始打包。如果最后打包成功了,恭喜你,你的软件版本没有问题,可以跳过下面的问题。我在使用ant的时候遇到下面两个问题导致最后打包失败。注意:ant 构建前先clean一下!问题1构建失败D:\Program Files\Android\android-sdk\tools\ant\build.xml:397: The Android Ant-based build system requires Ant 1.8.0 or later. Current version is 1.7.1这个很容易解决,下载ant1.8以上就可以了,遇到这个问题的小伙伴们可以提取码:66b4。我上传的ant压缩包。csdn资源上传审核真特么恶心,我只能放在360云盘了。下完之后解压即可,这里我解压到E盘,"E:\Ant",然后在eclipse中配置ant目录,窗口-&首选项-&Ant,选择“运行时”如下图点击Ant主目录选择"E:\Ant"确定后再按照前面讲的执行“ant 构建”,如果成功打包,说明没有其他问题了。问题2[dx] UNEXPECTED TOP-LEVEL EXCEPTION:& & & & & [dx] java.nio.BufferOverflowException& & & & & [dx]&at java.nio.Buffer.nextPutIndex(Buffer.java:501)& & & & & [dx]&at java.nio.HeapByteBuffer.putShort(HeapByteBuffer.java:296)& & & & & [dx]&at com.android.dex.Dex$Section.writeShort(Dex.java:818)& & & & & [dx]&at com.android.dex.Dex$Section.writeTypeList(Dex.java:870)& & & & & [dx]&at com.android.dx.merge.DexMerger$3.write(DexMerger.java:437)& & & & & [dx]&at com.android.dx.merge.DexMerger$3.write(DexMerger.java:423)& & & & & [dx]&at com.android.dx.merge.DexMerger$IdMerger.mergeUnsorted(DexMerger.java:317)& & & & & [dx]&at com.android.dx.merge.DexMerger.mergeTypeLists(DexMerger.java:423)& & & & & [dx]&at com.android.dx.merge.DexMerger.mergeDexes(DexMerger.java:163)& & & & & [dx]&at com.android.dx.merge.DexMerger.merge(DexMerger.java:187)& & & & & [dx]&at com.mand.dexer.Main.mergeLibraryDexBuffers(Main.java:439)& & & & & [dx]&at com.mand.dexer.Main.runMonoDex(Main.java:287)& & & & & [dx]&at com.mand.dexer.Main.run(Main.java:230)& & & & & [dx]&at com.mand.dexer.Main.main(Main.java:199)& & & & & [dx]&at com.mand.Main.main(Main.java:103)这个问题是由于sdk的build-tools版本导致的,我在19.x版本上都遇到这个问题,更新到20或者删掉19改用18问题解决。如果sdk Manager更新不了可以到网上直接下载最新的sdt把build-tools目录拷贝到当前sdk的build-tools中即可,如果安装了低版本的可以直接把19版本删掉。以上就是我遇到的两个问题和解决方法。如果打包成功,刷新bin目录会发现有未签名的apk:“xxx-release-unsigned.apk”,下面我们要为ant配置签名文件和混淆文件。加入混淆和签名数字签名大家都知道怎么一回事儿了,混淆代码就是将编译好的.class中的类名映射成一些看不出确切意义的字母,防止被反编译。我们在ant的配置文件中添加参数名和参数值。那我们就要看自动生成的build.xml里写的是什么东西了。build.xml:[html]&&?xml&version="1.0"&encoding="UTF-8"?&&&&project&&&&&&name="MainActivity"&&&&&&default="help"&&&&&&&&&&&property&file="local.properties"&/&&&&&&&&&&property&file="ant.properties"&/&&&&&&&&&&property&environment="env"&/&&&&&&&&&&condition&&&&&&&&&&property="sdk.dir"&&&&&&&&&&value="${env.ANDROID_HOME}"&&&&&&&&&&&&&&&isset&property="env.ANDROID_HOME"&/&&&&&&&&/condition&&&&&&&&&&loadproperties&srcFile="project.properties"&/&&&&&&&&&&fail&&&&&&&&&&message="sdk.dir&is&missing.&Make&sure&to&generate&local.properties&using&'android&update&project'&or&to&inject&it&through&the&ANDROID_HOME&environment&variable."&&&&&&&&&&unless="sdk.dir"&/&&&&&&&&&&import&&&&&&&&&&file="custom_rules.xml"&&&&&&&&&&optional="true"&/&&&&&&&&&&import&file="${sdk.dir}/tools/ant/build.xml"&/&&&&&&/project&&&build.xml里面就这么短的代码,其实,核心部分就是最后的“&import file="${sdk.dir}/tools/ant/build.xml" /&”我们执行的release就是在/tools/ant/build.xml中定义的,那我们自己的东西要写在哪里呢?看这两句“&property file="local.properties" /&”和“&property file="ant.properties" /&”,local.properties已经给我们生成了,ant.properties没有生成,如果你需要也可以新建一个,那么我们的配置就写在local.properties中。如果不知道怎么创建数字签名文件,我就再啰嗦一会儿,手动导出apk的时候提示选择一个keystore,这时候可以选择Create new keystore,存放目录放在当前工程根目录下,填完后先导出一个apk,之后就可以看到工程下有个.keystore文件了。接下来我们就可以配置ant打包选项了。下面来看local.properties的内容:[html]&#下面这句是自动生成的&&sdk.dir=D:\\Program&Files\\Android\\android-sdk&&#数字签名文件&&key.store=jingchen.keystore&&#别名alias&&key.alias=jingchen&&#数字签名的密码&&key.store.password=111111&&#alias的密码&&key.alias.password=111111&&#这里设置混淆代码,在当前项目的proguard-project.txt中编写混淆规则&&proguard.config=${sdk.dir}/tools/proguard/proguard-android.txt:proguard-project.txt&&上面注释都写着。好了,到这里我们的ant带签名和代码混淆的自动打包搞定了!注意:ant 构建前先clean一下!这个也可以写到配置文件里,下一篇文章会讲到。在下一篇文章中,我将会讲到如何批量多渠道打包!我的问题:注意,如果我们项目中有依赖其他项目,那么我们也需要把那些项目创建ant环境,比如说android应用一般会依赖android-support-v7-appcompat,这时我们需要cd到此项目运行第二步的命令,看到有build.xml就可以了。否则可能会看到如下错误:BUILD FAILED F:\android\adt-bundle-windows-x86-\sdk\tools\ant\build.xml:471: Invalid
发布于 4个月前,
阅读(4) | 评论(0) |
投票(0) | 收藏(0)
cocos2d-x 是目前比较流行的游戏引擎,为大多游戏开发爱好者所喜爱,
因此,本文的目的在于教导新手如何在win7下建立cocos2dx开发环境,
截止本文,cocos2dx的最新版本为 v3.2rc0版,我将假设您的开发
环境中已包含VS2012/VS2013集成工具(该IDE下载安装比较简单,
没有安装的朋友可以到微软官方下载安装),有了该工具,接下便
开始我们的游戏环境搭建之旅吧...
首先,建立该开发环境需要以下工具:
1.Visual Studio (微软IDE开发工具)
2.Cocos2d-x v3.2rc0版(游戏开发引擎)
3.JAVA JDK(JAVA开发工具包)
4.python工具(用于脚本编译)
5.ADT包(含Android SDK组件和一个内置ADT(Android开发者工具)的Eclipse IDE版本)
6.Android NDK(so和java应用一起打包成apk)
一.首先到中文官网下载cocos2d-x v3.2rc0版本,
下载完毕后,我们直接解压,即可得到主要目录(本文相关软件我全部解压至G:\),
然后进入到G:\cocos2d-x-3.2rc0\build,用VS2012打开cocos2d-win32.vc2012.sln,
这里需要注意的是,默认平台工具集为v110_xp,如果我们用的是win7平台,请将其改为Visual Studio ),
之后,我们将启动项改为CppTest,然后生成解决方案...
编译成功后,我们按下F5, 便可以成功启动,然后看到如下画面:
二.安装配置JAVA JDK
因为我们希望能移植到Android平台,所以JAVA JDK是必须的,这里推荐使用64 bit JDK for windows 64bit。
下载完毕后,对其进行安装,本机路径为:G:\Java\jdk1.8.0_05
配置系统环境变量:
1.新建系统环境变量:
变量名:JAVA_HOME&&&&&&&&&变量值:G:\Java\jdk1.8.0_05
变量名:CLASSPATH&&&&&&&&& 变量值:.;%JAVA_HOME%\(注意点号)
配置系统环境变量:
2.在系统变量后继续添加:
&系统变量名:Path&&&&&&&&&&& 变量值:;%JAVA_HOME%\bin
到此,环境变量配置完毕。
然后在Dos窗口下输入java -version
(开始-&运行-&cmd 或 win+R)
若看到如下画面,则表明安装成功:
三.下载ADT包,配置Android SDK环境
ADT包中包含名为Eclipse的IDE开发工具,同时也包含了Android SDK,内置的ADT开发工具其作用为使得
Eclipse能够使用Android SDK组件(相当于一座桥梁),
(可能被墙,本人用了代理服务器顺利进去)
配置用户环境变量:
变量名:ANDROID_SDK&&&&&&&&&变量值:G:\adt-bundle-windows-x86_64-\sdk\G:\adt-bundle-windows-x86_64-\sdk\G:\adt-bundle-windows-x86_64-\sdk\platform-
变量名:path&&&&&&&&&&&&&&&&&&&&&&&&&&& 变量值:%ANDROID_SDK%( 如存在继续添加,记得用分号隔开)
然后在CMD下的DOS窗口中输入adb -h检验是否安装成功。
四.下载并安装python工具
这里的版本为python2.73,
1.下载并安装python2.73。
本机的路径为G:\Python27,
配置用户环境变量:
变量名path&&&&&&&&&&&&&&&&&&&&&&& 变量值:G:\Python27(在后面继续添加,记得用分号隔开)
在CMD下的DOS窗口中输入python,如如下图所示便表明安装成功:
五.配置安装Android NDK
先下载并安装Android NDK,64位系统推荐使用Windows 64-bit, 本机路径为:G:\android-ndk-r9d,
配置用户环境变量:
变量名:NDK_ROOT&&&& 变量值:G:\android-ndk-r9d
变量名:path&&&&&&&&&&&&&&&&&&变量值:%NDK_ROOT% (从已有后面添加,记得用分号隔开)
六.创建生成Cocos2dx工程项目。
在CMD下的DOS窗口中进入G:\cocos2d-x-3.2rc0\tools\cocos2d-console\bin目录中,
python&cocos.py&new&beyondTest&-p&com.cocos2dx.org&-l&cpp&-d&beyond&&
用cocos.py脚本创建项目工程,如图所示:
参数说明:
beyondTest为项目名称
-p后面接包名
-l后面接开发语言类型,有cpp, lua, js三种类型
-d后面接项目存放的目录
然后进入到G:\cocos2d-x-3.2rc0\tools\cocos2d-console\bin\beyond\beyondTest\proj.android目录中,
在CMD下输入python build_native.py对build_native.py脚本进行编译,
七.对Cocos2dx项目用真机进行测试运行。
1.打开Eclipse,设置Android NDK的路径(选择Windows-&Preferences-&NDK)
(注意:第一次启动Eclipse,会同时出现一个即时对话框,要求设置默认工作区(workspace)。
你可以选择默认设置,也可以按自己的需求设置(我将其设置为G:\Java\workspace))
2.在Project Explorer空白处点击鼠标右键,选择Import...
3.选择Exiting Android Code Into WorkSpace,点击Next。
4.浏览目录为G:\cocos2d-x-3.2rc0\tools\cocos2d-console\bin\beyond\beyondTest\proj.android,
然后点击Finish。
5.刚导入时发现有错误,原因是缺少java的org.cocos2dx.lib,如图:
(Windows-&Show View-&Problems可显示该界面)
6.我们将G:\cocos2d-x-3.2rc0\cocos\platform\android\java\src目录下的org文件覆盖到
G:\cocos2d-x-3.2rc0\tools\cocos2d-console\bin\beyond\beyondTest\proj.android\src目录下,
然后重新导入beyondTest工程,然后我们可以看到错误消失了:
1.用USB连接上带ANDROID系统的手机,然后鼠标右击该工程,或者选择菜单中的Run,
之后点击Run As-&Android Application,进入如下界面(可以看到我的ANDROID版本仍为2.3.4,
但不影响结果^0^),选择后点击Ok,便可以成功在真机上运行了。
最后来张运行成功后的截图:
发布于 5个月前,
阅读(7) | 评论(0) |
投票(0) | 收藏(0)
以前要导出c++类到lua,就得手动维护pkg文件,那简直就是噩梦,3.0以后就会感觉生活很轻松了。
下面我就在说下具体做法。
1、安装必要的库和工具包,以及配置相关环境变量,请按照cocos2d-x-3.0rc0\tools\tolua\README.mdown说得去做,不做赘述。
2、写c++类(我测试用的是cocos2d-x-3.0rc0\tests\lua-empty-test\project\Classes\HelloWorldScene.cpp)
3、写一个生成的python脚本,你不会写,没关系,我们会照猫画虎
& &1)进入目录cocos2d-x-3.0rc0\tools\tolua,复制一份genbindings.py,命名为genbindings_myclass.py
& &2)把生成目录制定到咱工程里去,打开genbindings_myclass.py把
output_dir='%s/cocos/scripting/lua-bindings/auto'%project_root
output_dir='%s/tests/lua-empty-test/project/Classes/auto'%project_root
&&3)修改命令参数,把
cmd_args={'cocos2dx.ini': ('cocos2d-x','lua_cocos2dx_auto'), \
&&& & & & & & & & &'cocos2dx_extension.ini': ('cocos2dx_extension','lua_cocos2dx_extension_auto'), \
&&& & & & & & & & &'cocos2dx_ui.ini': ('cocos2dx_ui','lua_cocos2dx_ui_auto'), \
&&& & & & & & & & &'cocos2dx_studio.ini': ('cocos2dx_studio','lua_cocos2dx_studio_auto'), \
&&& & & & & & & & &'cocos2dx_spine.ini': ('cocos2dx_spine','lua_cocos2dx_spine_auto'), \
&&& & & & & & & & &'cocos2dx_physics.ini': ('cocos2dx_physics','lua_cocos2dx_physics_auto'), \
&&& & & & & & & & &}
cmd_args={'myclass.ini': ('myclass','lua_myclass_auto') }
&&&&4)这时你可能问myclass.ini在哪啊,我们下来就写这个文件。原理一样,我还是照猫画虎,拿cocos2dx_spine.ini改的。
# the prefix to be added to the generated functions. You might or might not use this in your own
# templates
prefix = myclass
# create a target namespace (in javascript, this would create some code like the equiv. to `ns = ns || {}`)
# all classes will be embedded in that namespace
target_namespace =
android_headers = -I%(androidndkdir)s/platforms/android-14/arch-arm/usr/include -I%(androidndkdir)s/sources/cxx-stl/gnu-libstdc++/4.7/libs/armeabi-v7a/include -I%(androidndkdir)s/sources/cxx-stl/gnu-libstdc++/4.7/include
android_flags = -D_SIZE_T_DEFINED_&
clang_headers = -I%(clangllvmdir)s/lib/clang/3.3/include&
clang_flags = -nostdinc -x c++ -std=c++11
cocos_headers = -I%(cocosdir)s/cocos -I%(cocosdir)s/cocos/2d -I%(cocosdir)s/cocos/base -I%(cocosdir)s/cocos/ui -I%(cocosdir)s/cocos/physics -I%(cocosdir)s/cocos/2d/platform -I%(cocosdir)s/cocos/2d/platform/android -I%(cocosdir)s/cocos/math/kazmath -I%(cocosdir)s/extensions -I%(cocosdir)s/external -I%(cocosdir)s/cocos/editor-support -I%(cocosdir)s
cocos_flags = -DANDROID -DCOCOS2D_JAVASCRIPT
cxxgenerator_headers =
# extra arguments for clang
extra_arguments = %(android_headers)s %(clang_headers)s %(cxxgenerator_headers)s %(cocos_headers)s %(android_flags)s %(clang_flags)s %(cocos_flags)s %(extra_flags)s&
# what headers to parse
headers = %(cocosdir)s/tests/lua-empty-test/project/Classes/HelloWorldScene.h
# what classes to produce code for. You can use regular expressions here. When testing the regular
# expression, it will be enclosed in "^$", like this: "^Menu*$".
classes = HelloWorld
# what should we skip? in the format ClassName::[function function]
# ClassName is a regular expression, but will be used like this: "^ClassName$" functions are also
# regular expressions, they will not be surrounded by "^$". If you want to skip a whole class, just
# add a single "*" as functions. See bellow for several examples. A special class name is "*", which
# will apply to all class names. This is a convenience wildcard to be able to skip similar named
# functions from all classes.
rename_functions =
rename_classes =
# for all class names, should we remove something when registering in the target VM?
remove_prefix =
# classes for which there will be no "parent" lookup
classes_have_no_parents =
# base classes which will be skipped when their sub-classes found them.
base_classes_to_skip = Ref ProcessBase
# classes that create no constructor
# Set is special and we will use a hand-written constructor
abstract_classes =
# Determining whether to use script object(js object) to control the lifecycle of native(cpp) object or the other way around. Supported values are 'yes' or 'no'.
script_control_cpp = no
&&&&改的时候要注意这些行
prefix = myclass
target_namespace =
headers = %(cocosdir)s/tests/lua-empty-test/project/Classes/HelloWorldScene.h
classes = HelloWorld
abstract_classes =
4、下面要自动生成代码了,打开命令行工具,cd到cocos2d-x-3.0rc0\tools\tolua下,敲入
python genbindings_myclass.py
回车运行。如果前面没问题的话你会在cocos2d-x-3.0rc0\tests\lua-empty-test\project\Classes多了一个文件夹auto,然后把里面生成lua_myclass_auto.cpp和lua_myclass_auto.hpp加入拽如工程
5、把我们生成的个module在脚本引擎初始化的时候加入lua。
编辑AppDelegate.cpp,包含lua_myclass_auto.hpp头文件,在
LuaEngine* engine = LuaEngine::getInstance();
register_all_myclass(engine-&getLuaStack()-&getLuaState());
6、编译运行。这样HelloWorld这个类就被导出到lua了。
测试------------------------------------------------
打开hello.lua,编辑local function main()这个函数
把前面改成
localfunctionmain()
&&&-- avoid memory leak
&&&collectgarbage("setpause", 100)
&&&collectgarbage("setstepmul", 5000)
&&&localhello = HelloWorld:create()
&&&localsceneGame = cc.Scene:create()
&&&sceneGame:addChild(hello)
&&&cc.Director:getInstance():runWithScene(sceneGame)
&&&if(1==1)then
&&& & &return
发布于 5个月前,
阅读(11) | 评论(0) |
投票(0) | 收藏(0)
原文地址:http://blog.csdn.net/smfwuxiao/article/details/8523479
Android NDK r5 开始支持预编译库(动态库和静态库),即程序能使用库的预编译版本。
该特性可用于以下两方面:
1)向第三方NDK开发人员发布你的共享库而不用提供源码。
2)使用一个提前编译好的库(预编译库)来加速编译过程。
本文说明该特性如何工作。
I. 声明一个预编译库的模块
对于Android编译工具而言,每个预编译库必须声明为一个独立的模块。这里举一个例子,假设 libfoo.so 文件与 Android.mk 位于同一个目录:
LOCAL_PATH&:=&$(call&my-dir)&&
include&$(CLEAR_VARS)&&
LOCAL_MODULE&:=&foo-prebuilt&&
LOCAL_SRC_FILES&:=&libfoo.so&&
include&$(PREBUILT_SHARED_LIBRARY)&&
按以下步骤声明这样一个模块:
1. 给该模块取一个名字(这里是 foo-prebuilt)。这个名字不需要与预编译库自身的名字相同。
2. 将 LOCAL_SRC_FILES 指定为你要提供的共享库的路径。通常,该路径是相对于 LOCAL_PATH 的路径。注意:必须保证共享库ABI的兼容性。
3. 如果你的库是共享库,则包含 PREBUILT_SHARED_LIBRARY 而不是 BUILD_SHARED_LIBRARY;如果是静态库,则包含 PREBUILT_STATIC_LIBRARY。
预编译模块不需要编译。该预编译模块会被拷贝到 $PROJECT/obj/local 下面,还会被拷贝到 $PROJECT/libs/&abi& 下面(这里的库被strip过)。
II. 在其他模块中引用这个预编译库
在依赖该预编译库的模块对应的Android.mk中,将预编译库的名字(前面取的)加入到 LOCAL_STATIC_LIBRARIES 或 LOCAL_SHARED_LIBRARIES 声明中。例如,一个使用上面libfoo.so的简单例子如下:
include&$(CLEAR_VARS)&&
LOCAL_MODULE&:=&foo-user&&
LOCAL_SRC_FILES&:=&foo-user.c&&
LOCAL_SHARED_LIBRARIES&:=&foo-prebuilt&&
include&$(BUILD_SHARED_LIBRARY)&&
III. 将预编译库的头文件导出
得到预编译库之后,一般需要它对应的头文件。例如前面的libfoo.so,它有对应的foo.h。编译依赖libfoo.so的模块时,需要将该头文件和它的路径提供给NDK编译系统。一种简单方法是,前面在定义该预编译库的时候,使用LOCAL_EXPORT_C_INCLUDES 变量。例如,假设文件 foo.h 位于当前预编译模块所在目录的 include 子目录,则可以在预编译模块的Android.mk文件中编写如下:
include&$(CLEAR_VARS)&&
LOCAL_MODULE&:=&foo-prebuilt&&
LOCAL_SRC_FILES&:=&libfoo.so&&
LOCAL_EXPORT_C_INCLUDES&:=&$(LOCAL_PATH)/include&&
include&$(PREBUILT_SHARED_LIBRARY)&&
这个 LOCAL_EXPORT_C_INCLUDES 定义确保了任何依赖这个预编译库的模块会自动在自己的 LOCAL_C_INCLUDES 变量中增加到这个预编译库的include目录的路径,从而能够找到其中的头文件。
IV. 调试预编译库
建议你在预编译库中保留调试信息。位于 $PROJECT/libs/&abi& 的版本都是不含调试信息的(被NDK编译系统执行strip过的),调试版的库才能用于 ndk-gdb。
V. 共享库ABI的选择
如前所述,共享库与目标系统的ABI兼容性至关重要。应检查一下 TARGET_ARCH_ABI 的值,可以是以下值:
armeabi&&&&&&& 目标系统CPU是ARMv5TE或更高
armeabi-v7a&&& 目标系统CPU是ARMv7或更高
x86&&&&&&&&&&& 目标系统CPU是x86
注意,armeabi-v7a的CPU可以很好地执行armeabi的程序。
举一个例子,我们提供一个预编译库的两个版本,然后选择不同的ABI:
include&$(CLEAR_VARS)&&
LOCAL_MODULE&:=&foo-prebuilt&&
LOCAL_SRC_FILES&:=&$(TARGET_ARCH_ABI)/libfoo.so&&
LOCAL_EXPORT_C_INCLUDES&:=&$(LOCAL_PATH)/include&&
include&$(PREBUILT_SHARED_LIBRARY)&&
这里假设要拷贝的预编译库所在的目录结构如下:
&&& Android.mk&&&&&&&&&&& --& 编译这个预编译库的Android.mk
&&& armeabi/libfoo.so&&&& --& armeabi版本的共享库
&&& armeabi-v7a/libfoo.so --& armeabi-v7a版本的共享库
&&& include/foo.h&&&&&&&& --& 预编译库导出的头文件
注意:你不必提供armeabi-v7a版本,因为armeabi版本的共享库能够被armeabi-v7a的兼容,但是反过来就不行。
发布于 6个月前,
阅读(3) | 评论(0) |
投票(0) | 收藏(0)
1. 如果在已经处于 ESTABLISHED状态下的socket(一般由端口号和标志符区分)调用
closesocket(一般不会立即关闭而经历TIME_WAIT的过程)后想继续重用该socket:
BOOL bReuseaddr=TRUE;
setsockopt(s,SOL_SOCKET ,SO_REUSEADDR,(const char*)&bReuseaddr,sizeof(BOOL));
2. 如果要已经处于连接状态的soket在调用closesocket后强制关闭,不经历
TIME_WAIT的过程:
BOOL bDontLinger = FALSE;&
setsockopt(s,SOL_SOCKET,SO_DONTLINGER,(const char*)&bDontLinger,sizeof(BOOL));
3.在send(),recv()过程中有时由于网络状况等原因,发收不能预期进行,而设置收发时限:
int nNetTimeout=1000;//1秒
//发送时限
setsockopt(socket,SOL_S0CKET,SO_SNDTIMEO,(char *)&nNetTimeout,sizeof(int));
//接收时限
setsockopt(socket,SOL_S0CKET,SO_RCVTIMEO,(char *)&nNetTimeout,sizeof(int));
4.在send()的时候,返回的是实际发送出去的字节(同步)或发送到socket缓冲区的字节
(异步);系统默认的状态发送和接收一次为8688字节(约为8.5K);在实际的过程中发送数据
和接收数据量比较大,可以设置socket缓冲区,而避免了send(),recv()不断的循环收发:
// 接收缓冲区
int nRecvBuf=32*1024;//设置为32K
setsockopt(s,SOL_SOCKET,SO_RCVBUF,(const char*)&nRecvBuf,sizeof(int));
//发送缓冲区
int nSendBuf=32*1024;//设置为32K
setsockopt(s,SOL_SOCKET,SO_SNDBUF,(const char*)&nSendBuf,sizeof(int));
5. 如果在发送数据的时,希望不经历由系统缓冲区到socket缓冲区的拷贝而影响
程序的性能:
int nZero=0;
setsockopt(socket,SOL_S0CKET,SO_SNDBUF,(char *)&nZero,sizeof(nZero));
6.同上在recv()完成上述功能(默认情况是将socket缓冲区的内容拷贝到系统缓冲区):
int nZero=0;
setsockopt(socket,SOL_S0CKET,SO_RCVBUF,(char *)&nZero,sizeof(int));
7.一般在发送UDP数据报的时候,希望该socket发送的数据具有广播特性:
BOOL bBroadcast=TRUE;&
setsockopt(s,SOL_SOCKET,SO_BROADCAST,(const char*)&bBroadcast,sizeof(BOOL));
8.在client连接服务器过程中,如果处于非阻塞模式下的socket在connect()的过程中可
以设置connect()延时,直到accpet()被呼叫(本函数设置只有在非阻塞的过程中有显著的
作用,在阻塞的函数调用中作用不大)
BOOL bConditionalAccept=TRUE;
setsockopt(s,SOL_SOCKET,SO_CONDITIONAL_ACCEPT,(const char*)&bConditionalAccept,sizeof(BOOL));
9.如果在发送数据的过程中(send()没有完成,还有数据没发送)而调用了closesocket(),以前我们
一般采取的措施是"从容关闭"shutdown(s,SD_BOTH),但是数据是肯定丢失了,如何设置让程序满足具体
应用的要求(即让没发完的数据发送出去后在关闭socket)?
struct linger {
u_short l_
u_short l_
linger m_sL
m_sLinger.l_onoff=1;//(在closesocket()调用,但是还有数据没发送完毕的时候容许逗留)
// 如果m_sLinger.l_onoff=0;则功能和2.)作用相同;
m_sLinger.l_linger=5;//(容许逗留的时间为5秒)
setsockopt(s,SOL_SOCKET,SO_LINGER,(const char*)&m_sLinger,sizeof(linger));
Note:1.在设置了逗留延时,用于一个非阻塞的socket是作用不大的,最好不用;2.如果想要程序不经历SO_LINGER需要设置SO_DONTLINGER,或者设置l_onoff=0;
10.还一个用的比较少的是在SDI或者是Dialog的程序中,可以记录socket的调试信息:
(前不久做过这个函数的测试,调式信息可以保存,包括socket建立时候的参数,采用的
具体协议,以及出错的代码都可以记录下来)
BOOL bDebug=TRUE;
setsockopt(s,SOL_SOCKET,SO_DEBUG,(const char*)&bDebug,sizeof(BOOL));
11.附加:往往通过setsockopt()设置了缓冲区大小,但还不能满足数据的传输需求,
我的习惯是自己写个处理网络缓冲的类,动态分配内存;&一般的习惯是自己写个处理网络缓冲的类,动态分配内存;下面我将这个类写出,希望对大家有所帮助: &
//仿照String & &改写而成 &
//============================================================================== &
// &二进制数据,主要用于收发网络缓冲区的数据 &
// &CNetIOBuffer &以 &MFC &类 &CString &的源代码作为蓝本改写而成,用法与 &CString &类似, &
// &但是 &CNetIOBuffer &中存放的是纯粹的二进制数据,'\0' &并不作为它的结束标志。 &
// &其数据长度可以通过 &GetLength() &获得,缓冲区地址可以通过运算符 &LPBYTE &获得。 &
//============================================================================== &
// & &Copyright &(c) &All-Vision &Corporation. &All &rights &reserved. &
// & &Module: & &NetObject &
// & &File: & & & &SimpleIOBuffer.h &
// & &Author: & &gdy119 &
// & &Email &: & & &&& && && && &
// & &Date: & & & &
//============================================================================== &
// &NetIOBuffer.h &
#ifndef &_NETIOBUFFER_H &
#define &_NETIOBUFFER_H &
//============================================================================= &
#define & &MAX_BUFFER_LENGTH & & &
//============================================================================= &
//主要用来处理网络缓冲的数据 &
class & &CNetIOBuffer & & &
protected: &
&& && && &&LPBYTE & & & & & & & & & & & & & &m_pbinD &
&& && && &&int & & & & & & & & & & & & & & & & &m_nL &
&& && && &&int & & & & & & & & & & & & & & & & &m_nTotalL &
&& && && &&CRITICAL_SECTION && && && &&m_ &
& & & &void & &Initvalibers(); &
&& && && &&CNetIOBuffer(); &
&& && && &&CNetIOBuffer(const &LPBYTE &lbbyte, &int &nLength); &
&& && && &&CNetIOBuffer(const &CNetIOBuffer&binarySrc); &
&& && && &&virtual &~CNetIOBuffer(); &
//============================================================================= && && && && &
&& && && &&BOOL & & & & & &CopyData(const &LPBYTE &lbbyte, &int &nLe &
--------------------------------------------------------------- &
其实我觉得第5条很应该值得注意 &
int &nZero=0; &
setsockopt(socket,SOL_S0CKET,SO_SNDBUF,(char &*)&nZero,sizeof(nZero)); &
记得以前有些朋友讨论过,socket虽然send成功了,但是其实只是发送到数据缓冲区里面了,而并没有真正的在物理设备上发送出去;而通过这条语句,将发送缓冲区设置为0,即屏蔽掉发送缓冲以后,一旦send返回(当然是就阻塞套结字来说),就可以肯定数据已经在发送的途中了^_^,但是这样做也许会影响系统的性能 &
--------------------------------------------------------------- &
setoptsock()这个函数 &设置成端口复用的时候,很容易对一些没有进行单独bind模式的程序造成危害。 &
比如old的 &ping &icmp &door,简单的sniffer后,收到包,然后设置setoptsock &bind &web服务,然后建立个cmd进程 &bind再80端口。
======================================
Example Code
The following example demonstrates the& setsockopt&function.
#include &stdio.h&
#include "winsock2.h"
void main() {
//---------------------------------------
// Declare variables
WSADATA wsaD
SOCKET ListenS
//---------------------------------------
// Initialize Winsock
int iResult = WSAStartup( MAKEWORD(2,2), &wsaData );
if( iResult != NO_ERROR )
printf("Error at WSAStartup\n");
//---------------------------------------
// Create a listening socket
ListenSocket = socket( AF_INET, SOCK_STREAM, IPPROTO_TCP );
if (ListenSocket == INVALID_SOCKET) {
printf("Error at socket()\n");
WSACleanup();
//---------------------------------------
// Bind the socket to the local IP address
// and port 27015
h tent* thisH
port = 27015;
thisH t = geth tbyname("");
ip = inet_ntoa (*(struct in_addr *)*thisH t-&h_addr_list);
service.sin_family = AF_INET;
service.sin_addr.s_addr = inet_addr(ip);
service.sin_port = htons(port);
if ( bind( ListenSocket,(SOCKADDR*) &service, sizeof(service) ) == SOCKET_ERROR ) {
printf("bind failed\n");
cl esocket(ListenSocket);
//---------------------------------------
// Initialize variables and call& setsockopt.&
// The SO_KEEPALIVE parameter is a socket option&
// that makes the socket send keepalive messages
// on the session. The SO_KEEPALIVE socket option
// requires a boolean value to be passed to the
//& setsockopt&function. If TRUE, the socket is
// configured to send keepalive messages, if FALSE
// the socket configured to NOT send keepalive messages.
// This section of code tests the& setsockopt&function
// by checking the status of SO_KEEPALIVE on the socket
// using the getsockopt function.
BOOL bOptVal = TRUE;
int bOptLen = sizeof(BOOL);
int iOptLen = sizeof(int);
if (getsockopt(ListenSocket, SOL_SOCKET, SO_KEEPALIVE, (char*)&iOptVal, &iOptLen) != SOCKET_ERROR) {
printf("SO_KEEPALIVE Value: %ld\n", iOptVal);
if ( setsockopt(ListenSocket, SOL_SOCKET, SO_KEEPALIVE, (char*)&bOptVal, bOptLen) != SOCKET_ERROR) {
printf("Set SO_KEEPALIVE: ON\n");
if (getsockopt(ListenSocket, SOL_SOCKET, SO_KEEPALIVE, (char*)&iOptVal, &iOptLen) != SOCKET_ERROR) {
printf("SO_KEEPALIVE Value: %ld\n", iOptVal);
WSACleanup();
Notes for IrDA Sockets
Keep in mind the following:
The Af_irda.h header file must be explicitly included.&
IrDA provides the following settable socket option:&
Value Type Meaning&
IRLMP_IAS_SET *IAS_SET Sets IAS attributes&
The IRLMP_IAS_SET socket option enables the application to set a single attribute of a single class in the local IAS. The application specifies the class to set, the attribute, and attribute type. The application is expected to allocate a buffer of the necessary size for the passed parameters.
IrDA provides an IAS& &that stores IrDA-based information. Limited& &to the IAS& &is available through the& Sockets 2 interface, but such& &is not normally used by applications, and exists primarily to support connections to non- &devices that are not compliant with the& &Sockets 2 IrDA conventions.
The following structure, IAS_SET, is used with the IRLMP_IAS_SET& setsockopt&option to manage the local IAS& :
typedef struct _IAS_SET {
char irdaClassName[IAS_MAX_CLASSNAME];
char irdaAttribName[IAS_MAX_ATTRIBNAME];
u_long irdaAttribT
LONG irdaAttribI
u_char OctetSeq[IAS_MAX_OCTET_STRING];
} irdaAttribOctetS
u_char CharS
u_char UsrStr[IAS_MAX_USER_STRING];
} irdaAttribUsrS
} IAS_SET, *PIAS_SET, FAR *LPIAS_SET;
The following structure, IAS_QUERY, is used with the IRLMP_IAS_QUERY& setsockopt&option to query a peer's IAS& :
typedef struct _ _IAS_QUERY {
u_char irdaDeviceID[4];
char irdaClassName[IAS_MAX_CLASSNAME];
char irdaAttribName[IAS_MAX_ATTRIBNAME];
u_long irdaAttribT
LONG irdaAttribI
u_char OctetSeq[IAS_MAX_OCTET_STRING];
} irdaAttribOctetS
u_long CharS
u_char UsrStr[IAS_MAX_USER_STRING];
} irdaAttribUsrS
} IAS_QUERY, *PIAS_QUERY, FAR *LPIAS_QUERY;
Many SO_ level socket options are not meaningful to IrDA. Only SO_LINGER is specifically supported.
Note& setsockopt&must be called before bind on& &NT 4.0,& &95, and& &98 platforms.
Requirements
Client Requires& &XP,& &2000 Professional,& &NT Workstation,& &Me,& &98, or& &95.&
Server Requires& &Server 2003,& &2000 Server, or& &NT Server.&
Header Declared in Winsock2.h.
Library Link to Ws2_32.lib.
DLL Requires Ws2_32.dll.&
Trackback: [url]http://tb.blog.csdn.net/TrackBack.aspx?PostId=1745293[/url]
发布于 1年前,
阅读(146) | 评论(0) |
投票(0) | 收藏(17)
原因有2个:
1、在子线程中是不能进行UI 更新的,而可以更新的结果只是一个幻像:因为子线程代码执行完毕了,又自动进入到了主线程,执行了子线程中的UI更新的函数栈,这中间的时间非常的短,就让大家误以为分线程可以更新UI。如果子线程一直在运行,则子线程中的UI更新的函数栈 主线程无法获知,即无法更新
2、只有极少数的UI能,因为开辟线程时会获取当前环境,如点击某个按钮,这个按钮响应的方法是开辟一个子线程,在子线程中对该按钮进行UI 更新是能及时的,如换标题,换背景图,但这没有任何意义
1、程序一开始运行就进入了主线程
2、处理某些数据太过费时,影响用户交互,可以开辟子线程处理,然后通知主线程进行界面更新
测试代码:
开辟一个多线程,直接在子线程里进行ui 更新:
-(void)testUIRefresh:(UIButton *)button{
&&&&&&&[NSThread detachNewThreadSelector:@selector(beginTest) toTarget:self withObject:nil];
-(void)beginTest&{
&&&&&&&NSLog(@” 当前线程&&&currentThread]);
&&&&&&&NSLog(@” 主线程&&&&&mainThread]);
&&&&&&//该button 为&&响应 testUIRefresh的button
&&&&&&&[self.button setTitle:@"AAA" forState:0];
12:14:02.147 TestProj[]&&当前线程&&{name = (null), num = 3}
12:14:02.147 TestProj[]&&主线程&&&&{name = (null), num = 1}
结果:当前的确是在子线程中,但是UI马上更新了??
结果分析:大家都说UI更新在主线程中做,上面的结果怎么解释
假设:如果在子线程里做了UI更新,待子线程运行完毕,程序自动进入 主线程进行指定的ui更新!
问题:如果子线程没结束呢?
在分线程中加入:
-(void)beginTest{
&&&&&&NSLog(@” 当前线程&&&currentThread]);
&&&&&&NSLog(@” 主线程&&&&&mainThread]);
&&&&&&//该button 为&&响应 testUIRefresh的button
&&&&&&[self.button setTitle:@"AAA" forState:0];
&&&&&&[NSThread sleepForTimeInterval:4.0];
self.button的title还是马上更新了
结果分析:难道上面的假设不成立?
问题:这次在分线程中add 一个button
-(void)beginTest{
&&&&&&NSLog(@” 当前线程&&&currentThread]);
&&&&&&NSLog(@” 主线程&&&&&mainThread]);
&&&&&&//该button 为&&响应 testUIRefresh的button
&&&&&&[self.button setTitle:@"AAA" forState:0];
&&&&&&UIButton *backButton = [UIButton buttonWithType:UIButtonTypeCustom];
&&&&&&[backButton setTitle:@"测试runloop" forState:0];
&&&&&&[backButton setTitleColor:[UIColor redColor] forState:0];
&&&&&&backButton.frame = CGRectMake(100, 200, 100, 50);
&&&&&&[backButton addTarget:self action:@selector(testRunLoop)&&&forControlEvents:UIControlEventTouchUpInside];
&&&&&&[self.window addSubview:backButton];
&&&&&&[NSThread sleepForTimeInterval:4.0];
结果:[self.button setTitle:@"AAA" forState:0];马上响应了,但是添加的这个Button却一直等到线程结束才绘制出来
分析:在子线程中:如果要对其他UI 进行更新,则必须等到该子线程运行结束,而对响应用户点击的Button的UI更新则是及时的!不管他是在主线程还是在子线程中做的更新,意义都不大了,因为子线程中对所有其他ui更新都要等到该子线程生命周期结束才进行。
1、在子线程中是不能进行UI 更新的,而可以更新的结果只是一个幻像:因为子线程代码执行完毕了,又自动进入到了主线程,执行了子线程中的UI更新的函数栈,这中间的时间非常的短,就让大家误以为分线程可以更新UI。如果子线程一直在运行,则子线程中的UI更新的函数栈 主线程无法获知,即无法更新
2、只有极少数的UI能,因为开辟线程时会获取当前环境,如点击某个按钮,这个按钮响应的方法是开辟一个子线程,在子线程中对该按钮进行UI 更新是能及时的,如换标题,换背景图,但这没有任何意义
发布于 1年前,
阅读(493) | 评论(1) |
投票(0) | 收藏(2)
&魔兽时间是暴雪著名的网络游戏,我以前也玩过一段时间的战士,这款游戏目前已进入晚年时期,不过里面各种丰富的游戏系统和游戏内容都非常让人印象深刻。开源的项目模拟魔兽服务器端非常成功,目前国内外也有不少基于Mangos模拟器而搭建的私服,多数服务端运转良好,非常稳定。国外有一个叫做MonsterWOW的魔兽私服,单服承载5000人,总共有几组服务器,几万人同时在线,这是我在网站上亲眼看到的实时数据,一般来讲,如果对MMORPG游戏服务端稍微熟悉都知道,5000人同服在线,而且允许游戏逻辑的是一台单独的服务器,支撑这么庞大一个游戏世界,肯定有非常过人之处,至少据我所知国内的单服性能与之相比都有较大差距,国内分布式的服务端架构基本也是将游戏逻辑分散到多台服务器上,单一世界承载数量也不算很高。几年前的Eve Online单一世界可以承载两万多玩家同时在线、实时交互。我想国内多数MMORPG服务端的承载人数应该都是在七八百、一两千这个数量级的。Mangos的源代码下载下来好久了,一直没时间研究,它目前是C++写成的,我的主要方向是C#,不过我一直有将C#做游戏服务端的打算,所以既然它有那么多过人之处,就算不能掌握全部也应该研究学习一下。
&&&& 今天粗略地看了一下,服务端主要又三大块组成,数据库、服务端逻辑、脚本。数据库用的MySQL,这里不是很关键暂且不说,脚本有自己的脚步引擎,简单的任务、战斗等都可以通过数据库配置相应条目来完成,复杂的战斗AI等在脚步库中由C++直接写成,这个脚本库是要被编译为机器代码的,执行效率相当高效,例如巫妖王的战斗比较复杂就用C++写,其它简单的就配置在数据库中由脚步引擎来驱动执行。国内不少服务端都是非常老式的C++早期服务端结构,不少嵌入了lua解释器,大量的写lua脚本,甚至用lua写逻辑。我个人很不理解这种方式,你说效率高吧,lua再快能多块,解释执行和编译执行不是一个数量级的,看看服务端的承载人数就知道了,lua JIT即时编译都不靠谱。或许有人会说lua简单,策划都可以学习之后写脚本,事实上却是写脚本的人写出一大堆的不敢说垃圾代码,也算是低质量代码,这样更加拖累服务端的性能了。为何不学学一些比较优秀的项目,也来想办法搞一个脚本引擎,然后写出工具就可以让策划配置大量的任务、战斗这些游戏内容,复杂的逻辑直接由游戏程序员来编写,用C++、C#多好,搞不懂为什么lua已经成为好多公司的标准了,就算不是lua也是python。就说剑网3这个游戏吧,我玩了两年多的剑纯阳,对这款游戏的体验有足够的了解。我们不和其它游戏的游戏比,至少在国内算优秀作品,也取得了一定的成功,虽然说抄魔兽也有点多。以前玩游戏的时候,二十多个人进个副本放些技能卡得要命,人多了在一个地图直接卡到爆,后来一个好朋友和我说,剑网3服务端用lua写了好多东西,能lua的多半都用lua了,一个天子峰老6,这个Boss的lua脚本竟有好几个lua文件,每个文件几百行代码,我想啊,服务端完全充斥着这种低质量的脚本,还谈什么效率,谈什么承载人数,能跑起来就不错了。关键是那个Boss的战斗并不复杂,和魔兽很多Boss比起来就算是非常简单的Boss了,mangos服务端一个复杂Boss的代码都比这个简单很多,代码总数也仅两百多行,执行效率更不是一个数量级的。这里发发牢骚,不用较真,言归正传。
&&&& Mangos服务端是一个多线程、逻辑单线程的服务端。每个线程内部都采用循环结构,主线程启动后将创建多个工作线程,主要包括负责游戏世界运作的核心线程,具有处理用户请求,执行定时器的能力。其它几个工作线程还有网络Io,该线程启动后其内部将使用线程池进行网络Io操作,不间断地接收数据包,并存储到相关玩家的消息队列中,由世界线程进行处理,其它几个工作线程先不讨论,因为今天也是第一次看mangos的源代码.务端启动后这些线程将永不停息地工作。世界线程是服务器的核心,负责处理所有玩家操作请求,定时器、AI等。以下是世界线程启动后执行的代码:
/// Heartbeat for the World void WorldRunnable::run()
{ ///- Init new SQL thread for the world database WorldDatabase.ThreadStart(); // let thread do safe mySQL requests (one connection call enough)
sWorld.InitResultQueue();
uint32 realCurrTime = 0;
uint32 realPrevTime = WorldTimer::tick();
uint32 prevSleepTime = 0; // used for balanced full tick time length near WORLD_SLEEP_CONST ///- While we have not World::m_stopEvent, update the world while (!World::IsStopped())
{ ++World::m_worldLoopC
realCurrTime = WorldTimer::getMSTime();
uint32 diff = WorldTimer::tick();
sWorld.Update(diff);
realPrevTime = realCurrT // diff (D0) include time of previous sleep (d0) + tick time (t0) // we want that next d1 + t1 == WORLD_SLEEP_CONST // we can't know next t1 and then can use (t0 + d1) == WORLD_SLEEP_CONST requirement // d1 = WORLD_SLEEP_CONST - t0 = WORLD_SLEEP_CONST - (D0 - d0) = WORLD_SLEEP_CONST + d0 - D0 if (diff &= WORLD_SLEEP_CONST + prevSleepTime)
prevSleepTime = WORLD_SLEEP_CONST + prevSleepTime -
ACE_Based::Thread::Sleep(prevSleepTime);
} else prevSleepTime = 0;
#ifdef WIN32 if (m_ServiceStatus == 0) World::StopNow(SHUTDOWN_EXIT_CODE); while (m_ServiceStatus == 2) Sleep(1000); #endif }
sWorld.CleanupsBeforeStop();
sWorldSocketMgr-&StopNetwork();
MapManager::Instance().UnloadAll(); // unload all grids (including locked in memory) ///- End the database thread WorldDatabase.ThreadEnd(); // free mySQL thread resources }
因为是直接粘贴的,看上去比较乱,这里先作一下说明,这是世界线程的根循环结构,在while(!World::IsStopped())内部只有一个核心函数调用,其他都是一些控制更新时间之类的代码,不用太关注:
sWorld.Update(diff);
sWorld是单一实例的World对象,它代表了整个游戏世界,和多数MMORPG一样,启动后进入根循环,在运行内部一直调用更新整个游戏世界的Update函数,服务端不停的Update游戏世界,每次Update能在100毫秒内完成,则客户端会感到非常流畅。在根循环退出后,清理服务器相关资源,线程结束被回收。Mangos使用的是开源跨平台的网络、线程处理库ACE,这个东西粗略的看了一下,比较复杂,如果要研究透彻是很困难的事,这里提一下,不对ACE探讨。到这里我们仅仅需要关注一个函数了,就是World的Update方法内部到底在干什么?
void World::Update(uint32 diff)
{ ///- Update the different timers for (int i = 0; i & WUPDATE_COUNT; ++i)
{ if (m_timers[i].GetCurrent() &= 0)
m_timers[i].Update(diff); else m_timers[i].SetCurrent(0);
} ///- Update the game time and check for shutdown time
_UpdateGameTime(); ///-Update mass mailer tasks if any
sMassMailMgr.Update(); /// Handle daily quests reset time if (m_gameTime & m_NextDailyQuestReset)
ResetDailyQuests(); /// Handle weekly quests reset time if (m_gameTime & m_NextWeeklyQuestReset)
ResetWeeklyQuests(); /// Handle monthly quests reset time if (m_gameTime & m_NextMonthlyQuestReset)
ResetMonthlyQuests(); /// Handle monthly quests reset time if (m_gameTime & m_NextCurrencyReset)
ResetCurrencyWeekCounts(); /// &ul&&li& Handle auctions when the timer has passed if (m_timers[WUPDATE_AUCTIONS].Passed())
m_timers[WUPDATE_AUCTIONS].Reset(); ///- Update mails (return old mails with item, or delete them) //(tested... works on win) if (++mail_timer & mail_timer_expires)
mail_timer = 0;
sObjectMgr.ReturnOrDeleteOldMails(true);
} ///- Handle expired auctions
sAuctionMgr.Update();
} /// &li& Handle AHBot operations if (m_timers[WUPDATE_AHBOT].Passed())
sAuctionBot.Update();
m_timers[WUPDATE_AHBOT].Reset();
} /// &li& Handle session updates
UpdateSessions(diff); /// &li& Handle weather updates when the timer has passed if (m_timers[WUPDATE_WEATHERS].Passed())
{ ///- Send an update signal to Weather objects for (WeatherMap::iterator itr = m_weathers.begin(); itr != m_weathers.end();)
{ ///- and remove Weather objects for zones with no player // As interval & WorldTick if (!itr-&second-&Update(m_timers[WUPDATE_WEATHERS].GetInterval()))
delete itr-&
m_weathers.erase(itr++);
m_timers[WUPDATE_WEATHERS].SetCurrent(0);
} /// &li& Update uptime table if (m_timers[WUPDATE_UPTIME].Passed())
uint32 tmpDiff = uint32(m_gameTime - m_startTime);
uint32 maxClientsNum = GetMaxActiveSessionCount();
m_timers[WUPDATE_UPTIME].Reset();
LoginDatabase.PExecute(&UPDATE uptime SET uptime = %u, maxplayers = %u WHERE realmid = %u AND starttime = & UI64FMTD, tmpDiff, maxClientsNum, realmID, uint64(m_startTime));
} /// &li& Handle all other objects ///- Update objects (maps, transport, creatures,...)
sMapMgr.Update(diff);
sBattleGroundMgr.Update(diff);
sOutdoorPvPMgr.Update(diff); ///- Delete all characters which have been deleted X days before if (m_timers[WUPDATE_DELETECHARS].Passed())
m_timers[WUPDATE_DELETECHARS].Reset();
Player::DeleteOldCharacters();
} // execute callbacks from sql queries that were queued recently
UpdateResultQueue(); ///- Erase corpses once every 20 minutes //每20分钟清除尸体 if (m_timers[WUPDATE_CORPSES].Passed())
m_timers[WUPDATE_CORPSES].Reset();
sObjectAccessor.RemoveOldCorpses();
} ///- Process Game events when necessary //处理游戏事件 if (m_timers[WUPDATE_EVENTS].Passed())
m_timers[WUPDATE_EVENTS].Reset(); // to give time for Update() to be processed uint32 nextGameEvent = sGameEventMgr.Update();
m_timers[WUPDATE_EVENTS].SetInterval(nextGameEvent);
m_timers[WUPDATE_EVENTS].Reset();
} /// &/ul& ///- Move all creatures with &delayed move& and remove and delete all objects with &delayed remove&
sMapMgr.RemoveAllObjectsInRemoveList(); // update the instance reset times
sMapPersistentStateMgr.Update(); // And last, but not least handle the issued cli commands
ProcessCliCommands(); // cleanup unused GridMap objects as well as VMaps
sTerrainMgr.Update(diff);
这是World::Update函数的全部代码,服务器循环执行这些代码,每一次执行就能更新一次游戏世界。这个函数看似比较长,实际上不算很长,其中的关键之处在于首先是根据定时器来执行特定的任务,而执行这些任务则是通过调用各个模块的Manager来完成,比如游戏世界里面的尸体每20分钟清除一次,就检测相关的定时器是否超时,超时则清理尸体,然后重置定时器。通过这些定时器,来执行游戏中由服务器主动完成的任务,这些任务基本上是通过定时器来启动的。游戏中的天气系统、PvP系统、地形系统等等都根据定时器指定的频率进行更新。除了更新各个模块之外,其中还有个非常重要的调用:
UpdateSessions(diff);
如果翻译过来就是更新所有会话,服务器端为每一个客户端建立一个Session,即会话,它是客户端与服务端沟通的通道,取数据、发数据都得通过这条通道,这样客户端和服务端才能沟通。在mangos的构架中,Session的作用非常重要,但其功能不仅仅取客户端发过来的数据、将服务端数据发给客户端那么简单,后面会继续结束这个Session,很关键的东西,下面是UpdateSessions的具体实现:
void World::UpdateSessions(uint32 diff)
{ ///- Add new sessions WorldSession* while (addSessQueue.next(sess))
AddSession_(sess); ///- Then send an update signal to remaining ones for (SessionMap::iterator itr = m_sessions.begin(), itr != m_sessions.end(); itr = next)
next = ++ ///- and remove not active sessions from the list WorldSession* pSession = itr-&
WorldSessionFilter updater(pSession); if (!pSession-&Update(updater))
RemoveQueuedSession(pSession);
m_sessions.erase(itr);
其内部结构很简单,主要遍历所有会话,移除不活动的会话,并调用每个Session的Update函数,达到更新所有Session的目的,有1000玩家在线就会更新1000个会话,前面提到了Session,每个会话的内部都挂载有一个消息队列,这里队列存储着从客户端发过来的数据包,1000个会话就会有1000个数据包队列,队列是由网络模块收到数据包后,将其挂载到相应Sesson的接收队列中,客户端1发来的数据包被挂载到Session1的队列,客户端2的就挂载到Session2的队列中。mangos的架构中Session不止是收发数据的入口,同样也是处理客户端数据的入口,即处理客户端请求的调度中心。每次Update Session的时候,这个Update 函数的内部会取出队列中所有的请求数据,循环地对每一个数据包调用数据包对应的处理代码,即根据数据包的类型(操作码OpCode)调用相应的函数进行处理,而这些“相应的函数”是Session内部的普通成员函数,以HandleXXXXXX开头,为了便于理解,我将Session的Update函数主体核心代码写在这里:
bool WorldSession::Update(PacketFilter& updater)
{ ///- Retrieve packets from the receive queue and call the appropriate handlers /// not process packets if socket already closed WorldPacket* packet = NULL; while (m_Socket && !m_Socket-&IsClosed() && _recvQueue.next(packet, updater))
OpcodeHandler const& opHandle = opcodeTable[packet-&GetOpcode()];
ExecuteOpcode(opHandle, packet);
这样看起了比较清楚了,Session在Update的时候,取出所有数据包,每个数据包都有一个操作码,opcode,魔兽模拟器有1600多个操作码,玩家或者服务器的每个操作都有一个对应的操作码,比如攻击某个目标、拾取一件东西、使用某个物品都有操作码,被追加到数据包头部,这样每次取数据包的操作码,就可以查找相应的处理代码来处理这个数据包。
从代码里面可以看到opHandle就是根据操作码查找到的数据处理程序,内部有相应数据处理函数的指针,ExecuteOpcode即是通过这个函数指针调用该函数来处理数据包。而处理函数实际上都是 Session的普通成员函数,当然调度处理代码的时候并非根据操作码进行switch判断来调用相应处理函数,这样会写一个非常巨大的switch结构,mangos的方式是通过硬编码将这些处理函数的地址存在opcodeTable这个全局的表结构中,使用OpCode作为索引,迅速地定位到相应的处理函数,即找到改数据包对应的Handler,并执行他们。
void HandleGroupInviteOpcode(WorldPacket& recvPacket);&
void HandleGroupInviteResponseOpcode(WorldPacket& recvPacket);&
void HandleGroupUninviteOpcode(WorldPacket& recvPacket);&
void HandleGroupUninviteGuidOpcode(WorldPacket& recvPacket);&
void HandleGroupSetLeaderOpcode(WorldPacket& recvPacket);
void HandleGroupDisbandOpcode(WorldPacket& recvPacket);&
void HandleOptOutOfLootOpcode(WorldPacket& recv_data);&
void HandleSetAllowLowLevelRaidOpcode(WorldPacket& recv_data);
void HandleLootMethodOpcode(WorldPacket& recvPacket);&
void HandleLootRoll(WorldPacket& recv_data);
void HandleRequestPartyMemberStatsOpcode(WorldPacket& recv_data);&
void HandleRaidTargetUpdateOpcode(WorldPacket& recv_data);&
void HandleRaidReadyCheckOpcode(WorldPacket& recv_data);&
void HandleRaidReadyCheckFinishedOpcode(WorldPacket& recv_data);
void HandleGroupRaidConvertOpcode(WorldPacket& recv_data);&
void HandleGroupChangeSubGroupOpcode(WorldPacket& recv_data);&
void HandleGroupAssistantLeaderOpcode(WorldPacket& recv_data);
void HandlePartyAssignmentOpcode(WorldPacket& recv_data);
上面是极小部分的处理函数,他们都是Session的成员函数,这些函数并非是最终处理数据的,往往一个函数对应一个逻辑模块,与这个模块相关的操作码有很多,比如聊天系统客户端发来的操作码可能是密聊、队聊、地图聊天,但是在Session收到数据包时,会将这个模块的这些操作码都调用HandleMessage函数,这些Handle函数内部会根据具体的操作码再调用相应模块的处理函数,就是说消息的调度是两级的。先从入口点,通过查找OpCodeTabel找到一级调度函数、数据包传过去后又进行二级调度,分发到更小的子模块,直到分发的具体模块为止。
今天暂时写到这里,还有很多想说的,以后继续慢慢吹,下次继续今天没完善的内容、谈一谈mangos的二进制协议、数据通信机制等内容,长期研究下mangos,肯定有好处的。
发布于 2年前,
阅读(500) | 评论(2) |
投票(0) | 收藏(25)

我要回帖

更多关于 mangos编译教程 的文章

 

随机推荐