5.2 模型创建
-
Xml 基本节点
-
project
应用的根
namespace,state,service,zdb 节点均可以定义在project节点下,命名了名字空间的根
<project name="limax" xmlns:xi="http://www.w3.org/2001/XInclude"> </project>
定义项目limax, 启用XInclude支持
-
state
定义在project节点下,被manager节点引用
网络服务器可以运行在多个状态下,state节点定义了该状态下网络服务器允许接受的protocol,rpc——尽管强烈推荐使用View,但是protocol,rpc也可以支持,毕竟View本身在系统内部也是通过protocol实现
<state name="XXX"> <namespace ref="a"/> <protocol ref="b.a"/> <rpc ref="b.c"/> </state>
定义状态XXX,引入了a名字空间下的所有protocol,rpc,b名字空间下的a协议,b名字空间下的c协议。
名字空间允许嵌套,为了避免混淆,使用state引用名字空间时,只允许引用最外层。
-
namespace
定义在project,namespace节点下,被state节点引用
namespace, bean, protocol, rpc, view 均可以定义在namespace下
<namespace name="gs" pvid="12"> </namespace>
定义了名字空间名gs,设定该名字空间的pvid为12。名字空间嵌套的情况下,最外层pvid有效,内层pvid被忽略。
-
bean
定义在project,namespace下,作为结构化类型被用来定义变量
enum,variable可定义在bean下
<bean name="b1"> <enum name="e0" value="1"/> <variable name="v0" type="int"/> <variable name="v1" type="binary"/> </bean> <bean name="b2"> <variable name="a0" type="b1"> <variable name="a1" type="string"/> <variable name="a2" type="list" value="float"/> <variable name="a3" type="vector" value="b1"/> <variable name="a4" type="set" value="b1"/> <variable name="a5" type="map" key="int" value="b1"/> </bean>
在这里,bean b2引用了b1,具体的类型映射后文描述。
-
protocol
定义在namespace下,被state引用
protocol下可定义enum,variable,与bean类似
<protocol name="CHandShake" type="101" maxsize="1030"> <variable name="dh_group" type="byte" /> <variable name="dh_data" type="binary"/> </protocol>
定义了101号协议CHandShake,该协议允许的最大尺寸为1030。一个pvid——最外层名字空间决定——下的协议编号不可重复,最大尺寸定义为0表示不限制,实际运行环境中服务器存在一个约束所有协议尺寸的硬限制,可以通过虚拟机运行参数调整,java -D limax.net.Engine.limitProtocolSize=xxx …,默认为1M。
-
rpc
定义在namespace下,被state引用
<bean name="CheckProviderKeyArg"> <variable name="pvid" type="int" /> <variable name="pvkey" type="string"/> </bean> <bean name="CheckProviderKeyRes"> <variable name="errorcodes" type="int" /> </bean> <rpc name="CheckProviderKey" type="305" argument="CheckProviderKeyArg" result="CheckProviderKeyRes" maxsize="1024" timeout="10000" />
定义了305号rpc, CheckProviderKey, 允许最大尺寸1024, 10000毫秒超时, rpc参数和结果必须是bean。rpc与protocol在同一空间下编号, 互相不可重复, 最大尺寸单独限制参数和结果, 应该取两者的最大值。
特别注意,rpc仅在java环境下支持,提供给limax框架自身使用, java之外的客户端版本均不提供支持,不建议使用。
-
view
定义在namespace下,被state引用。
不包含ref节点的bind节点,不可引用包含any类型xbean的table;包含ref节点的bind节点,ref指向的xbean字段不能包含any类型。
<view name="globalview" lifecycle="global"> <variable name="var4" type="list" value="MyBeanAll" /> <bind name="bindfirst" table="first"> <ref name="s" /> <ref name="sets" /> </bind> <bind name="bindsecond" table="second" /> <control name="control1"> <variable name="var1" type="int" /> <variable name="var2" type="MyBean" /> </control> </view>
定义了全局globalview,包含字段var4,bindfirst,bindsecond,控制control1。var4为一般字段,通过在view对象上set来改变;bindfirst为bind字段,first表的value结构中s,sets两字段任何一个发生变动,bindfirst被设置成value.s,value.sets组织起来的结构;bindsecond为bind字段,second表的value发生变动,bindsecond被设置为value;contol1为control,参数为var1,var2组织起来的结构。
<namespace name="gs" pvid="12"> <namespace name="for_session"> <view name="firstview" lifecycle="session"> <variable name="var1" type="int" /> <bind name="bindsecond" table="second" /> </view> </namespace> <namespace name="for_temp"> <view name="TestTempView" lifecycle="temporary"> <variable name="var1" type="int" /> <subscribe name="_var1" ref="gs.for_session.firstview.var1" /> <subscribe name="_bindsecond" ref="gs.for_session.firstview.bindsecond" /> </view> </namespace> </namespace>
I在这里,临时View TestTempView的字段var1发生变动,所有TestTempView成员都将收到这一变动。TestTempView的订阅字段_var1,_bindsecond分别订阅了会话View firstview的var1,bindsecond字段,TestTempView成员中,某一成员的firstview的var1或者bindsecond字段发生变动,该变动通过_var1或者_bindsecond被广播给所有成员。实际上_var1,_bindsecond被作为map实现,key为SessionId。特别要注意的是,成员用户的firstview.var1发生变动,该用户将收到两个变动,firstview.var1和TestTempView._var1[SessionId]。
-
service
定义在project下,描述了该项目支持的服务,一个项目下允许多个
<service name="switcher"> <manager name="SwitcherServer" type="server" initstate="EndpointKeyExchange" port="10000"> <state ref="EndpointSessionLogin"/> <state ref="EndpointClient" /> </manager> <manager name="ProviderServer" type="server" initstate="ForProvider" port="10100" /> <manager name="AuanyClient" type="client" initstate="AuanyClient" port="10200" /> </service>
这是limax Switcher服务器组件的定义。
一个服务器由一系列网络服务构成,manager定义了网络服务。SwitcherServer作为网络服务器运行在10000端口上,接受客户端的连接请求;ProviderServer作为网络服务器运行在10100端口上,接受Provider的连接请求;AuanyClient作为Auany服务器组件的客户端,连接Auany的端口10200。
一个网络服务可以运行在一个或多个状态下,通过state引用了该状态下允许的protocol,rpc,view,初始状态由属性initstate指定,其他状态定义在manager节点内。
<state name="GsProvider"> <namespace ref="gs" /> </state> <service name="demogsd" useGlobalId="true" useZdb="true"> <manager name="Provider" type="provider" initstate="GsProvider" port="10100"/> </service>
这里定义了服务demogsd,注意manager下的type为provider,意味着生成provider需要的代码,前面提到实现limax应用一般就是实现provider,这是最常用的。事实上provider是Switcher服务器组件的客户端,所以port 10100决定了该provider连接Switcher服务器的10100端口。同时provider又是应用客户端的服务器,应用客户端与provider之间通过Switcher转发protocol,rpc,view数据。
useGlobalId, 在服务配置文件中生成使用GlobalId服务组件需要的配置。
useZdb,在服务配置文件中生成使用Zdb的启动配置模板。
特别要注意,provider模式下的manager仅支持一个状态,定义多个没有意义,并且该状态只能引用一个namespace,namespace的PVID被作为该provider的PVID。
-
zdb
描述了应用使用的数据库的全部信息,一个应用只允许使用一个zdb数据库,project下最多定义一个。
-
cbean
定义在project,namespace,zdb下。
结构化类型作为表的key,map的key,set的value时,必须定义为cbean,与bean的定义类似,字段成员的类型可以是除binary和any以外的基本类型以及cbean类型,不能使用容器类型。
特别的,cbean可以被bean,protocol,rpc,view,control引用。
<cbean name="xcompare"> <variable name="b" type="boolean" /> <variable name="s" type="short" /> <variable name="i" type="int" /> <variable name="l" type="long" /> <variable name="text" type="string" /> </cbean> <cbean name="xcompare2"> <enum name="eX" value="1" /> <variable name="xc1" type="xcompare" /> </cbean>
-
xbean
定义在zdb下
结构化类型作为表的value时,必须定义为xbean,与bean的定义类似,字段成员类型可以是各种基本类型,容器类型,如果需要嵌套结构化类型可以使用cbean或者xbean,既然cbean是常量bean,意味着该嵌套类型的对象没有修改的机会。xbean支持一种特殊类型any,any类型不可持久化,使用了any类型的xbean作为表的value时,该表只能是内存表。
特别的,不包含any类型的xbean可以被bean,protocol,rpc,view,view,control通过variable节点引用。bind不能完整引用值为包含any类型的xbean的table,但是bind部分引用非any成员是允许的。
<xbean name="BasePlayerInfo"> <variable name="name" type="string" /> <variable name="level" type="int" /> <variable name="sid" type="long" /> </xbean> <xbean name="WaitingPlayerInfo"> <variable name="baseinfo" type="BasePlayerInfo" /> <variable name="ready" type="boolean" /> </xbean> <xbean name="ObservePlayerInfo"> <variable name="baseinfo" type="BasePlayerInfo"/> <variable name="obpos" type="int" /> </xbean> <xbean name="WaitingTableInfo"> <enum name="POS_EAST" value="0"/> <enum name="POS_SOUTH" value="1"/> <enum name="POS_WEST" value="2"/> <enum name="POS_NORTH" value="3" /> <enum name="POS_COUNT" value="4"/> <enum name="POS_OBSERVE" value="4" /> <variable name="tableid" type="int"/> <variable name="players" type="vector" value="WaitingPlayerInfo" /> <variable name="observe" type="vector" value="ObservePlayerInfo"/> <variable name="playing" type="boolean" /> </xbean> <xbean name="RoomInfo"> <variable name="name" type="string" capacity="32" /> <variable name="hallid" type="long" /> <variable name="players" type="set" value="long" /> <variable name="tables" type="vector" value="WaitingTableInfo"/> </xbean>
这里定义了一个比较复杂的xbean结构。
<xbean name="Any"> <variable name="any" type="any:Object" capacity="32"/> <variable name="anyset" type="set" value="any:Object"/> <variable name="boolv" type="boolean" /> </xbean>
这里定义了包含any类型的xbean,Object是java.lang.Object,可以引用任何对象。any冒号之后可以引用所有合法的java类型以及描述中出现的普通bean,view,因为bean,view本身在生成代码以后也会生成对应的java类。
<view name="Info" lifecycle="session"> <variable name="info" type="BasePlayerInfo"/> <variable name="testany" type="Any"/> </view>
这个view的变量字段引用了xbean,字段testany是any类型的,是错误的,代码生成过程中会抛出异常指出Info.testany存在any类型依赖。any依赖是递归检测的。
<table name="anytable" key="int" value="testany" persistence="MEMORY"/> <view name="Info" lifecycle="session"> <variable name="info"type="BasePlayerInfo"/> <bind name="bindtestany" table="anytable"> <ref name="boolv"/> </bind> </view>
这里的bind绑定了anytable,anytable的值类型是xbean Any,尽管这个xbean包含了Any类型,但是这里只引用了boolean类型的字段,所以上面的描述是正确的。
-
table
定义在zdb下
<table autoIncrement="true" cacheCapacity="4096" key="long" name="roominfos" persistence="MEMORY" value="RoomInfo"/>
zdb数据库中表的描述
roominfos表,key类型为long,value类型为前面定义的xbean RoomInfo,long类型的key在配置了autoIncrement="true"的情况下支持自动增量,persistence="MEMORY"决定了这张表是内存表,cacheCapacity="4096"决定了这张表在内存中最多cache 4096条记录,对于内存表而言,记录数一旦超过cacheCapacity,某些记录将丢失。
<table name="keyisxcompare2" key="xcompare2" value="string" cacheCapacity="4096"/>
keyiscompare2表,key为前面定义的cbean xcompare2,value为字符串,没有指明persistence,这是张磁盘表。
<table name="tany" key="int" value="Any" cacheCapacity="4096" persistence="MEMORY"/>
tany表,value的类型是前面的定义的xbean Any,xbean包含了any类型字段,该表只能是内存表。
<table autoIncrement="true" cacheCapacity="4096" key="long" name="testtype" value="TestType" lock="abc"/> <table name="secondaryindex" key="long" value="SecondaryIndex" cacheCapacity="2000" lock="abc"/>
这里多了lock这一属性,zdb的锁都是行锁,基本锁定方式是lock(table, key),具有相同lock属性的表共用锁。在这里意味着锁定了表testtype的key行,同时也就锁定了secondaryindex表的key行,反之亦然。同时锁定testtype与secondaryindex表的key行也不存在问题。适当规划lock可以简化锁。
-
monitorset
定义在project,namespace下,为应用提供相关的运行数据监视能力。
<monitorset name="TransactionMonitor" supportTransaction="false"> <monitor name="runned" type="counter"/> <monitor name="false" type="counter" /> <monitor name="exception" type="counter"/> <key name="procedureName" type="string"/> </monitorset>
这是limax.xml中的zdb事务监视器集合定义,key为存储过程名,分别为存储过程定义了3个计数信息,执行次数,执行返回false的次数,执行抛出异常的次数。其中:
supportTransaction指出了这一监视器集合需不需要事务特性,简单的说如果为true,那么在事务环境下进行的计数,只有事务成功提交才真正执行,默认为支持。
monitor的type分为counter和gauge两种。counter在生成代码中提供increament方法,gauge提供set方法。
一个monitorset允许多个key,作更细致的划分,key的类型,只能使用除了binary与any之外的简单类型。
-
-
Xml描述规范
Limax使用xml描述项目模型的构建,定义了命名节点和引用节点两类xml节点。存在同时是命名节点和引用节点的这种节点。xml内部名字的解析使用内部名字空间规范。
-
命名节点
命名节点包含name属性,生成代码按照特定规范连接这些name,组织项目名字空间。zdb中table的name必须全部小写,其它name不限。
-
引用节点
凡是使用了ref属性的节点属于引用节点,ref属性的值,必须是命名节点的引用。临时View节点下的subscribe节点,既是引用节点——引用了会话View的字段,又是命名节点——作为当前View的字段被命名。
各种节点下的variable节点,必须指定type属性,当type指向了xml内部描述的结构,bean,cbean,xbean时,可以理解为引用节点。variable节点作为上层节点的字段节点被命名,因此也是命名节点。
-
内部名字空间规范
同一xml节点下的命名节点不可重名。
父节点名 “.” 子节点名,构成节点路径,节点路径允许逐层向外扩展,直到根节点,根节点开始的节点路径为全路径。节点名,节点路径,都可以合法地引用节点自身。
代码生成过程中,报告二义性节点引用时,应该使用更长的节点路径解决二义性。例如,名字空间A下定义bean X,名字空间B下定义bean X。某一variable定义字段时指定了type为X,代码生成过程报告二义性错误,这种情况下,必须正确选择type为A.X或者B.X,消除二义性。
-
特殊情况讨论
1. zdb节点也是命名节点,它的名字不会应用到生成代码中,所以允许不进行命名,这种情况下默认名为空串。为了名字空间解析方便,需要的情况下也可以命名。
2. cbean作为特殊的常量类型bean,具有全局性,不受名字空间约束,整个项目内,任何地方定义的cbean均不可重名。
3. table的name要求必须小写基于此原因:使用文件数据库时,该name被用来直接命名表文件。如果操作系统使用了大小写不敏感的文件系统,比如windows,应用运行在这类操作系统上时,为了规避可能产生的冲突,全部小写是最好的解决方案。
4. bind节点下的ref节点,看起来是命名节点,但是应该解释成,与bind节点的table属性结合到一起,引用了xbean的字段。这样设计的原因:描述简单清晰,否则就应该在bind节点中加入ref属性,xbean的字段名逗号分隔。
-