??xml version="1.0" encoding="utf-8" standalone="yes"?>M设计和登陆服务器 [游戏服务器的设计思\ 转]
http://blog.csdn.net/yahle
大纲Q?br>目的历史背?br>服务?/font>的设计思\
服务器的技?br>服务器的设计
服务器的改进
囑Ş引擎myhoho及UI库的设计
客户端与服务器的集成
|络游戏一般采用C\S模式Q网l游戏的设计重点Q我认ؓ在于Server?也就是我们说的服务器。在服务器端的设计,我把服务器按照功能分?个部分,一个负责游戏世界的处理Q一个服务器服务器与客户端的通讯。在负责游戏世界的处理的服务器,我又按照功能分ؓ地图服务器和逻辑服务器。这样划分的依据是他们处理的内容不同q行。当初的设计q考虑到系l的集群功能Q可以把游戏的地囄动处理和游戏的逻辑处理都分别分摊到其它服务器里面去。但是做到最后,发现q样的设计也不是太好Q主要是因ؓ在处理一些游戏事件的时候需要两个服务器之间q行协同Q这样势必要创徏一定的|络游戏消息Q在开始制作游戏的时候,因ؓ需要系l的东西不是很多Q所以没有太注意Q到目的后期,惛_加一个功能的时候,发现在处理船只沉没的时候,服务器需要传递很多同步数据,而且服务器各自在讄玩家数据的时候,也有很多重复的地斏V如果今后还要再加点什么其它功能,那要同步的地方就实在是太多了Q所以按照功能把服务器分?个部分的设计q是存在~陷的,如果让我重新再来Q我会选择单服务器的设计,当然q个服务器还是要和连接服务器q行分离Q因为游戏的逻辑处理和与玩家的通讯q是很好分开的,而且分开的话Q也有利于逻辑服务器的设计?br>
登陆Q连接)服务器的设计Q?br>
在网l游戏里Q其中一个很大的隄是玩家与服务器的通讯Q在Windos的服务器架构下,|络游戏服务器端采用的I/O模型Q通常是完成端口。在目开始时研究完成端口Q感觉很难,Ҏ看不懂,因ؓ它在很多地方与以前写|络通讯软g时用的方法不同。但是当我分析过3个完成端口的E序后,基本了解的它的用方法。而且在懂以后Q回q头来看Q其它完成端口的概念也不是很复杂Q只要能清楚的了解几个函数的使用Ҏ以及基本的处理框架流E,你就会发现它其实非常的简单?br>
完成端口的一些需要理解的地方Q?br>
1。消息队?br>
2。工作线E?br>
3。网l消息返回结构体
一般我们在设计服务器端的时候,最关键的地Ҏ如何分辩刚刚收到的网l数据是由那个玩家发送过来的Q如果是采用消息事g驱动的话Q是可以得到一个socket的|然后再用q个gpȝ里存在的socketq行比对Q这样就可以得到是那位玩家发送过来的游戏消息。我在还没有使用完成端口的时候,是使用q个Ҏ。这L设计有一个缺点就是每ơ收到数据的时候回费很多旉在于定消息发送者n份上。但是在完成端口的设计里Q我们可以采用一个取巧的Ҏq行设计。所以,q个问题很轻易的q局了,而且pȝ开销也不是很大,关于完成端口Q可以参考一下的文章Q?br>
《关于Winsock异步I/O模型中的事g模型?br>
http://search.csdn.net/Expert/topic/166/166227.xml?temp=.4639093
《手把手教你玩{SOCKET模型之重叠I/O?br>
http://blog.csdn.net/piggyxp/archive/2004/09/23/114883.aspx
《学习日记]IOCP的学习-Q初步理解?br>
http://www.gameres.com/bbs/showthread.asp?threadid=25898
《用完成端口开发大响应规模的Winsock应用E序?br>
http://www.xiaozhou.net/ReadNews.asp?NewsID=901
《理解I/O Completion Port?br>
http://dev.gameres.com/Program/Control/IOCP.htm
几个关键函数的说明:
http://msdn.microsoft.com/library/en-us/fileio/fs/postqueuedcompletionstatus.asp?frame=true
http://msdn.microsoft.com/library/en-us/fileio/fs/createiocompletionport.asp?frame=true
http://msdn.microsoft.com/library/en-us/fileio/fs/getqueuedcompletionstatus.asp?frame=true
http://msdn.microsoft.com/library/en-us/winsock/winsock/wsarecv_2.asp?frame=true
如果你能认真的搞清楚上面的东西,我估计你ȝ解完成端口就只有一步了。剩下的q一步就是自q码实C个下了。有些时候,看得懂了不一定会实际应用Q不实实在在的写一点程序,验证一下你的想法,是不会真正搞清楚原理的?br>
不过除非你想深入的研I网l技术,否则只要知道怎么用就可以了,剩下的就是寻找一个合适的别h装好的cL使用。这样可以节省你很多的事Ӟ当然拿来的东西最好有源代码,q样如果发生什么问题,你也好确定是在那个地方出错,要改或者扩充功能都会方便很多。当Ӟq要注意人家的版权,最好在引用别h代码的地方加一些小的注解Q这L不了多少旉Q而且对你Q对原作者都有好处^_^?br>
不过在完成端口上我还是没有成为拿来主义者,q是自己装了完成端口的操作Q原因找到的源代码代码封装的接口函数我怎么看怎么觉得别扭Q所以最后还是自己封装了一个完成端口,有兴的可以ȝ我的源代码,里面有很详细的注解。而且我看来Q要拿我装的完成端口类使用hq是很简单的。用的时候,只要l承我的CIOCPQ然后,Ҏ需要覆?个虚函数QOnAcceptQOnReadQOnCloseQ就可以了,最多是在连接函数里Q需要用一个函数去讄一下完成端口信息。当Ӟ我封装的cȝ微简单了一些,如果要拿来响应大规模q接Q还是存在很多的问题Q但是如果只是针对少量连接,q是可以应付的?br>
对于客户端的I/O模型Q我没有那么用心的d找什么好的解x案,采用了一个最单的Q最原始的阻塞线E的Ҏ做。原理很单:创徏一个socktQ把socket讄为阻塞,q接服务器成功后Q启动一个线E,在线E里面用recv(){待服务器发q来的消息。在我的代码里,也是把阻塞线E的Ҏ装成一个类Q在使用的时候,先承TClientSocketQ然后覆盖(重蝲Q里面的OnRead()函数Qƈ在里面写入一些处理收到数据后的操作代码。在用的时候,只要connect成功Q系l就会自动启动一个接收线E,一旦有数据p发刚才覆盖的OnRead函数。这个类我也不是完全直接写的Q在里面使用了别人的一些代码,主要是让每个c都能把U程装hQ这样在创徏不同的类的实体的时候,每个cȝ实体自己都会有一个单独的数据接收U程?br>
当然除了dU程的方法,比较常用的还有就是用消息事g的方法收取数据了。我刚开始的时候,也是采用q个ҎQ以前用q^_^Q,但是后来发现不太好封装,最后采用阻塞线E的ҎQ这样做q有一个好处可以让我的代码看v来更加舒服一些。不q就我分析《航世U》客L采用的是消息事g的I/O模型。其它的|络游戏׃太清楚了Q我想也应该是采用消息事件方式的吧。?br>
我记得在gameres上看到过某h写的一关于完成端口的W记Q他在篇末结束的时候,提出一个思考题Q我们在学习完成端口的时候,都知道它是用于server端的操作Q而且很多文章也是q样写的Q但是不知道有没有考虑q,用完成端口做客户端来使用Q?br>
其实q个问题很好回答Q答案是OK。拿IOCP做客L也是可行的,׃装的IOCPZQ只要在l承原来的CIOCPcȝ基础上,再写一个ConnectQchar * ip, int portQ的函数Q就可以实现客户端的要求了?br>
前面一D|用来q接服务器,所有的客户端程序都是要q样做的Q当q接成功后,m_socket是我们惌的用于与服务器端通讯的socketQ然后,我们启动工作U程Qƈ使用SetIoCompletionPort来设|完成端口监听的socket。只要在原来的基上增加一个函敎ͼ可以把用于服务器的ICOP变成用于客户端的IOCP?br>
在收到网l数据以后,下一步就是根据需要,把收到的|络数据包{变ؓ游戏消息数据包。在转换之前Q首先是要从收到的网l数据里面提取出有效的消息。这里ؓ什么说是要提取有效部分Q其主要原因是,我们创徏的游戏消息数据,在进行网l传输的时候,不是以消息的长度来传的,而是Ҏpȝ在接收到发送数据请求的时候,Ҏ实际情况来发送的。例如我q里有一条很长的游戏消息Q有3kQ但是系l一ơ只能发?k的数据,所以,我们的游戏消息,只能把我们的游戏消息分ؓ3个包Q分3ơ发送,q样在我们接收消息的时候,׃触发3ơOnReadQ而这3ơOnRead收到的数据都不是一ơ完整的游戏消息。所以,我们在收到网l数据后Q要先和上一ơ收到的|络数据q行合ƈQ然后再在里面提取出有效的游戏消息,q在提取后,把已l提取的部分删除。我在这里把q一步操作封装到一个类里CBuftoMsg。这里顺便说明一下:一条游戏消息的|络数据包是?x00EEEE(16q制)为结束标讎ͼ《航世U》的做法Q?br>
我在q里?CBuftoMsg 的代码脓出来Q主要是因ؓQ我在写本文的时候,发现一个惊天动地的bugQ有兴趣的读者可以自己去找一下。不q一开始写代码的时候,q不是这LQ当初的代码bug比这个还要多Q问题还要严重,严重到经常让服务器程序莫名其妙的崩溃Q而且q个问题Q一直到5月䆾Q系l在q行集成试的时候才发现q解冻Iq没有彻底解冻I臛_目前我还发现了bugQ)Q以前一直都没有怎么注意到这个问题,而且我们q把因ؓq个bug造成的问题,归结到线E的互斥上去^_^!
我的登陆服务器,除了基本的处理网l数据包以外Q还负责玩家pȝ的登陆验证,q部分东西不是很复杂Q在我的E序里,只是单的从ini文g里读取玩家的信息而已Q有兴趣的自己去看我的代码(不过q部分远q没有真正的完善Q存在很多问题)?br>
除了登陆验证以外Q在登陆E序q负责进行消息{发,是把客L的消息分别发送到不同的服务器。如果当初设计的是一个逻辑服务器,q个功能可以简单很多,只要发送到一个服务器里就可以了。现在的要发?个服务器Q所以还需要对收到的游戏消息进行分cRؓ了方便,我对原来定义消息的IDq行了分c,所以,在GameMessageID.h文g里定义的游戏消息对应的ID~号不是序~排的。不q也因ؓq样Q在现在看来Q这L设计Q有一些不太好。在整个pȝ里,存在?个主体,他们之间互相发送,q?2l的数据Qؓ了方便计,我把一个变量的范围分ؓ16个不同的区域Q这h个区域只?6个值可以用Q我q里是用charcd256/16=16Q。在加上用另外一个变量表C逻辑上上的分c(目前按照功能分了12l,有登陆、N易、银行、船厂等Q这样对于N易这个类型的游戏消息Q从客户端发送到逻辑服务器上Q只能有16中可能性,如果要发送更多消息,可能要增加另外一个逻辑分类QN?^_^!当初q样的设计只是想化一下系l的处理q程Q不q却造成了系l的扩充困难Q要解决也不是没有办法,把类型分cȝ变量由charcdQ改为intcdQ这样对一个变量分区,在范围上会款很多Q而且不会造成逻辑分类上的困扰Q但是,q样存在一个弊端就是就是每条网l消息数据包的长度增加了一点点。不要小看这一个字节的变量Q现在设计的一条游戏消息头的长度是10个字节,如果把char改ؓintQ无形中增加了3个字节,在和原来的比较,q样每条消息在消息头部分Q就多出23Q,也就是我?00M的网l现在只能利?7Q而已?br>
^_^呵呵看出什么问题没有?
没有Q那我告诉你Q有一个概念被h了,消息头的数据不等于整条游戏的消息数据Q所以,消息头部分虽然多Z23Q,但是整条游戏消息q不会增加这么多Q最多增?7Q,最应该不会操?Q。^均v来,应该?0Q左叻I游戏消息里,很多消息的实际部分可能就一个int变量而已Q。不q,q?0Q,也占用了带宽?br>
^_^呵呵q看Z么问题没有?
^_^先去M下我的代码,再回头看看,上面的论q还有什么问题?br>
实际上,每条游戏消息由:消息头、消息实体、结束标记组成,其中固定的是消息头和l束标记Q所以,实际上一条实际上游戏消息的数据包Q最多比原来的多15Q,q_hQ应该是8Q~10Q的增量而异?br>
好了Q不在这个计细节上扣太多精力了。要解决q个问题Q要么是增加|络数据的发送量Q要么,是调整游戏l构Q例如,把两个功能服务器合ƈZ个服务器Q这h务器的对象实体就由原来的4个分?个,两两间的通讯Q就由原来的12路羃减ؓ6路,只要?个区域就ok了。这h个逻辑分类有32条游戏消息可以用。当Ӟ如果q一步合q服务器Q把服务器端都合q到一个程序,那就不用分类了^_^!
在登陆服务器目录下,q有一lmynet.h/mynet.cpp的文Ӟ是我当初为服务器端设计的函数Q封装的是消息事件网l响应模型。只不过装得不是怎么好,被抛弃不用了Q有兴趣的可以去看看Q反正我是不推荐看的。只不过是在q里说明一下整个工E目录的l构而已?br>
]]>
CSV全称 Comma Separated valuesQ是一U用来存储数据的U文?br>文g格式Q通常用于电子表格或数据库软g。有单易用,占用存储字节的特点?/p>
规则
0 开头是不留I,以行为单位?br>1 可含或不含列名,含列名则居文件第一行?br>2 一行数据不垮行Q无I?br>3 以半角符PASCIIQ,作分隔符Q列为空也要表达其存在?br>4 列内容如存在Q,则用""包含h?br>5 列内容如存在""则用""""包含?br>6 文gd时引P逗号操作规则互逆?br>7 内码格式不限Q可为ASCII、Unicode或者其他?br>
XLS是Excel的原生格式,但是可以之转存为csvQcsv的格式非常简单,主要格式是用逗号隔开每个数据?br>所以,q是一个很不错的东ѝ?br>{划可以用Excel~辑文档Q{存csv之后Q就可以用于E序的读取?br>
见下例:
<1>. xls 文档Q?/p>
<2>.csv文档Q?br>
用程序读取这个csv文档Q很easy了!