网络游戏服务器端的设计与实现(硕士学位论文)


电子科技大学 硕士学位论文 网络游戏服务器端的设计与实现 姓名:刘树杰 申请学位级别:硕士 专业:计算机应用技术 指导教师:黄迪明 20090501 摘要摘要随着网络技术的发展,以及国家政策对游戏产业的支持,网络游戏产业成为蓬勃发展的产业。网络游戏也变得日益复杂,由最初的简单网络对战演变为今天复杂的游戏虚拟世界。如今,一款优秀的网络游戏,其同时在线人数将达到数十万甚至上百万。有很多公司在网络游戏领域都取得了比较好的业绩,如完美时空和巨人。但出于商业因素考虑,这些公司并不愿意公开其研究成果。服务器端在网络游戏中占有比较重要的地位,本文致力于设计并实现一款网络游戏的服务器端系统。论文首先介绍了网络游戏的定义与种类,并介绍了网络游戏的发展现状以及发展趋势,研究了网络游戏服务器端所采用的主要技术,分析了网络游戏体系架构。为了设计出一款网络游戏的服务器端系统,论文对相关技术进行了研究,这些技术主要包括网络通信技术、多线程技术、序列化以及有限状态机。本文主要完成了一款网络游戏服务器端的设计。文章首先给出了服务器端的总体设计。在此基础上对服务器端的通信模型、多线程工作模型游戏协议以及游戏的状态机等方面分别进行了分析和设计。其中重点介绍了如何进行完成端口I/O模型的实现和游戏数据包的设计。论文在最后对全文进行了总结和评价,并且对后续工作和研究工作做了一些展望。网络游戏作为未来的热门领域,仍然需要不断的深入的去学习研究。本文提出的网络游戏服务器端,在理论和商业应用上都有一定的价值。关键词:网络游戏,服务器端,多线程,有限状态机 ABSTRACTABSTRACTWiththerapiddevelopmentofnetworktechnologyandtheofficialsupportforgameindustrytheindustryofonlinegamesisdevelopingprosperously.Onlinegamesalsohavethetrendtobecomemoreandmorecomplex,evel"sincethesimpleonline孚tlnesthatsupportagroupofplayerstotoday'smassivemultiplayeron-linegameswhichletlargenumbersofplayersinteractinavisualonlineworldsimultaneously.SeveralcompaniessuchasPerfectWorldandGiantareprofessionalindevelopingonlinegames,buttheyregardthetechnologiesascommercialsecret.Serversideisveryimportantforonlinegames.Adesignofaserversideofonlinegamesisdescribedinthisthesis.First,thedefinitionandthesortsofoulinegamesaresmnmarized,andalsothedevelopmentstatusquoandthetrendofonlinegamesareintroduced.ThemaintechnologiesofonlinegamesandthearchitectureofonlinegamesarOdiscussed.OnpRrposetodesignaserversideofonlinegames,wefirstdosomeresearchoftherelatedtechnologies,containingnetworkcommunication,multiplethread,serializationandfinitestatemachine.AdesignofSOlVersideofonlinegamesisdescribedinthisthesis.Wegivethesofh,varearchitecturedesignatfirst.Onthisbase,communicationmodel,multi.threadmodel,gameprotocolandfinitestatemachineareanalyzedanddesignedrespectively.HowtorealizethecompletionFOmodelandthedesignofpacketofgamedataareintroducedindetail.Inthefinal,wesummarizeandevaluatethefulltext,thenprospectthefollow—upworkandresearch.Asbeingapopularfieldinrecentyears,networkgameisworthyofstudyingandresearchingharder.TheSerVersidedesigneediscussedinthisthesishassignificantacademicandcommercialessenfiali妙.Keywords:onlinegame,Sel"Verside,multi—thread,finitestatemachinen 独创性声明本人声明所呈交的学位论文是本人在导师指导下进行的研究工作及取得的研究成果。据我所知,除了文中特别加以标注和致谢的地方外,论文中不包含其他人已经发表或撰写过的研究成果,也不包含为获得电子科技大学或其它教育机构的学位或证书而使用过的材料。与我一同工作的同志对本研究所做的任何贡献均已在论文中作了明确的说明并表示谢意。关于论文使用授权的说明本学位论文作者完全了解电子科技大学有关保留、使用学位论文的规定,有权保留并向国家有关部门或机构送交论文的复印件和磁盘,允许论文被查阅和借阅。本人授权电子科技大学可以将学位论文的全部或部分内容编入有关数据库进行检索,可以采用影印、缩印或扫描等复制手段保存、汇编学位论文。(保密的学位论文在解密后应遵守此规定)签名:各1棚杰签名:竺I塑三竺 第一章绪论1.1网络游戏概述第一章绪论随着信息产业不的断发展,互联网开始普及到干家万户,人类社会进入了网络信息时代。遍布全球的互联网系统是信息技术的发展史上的又一次飞跃。信息互联网化的水平成为了衡量国家的现代化水平和综合国力的一项重要指标。互联网的发展和普及给中国的政治、经济和文化都带来了深远的影响。互联网也使人们的生活的方式进入了信息化时代。网络游戏也随着互联网的发展和普及出现在了人们视野中。经过近十年的发展,网络游戏已经成为了一种文化,成为了主流的社会娱乐方式。网络游戏产业作为一个高科技产业,有着非常大的发展潜力,一已经可以和影视、音乐等产业并驾齐驱成为全球重要的娱乐产业之一。不仅如此,网络游戏产业的发展还可以带动其他相关产业的发展,网络游戏已渐渐地成为了互联网经济和数字娱乐产业的重要支撑,也成为了文化产业的一个增长点,对国家的经济和社会的发展有着非常大的推动作用,不少的国家也越来越重视网络游戏。1.1.1网络游戏的定义与种类网络游戏是通过数字化网络传递信息的一种互动娱乐方式,是信息化与社会文化交织在一起的产业。网络游戏中的网络这个词所代表的不仅是家喻户晓的互联网,它还包含了移动网络、电话网、卫星通信网络、光纤通信网络、有线电视网、电线通信网络等所有基于网络协议的并且能够实现互通的网络。网络游戏作为互联网产业、电子游戏产业、娱乐产业、动漫产业和信息化产业结合的产物,有着多产业的性质。网络游戏是电子游戏的一个分支,在互联网出现以前,所有的电子游戏都不能进行大规模网络交互,最多就是几个人的局域网对战。而随着互联网的出现,这种局面被打破。通过使用互联网上的服务器,成千上万的游戏玩家同时出现在了同一个游戏世界中,并能相互交流,这就大大增加了游戏的互动性,越来越多的人喜欢上了这种娱乐方式。网络游戏如今已成为了最流行的娱乐方式,它同传统的娱乐方式相比有着本质上的不同。网络游戏是应用在互联网上的~种娱乐软 电子科技大学硕+学位论文件,有一定的文化基础,是一种新颖的娱乐手段。与传统的游戏相比较,网络游戏互动性非常强,玩家好像生活在另一个世界中。使用虚拟的身份同其他的玩家一起交流,互动可以感受真实世界中不能感受到的快乐与痛苦,还可以扮演现实世界中不能扮演的角色。网络游戏可以根据不同的标准,划分不同种类的游戏。通过游戏运行的平台可以将网络游戏划分为电脑网络游戏、平台游戏机网络游戏、掌机网络游戏和手机网络游戏。通过游戏的内容可以将网络游戏划分为角色扮演网络游戏、策略类网络游戏、动作冒险类网络游戏、经营养成类网络游戏、体育类网络游戏和棋牌类网络游戏。按照游戏的性质可以将网络游戏划分为休闲类网络游戏和即时对战类网络游戏。目前的主流方式是将网络游戏划分为大型多人在线角色扮演游戏,棋牌桌面类游戏、休闲动作类游戏、和网页游戏四类。(1)大型多人在线角色扮演游戏大型多人在线角色扮演游戏,可以让大量玩家同时在线并处于同一个游戏世界中,整个游戏世界也是持续发展而不是每次游戏都要初始化的一种游戏类型。这是现在最流行的游戏类型,游戏构建了一个同真实世界相仿的虚拟世界,拥有自己的社会和经济体制。玩家在虚拟世界中扮演一个角色,通过自己的努力和其他各个方面的投入来实现自己所扮演的角色在虚拟世界中的成长。玩家还可以在这个虚拟世界中建立自己的人际关系并参加虚拟社交活动。现在上市的网络游戏中,大部分都是这种游戏类型。大型多人在线角色扮演游戏之所以得到广大玩家的芳心,主要原因都是这类游戏通过互联网构建了一个有着完整世界体系,与现实社会对应的虚拟世界。在这个世界中,有着完整的社会观、价值观和世界观。当然,最吸引广大玩家的还是这个虚拟世界的故事背景,以及在这个故事背景下所得到的知识和快乐。现在较为流行的大型多人在线角色扮演游戏有《魔兽世界》、《梦幻西游》、《完美世界》等。(2)棋牌桌面类游戏棋牌在现实生活中无处不在。棋牌桌面类游戏实际就是把现实生活中的棋牌通过互联网来实现。游戏玩家通过网络通信并通过游戏厂商提供的游戏平台来进行对战。一般来讲,棋牌桌面类游戏都依附于一个网络游戏运营商提供的一个游戏平台,而游戏就通过这个游戏平台下载。通过游戏平台,玩家就可以自己寻找登录这个平台的其他玩家一起进行游戏。除了传统的棋牌游戏,游戏平台还提供有许多非常受欢迎的益智类或者趣味类小游戏。现今流行的棋牌桌面游戏平台有联众世界、腾讯等。(3)休闲动作类游戏2 第一章绪论休闲动作类网络游戏,顾名思义是目的是为了休闲,使玩家的心情得以放松。这类游戏没有大型多人在线角色扮演游戏所拥有的虚拟世界,不需要扮演一个特定的角色。休闲类网络游戏玩起来十分轻松畅快,没有非常紧张的感觉。玩家可以在工作和学习之余玩这类游戏,好好享受游戏带来乐趣。通过玩此类游戏玩家还可以找到和自己兴趣相仿的其他玩家,并进行充分的交流。游戏故事背景比较简单,主要注重操作性,上手容易,耗费时间少,因此休闲动作类网络游戏成为许多玩家最喜爱的休闲娱乐方式。市面上知名的《劲舞团》、《跑跑卡丁车》等都属此类游戏。(4)网页游戏网页游戏从技术上讲同开发网站的技术相似,它们都是以超文本传输协议为基础,而客户端则是使用浏览器。网页游戏比起普通的网络游戏最大的优势在于不用下载庞大的客户端,这就使得网页游戏可以很方便地被移植到其他可以使用浏览器的平台中。2008年是网页游戏开始活跃的一年,作为一个网页游戏开始崛起的一年,许多游戏公司也把今后研发对象转向了网页游戏,而玩家也在越来越关注网页游戏,网页游戏不仅做为网络游戏类型的一个填补,网页游戏其表现的优势与特点,也是以后网络游戏未来发展的最终模式。1.1.2网络游戏的发展现状中国网络游戏行业从初创走到今天,网络游戏公司已经划分为三代【l】。第一代网络游戏企业以盛大和九城为代表,它们主要依靠代理韩国和欧美的网络游戏完成财富积累。第二代网络游戏公司以网易、金山、完美时空、巨人为代表,大部分都是从其他行业转型,以自主研发为主代理游戏为辅敛财。第三代网络游戏企业则主要是由第一代和第二代中有着丰富经验积累的资深游戏人士创办,主要依靠自主研发,有部分公司还自主运营游戏。第一代网络游戏企业主要依靠代理游戏起家,目前的代表是盛大和九城,它们在本世纪初崭露头角,依靠代理国外网游实现财富积累。盛大的《传奇》和九城的《奇迹》,成为当时最流行的网络游戏,也给其它游戏公司带来了学习的榜样。盛大从此走上了以《传奇》系列游戏为主,开始了网络游戏平台化的发展轨道,其野心是打造能同时运营数百款网络游戏的巨无霸公司,并在和游戏相关的产业上全面开花。而九城则把所有心血放在了少数几款网络游戏身上,搞精品化路线,特别是《魔兽世界》,九城在此款游戏上倾注了大量心血。 电子科技大学硕十学位论文第二代网络游戏企业都是从其他行业转型过来的,在看到网络游戏所蕴藏的巨大商机后,门户网站网易的首席执行官丁磊在2001年决定开始研发网络游戏,这一决定让网易成为了网络游戏企业,并在第二代网络游戏企业里占据一席之地。第二代网络企业大部分都依靠自主研发,它们多数也是由相关的计算机行业转型过来进入网络游戏行业,如即时通信领域的腾讯、软件行业的金山和完美时空等,当然也有非计算机行业转型过来的公司,如史玉柱带领的巨人网络。它们具有完整的技术团队,并依靠原有业务进行资金上的支持,对第一代网络游戏企业构成了巨大的威胁。在运营模式方面,第一二代网络游戏企业开创了中国网络游戏的基本类型,给后来的企业一个参考方式。后来的企业一般都要依照并遵循由前人留下来的网络游戏的发展道路。比如与传奇类似的游戏就会在游戏装备和玩家对战上大做文章,与魔兽世界类似的游戏就会表现出华丽的三维画面和曲折的故事情节,而回合制的游戏则和网易的大话西游系列神似。基本上所有的网络游戏公司都是依靠一款非常卖座的游戏壮大进而在海外上市的。盛大一直都是网络游戏业界的龙头老大,市场份额居高不下,而其他游戏公司的份额在一直都处在变化之中,特别是排在五名以后的企业经常大起大落。艾瑞咨询发布2009年一季度网络游戏市场数据显示【2】,网络游戏市场规模上,腾讯超过了网易,成为第二大网络游戏公司,第一季度网络游戏市场份额排名第一的是盛大,市场份额为17.7%,排名第二的是腾讯,市场份额为15.4%,排名第三的是网易,市场份额为11.1%。九城的市场份额跌落到了6.8%,排名仅为第七位,在完美时空、搜狐畅游和久游之后。由于最近网易和暴雪达成协议,拿到了魔兽世界巫妖王之怒的代理权,其市场份额有望上升,夺回第二的位置,并向位居第一的盛大发起挑战。九城则由于过分依赖魔兽世界,在失去了魔兽世界代理权后,其市场份额不断下滑。目前在国外上市的网络游戏公司已经超过了十家,从2008年全年来看,大部分上市的网络游戏公司业绩仍然保持高速增长,大部分都在百分之五十以上。据统计,2008年中国网络游戏市场规模达到了187.8亿元,比2007年增长了约77%,在全球遭受金融危机的同时,网络游戏业一枝独秀,给经济增添了一丝活力。预计未来三年也会保持高速增长。而且,在2009年一季度,中国的互联网经济持续下滑、市场规模不断缩小的情况下,只有网络游戏行业保持了继续增长,其余包括在线广告、电子商务等行业都出现了一定程度的负增长。中国网络市场经济规模出现了戏剧性的场面,在线广告市场的份额下滑至22.8%,网络游戏的市场份额却飙升至48.3%。第三代网络游戏企业已经开始崭露头角,正逐步蚕食着第一代和第二代网络4 第一章绪论游戏企业的市场份额。第三代网络游戏企业的特点是不论创始人还是核心团队人员都是从第一代和第二代的网络游戏公司中走出来的。他们大部分在2006年和2007年开始创业,在金融危机来临以前得到了一笔又一笔的风险投资。由于都是资深的游戏人士,这批团队非常专业,并专注于开发网络游戏,相当于用丰富的行业经验和良好的专业背景换取创业资本和游戏市场。第三代网络游戏企业主要是由具有多年游戏行业研发、运营和销售经验的行内人士创办,所以基本都是依靠自主研发,并且有些公司还自主运营游戏。由于网络游戏市场远没有饱和,还有很大的利润空间等待发掘,预计2009年网络游戏市场规模将有50%左右的增长率,有望达到280亿元,比起2008年增长了90多亿元。正是这90多亿元的市场增量增强了第三代网络游戏企业的信心。还有一点让第三代网络游戏企业觉得生机盎然的是,第一代和第二代网络游戏公司的畅销游戏都差不多运行了三四年了,在游戏生命周期的后期,它们都略显颓势,这就为第三代网络游戏公司提供了千载难逢的机会。业界同仁认为,与以代理起家的第一代网络游戏公司和从其他行业转型的第二代网络游戏公司不同,第三代网络游戏公司都具有丰富的游戏研发和管理经验,还有很多是第一代和第二代网络游戏公司的核心人员在此任职,他们依靠自主创新,给网络游戏市场带来了新的气息。1.1.3网络游戏的发展趋势中国网络游戏产业经过几年的快速发展,已经形成了一定的产业规模【31,中国网络游戏市场的发展趋势如下:.(1)中国用户稳定,市场份额有望超越美国最新的市场调查在充分分析全球网络游戏产业的形势后认为,在2009年中国极有可能超过美国成为全球第一网游大国。目前排位第一的美国国内网游用户数量有限,通过游戏出口取得的权利金是其最主要的收入,而从美国近期的产品储备来看,《魔兽世界》已逐步进入衰退期,新产品的质量亦达不到《魔兽世界》的高度,因此美国在未来一段时间内全球份额会逐步下降。排位第三的韩国与美国不同的是,韩国有部分收入来自于用户供给,而出口的主要方向则是东南亚地区。近几年,中国用户对韩国游戏的热情严重下降,这也大大影响了韩国游戏产业的收入,韩国游戏的市场份额虽然不会出现大规模下降,但也没有出现上升的迹象,因此韩国将保持现有状态。综上所述,游戏出口受阻是导致美、韩市场份额下降的主要因素,而中国的游戏收入主要来自用户供给,只要用户数量不出现下降, 电子科技大学硕十学位论文中国网游市场的份额亦不会出现下降,在2009年中国极有可能超过美国成为全球第一网游大国。(2)市场占有率稳步提升2008年中国网络游戏市场的收入约占全球27%的份额,排名第二,美国则以29%位居榜首,韩国21%排名第三。中国网游市场占有率首次超过韩国位居第二。从上榜的三个国家的收入构成来看,美国的收入主要来自于游戏出口,而韩国是出口和国内运营齐头并进,中国的绝大部分收入则来自于国内的运营收入。换言之,美国和韩国的收入也有很大一部分来自于中国市场。2008年中国网络游戏市场规模为207.8亿元,同比增长52.2%。网络游戏产业在经历了10年的高速发展后在2007年达到峰值,虽然在今后的3.5年中,还将保持20%以上的增长率,但行业整体发展将趋于平缓。网络游戏产业未来的发展方向将是多元化、多平台的,市场的增长也将主要来自于用户群体的开拓而非用户的深度挖掘,预计到2012年,网络游戏的市场规模将达到686.2亿元。(3)网页游戏用户急剧增长2007年中国网页游戏的用户规模为250万人,到2008年用户规模达到900万人,同比增长260%。保守估计,随后的3年中网页游戏的用户以每年500万人左右的速度增长,预计到2010年将突破2000万人,达到2020万人。由于近期国内网页游戏新产品不断面世,但国内相关监测尚未完全跟上,因此许多中小规模的网页游戏覆盖人数并没有列入现有用户规模统计中。考虑中国小规模网页游戏的用户,2008年中国网页游戏用户预计已经突破千万大关。目前业内人士普遍看好网页游戏的发展前景,部分行业人士预计,与2007年相比国内2008年网页游戏用户至少翻二至三番,增长的原因是网页游戏进入门槛较低,吸引许多中小型团队加入研究和运营的行列,当运营商与产品数量急剧增加时,整个市场也将被充分调动起来,从而形成成倍增长的态势。(4)网络游戏向多机种发展从中国网络游戏未来几年的发展趋势来看,大型多人在线游戏MMOG将继续以每年100亿左右的速度增长,并始终保持80%以上的市场份额,是当之无愧的主流游戏。另一方面,随着用户需求趋于多元化,新的游戏形式、游戏终端也将逐步进入市场,其中最具代表性的将是以IE为载体的网页游戏、以手机为主的移动终端和逐渐网络化的次世代游戏机带来的游戏收入。从2008年开始,网页游戏市场收入已经超过了平台游戏,达到5亿元,而以手机网游为代表的移动终端将在2009年3G时代到来时发力,预计在2009年,这部分收入将达到3亿元左6 第一章绪论右。(5)网络游戏用户群多样化2008年中国游戏用户在年龄方面呈现出两端增长的局面,即18岁以下用户和40岁以上用户增长明显,而原来网络游戏的主力人群18.30岁用户的比例则明显下降。产生这一现象的主要原因是:在网游发展初期的主力用户,即25岁左右的用户正处于事业与家庭的重压阶段,其中有很大一部分用户由于时间和家庭的原因,放弃了玩网络游戏的习惯,这直接导致用户比例下降。另一方面,18岁以下和40岁以上用户则由于有较多的空闲时间,因此这部分用户玩游戏的比例逐步提高。(6)网络游戏市场暗酿变化目前中国网络游戏行业还处于依靠单款明星产品拉动企业营收的状态,一旦这款产品出现下滑或成功推出另一款明星产品,都将对排名产生巨大影响,这从腾讯在2008年中后期凭借《地下城与勇士》的出色表现异军突起可以看出,排名前十企业间的差异仅一款产品,运营商的份额在2009年还将出现很大变数。调查显示,2008年中国网络游戏市场规模为30.4亿美元,约占全球网络游戏市场总收入的27.1%,比2007年高出5.9%。从全球网游的发展趋势来看,中国市场的占有率还将以每年5%左右的速度递增,预计到2012年,中国市场的占有率将接近50%,达到46.9%。1.2论文的主要工作及结构安排本课题基于已立项的网络游戏项目。该项目设计及制作面向各个年龄段的玩家,是一款以休闲娱乐为主的网络游戏,游戏类型是3D横版格斗动作RPG网络游戏。网络游戏面向Interact的连接人数巨大,为保证其平滑,稳定的运行,需要复杂的技术作为支持,而此项技术的核心内容之一就是服务器端的设计,它是游戏是否能够生存的关键。作为游戏服务器端程序员,主要工作是分析游戏服务器的特点,并结合项目自身的特定需求,在已有服务器结构的基础上,加以研究和应用,设计了一套完整的网络游戏服务器开发方案,以满足网络游戏服务器复杂功能的需求。全文共分五章,对网络游戏服务器设计进行了研究和讨论。具体的章节安排如下:第一章:介绍了本课题的来源以及网络游戏的发展现状以及未来的发展趋势。7 电子科技大学硕士学位论文指出了论文的主要研究内容和章节安排。第二章:介绍了本课题所使用的软件开发平台和开发环境。分析了不同网络游戏体系结构协议的优缺点。阐述了游戏服务器端的总体设计框架。第三章:首先对SocketAPI进行了阐述,并重点介绍了WindowsSocket以及WinSockI/0方法。然后设计了基于套接字I/0模型的网络通信模型。最后介绍了多线程编程在网络游戏服务器开发中的应用。第四章:介绍了游戏协议的概念和格式。着重讨论了游戏数据包粘包和截断的处理和数据包的序列化方式。第五章:介绍了简单状态机和面向对象的状态机并实现状态机。最后,对全文的工作做出总结,并对今后需要探索与解决的问题提出展望。 第二章服务器端总体设计第二章服务器端总体设计2.1平台和开发环境网络游戏是一个大型的网络应用系统,整个系统主要包括客户端、服务器端和数据库。由于网络游戏逻辑非常复杂,在加之所涉及的学科种类繁多,同一般的应用系统相比,在初期的设计和实现,后期的运行和维护上都有相当高的难度,而网络游戏服务器又是网络游戏稳定性的保障,这就对网络游戏服务器的设计提出了很高的要求。为了满足网络游戏服务器的功能需求和技术特征,做好开发的前期准备非常关键。2.1.1操作系统平台随着网络游戏提供的服务越来越多,网络游戏玩家数量也不断增加,网络游戏运营商迫切需要运行效率更加高效,运行状态更加稳定,运行环境更加安全的网络游戏服务器。当前服务器开发所使用的操作系统一般为微软的Windows操作系统和免费开源的Linux操作系统。Windows操作系统是微软的主打产品,其构成非常复杂,当然也提供了非常丰富的功能供开发使用。随着Windows不断地改进,Windows在服务器领域不断迸取,越来越多的服务器开始使用Windows操作系统。使用Windows操作系统开发服务器的好处在于,Windows有微软强有力的技术支持,操作界面的交互比较友好,操作容易上手。在后期维护和周边支持方面也强于其它操作系统。Linux操作系统是开源、免费的操作系统,所以采用Linux作为服务器的操作系统的运营成本比采用Windows操作系统要低,而且Linux操作系统由于设计的方式不同,其运行效率也比Windows操作系统要高,安全性也要强于Windows操作系统。但是Linux操作系统不够人性化,无论是操作方式还是交互界面都比Windows操作系统稍差。再加之使用Linux操作系统开发服务器应用的编程人员也比较少,这在某种程度上限制了Linux操作系统编程在程序员中的普及。由于这两种操作系统都有各自的有点和缺点,所以越来越多的网络游戏服务器开发都开始使用跨平台开发技术,让服务器端可以运行在不同的操作系统上,这样也可以满足不同网络游戏运营商的需要。考虑到Windows的易用性以及使用Windows开发的周期和周边支持等因素,9 电子科技大学硕士学位论文本课题服务器端使用Windows操作系统平台,但为了适应网络游戏服务器程序跨平台的发展方向也为Linux操作系统开发了部分模块,便于以后的扩展和升级,实现服务器的跨平台使用。2.1.2开发语言和环境本课题服务器端程序采用C++语言进行开发。前些年,C语言是游戏开发理所当然的选择。到了现在,C++取代了C语言的位置,变成了首选的语言【41。由于C语言是C++语言的一个子集,所以由C升级到C++也很容易。从C语言到C++语言的转化有两个主要的原因。第一个原因是由于C++的复杂性。由于程序变得越来越复杂,需要寻找新的办法使得它们能够处理这种增加的复杂性。第二个原因是C++成熟了。20世纪的90年代,C++终于发展到了成熟稳定的阶段,1998年C++由国际标准化组织完成了标准化,从此以后C++拥有的统一的标准,所有的编译器也都开始逐步支持C++标准,这样程序员写出的代码就可以在不同平台之间移植。C++标准还实现了C++标准库。使用C.H标准库,程序员能够更方便地使用常用的数据结构和算法写出功能复杂的代码。C++是一种面相对象的语言,易于模块化,并支持泛型编程,所以使用C++编写程序的方法多种多样,既支持面相对象的程序设计方法又支持基于过程的程序设计方法。还有一点比较重要的是C++是编译型语言,源程序在编译后形成二进制代码,这样使用C++编写的程序运行速度快,效率也比较高,特别适用于快速处理大量的逻辑运算,比如网络游戏服务器。本课题大部分程序是基于windows开发的,所以采用微软公司出品的VisualStudio2003作为集成开发环境。VisualStudio2003是一套完整的开发工具集,可以用来创建WindOWS平台下的Windows应用程序和网络应用程序,也可以用来创建网络服务、智能设备应用程序。其中的VisualC++是Windows下C++开发的首选。2.1.3脚本语言的选择引入脚本有利于增加游戏开发的灵活性,早期的许多游戏并没有内嵌一个可运行的脚本语言,它们常常会把一些需要变动的数据写在人可阅读的文件里,这个文件被看成配置文件[51。可以随时由人手工编辑,而不需要改动执行文件。我们可以将其看做是一个静态数据描述的脚本。但游戏不比通用软件,需求会随着10 第二章服务器端总体设计项目的进展时刻变化,我们必须随时改动游戏中的许多东西。如果在程序中写死这些内容,日后的细微变动都会引起执行文件的重生成,这在项目非常小以及开发人员比较少时,是可以容忍的。一旦项目扩大,那就会成为一件劳民伤财的差事。使用脚本还有利于减少和底层的耦合度。如果游戏中的一些东西经常被修改,那么它们出错的几率就会提高,这些模块应该减少和那些不被经常修改的部分的耦合度,才能保障软件的稳定性。使用脚本而不是另一个C++模块,强迫我们实施这种低耦合的结构。而且在脚本解释层很容易做到捕获错误脚本的异常情况,部分脚本的BUG不至于让整个程序崩溃。作为开发人员,也容易在扩大游戏设计的规模的同时,维持基本模块的简洁。脚本在增加代码复用性和降低开发门槛方面也有突出的贡献。如果我们把所有游戏逻辑有关的东西都放进了脚本中,那么脚本以下的模块实际上可以被很多游戏项目复用。这是低耦合度带来的好处,如果采用一套C++代码实现两个不同的游戏,第一个游戏制作的经验可以让我们了解程序能够做到哪些,哪些需要加强,哪些问题应该避免。从脚本层重写出新的游戏不是件难事,而且针对新游戏对C++层的改动会让两个项目都受益,而不是迫使开发人员分出两个程序版本分开维护。C++是一门极其复杂而容易犯错误的语言。即使可以熟练地使用,但是也很难保证整个团队的程序员都是C++高手,这个时候,一个容易学习的脚本语言降低了开发门槛,方便我们往项目中增加人手,而不至于把整个项目搅乱。目前游戏编程中用得较多的脚本语言是L∞和Python。Lua是运行效率最高的脚本语言,同时也是最轻量的一个。整个虚拟机编译出来只有不到100K字节的体积,没有什么多余的东西。它的精练设计使得其工作起来速度很快。Lua的操作全部面向操作栈,可以很快捷地复制移动数据;而且Lua的gc机制也并不依赖对象引用计数,这样,虚拟机里的对象复制仅仅做简单的指针拷贝就够了。Python是现在最流行的脚本语言之一。这是一个设计优美的语言,能和C++结合得很好,解决许多C++的语言模型中看起来很繁杂的事务。Python的第三方模块也非常丰富,对于提高开发效率有莫大的帮助。Python比之h语言更为优雅,Python容易使人写出漂亮的脚本代码,而Lua会引导程序员把代码写得很难看。但是,Python优雅是有代价的,正是这种优雅导致了Python的运行效率大大不如Lua。由于追求程序的高效和轻量,本课题采用Lua和C++混合编程的方法进行开发。№主要用于编写游戏逻辑部分的代码,因为逻辑代码的修改频率非常高,而CH则用于对运行速度要求很高的模块。 电子科技大学硕十学位论文2.1.4网络协议的选择网络协议是保障网络游戏基本通信的前提,而网络协议的选择则决定了一款网络游戏通信的性能是否高效,通信方式是否安全。因特网传输数据是通过交换数据包实现的,传输过程也是容错的。交换数据包的意思是在因特网上传输的信息会被封装成一个一个的小数据包,从一端传送到另一端【61。容错,顾名思义就是允许错误发生,比如在传输的过程中出现网络问题、软件问题等。如果在传送的过程中,服务器发生了故障,那么传送的数据包将会选择通过另外的路径,直到到达接收端。在发送端发送数据的时候,有两个问题是至关重要的。第一,是数据在传输之前怎么被分割成一个一个的小数据包,以及在数据包到达接收端后又怎么被重新组合。第二,每个数据包是单独在网络上进行传输的,怎么保证数据能够稳定的传输到接收端。选择合理的网络协议将使游戏的通信更加安全、稳定和高效。游戏开发中,常用的网络协议是TCP和UDP。TCP,即传输控制协议,是一种面向连接的、可靠的、基于字节流的运输层通信协议。由于不同主机的应用程序之间经常需要可靠的、像管道一样的连接,但是IP协议却不提供这样的流机制,而只是提供了不可靠的包交换机制。使用TCP就可以解决这样的问题。TCP在TCP/IP中位于m之上,应用层之下,为应用程序提供了可靠的数据传输机制。TCP是面向连接的,它保证了对数据进行先来先服务的操作,还保障了所有发送的数据都能到达目的地。由于要保证所有发送的包要按照发送顺序到达接收端,TCP就需要等待丢失的包,当丢失的包重新到达后才能接收新到来的包,同时TCP也需要随时检查有没有发生丢包,所以TCP的传输速率比较低。相当于TCP采用了降低传输性能的方式,来获得可靠的网络传输质量。为了提供这种可靠的,面向连接的服务,TCP采用了多种机制,包括差错检测和纠正,重传数据包,分组确认和拥塞控制等。UDP则和TCP完全相反,UDP是无连接的,不可靠的协议。UDP把数据报从网络上的一台主机传送到另一台主机,但并不保证数据报能够到达,也不保证数据报的顺序性17]。UDP只提供一种尽力而为的服务,尽可能能地把数据发送出去。和TCP相比,UDP的传输速率就要快得多。TCP每次发送一个数据包后都要等待接收方发送一个应答信息,这样TCP才可以确认数据包通过因特网完整地送到了接收方。如果在一段时间内TCP没有收到接收方的应答,他就会停止发送新的数据包,转而去重新发送没有收到应答12 第二章服务器端总体设计的数据包,并且持续这种发送状态知道收到接收方的应答。所以这会造成网络数据传输的延迟,若网络情况不好,发送方会等待相当长一段时间。当然,对于下载文件和浏览网页,这种影响是可以忽略的。但是在实时性要求较高的应用程序中,这种延迟可能会带来相当大的损失。在网络游戏中应用哪种协议主要依赖于游戏的类型。如果游戏对实时性要求不高,如回合策略类游戏,有一点数据延迟对游戏没有太大影响的话,选择TCP是非常不错的。如果游戏实时性要求非常高,且容许存在丢包的话,如格斗类游戏,就可以选择UDP作为主要的数据传输协议。一般来说,现在的网络游戏都不会采用单一的协议进行数据传输了。同时使用TCP和UDP能更好地满足网络游戏的需要。对于重要的、不能丢失的数据使用TCP传输,不太重要的数据则使用UDP传输。现阶段很多网络游戏已经在客户端之间使用了P2P技术,通过这种技术,可以让游戏的消息直接在客户端之间传输,而不必将所有的消息都通过服务器,这样就减轻了服务器的压力。P2P技术就是UDP的一个典型应用。TCP与UDP的比较如表2.1所示。表2-1TCP协议和UDP协议的比较TCPUDP连接面向连接无连接可靠性可靠不可靠传输顺序保证顺序到达不保证顺序到达速率慢快2.2网络游戏体系结构2.2.1g/S体系结构C/S体系结构是当前网络游戏使用得最多的一种结构,如图2-1所示。这种体系结构主要用于大型多人在线游戏,比如大型多人在线角色扮演游戏,这种游戏可以同时容纳上万人在线。服务器上存有整个游戏世界的数据,包括地图信息,人物信息等。玩家通过客户端连接到服务器,得到游戏世界的所有信息。在这种体系结构中,客户端之间是不能直接通信的,所有客户端的消息都必须先发送给服务器。在客户端发送的消息通过服务器分析验证并处理之后,才能再通过服务器转发给其他的客户端。和单机游戏不一样的是,在网络游戏中,所有的玩家信息都是存放在服务器 电子科技大学硕士学位论文上的,比如人物等级、装备等信息。这些信息全部都由服务器发送给客户端,客户端只是用于显示这些信息。整个游戏的逻辑也是统一由服务器处理,然后把处理的结果发送给客户端。这样做的目的是保证游戏数据不会被恶意破坏,防止了不法用户窜改游戏数据的恶劣行为。但是随着用户数量的提升,服务器所承受的压力会越来越大,并成为整个系统的瓶颈。致命的一点是服务器的瘫痪会造成整个网络游戏的瘫痪,给运营商带来巨大的损失。客尸端客户端图2-1C/S体系结构随着网络游戏不断发展,网络游戏用户数量呈几何级数增长。再加之用户对游戏服务质量的要求也日趋提高,传统的C/S网络游戏服务器已不能满足用户的需求。取而代之的是基于C/S结构的服务器组,也就是说,网络游戏中的服务器是由多个物理服务器和对应的多个服务器程序来构成的。服务器组根据不同的需求会有多种构成方式,下面要介绍服务器组常用的构成方式。网络游戏会根据用户的需求和运营的需要,将服务器划分为几个不同的逻辑服务器。若有需要的话,这些逻辑服务器也会被分配到不同的物理服务器中。早期的网络游戏逻辑比较简单,用户数量也不多,一般会采用如图2.2的构成方式。这种方式包括一个数据库服务器,多个游戏服务器和一个登陆服务器。从逻辑上划分连接服务器和游戏服务器,不仅解决了服务器验证问题,也有利于网络游戏运营商的运作。因为一般来说,游戏开发商都不会运营自己的游戏,而是依靠游戏运营商代理自己的游戏,但是游戏运营商都有自己的游戏运营平台,把登陆服务器独立出来,运营商就能够对用户和收费等项目进行统一的管理。登陆服务器是游戏的唯一入口,客户端首先连接的就是登陆服务器。登陆服务器起到向导的作用,负责向客户端提供游戏类型及游戏服务器列表以及游戏服务器当前在线人14 第二章服务器端总体设计数等信息。数据库历来是网络游戏服务器的瓶颈之一,把数据库服务器独立出来,更有助于游戏服务器的良好运行。数据库服务器一般都使用数据库管理系统,其中大部分基于SQL的引擎,如MySQL,Oracle,SQLServer等。主要使用基于SQL的数据库管理系统的原因是大部分SQL数据库管理系统支持网络的数据通信,方便使用和管理。另外,由于使用结构化标准查询语言,即使替换数据库管理系统,也没有必要修改实际服务器程序的源代码。图2-2服务器组模型1随着网络游戏用户数的增多,一个游戏服务器已不能保证游戏的服务质量,多游戏服务器组应运而生。图2.2所示的服务器组中各个游戏服务器独立地运行游戏,所有的游戏服务器连接到同一个数据库服务器。使用这种模型可以增加接入的用户数,只要增加游戏服务器的数量就可以增加用户数量。但在这种模型中,每个游戏服务器中的数据毫不相干,因为每个服务器之间没有数据通信。这种模型仅仅是单纯地增加了接入用户数量,并没有增加用户的体验,因为用户得到的服务器最大在线人数并没有增加。不过游戏服务器连接到通一个数据库服务器,为用户转服提供了方便。为了使用户看到的最大在线人数增加,各个游戏服务器之间就必须进行通信。图2—3所示的服务器组能满足这一需求。相对图2.2所示的模型,增加了一个在线服务器,该服务器主要记录在线的用户,并担当各个游戏服务器之间通信的桥梁,不过这一切对用户来说都是透明的。通过该模型,既能够增加接入的用户数,也能够增加同时在线人数,使用户感觉到服务器最大在线人数得到了提升,增强了用户的体验。不过由于这种模型需要保持各个游戏服务器之间的同步,增加了 电子科技大学硕+学位论文服务器设计的难度。随着新技术的到来,物理服务器的成本逐年下降,性能也越来越强。不管从扩展性还是管理功能上都达到了前所未有的高度。通过购置多个物理服务器来增加玩家数量是不错的选择。现阶段,国内已经有多家网络游戏运营商通过增加物理服务器的方法达到了游戏同时在线人数超过万人。因而越来越多的网络游戏开始采用此模型。器图2-3服务器组模型2前面论述的两种模型都是按照游戏逻辑划分的。除此之外,还有一种常用的模型,该模型是按游戏区域划分的,如图24。在此服务器组模型中,各个区域服务器负责各自的区域,这是大型多人在线角色扮演游戏中常用的服务器模型。由于这种类型的游戏地图非常庞大,需要服务器处理的事物也非常多,把整个地图划分成各个区域后,每个服务器处理各自的事务,减轻了服务器的负担。在这种模型中,为了确保所有的玩家都是在同一个游戏世界种进行游戏,设置了专门管理游戏区域的区域管理服务器。通过管理服务器,玩家可以在不同的区域之间进行移动。同时,不同区域之间的玩家也可以通过区域管理服务器进行通信。16 第二章服务器端总体设计在网络游戏刚开始发展的那几年,很多网络游戏都采用了如图2_4所示的模型,但是该模型存在一些架构上的问题。因为该模型不能保证玩家能够大致平均地分配在各个区域服务器上,主要原因是玩家可以选择他想进入的区域。这样就造成了某些服务器过载,而某些服务器却非常闲,浪费了宝贵的服务器资源。网络游戏新开服务器的初期,大部分玩家的等级都比较低,因此都集中在类似于新手村的区域。但是就算运营了一段时间过后,这种现象也得不到消除,因为某些区域可能会特别吸引玩家。正是由于这样的原因,图2.4所示的模型已经不再实用。区域服务器l2.2.2P2P体系结构数据库服务器图2.4服务器组模型3器区域服务器3点对点技术(peer-to—peer,简称P2P)又称对等网络技术,依赖网络中参与者的计算能力和带宽,而不是把依赖都聚集在较少的几台服务器上。P2P技术现在已经广泛地应用于各个领域,包括网络视频、文件传输等。它和传统的C/S模型有很大的不同,在P2P模型中,每一个节点都可以既是服务器又同时是客户端,17 电子科技大学硕士学位论文它们之间没有分别。现在网络游戏也开始大量使用P2P技术。如图2.5所示。当网络游戏使用了基于对等网络技术的体系结构后,游戏玩家之间就可以直接进行通信了。很多信息就不需要服务器来保存,而是通过客户端来保存。当客户端消息产生后,客户端直接把消息发送给其他客户端,而不用经过服务器转发。这样不但减少了消息在传递过程中的延迟,还减轻了服务器的负担,使服务器能够更专注于处理游戏逻辑。现在市面上出现了很多格斗类的网络游戏,这种游戏对实时性要求比较高,所以大部分都采用了P2P体系结构。从另外一个角度看,消息通过客户端之间传递,也减少了网络带宽的使用量,为网络游戏运营商节约了资金。客户端客户端窨尸鞴图2.5P2P体系结构然而,由于玩家计算机的配置不同,硬件差异可能比较大,再加之网络通信的问题,如果在游戏客户端中保存游戏信息,会造成不同玩家之间的游戏世界存在差异。比如消息在传送过程中由于不可抗拒的因素丢失了,或者到达不同客户端之间的时间差异过大,必然会导致游戏数据的不一致。通常在采用对等技术的情况下,会将一部分游戏逻辑放到客户端来处理,这样可以充分利用客户端的资源来进行逻辑运算。但是,这样会导致更大的问题。当客户端可以进行游戏的逻辑运算后,肯定会出现大批破解者对游戏的客户端进行破解,找出其中的逻辑代码段,然后通过非常规手段来改变游戏逻辑运算的结果。如果把决策机制也放在客户端的话,破解者就可以更容易地通过修改执行文件来作弊。这样客户端就还 第二章服务器端总体没计需要能够验证其它客户端发来的信息,或者把决策机制放在服务器上。P2P在网络游戏中的应用,最大的问题是游戏逻辑运算的分配以及验证。在客户端作决策容易导致数据安全的问题。如果我们不在客户端作这些与决策机制有关的逻辑运算,而只作与决策机制无关的逻辑运算,而把那些与决策机制有关的逻辑运算放到服务上运行,那么就不会影响到整个游戏的安全和运营。基于这种思路演化出了基于服务器验证的P2P模型,如图2-6所示。当前也有很多网络游戏采用了此模型。客户端客户端图2-6基于服务器验证的P2P模型2.2.3分布式体系结构客户端分布式体系结构是综合了C/S和P2P的优点而产生的一种更好的结构,由于玩家所在的地区不同,使用该结构能够很好地解决此问题。这种结构有很多种实现方式,最常用的一种方式是使用镜像服务器来构建,如图2.7所示。在这种架构中,取消了单一的中心服务器,取而代之的是通过分布式技术连接起来的多个镜像服务器,这些镜像服务器都保存着相同的游戏数据。玩家在登陆服务器的时候,客户端会首先查找离自己最近的服务器,也就是查找网络延迟最小的服务器,然后使用这个服务器登录。客户端和服务器端的通信方式和C/S结构相同,但镜像服务器之间则使用私有的、低延迟的网络相互连接,他们之间的数据交换采用的是对等网络技术。使用分布式体系结构时,每个镜像服务器都保存了相同的游19 电子科技大学硕十学位论文戏数据,这样就算有一个服务器瘫痪了,游戏照样能够运行下去。但这就需要客户端判断服务器的状态,当客户端发现服务器状态异常时,自动去连接其他镜像服务器。随着网络游戏的不断发展,游戏玩家的数量也急剧上升,网络游戏服务器的处理能力受到了极其严峻的挑战。不管怎么提高服务器的硬件性能,仅仅使用一台服务器不能满足玩家日益增长的需求,更不能从根本上解决问题。分布式体系结构具有高科扩展性、高可靠性和高性价比,不失为网络游戏服务器的新方案。客户端客户端图2.7分布式体系结构2.3服务器端架构的设计与分析2.3.1体系结构客户端通过前面的分析我们已经知道目前网络游戏主要有三种体系结构:C/S结构,P2P结构和两者相结合的分布式体系结构。本课题采用的是C/S和P2P两种体系结构,但这并不同于分布式结构。由于本课题开发的是3D横版格斗动作RPG网络游戏,要求的实时性比较高,所以客户端之间采用P2P方式进行通信,而客户端与服务器端之间的通信仍采用传统的C/S模式。整个体系结构类似于前面提到的基于服务器验证的P2P模型。 第二章服务器端总体设计服务器端的体系结构如图2.8所示,由客户端、登陆服务器、在线服务器、游戏服务器、房间服务器和数据库服务器六部分组成【251。客户端连接到登陆服务器,认证成功后,从登陆服务器得到游戏类型和游戏服务器列表以及在线人数。客户端选择游戏服务器。在登入游戏服务器之前会进行二次验证,若验证通过,则开始游戏。此时客户端将绕开登陆服务器,直接连接到游戏服务器。游戏服务器主要负责游戏逻辑。在线服务器负责玩家重复登陆判断,并作为游戏服务器之间通信的桥梁。房间服务器是专门为本课题所开发的网络游戏设计的,当玩家的游戏角色进入战斗状态后,所有的游戏逻辑都转交由房间服务器处理,以减轻游戏服务器的负担。房『HJ服务器图2.8服务器端体系结构数据库服务器2.3.2登陆服务器客户端首先连接的就是登陆服务器。登陆服务器起到向导的作用,负责向客2l 电子科技大学硕+学位论文户端提供游戏类型及游戏服务器列表以及该游戏服务器当前在线人数等信息。此外,登陆服务器还用于验证客户端用户身份的合法性。客户端通过输入用户名和密码,然后加密传输到登陆服务器上,登陆服务器通过查询数据库以判定该用户是否合法,最终为客户端返回用户的完整信息。玩家的具体登陆过程如图2-9所示。图2-9登陆顺序图 第二章服务器端总体设计(1)客户端启动后,首先连接到登陆服务器。登陆服务器向在线服务器请求游戏服务器列表,以及游戏服务器信息。(2)在线服务器将服务器信息发送给登陆服务器,登陆服务器再把信息转发给客户端。(3)客户端选择好需要登陆的服务器后,把服务器的ID以及用户名和密码发送给登陆服务器。登陆服务器将用户信息传送给数据库服务器进行验证。(4)数据库服务器将认证信息返回给登陆服务器,登陆服务器在把信息转发给客户端。(5)如果认证通过,客户端就会去连接刚才已经选择过游戏服务器,并通过数据库服务器进行二次验证。(6)若验证通过,就会向游戏服务器发出登陆请求。游戏服务器会先向在线服务器发送登陆请求,验证该玩家是否重复登陆。若玩家没有重复登录,则在游戏服务器和在线服务器中都保存玩家的信息,并通知玩家登陆成功。2.3.3在线服务器在线服务器的主要功能是向登陆服务器提供游戏服务器列表以及游戏服务器在线人数信息。判断玩家是否可以登陆进入游戏。各个游戏服务器之间的通信也靠在线服务器来传递。比如游戏的聊天功能,如果玩家在同一个游戏服务器,聊天信息就直接通过游戏服务器发送。由于登陆游戏的玩家可能不在同一个游戏服务器中,但他们处在同一个游戏世界中,这种情况下他们之间聊天的信息需要通过在线服务器中转。2.3.4游戏服务器游戏服务器是整网络游戏服务器的关键所在,它的设计和实现方式很大程度上决定了一款网络游戏的成败。当玩家通过登录服务器成功验证了身份信息后就会进入到游戏服务器开始畅游游戏世界。游戏服务器负责处理游戏玩家大部分与游戏世界相关的请求。游戏服务器需要实现如下功能:接受玩家的登陆请求,并对玩家的身份进行二次验证。若合法,则在服务器 电子科技人学硕士学位论文端创建游戏角色。维护一个持续稳定、完整的虚拟游戏世界,并处理玩家向服务器发来的请求。向在线服务器报告自己的状态和登录玩家的状态。以便于在线服务器了解所有游戏服务器的情况。向房间服务器转发在游戏房间中进行游戏的玩家的消息。接收房间服务器的消息并转发给客户端。游戏服务器架构类图如图2-10所示图2.10游戏服务器架构类图CPerformer类是整个游戏服务器开发的基类,包含了最基础的操作,其他类大部分都从此类派生而来。CBaseServer类是服务器的基类,所有的服务器都基于此类开发。主要记录有服务器的IP地址,端口号,服务器允许的最大玩家数量等信息。CGameServer类是游戏服务器类,主要功能是提供游戏服务器的底层操作。游戏服务器启动时CGameServer类负责初始化所有游戏数据,并启动所有子线程, 第二章服务器端总体设计用于网络通信和数据库查询。CThread类是线程基类,服务器中的其他线程类都由此类派生。该类封装了基本的线程操作,包括创建线程、运行线程、结束线程和设置线程管理类的指针。CGSDBThread类是专门用于游戏服务器与数据库通信的线程类。所有和数据库有关的具体操作都由此类处理。cThreadManager类用于管理线程。线程的各种操作和设置都将在该类中实现。这样以后我们在任何程序中都可以很方便的使用该类来管理已经创建的线程。CDBLayer类是服务器数据库层总控制类,其中包含了需要连接的各个数据库的信息。该类还用于初始化连接各个数据库的ThreadManager类,并将需要处理的数据库消息发送给数据库线程。CSimLayer类实现了对游戏玩家逻辑事务的管理,该类继承于CThread类,是一个运行在服务器上的线程。从客户端发来的每一个消息都会存入CSimLayer类的消息队列里面。CSimLayer类通过调用Tick()函数进行消息的处理,并把消息的具体逻辑交给相应的游戏玩家类处理。游戏服务器玩家类图如2-11所示。图2-11游戏服务器玩家类图 电子科技大学硕十学位论文UserState是一个枚举值,标明了玩家所处的各种状态。CActor类是玩家信息的基础类,主要记录了玩家ID和玩家的IP地址等信息。CRoom类用于游戏服务器记录游戏房间信息,如房间ID,房间种类,房间状态以及对玩家等级的要求等信息。CChannel类是游戏频道类,每个房间都属与一个频道,所以CChannel类中存在一个房间列表m_mapRoomList,保存了每一个房间的指针。CGSUser类是整个游戏非战斗逻辑处理的核心,其中包含了每一个逻辑处理的方式。每当客户端发送一个消息到游戏服务器后,游戏服务器就把消息交给相应的CGSUser的对象,然后根据不同的消息ID,CGSUser通过switch语句进行处理。CGSUser对象在客户端通过验证后生成,其中还包括了玩家的所有信息,例如等级、游戏币、道具信息。CUserManager类用来管理CGSUser的对象比羽。其中包括一个玩家列表m_mapUserList:此数据结构是采用STL中的map容器来实现的,结构为map,其中UserUID是玩家账号,*CGSUer是玩家指针。这样我们可以通过玩家账号很快地找到玩家,并通过指向玩家的指针对玩家进行操作。成功登录游戏后,在该列表中添加此玩家的相应信息。玩家登录时,除了会在在线服务器中验证重复登陆以外,还会到游戏服务器来验证是否重复登录,通过查询此列表,若已存在该玩家,则判为重复登录,否则就在列表中加入该玩家的信息,通过登录验证。玩家在退出游戏后,将玩家信息将从此表中删除。2.3.5房间服务器房间服务器也是游戏服务器的另一个关键所在,因为整个游戏的战斗部分都是由此服务器负责。比如玩家清理完关卡的结算,经验值的计算,战斗逻辑等。房间服务器的另一个重要功能就是将房间内的信息广播给玩家。这些信息包括玩家昵称、等级,在此游戏中的积分、输赢局数,此时在房间中的状态等等。另外,房间还要实时地将房间的状态告诉给房间里的玩家,比如是不是正在游戏,没有游戏的话目前有几个玩家,等等。玩家状态信息和房间状态信息变化会非常频繁,要将这些信息广播给房间内的所有玩家,会对服务器造成比较大的负载。为了尽量降低服务器的负载,我们在每个房间服务器上设置一个特殊的列表,用来存储状态发生改变的玩家。一旦玩家的状态发生改变,就会将其添加到列表中。每隔一段时间,就将这个列表广播给玩家,广播完毕后清空列表,等待下次的存储和 第二章服务器端总体设计广播。客户端根据收到的信息,对玩家状态进行相应修改,这样就维护了玩家状态的实时性。这样大大减少了服务端的负载,还节约了网络资源。在对服务器端的软件进行架构设计时,我们使用了面向对象的设计模式,不仅对各个功能不同的服务器设计了高效的数据结构,还为整个服务器的设计抽象出了~套高复用性的架构,提高了编码时的效率。所以房间服务器和游戏服务器在架构上非常类似,只是逻辑处理部分不同而已。游戏房间种类有多种,包括多人对战房间、游戏关卡房间和广场房间。由于这些房间都有一些共有的属性,所以设计一个房间基类,而具体的房间类都由此基类派生而来。同样为了管理所有的房间,我们设计了房间管理类,用于将游戏服务器发来的消息转发给对应的房间。房间类图如图2-12所示。图2.12房间类图27 电子科技大学硕士学位论文第三章网络通信模型的设计与实现3.1SocketAPI概述socket可以被简单地看作一个输入输出设备,它可以在两个通信端点之间建立一个数据交换的桥梁。为了把消息传送到对方,两个通信端点都必须开启这个输入输出设备。当双方都设置好socket后,消息就可以很容易地传送了。一个通信端点可以向自己的socket写入数据,另一个通信端点就可以从自己的socket中读取传送过来的数据。socket可以运行在TCP或者UDP模式下f14】。在使用TCP通信时,socket需要分割数据包和组合数据包,数据包会按顺序到达另一个端点,在编写程序时程序员不需要关心这些功能是怎么实现的,这些都由协议自动完成,对程序员是透明的。程序员只需要使用socket发送数据,而这些数据会自动到达通信的另一个端点。在使用UDP通信时,socket传送数据的速度更快,效率也很高。但是在使用UDP时,数据包能不能到达另一端,数据包是否是顺序到达的,这都不能保证。socket最早是在UNIX操作系统中开发的,主要作用是为TCP/IP提供编程所使用的接口。由于socket的易用性,后来大部分操作系统都提供了socket编程。在TCP/口编程上,已经成为业界的标准。SocketAPI基本上都是纯C函数,程序员使用这些函数来创建和管理socket。每个socket都有一个唯一的标识,在Windows下称作句柄,在UNIX下面叫做描述符。每一个socket旬柄都代表了操作系统下的一个通信端点,而且使用socket不会使编写的程序收到操作系统底层的影响。在UNIX操作系统中,对于大部分操作来说,socket描述符和其他描述符(例如,文件描述符、设备描述符等)可以互换使用。在Windows操作系统中,对于大部分操作来说,socket句柄和其它句柄不能互换使用。为了使用socket通信,需要把一个本地地址或者一个远程地址绑定在这个socket上。这些地址建立了socket之间的通信关系。SocketAPI包含大约二十多个系统函数,这些函数可以分为以下五类:(1)本地环境管理。SocketAPI提供了一些函数,用来管理本地环境信息,这些信息通常存储在操作系统内核或系统库中,如表3.1所示。 第三章网络通信模型的设计与实现表3-1本地环境管理函数函数说明socketO工厂函数。用于分配一个socket旬柄,并返回给调用者。bindO将一个socket旬柄和一个本地或远程地址关联起来。getsockname()返回socket绑定的本地地址。getpeernameO返回socket绑定的远程地址。close()释放socket句柄,使其可复用。(2)连接的建立和终止。SocketAPI提供了一些函数,用来建立和终止连接,如表3.2所示。表3-2连接的建立和终止函数函数说明connect()主动在一个socket句柄上建立连接。listen()表示愿意被动侦听来自客户的连接请求。accept()工厂函数。响应客户请求,创建一个新的连接。shutdown()有选择地终止双向连接中读取方和(或)写入方的数据流。(3)数据传输机制。SocketAPI提供了一些函数,用来通过socket句柄发送和接收数据,如表3.3所示。表3.3数据传输机制函数函数说明send()通过某一特定的I/O句柄,传送和接收数据缓冲区。recvOsendtoO交换“无连接”数据报。其中,每一个sendtoO调用都要提供reevfrom0接收方的网络地址。(4)选项管理。SocketAPI定义了一些函数,允许程序员改变缺省的socket行为,从而支持多播和广播,并能修改/查询传输缓冲区的大小。如表3-4所示。表3_4选项管理函数函数说明setsockopt()在协议栈的不同层修改选项。getsockoptO在协议栈的不同层查询选项。29 电子科技大学硕十学位论文(5)网络地址。除了以上介绍的函数之外,网络应用程序还经常使用一些函数来将具有可读性的名称解析为低级网络地址。如表3.5所示。表3-5网络地址函数函数说明gethostbynameO处理主机名和IPV4地址之间的网络地址映射。gethostbyaddr()getipnodebynameO处理主机名和IPv4/IPv6地址之间的网络地址映射。getipnodebyaddr()getservbyname0通过具有可读性的名称标识服务。虽然SocketAPI常用来编写TCP/IP应用程序,但它用途之广,足以支持多个通信领域。3.2w.nsock基础3.2.1Winsock简介Winsock是Windows操作系统下的一种网络编程接口。Winsock支持多种网络协议,并得到广泛应用。从W'msock开始出现到Winsock2的完善一共用了多年时间,并得到了世界各大计算机公司的支持,包括英特尔、微软等。现在Winsock已经成为windows网络编程的规范【15】。Winsock主要是以UNIX套接字规范为基础,在Windows在重新定义的一套新的网络编程接口。Winsock函数的风格与伯克利套接字的风格非常相似,使熟悉UNIX套接字编程的程序员能够很快地适应Winsock编程。Winsock还扩展了UNIX套接字,这样程序员还可以利用Windows的特性来进行网络编程。Winsock提供了一套Windows网络编程的规范,让软件开发人员共同遵守。3.2.2Winsock函数(1)WSAStartup()函数该函数必须是应用程序调用的第一个Winsock函数。使用了这个函数以后,才能使用其他网络编程的函数,如果最早没有调用这个函数的话,再使用其他的网络编程函数就会失败,并返回一个值表示失败的原因。比如返回了一个值是30 第三章网络通信模型的设计与实现WSAEFAULT,说明有一个参数出错了,这个参数现在不是一个有效的指针。又或者返回值是WSASYSNOTREADY,说明了操作系统的底层还没有准备好进行网络通信。WSAStartup()i噩i数有一个参数可以指定Winsock的版本号。其中版本号包括了高位版本号和低位版本号,高位版本号代表了通信所使用的次要版本,而低位版本号说明了通信所使用的主要版本。由于Winsoek是向下兼容的,当推出了新的版本过后,老版本使用的函数都还是可以使用,编写好的程序也可以不做改变。(2)WSACleanup()i函数WSACleanup()函数的调用对于操作系统来说是至关重要的。对于每一个调用了WSAStartupO函数的程序来说,在程序不再使用网络通信时,都需要调用一次WSACleanup0函数,如果这个程序调用了多次WSAStartup()i蚕l数,最后也必须调用响应次数的WSACleanup0函数。因为操作系统对每个程序的每一次WSAStartup0调用都记录在案,而且还是用了一个计数器来记录调用的次数。如果程序都结束了,计数器的的值都还不是零的话,系统资源就得不到释放,浪费了宝贵的硬件资源。其实只有最后把计数器变成零WSACleanup0函数调用在清除系统分配的资源,其它的每一次调用只会把计数器减一。如果函数的返回值是零就说明清除成功,其它的值都表示失败。具体的原因可以调用错误函数来查明。(3)WSAGetLastError0函数对于网络编程来说,有错误检查和控制的机制是非常必要的。对于微软的操作系统来说,使用了网络编程函数过后,经常会返回错误代码。但是在很多情况下,就算发生了错误,程序照样可以运行,还是可以通过套接字发送和接收信息。在发生了错误的情况下马上调用这个函数,就可以得到操作系统返回的错误码,通过错误码,就可查到到底是哪一点出了问题。WSAGetLastError0函数的返回值都是操作系统已经定义好了的常量。(4)WSASocket0函数和socketO函数WSASocketO函数和socket()函数都可以用于建立新的套接字。不过WSASockctO函数是第二代微软套接字编程中新增的。WSASocket函数的前三个参数与socket函数的前三个参数相同,用于指定地址家族、套接字类型和协议类型。现今在使用的地址家族一共有七个,其中有一个就是网络游戏经常使用的互联网家族。套接字类型也有五种,网络游戏常常使用的是基于流的套接字和数据报套接字,他们分别对应了TCP和UDP。前面的三个参数有多种组合,但不是所有的组合都可用,操作系统对于这些组合都有说明。WSASocket()i涌数建立的 电子科技大学硕十学位论文套接字可以指定该套接字是否可以被重叠使用。而使用socket()函数创建的套接字默认就是可以重叠使用的,相当于调用了后面三个参数都是默认值的WSASockct0函数。重叠使用的好处就是在使用套接字发送和接收数据的时候,程序不用等待套接字发送完数据或者接收完数据才能进行其他的工作,而是在开始发送或接收以后,就做别的事情,做到异步处理。重叠的套接字就可以被发送和接收函数多次调用而不必阻塞。需要发送和接收的数据都放入缓冲区,然后组成一个队列,等到操作系统对它们进行处理。在函数成功调用以后,系统就会返回一个非负的整数值,用于标识套接字,通常把这个整数称为套接字描述符。这个定义和句柄非常相似,不过微软的套接字和其它的普通句柄是不同的,不能混用。(5)shutdownO函数和closesocketO函数当套接字完成了它的任务过后,就需要关闭它所建立的连接,并且要释放它所拥有的一切资源。调用closesocket()函数就可以完完全全地做到这一点,所有与这个套接字相关的资源都会被立即释放掉。但这样就带来了预料不到的错误,因为立即释放资源可能会导致某些还未完成处理的数据丢失。所以安全的办法是在调用closesocket0函数之前,先调用一次shutdown()I函数来告诉发送方和接受方,该连接即将中断,这样双方就不会再接收和发送数据。当数据处理完成过后再调用elosesoeket()i函数就不会造成数据丢失。shutdown()i函数有一个参数就指明了套接字应该怎样响应这种请求。这个参数一共可以取三个值,它们分别表示了不允许再使用发送函数,不允许再使用接收函数和两种函数都不允许使用。需要注意的是,在调用了closesocket()i函数的套接字上再调用closesocketO函数就会发生错误。因为当没有程序再使用这个套接字的时候,它所对应的资源全部都被释放了。(6)bind()函数在创建套接字的时候,已经指定了套接所使用的协议,但是要使用套接字还需要为它绑定一个地址,使用bind()函数就可以做到这一点。通过这个地址才能使用套接字接收和发送数据。bindO函数中的第二参数就是指向这个地址结构的指针。一般来说,在使用互联网的TCP/IP协议族时,会用到一个专有的地址结构。这个地址结构包含了四个成员变量,有地址家族、端口号、IP地址和填充数组。在很多做为服务器的机器上,会有多个口地址,为了让服务器服务于所有的客户,会在绑定地址的时候选择一个特殊值,让不同网络上的所有用户都能连接到服务器。如果调用bind()函数后,返回值是零,就表示绑定成功,否则绑定失败。最常见的是错误是重复绑定错误,当绑定了的套接字再次绑定的时候就会出现此错32 第三章网络通信模型的设计与实现误。(7)listen()i函数listen()函数的主要作用是让服务器在同一时间能够接收到多个连接请求,不过当连接请求的队列满了以后,请求连接方就会收到错误消息报告。listen()函数的第二个参数就可以指定队列的长度。当服务器接受了一个连接以后,这个队列的第一个请求就会出队,这样队列就多了一个空闲的空间可以让新的请求入队。但是这个参数的值是有一定限制的,这取决于操作系统底层的设计。如果在调用此函数的时候使用了不允许的值,那么操作系统会自动设置一个有效值。(8)WSAAcceptO函数和accept()[函数WSAAccept0函数和accept0函数都用于接受客户连接请求,并创建一个新的套接字来与客户通信。但WSAAccept0函数能根据一个条件函数的返回值,选择性地接受一个连接。服务器通过这两个函数从正在等待连接服务的队列里面取出第一个需要建立连接的服务,然后创建一个新的套接字用于服务器和客户之间的通信,在连接成功的情况下,函数会返回一个非负整数,也就是新的套接字描述符,客户和服务器之间的通信就可以使用这个新的套接字。当函数调用失败后,就会返回一个标识无效套接字的值。注意,这两个函数都只能用于流式套接字,其它形式的套接字使用这个函数会发生错误。函数还有两个参数用来保存新创建的套接字的地址结构和地址结构长度。调用这两个函数过后,请求连接队列里的第一个连接请求就会得到服务,并且出队,为新来的连接请求腾出位置。当函数返回以后,作为函数参数传入的地址结构就会被填充为客户端的地址信息。函数返回的新的套接字也就和提出连接请求的客户端绑在了一起。从此以后,不管是发送操作还是接收操作都可以使用此套接字。用于监听的套接字还是继续按照原来的方式接受其他用户的连接。在调用函数过后,如果请求连接的队列为空,那么程序就会被挂起直到有新的连接请求。(9)WSAConnect()"i函数和connect()函数WSAConnect0函数和connect()函数都用于请求连接。WSAConnect0函数的前三个参数与connect()函数是一样的。另外两个参数是字串缓冲区,用于收发请求连接时的数据。WSAConnect0函数可以定义将要建立的通信连接上的带宽,为连接的质量提供了服务。如果服务器没有对客户提供的端口进行监听,函数调用就会失败,并产生错误码发送给客户,一般来说,这个错误码是连接被拒绝。还有一种错误就是连接超时的错误,当客户要连接的服务器没有开机或不可达的时候就会返回这种错误。33 电子科技大学硕十学位论文(10)WSASend0函数和send()函数WSASend0函数和send()函数都可用于发送数据。WSASend0函数是Winsock2版本的发送函数,不但使用了WSABUF作为缓冲,并支持异步I/O模式。使用send()函数的时候,有一个参数是指向字符缓冲区的指针,这个字符缓冲区用于存储将要发送的数据。还有一个参数说明了要发送的字符数。调用send0函数过后,系统首先执行的步骤是比较需要发送的字节数和套接字缓冲区最大能容纳的字节数,如果需要发送的字节数比套接字缓冲区最大能容纳的字节数还要大,函数就会返回错误。如果需要发送的字节数小于套接字缓冲区最大能容纳的字节数,系统就会先去检查发送是否还在进行中,如果发送还在继续的话,那么系统就会等待,直到发送停止,如果还没有开始发送操作的话,系统就会去比较需要发送的字节数和套接字缓冲区的剩余空间字节数,当剩余空间字节数小于需要发送的字节数时,就等待发送完套接字缓冲区的数据,否则,把要发送的数据拷贝到套接字的发送缓冲区。注意,send0函数的作用仅仅是把要发送的数据拷贝到套接字缓冲区,而不是发送数据,发送数据的操作全部是由操作系统完成的。当函数调用成功以后,会返回拷贝的字节数,否则就返回错误。函数返回的时候只是需要发送的数据成功拷贝到了套接字缓冲区,而并不是需要发送的数据已经到达了接收端。(11)WSARecv0函数和recv()函数WSARecv()函数和recv()函数都可用于接收数据。同发送函数一样,WSARecv0是Winsock2版本的接收送函数,同样使用了WSABUF作为缓冲,并支持异步YO模式。使用recv()函数的时候,有一个参数是指向字符缓冲区的指针,这个字符缓冲区用于存储将要接收的数据。还有一个参数说明了准备接收的字节数。调用recv()函数后,系统首先会去检测套接字的发送缓冲区里面还有没有数据,如果在发送的途中出现错误的话,函数就会返回错误,如果套接字的发送缓冲区里面的没有数据或者数据已经全部传送完成,那么系统就会接着去检查套接字的接收缓冲区。如果套接字的接收缓冲区没有数据或者系统正在进行数据接收的话,函数就会等待,直到系统完成这次接收的过程。当系统完成接收操作过后,函数就会把接收到的数据从套接字的接受缓冲区拷贝到用于接收的字符缓冲区。由于接收到的数据可能大于接收字符缓冲区,所以调用一次recv()i函数是不够的,需要调用多次才能把套接字接收缓冲区的所有数据全部拷贝到用于接收的字符缓冲区。当然函数起到的作用也只是拷贝数据,接收数据的任务还是靠操作系统来完成的。拷贝成功以后,函数会返回成功拷贝的字节数,如果拷贝出错,函数就 第三章网络通信模犁的设计与实现会返回错误代码。3.2.3WinsockI/0方法Windows操作系统提供了两种输入输出方法,这两种方法分别是套接字模式和套接字输入输出模型,这样就可以对套接字的输入输出进行操作,让套接字接收和发送数据的时候更方便。套接字模式主要定义了怎么样来操作这个套接字,系统的函数应该怎么样使用。套接字模型则说明了网络程序应该使用什么方法来对套接字的输入和输出操作进行管理。其实这两种输入输出方法是无关的,套接字模型设计出来就是为了弥补套接字模式先天的不足。3.2.3.1套接字模式Windows套接字在两种模式下执行I/O操作:锁定和非锁定【161。如果所使用的套接字处于锁定模式,那么所有其他的操作都会被挂起,等待当前的操作结束。当前的操作结束以后,操作系统才会将控制权返回给应用程序。如果所使用的套接字处于非锁定的状态,那么所有网络编程所调用的函数都会马上返回结果,而不会阻塞应用程序。(1)锁定模式在锁定模式的情况下,不论对套接字调用什么网络编程函数,都会等待或多或少的一段时间,因为基本上所有的windows网络用应都是通过互斥来实现程序同步的。但是在这种模式下就有一个很大的缺点,因为一旦使用了这种模式,就不能对多个套接字同时提供服务,交互性比较低。(2)非锁定模式非锁定模式比起锁定模式来说,很多方面都得到了提升,还有新加入了一些功能,使性能更强。在使用这个模式的时候,所有的函数在调用以后都会马上返回,不会被阻塞。但是在函数返回过后一定要检查函数的返回值,因为经常会出现函数调用的失败的情况。不过这些检测也会耗费不少处理机资源。还有一个不足之处是,使用这个模式在编程和逻辑处理方面有不小的难度,当然如果能够客服这个困难的话,使用非锁定模式是很不错的一个选择。3.2.3.2套接字I/0模型共有五种类型的套接字I/O模型,可让Winsock应用程序对I/O进行管理,它们包括:select(选择)、WSAAsyncSelect(异步选择)、WSAEventSelect(事件 电子科技大学硕士学位论文选择)、overlapped(重叠)以及completionport(完成端口)。(1)select模型select模型是使用最频繁的模型之一,它主要就是使用select函数对输入输出操作进行管理。最早设计这个模型的时候使用的全部都是UNIX操作系统,它们全部也都采用的是伯克利套接字。后来微软把这个模型也引入到了自己的操作系统中,通过这个模型可以对多个套接字进行处理,而不会导致阻塞问题。微软的套接字兼容伯克利的套接字解决方案,如果伯克利的套接字使用了select函数,那么不必对它做任何修改,就可以继续使用。(2)WSAAsyncSelect微软提供了一个可以处理异步通信的的输入输出模型。这个模型就是WSAAsyncSdect模型。使用这个模型,应用程序可以在使用了套接字进行数据传输操作过后等待操作系统的反馈。具体的使用方法就是首先创建一个新的套接字,然后在这个套接字上调用WSAAsyncSeleet函数。使用WSAAsyncSelect函数之前必须要新建一个窗口句柄,然后再为这个窗口编写一个窗口例程支持函数。接着就可以新创建一个套接字,并调用该模型所使用的函数,最后打开窗口消息通知。如果应用程序对套接字使用了这个模型函数,套接字的模式就会从锁定状态变成非锁定状态,因为这个原因,很多函数都会返回失败的信息,所以应用程序一定要检查操作系统返回的通知,然后判断套接字上发生的事件。(3)WSAEventSelectWSAEventSelect模型是微软操作系统提供的另外一个异步输入输出模型。它和WSAAsyncSelect模型相似,可以让应用程序同时在多个套接字上面进行输入输出操作,并且同样可以接收操作系统返回的网络事件消息。对于使用了WSAAsyncSelect模型的网络应用程序,可以在只改变少许代码的情况下,把它们换成WSAEventSelect模型。在新的模型中,同样能够接收和合处理原来的那些消息。这两个异步输入输出模型的最大差异在网络通信所返回的消息会投递到不同的对象上面,WSAAsyncSeleet模型是一个窗口例程,而WSAEventSelect模型是一个事件对象的句柄。(4)重叠模型重叠模型主要是要使用一个操作系统定义的重叠的数据结构,在完成请求过后通知网络应用程序为它们服务。重叠模型在使用的时候要把操作系统的事件对象和一个重叠数据结构绑定在一起,当调用收发数据的函数以后,函数会马上返36 第三章网络通信模型的设计与实现回。等到输入输出操作完成以后,事件对象的状态会改变状态,通过对事件状态的判断,就可以处理这次I/O请求。(5)完成端口模型完成端口模型是微软为了应付大规模的输入输出操作而开发出来的高效模型。通过这个模型,系统在处理输入输出操作的时候能够达到极高的性能。当然并不是只要有输入输出操作就要使用这个模型。这个模型只有在满足某些条件的时候才能发挥出最大的性能。比如在处理套接字接收和发送消息的时候,最好是套接字的连接在几百个或者上千个,而且服务器的处理机数量也最好不是一个。这样才能完全发挥完成端口的威力,不然使用完成端口达不到预期的效果。完成端口主要通过几个函数来控制,最先要把套接字和完成端口关联起来,然后创建多个工作者线程,不断地对完成端口处理的情况进行检测,然后处理消息。3.3通信模型的实现网络通信是游戏服务器设计的重点,建立连接、接收和发送数据都是它的主要任务。设计游戏服务器最重要的一点就是能够承受巨量连接,适应较高的吞吐量。另外,在设计游戏服务器时也要考虑服务器的架构是否能够复用,这样才能在开发时节省大量时间。为了达到这一目的,项目采用了大量设计模式,构建了复用性较强的网络游戏服务器系统。通信模型的总体结构分成三个层次,如图3.1所示。操作系统适配层(OSAdaptationLayer)操作系统适配层是操作系统应用程序接口和通信模型上层之间的代码层,使用这些代码可以屏蔽操作系统接口与上层应用的关系,使上层编写的代码与底层的操作系统平台无关。开发者很容易就可以把应用程序移植到其他操作系统平台上。C++包装层(C++WrapperLayer)C++包装层包括一些C++包装类,使用这些类可以进行可移植的和类型安全的C++网络编程。C++包装类将函数和数据封装在类型安全的面向对象接口中。C++包装层位于操作系统适配层之上,并提供了大致相同的功能。因为所有的功能都被封装成了类,而不是单纯的C函数,所以在使用时更加方便。框架层(FrameworkLayer)框架层是通信模型中的最高层,它采用了通信软件领域独特的设计模式。程序员在使用框架提供的组件时不必考虑过多底层细节,直接在上层构建系统即可。37 电子科技大学硕士学位论文一<>\7C++Wrapper\/CAddressCSockStreamCSockDgramCSocklO<>≮7OSAdaptation、/OSAdaptationsocketacceptoonnect“料send图3-1通信模型的总体结构图3-2通信模型的编程接口38 第三章网络通信模型的设计与实现3.3.1框架层从应用程序的角度来看,通信模型的框架层由两个通信类组成,它们之间的关系由图3.2所示。连接器类(CConnector):连接器类是一个工厂,用于主动地建立一个新的通信端点,方法ConnectO用于主动发起连接。客户端通过连接器向服务器发起连接请求,如果连接成功,连接器会创建一个字节流类对象用于数据交换。接收器类(CAcceptor):接收器类也是一个工厂,用于被动地建立一个新的通信端点,方法AcceptO用于等待连接。服务器通过接收器等待客户端的连接请求,如果客户端连接成功,接收器也创建一个字节流类对象用于数据交换。从图3.2中看到,连接器类和接收器类都有一个用于地址绑定的函数,他们分别是LocalBindO和RemoteBindO。CAcceptor的成员函数LocalBindO可以把一个协议地址对象绑定到接收器对象上,接着调用成员函数aeeeptO,接收器对象就会使用刚才绑定的地址等待连接请求。CConnector的成员函数RemoteBindO可以把一个协议地址对象绑定到连接器对象上,接着调用成员函数connectO,连接器对象就会使用刚才绑定的地址发出连接请求。在使用通信模型以前,首先应该创建协议地址对象,并根据需要使用的协议初始化它,接着把它与连接器对象或者接收器对象绑定起来。由于连接器和接收器都是工厂,每次建立连接过后,就会新建字节流对象用于接收和发送数据。此后数据的传输都与连接器和接收器无关。3.3.2C++包装层协议地址类(CAddress):协议地址类的作用是封装不同通信协议的共性。具体的协议地址类都是通过协议地址类派生出来的。比如使用互联网领域套接字的协议地址类就是继承自协议地址类的,不过还增加了一些它自己的成员函数和成员变量。字节流类(CSockStream):封装了使用TCP发送和接收数据的一系列操作。当连接建立成功以后,该类对象就可以使用自己的成员函数进行数据的接收和发送了。字节流对象不能用作其他任何角色,只能用作数据传输。数据报类(CSockDgram):网络游戏服务器主要使用面向连接的流通信,但是,也提供无连接的服务,它使用因特网协议组中的UDP。数据报类为在运行在本地和远程主机上的进程间交换数据报提供机制。IO类(CSockIO):IO类用于网络通信Io的同步和异步操作。在网络游戏中 电子科技大学硕士学位论文网络通信主要使用异步操作。IO类的异步机制允许服务器通过一个或多个I/O句柄来启动I/0请求,不用因为要等待请求完成而被阻塞。而当请求结束时,操作系统会通知调用者,服务器则可以在已经结束的I/O句柄上继续处理。框架层和C++包装层主要通过地址绑定进行构架。地址绑定的主要作用在于把与协议有关的操作与具体的通信分离,使用地址绑定以后,可以把与通信协议有联系的操作在初始化地址对象的时候进行,并且仅仅在这个时候使用与协议有关的操作。在通信模型中,地址绑定用于连接器和接受器中,图3.3以接受器为例,接收器工厂类和接收器工厂实现类之间采用了桥接设计模式【231,每个接收器工厂类对象聚合了一个接收器工厂实现类对象,这个对象使用了一种具体的通信协议。在地址绑定模式中,最重要的地址类定义了两个工厂函数,使用这两个工厂函数来创建具体的通信对象。具体要使用的通信协议是在定义具体的地址类时,从地址基类继承而来的,并实现了这两个工厂,使用它们分别创建具体的连接器对象和接收器对象。应用程序在调用地址本地绑定函数的时候,会传入具体的地址对象参数,本地绑定函数使用传入的地址对象创建一个特定的接收器对象,使用该对象就可以进行网络通信。通过使用地址绑定可以把与协议有关的操作集合绑定到和协议不相关的接收器工厂类中。此时在调用接收器工厂类的接受函数后,接收器工厂函数类对象就可以调用接收器工厂实现类的接受函数。当连接请求来到后,接受函数就会创建一个字节流类对象,使用这个字节流类对象就可以实现与发送端之间的通信。字节流类对象在实现时也使用了策略模式【241,这样做可以使抽象部分和实现部分相分离,便于以后扩展。CAddressCAcccptorCAcceptorlmpCreateAcceptorlmP0ILocalBindOLocalBind0CreateConnectorlmlX):Ace,opt0Accept(,)△!△:险LocalBind(){。一re_imp2addr->CreateAcceptorlmp();lCAddr℃ssSockctCSocketAcceptodmpIpAddrPOnAcce:pt0CreateAcceptorlmp0‘『et哪鹏weA鹏}wcIptorlmlR){CAcceptorlmpSocket;,气Creat正onnectorlmp0图3-3地址绑定 第三章网络通信模型的设计与实现C++包装层把与通信模型有关的操作放在了地址类,连接器实现类,接收器实现类和字节流类中,然后再使用地址绑定,将所有的与通信协议有关的操作局限于具体的地址类。由于地址类的中连接器和接受器的具体实现函数都是私有的成员函数,因此所有与通信协议有关的操作都是在地址类初始化的时候使用模板函数实现的。图34以接收器为例展示了C++包装层中通信模型的序列图。整个过程包含下面几个步骤:囤I甲CAddmss囤[CAccemodmp_]曰CSlream囤IC Ireale::——T一z●IIIII●—LocalBind0:c嘲睡Ac∞呻砌mp(),!’⋯Creale:ruIAccept()IAccepl0Cn赡ru1一;::二)Storeobject图3_4地址绑定和连接建立(1)通信模型首先通过需要用到的协议创建具体的地址对象。(2)程序使用本地绑定函数把具体的协议地址对象绑定到接收器工厂类对象上。接收器工厂类对象使用地址类对象的接收器实现函数创建一个使用该协议的接收器工厂实现类对象。(3)接收器调用接收函数等待新的连接请求,若没有请求则挂起,直到新来的请求唤醒它。具体实现时,接收器工厂类对象直接调用接收器工厂实现类对象的接收函数。(4)当有新的连接到来时,接收器会创建一个字节流类对象。这个对象根据所要使用的通信协议初始化。当初始化完成以后,应用程序就可以使用字节流类41 电子科技大学硕士学位论文对象进行数据传输。接收器对象在完成创建字节流类对象后,进入下一次循环,等待接受新的连接。3.3.3操作系统适配层(1)Socket类的实现为了实现游戏通信模块的功能,在Windows操作系统下设计SocketObject类作为网络通信的基类,该类中封装了WinSock1.1版本和WinSock2.0版本中的基本SOCKETAPI函数。支持TCP及UDP。首先介绍一下SocketObjeet类的接口函数,如下所示:classCSocketObjeet{public:InitSocket0;CloseSocketO;Connect();AcceptConnectionO;CloseConnection0;OnSendComplete0;OnRecvComplete0;Send();RecvO;Proceed();AllocBuffer();protected:m_RecvBuf;m_SendBuf;);当服务器启动后初始化网络连接,调用InitSocketOi函数设置基本的参数,并在指定的端口上侦听套接字,判断客户端是否有新的连接请求。若没有连接请求则继续监听端口。如果客户端有新的连接请求,则调用成员函数AcceptConnection()建立新的连接,生成用于和客户端通信的新的套接字对象。此时客户端和服务器42 第三章网络通信模型的设计与实现端便可以通过RecvO和Send()两个成员函数进行数据交互了。OnSendComplete()函数和OnRecvCompleteO函数的主要作用是异步处理套接字通信。当数据发送完成以后,程序会调用OnSendCompleteO函数通知套接字对象对数据进行处理。同理,当数据接收完毕以后,程序会调用OnReevCompleteO函数通知套接字对象对数据进行处理。数据包接收的工作完成以后,调用Process()就可以将接收到的数据交给协议层进行进一步的处理。当客户端和服务器端的通信工作结束以后,需要关闭连接和套接字,通过调用CloseCormeetionO和CloseSocketO来实现。AllocBufferO是用来为缓冲区分配内存的函数。mRecvBuf是接收缓冲区,mSendBuf是发送缓冲区。SocketObjeet类成员函数调用流程图如图3.5所示。N∈户端端程序开多∈务器端程序开事上上初始化网络连接lnitSoeket初始化网络连接J,lnitSoeket监听端口’,0连接服务器申请连接服务器,◇Conneet<◇朦AcceptCon矿ncmion0建立新的套接字YlnitSoeket1,0收发数据包.数据交互、收发数据包Send/ReevSend/Recv0上处理数据包处理数据包ProceedProceed审◆关闭连接关闭连接aoseConnectionaoseCo㈣CloseSocketCloseSocket/~t、/、、(程序结束)\、.、.——、————.———————....—.—,,/7图3-5函数调用流程图43 电子科技大学硕+学位论文(2)IOCP类的实现构架一个服务器应用程序一般采用以下两种模型之一。串行模型:一个线程等待一个客户发出请求。当请求到达的时候,线程会被唤醒并对客户请求进行处理。并发模型:一个线程等待一个客户发出请求,并创建一个新的线程来处理请求。当新线程正在处理客户请求的时候,原来的线程会进入下一次循环并等待另一个客户请求。当处理客户请求的线程完成整个处理过程的时候,该线程就会终止。串行模型的问题在于它不能很好地同时处理多个请求。如果两个客户同时发出请求,那么一次只能处理一个,第二个请求必须等待第一个请求的处理结束。使用串行模型设计出来的服务不能充分发挥多处理器机器的优势。串行模型只能满足最简单的服务器应用程序,在这类应用程序中客户请求非常少,而且能够非常快地完成处理。由于串行模型存在这样的限制,因此并发模型及其受开发者的欢迎。在并发模型中,每个客户请求都会由一个新创建的线程来对其进行处理。这种模型的有点在于等待请求的线程只有很少的工作需要做。大多数时间它都处于睡眠状态。当客户的请求到达后,该线程会被唤醒,创建一个新的线程来处理请求,然后等待下一个客户请求。这意味着能够对客户的请求进行快捷的处理。此外,因为每个客户都有自己的线程,所以服务器应用程序具备非常好的伸缩性,能够轻易地发挥多处理器机器的优势。但是当大量请求到来时,应用程序的性能不如预期的高。同时处理许多客户请求意味着系统中会有许多线程并发执行。由于这些线程都处于可运行状态,因此操作系统内核在各个可运行的线程之间进行上下文切换花费了太多时间。WindoWS为了解决这个问题,打造了完成端口内核对象。微软为了解决大规模网络应用中的输入输出问题制定了完成端口这一新技术。完成端口的主要原理是操作系统内部维护了一个输入输出通知队列,在应用程序请求一个重叠输入输出后,操作系统会对这个输入输出请求进行处理,当处理完成以后,操作系统把这个处理完毕的消息放入到通知队列的尾部,应用程序再从这个通知队列中取出这个消息,然后再根据消息的内容进行处理【261。网络游戏的在线人数比较多,一般来说一台服务器至少会服务于几百个客户端,有时候甚至会同时和几千个客户端进行通信,对于如此大规模的通信,服务器常常会达到崩溃的边缘。怎么样能够处理大量客户消息,而又不至于影响服务质量,是架 第三章网络通信模型的设计与实现构服务器的一大难题。由于服务器需要与大量的客户端建立连接,所以该游戏的通信系统采用套接字I/O模型中的完成端口模型271。我们将IOCP类实现为一个单件,并建立多个IO线程为完成端口服务。完成端口IOCP类的实现如下所示:classCIOCP:publiccstn百eton(public:InitO;AssociateSocket0;GetStatus0;GetSocketObject0;OnlOComplete0;SetlOThreadNum0;BeginlOThread0;EndlOThread0;protected:std::vcctor<+CThrcad>mveeThread;std::mapm_mapAssociSock;m_criticalsection;)IOCP类成员函数的用途:InitO:初始化IOCP,调用了函数CreateloCompletionPort0。AssociateSocket0:用于关联套接字和完成端口。GetStatus0:检测完成端口状态,调用了函数GctQueuedCompletionStstus0。GetSocketObject0:返回指向CSocketObjcct的指针。OnlOCompleteO:服务已经完成的IO事件。SetlOThreadNumO:设置IO线程的数量。BegirdOThread():启动IO线程。EndlOThread():终止IO线程。IOCP类成员变量的用途:mvecThread:存储了IO线程的指针。mmapAssociSock:存储了所有和完成端ISl关联的套接字。45 电子科技大学硕士学位论文mcriticalsection:用于线程同步的临界区变量。Io线程类的实现如下所示:classCIOThread:publicCThread{public:R1m();Loop();OnSendComplete0;OnRecvCompleteO;)IO线程类成员函数的用途:RunO:循环判断线程状态,若状态正常则调用成员函数Loop()。Loop():调用CIOCP类的GetStatusoi函数来获取I/O操作的结果并处理。OnSendCompleteO:数据包发送完成后的处理函数。OnRecvComplete0:数据包接收完成后的处理函数。完成端口的流程图如图3-6所示。主线程广——J——];广——工——]异步收发数据}⋯⋯一j完成I,o操作l⋯⋯..o【—..。———.—..。..—,.—,———、——。——————,J【——.。........、——......—..—..,——..一图3-6完成端口流程图 第三章网络通信模型的设计与实现其处理过程为:(1)通过继承单件模板类创建一个完成端口单件。(2)调用完成端口类的线程管理函数创建IO线程。(3)主线程里循环检查是否有新的客户请求。(4)如果有新的客户请求连接,则新建一个套接字,并把这个新的套接字用CIOCP类的AssociateSocket()成员函数关联到完成端口。(5)套接字对象发出一个异步的发送或者接收请求,因为是异步请求函数,函数会马上返回,实际的发送或者接收数据的操作由操作系统去做。(6)主线程进入下一次循环,并阻塞等待客户请求。(7)操作系统完成发送或者接收操作后,把结果发到完成端口队列。(8)10线程循环调用CIOCP类的成员函数OetStatusO从完成端口队列得到I/O操作结果。若没有I/O操作完成,线程将自己切换到睡眠状态。直到有I/O操作完成并进入完成端口队列后,线程才会被唤醒。(9)IO线程根据I/O结果调用不同的I/O服务程序。3.4多线程编程微软的操作系统支持抢占式多任务和多线程编程。由于支持抢占式多任务,在Window操作系统上进行编程可以使程序同时处理多个任务,这样多个任务就可以并发执行。因为Windows操作系统支持多线程,这样每一个进程中都可以创建多个线程,使每个任务中再同时处理多个子任务成为了可能。在同一个进程中的线程都有属于自己的空间,相互不会干扰,并通过多种同步手段合作完成同一个任务。3.4.1线程概述一般将进程定义成一个正在运行的程序的一个实例,它由以下两个组件构成:一个内核对象,操作系统用它来管理进程。内核对象也是系统保存进程统计信息的地方。一个地址空间,其中包含所有执行体或动态链接库模块的代码和数据。此外,它还包含动态内存分配,比如线程堆栈和堆的分配。进程是有惰性的。进程要做任何事情,都必须让一个线程在它的上下文中运行。该线程要执行进程地址空间包含的代码。事实上,一个进程可以有多个线程,所有线程都在进程的地址空间中同时执行代码。为此,每个线程都有它自己的一组CPU寄存器和它自己47 电子科技大学硕士学位论文的堆栈。每个进程至少要有一个线程执行进程地址空间包含的代码。一个进程创建的时候,系统会自动创建它的第一个线程,这称为主线程。然后,这个线程再创建更多的线程,后者再创建更多的线程。如果没有线程要执行进程地址空间包含的代码,进程就失去了继续存在的理由。所以,系统会自动销毁进程及其地址空间。线程也由两个组件组成:一个是线程的内核对象,操作系统用它管理线程。内核对象还是系统用来存放线程统计信息的地方。一个是线程堆栈,用于维护线程执行时所需的所有函数参数和局部变量。由于进程是有惰性的,它从来不执行任何东西,只是一个线程的容器。线程必然是在某个进程的上下文中创建的,而且会在这个进程内部终其一生。这意味着线程要在其进程的地址空间内执行代码和处理数据。所以,假如一个进程上下文中有两个以上的进程运行,这些线程将共享同一个地址空间。这些线程可以执行同样的代码,可以处理相同的数据。此外,这些线程还共享内核对象句柄,因为句柄表是针对每一个进程的,而不是针对每一个线程。可以看出,相较于线程,进程所使用的系统资源更多。其原因在于地址空间。为一个进程创建一个虚拟的地址空间需要大量系统资源。系统中会发生大量的记录活动,而这需要用到大量内存。而且,由于可执行文件和动态链接文件要加载到一个地址空间,所以还需要用到文件资源。另一方面,线程使用的系统资源要少得多。事实上,线程只有一个内核对象和一个堆栈;几乎不涉及记录活动,所以不需要占用多少内存。对于所有要运行的线程,操作系统会轮流为每个线程调度一些CPU时间。它会采取轮询或轮流方式,为每个线程都分配时间片,从而营造出所有线程都在并发运行的假象。如果计算机配备了多个CPU,操作系统会采用更复杂的算法为线程分配CPU时间。Windows操作系统可以同时让不同的CPU执行不同的线程,使多个线程能真正并发运行。在这种类型的计算机系统中,Windows内核将负责线程的所有管理和调度任务。程序员不必在自己的代码中做任何特别的事情,即可享受到多处理器系统带来的好处。不过,为了更好地利用这些CPU,需要在应用程序的算法中多做一些文章。在上一代操作系统中,进程只能控制一个线程。这种单线程的进程模型可以促进程序的健壮性;因为如果没有程序员明确介入,进程之间不会相互干扰。但是,通过单线程进程难以开发某些类型的应用程序,特别是高性能服务器或实时服务器。需要相互通信或需要响应管理请求的服务器必须使用某种形式的进程间 第三章网络通信模型的设计与实现通信,这增加了它们的复杂性。此外,如果使用多进程,将难以对调度和进程优先级实施高效率、高精度的控制。为了减轻上面提及的进程存在的问题,如今,大多数操作系统平台都能在一个进程中支持多个线程。一个线程是一组单独的指令序列,执行在进程的保护范围之内。除了一个指令指针之外,线程还要管理某些资源,例如,保存函数活动记录的运行时堆栈、一组寄存器、信号屏蔽设备、优先级以及和线程有关的数据等等。如果有多个CPU,多线程服务器中的服务则可以并行执行。在并发式网络应用程序的实现中,如果多个操作在各自的线程中执行,而不是在各自的进程中执行,并发开销就能得以降低。和进程相比,线程维护的状态信息要少;因而,和对应的进程的生命活动相比,线程创建和环境切换的开销要少。例如,在一个进程中切换线程时,进程范围内的资源(如虚拟地址映射和缓存)不需要改变。在调度、执行一个应用程序线程时,核心模式和用户模式之间的切换可能不必要。而且,进程内同步的开销通常没有进程间同步昂贵,因为被同步对象位于进程内部,因而不需要操作系统内核的干预。相反,进程间线程同步一般需要牵涉到操作系统内核。多线程对于互联网应用程序尤为重要。任何应用程序中都至少有一个线程,可以根据需要随时启动和停止任意多的线程。在启动附加线程的同时,主线程一直保持运行阶段。在网络游戏中,一般主线程启动多个副主线程分别用于接收数据、发送数据、处理数据。3.4.2线程通信为了完成某些功能,同时主线程又不能停止服务时,主线程就需要创建另一些线程来辅助程序工作,由于是共同完成一项任务,所以主线程需要和辅助线程之间通信以完成数据交换。在网络游戏服务器启动后,主线程会启动其他辅助线程来完成数据收发和数据库处理任务。由于辅助线程没有天生的循环处理机制,不能像Window操作系统一样使用消息通信,所以需要程序员自己处理线程之间的通信。线程通信主要有以下几种方法:全局通信变量、用户自定义消息、事件内核对象。(1)全局通信变量在程序中设置一个全局变量,所有的线程都可以访问这个变量,这种方式非常简单,所以成为线程通信最容易实现的方式。比如在服务器应用中,使用主线49 电子科技大学硕士学位论文程启动多个辅助线程用于数据库的处理。然后为每一个线程都设置一个全局变量用来标识是否要终止此线程。在辅助线程中不停地监视这些全局变量,当变量变为需要终止的状态时,则结束该线程。(2)用户自定义消息使用全局变量可以实现主线程对辅助线程的通信。而辅助线程对主线程的通信可以使用用户自定义消息。使用该方法首先需要自定义一个消息,这个消息值要大于操作系统保留的值。所以一般来说这个值都大于WMUSER。WMUSER是一个常数,是Windows操作系统定义的,它是用户能够使用的第一个消息值。在辅助线程需要向主线程发送消息时,就调用操作系统的应用程序接口把消息发送给主线程。实现这一功能主要会用到的函数包括PostMessageO函数和SendMessage()函数。Postmessage()i函数和SendMessage()i函数都用于传递消息,不过它们有一些不同之处。Postmessage()在调用后会立即返回,而SendMessageO在调用后会阻塞,直到消息处理完毕才会返回。主线程可以通过窗口过程函数来捕获辅助线程发来的消息,捕获的方式也可以一条一条地捕获或者一次捕获所有的消息。(3)事件内核对象线程通信还可以使用事件内核对象来实现。事件内核对象一共有两种状态,一个是信号态,另一个是非信号态。线程可以通过监视事件内核对象的状态来实现线程之间的通信。由于事件状态的变换方式不同,事件被分成了两种类型:手动重置事件和自动重置事件。手动重置事件必须由程序员来控制,当事件需要成为信号态时,使用SetEvent()函数来设置事件,而当事件需要变为非信号态时,可以调用ResetEvent()i銮i数。自动重置事件在完成相应的任务过后会自动变成非信号态,不需要程序员手动修改。创建事件内核对象后,它的状态会默认成为非信号态。要使对象的状态变成信号态,需要手工对它进行状态转换。辅助线程可以通过监视事件内核对象的状态来进行相关的操作。一般使用系统应用程序接口函数WaitForSingleObject0来监视对象的状态。该函数的原型是:DWORDWaitForSingleObject(HANDLEhHandle,//handletoobjecttowaitfor 第三章网络通信模型的设计与实现DWoRDdwMillisec妁nds//time-outintervalinmilliseconds);其中第一个参数为要检查的事件的句柄;第二个参数为该函数的等待时间。函数在参数指定的时间段内,若事件对象未处于信号态就不会返回,即系统将阻塞这个线程直到事件对象变为信号态。当该参数为INFINITE时,表示线程将无限期地等待下去,直到事件触发。当该参数为0时,表示等待的时间为0。返回值如果为WAITOBJECT0,表示事件己经处于信号态,否则仍处于非信号态。返回值如果为WAITTIMEOUT表示等待超时。事件内核对象的使用方法是,首先让一个线程执行主要工作,当有其他工作需要执行用于共同完成任务时,这个线程就会创建其他线程来完成另外的工作。然后定义一个事件内核对象,并将这个对象设置成非信号态。当新创建的线程完成工作后,就触发这个事件,把状态设置为信号态。这样首先创建的那个线程就知道其他线程的工作已经结束。3.4.3线程同步程序使用了多线程后,为了让所有的线程能协调工作,需要对线程进行同步。线程同步方法包括临界区、互斥对象和信号量。临界区实际上是一段代码,该代码实现了对共享资源的独占,无论何时只能有一个线程可以访问被临界区保护的共享资源【33】。当有多个线程需要使用被临界区保护的共享资源时,只有第一个进入临界区的线程可以访问共享资源,而其它想要访问共享资源的线程会被挂起,直到进入临界区的线程离开临界区。在临界区中没有线程后,其它需要访问共享资源的线程又会去抢占临界区,如此反复,达到互斥访问共享资源的目的。有一点需要注意的是,进入临界区的线程不能运行太长的时间,因为一旦有线程进入临界区后,其它所有要进入临界区的线程都会被阻塞,若运行时间太长,被阻塞的线程也会等待得越久,这样会影响整个程序的性能。互斥对象与临界区非常相似,只有得到互斥对象的线程才可以访问共享的资源。由于共享资源只会绑定一个互斥对象,所以无论何时共享资源都不会被多个线程同时访问。当拥有互斥对象的线程在完成任务以后就会释放它所占有的互斥对象,以便让其它线程得到互斥对象并访问共享资源。互斥对象的使用方式和临界区大致相似,有一点不同的是互斥对象是内核对象,而临界区是用户对象,这5l 电子科技大学硕士学位论文样互斥对象可用于不同进程间的线程同步。信号量对象的作用还是用于对资源的互斥访问,不过它与前两种方式有很大的不同,它允许多个线程同时访问共享资源,但需要指定可以访问共享资源的最大线程数。信号量的数值在最大允许的线程数值和零之间变动,当有线程开始访问共享资源时,它的数值会减一。当数值减到零后,就不允许更多的线程访问共享资源。信号量在其数值大于零的时候处于信号态,而当数值减到零后,状态变成非信号态。线程同步所使用的方法使得在同一个进程内运行的线程保持步调的一致。同步所使用的对象要么处于信号态,要么处于非信号态。在信号态的状态下,同步对象让等待的函数得以执行,而在非信号状态下,同步对象会阻塞函数的执行。在游戏服务器的设计中,为了访问被同步对象控制的资源,需要定义两个类:CCriticalSection和Clockel"用于线程同步。类的定义如下:临界区类structCCriticalSection:publicCRITICALSECTION{CCriticalSectionO{“tializeCriticalsection(this);)--CCriticalSectionO{DeleteCritialSection(this);))互斥锁类classCLocker{public:Clocker(CRITICAL_sECTION&cs):mpcs(&cs){Lock();)---Clocker(){if(m_pcs)UnLock();)voidLock(){EmerCriticalSection(m_pcs);)voidUnLock()52 第三章网络通信模型的设计与实现{LeaveCriticalSection(m__pcs);m__pcs=NULL;}protected:CRITICAL—SECTION·m__pcs;)其中临界区类继承与Windows的内核对象CRITICALSECTION,并在默认构造函数中进行初始化。而互斥锁类主要用于使用多线程时,对临界区的互斥。该类只需要在调用函数的开始处进行初始化,并不需要在函数结束时进行手动释放,因为该类的对象是在函数中声明的,是自动变量,在函数退出时会自动销毁,而它会调用自己的析构函数退出临界区。 电子科技大学硕十学位论文第四章游戏协议的设计与实现为了在网络中交换数据,需要建立一套标准,让所有需要在网络中传送数据的应用都需要按照标准执行,这就是网络通信协议。在网络游戏中,客户端和服务器之间就是通过TCP/IP这个网络通信协议进行数据通信的。在通信时,客户端与服务器端所传输的数据会按照通信协议所规定的格式进行封包。但是仅仅是建立通信连结,使用TCP或者UDP传输数据,并不能完成复杂的游戏数据的传递工作。因为游戏数据的传递与普通的网络数据传输有很大的不同,所以需要建立一套游戏协议来规范所要传送的游戏数据包,从而满足游戏所需要的特定功能。这样也使得网络游戏的通信更加规范,更加容易管理,增强了网络游戏代码的复用性。4.1游戏协议概念游戏协议是指在网络游戏的客户端和服务器端之间定义一套数据传输的格式和处理方式,并通过这一格式来分解和组合数据,使数据能够被正确地理解和处理,最后达到控制和传输游戏数据的目的。简单地说,也就是通过一种特定的规则来解析客户端发送给服务器的数据包和服务器发送给客户端的数据包。相当于在网络通信协议之上在定义了一个应用层协议。4.2游戏协议格式游戏通信协议并没有一个业界标准,比如TCP/IP、HTTP等。游戏通信协议都是由游戏开发商自己为特定的游戏定制的。网络游戏的通信协议不会向大众公开,只会在游戏开发小组内部公开,用于游戏代码的编写。所以游戏通信协议格式的制定不需要太严格。在游戏中传输的数据包封包形式多种多样,设计者可以视情况设定。一般来说,一个数据报文包括报文头和报文体。为了能解释双方数据包的意义,一般都为双方数据包定义一个统一规则的报文头,报文头具有固定的格式,并且长度固定。报文体中为传输数据的内容,它的长度是不固定的。本课题游戏数据包中前 第四章游戏协议的设计与实现5个字节为报文头,从第6个字节开始为报文体。报文头中协议类别占1个字节,事件代码占2个字节,报文体长度占2个字节。报文头格式如表4.1所示。表4-1游戏数据包报文头格式协议类别表示该协议是用于客户端和服务器端的通信还是服务器之间的通信。事件代码是专门用于标识游戏中各个通信事件的唯一值。因为我们的协议将用在TCP中,所以我没有加入校验位。原因是TCP可以保证数据的完整性,校验位是没有必要存在的。4.3游戏数据包的设计4.3.1游戏数据包的模式设计游戏数据包的方式多种多样,网络游戏初期的方法是使用一个数据包类包含所有的数据操作方式。如图4_l所示CPacket-m_CompressMm-k-m_EncryptMatlc·m—Veri移Mark+CPacket()“P∽ket()+CompressO+DeCompressO+Encrypt()+DeEnerypt0+Verify()图4-1Packet类使用一个类来处理所有的数据操作在后期不容易维护,本课题采用操作与数据分离的方式来设计数据包。游戏数据包操作由操作基类,加密类,验证类及数据压缩类组成。Handel"类(操作基类):抽象类,定义了对游戏包的基本操作。Encrypt类(加密类):派生自Hander类,用于加密数据。Verify类(验证类):派生自Hander类,用于验证数据。Compress类(数据压缩类):派生自Hander类,用于压缩数据。55 电子科技大学硕+学位论文数据包操作类图如图4.2所示图4-2数据包操作类图使用这种设计,不管是在客户端还是在服务器端,都可以很容易地对数据处理方式进行扩充,维护起来也非常方便。数据包类和服务器端的架构也不会因为操作的改变而变化。数据包类对象是服务器通信的基础,使用会非常频繁,服务器每次发送和接收数据包都需要创建或者销毁一个数据包类对象。这样会造成频繁的内存分配和释放,既造成性能的下降又会产生大量内存碎片。比较好的解决办法是使用内存池。如图4.3所示预分配需要的数据包对象,使用时从内存池中取出一个空闲的数据包,使用完毕后再放回内存池中。图4.3对象池(立C立C立C立(::::::::::!弓.⋯..............(二EC立4.3.2粘包与截断的处理粘包与截断是指,在使用TCP通信时,发送端多次发送的小数据包会被连在 第四章游戏协议的设计与实现一起被接收端同时接收到,多个小包被组成一个大包被接收。而一个大数据包又会被拆成多个小数据包发送。这样就存在一个将数据包拆分和重新组合的问题。粘包与截断是在传输数据的过程中使用TCP造成的。TCP自身的传输机制导致了发送方的数据粘包。TCP为了增强传输效率,避免多次发送的开销,发送方一般要收集到足够多的数据后才发送一包数据。若连续几次发送的数据都很少,通常TCP会根据Nagle算法把这些数据集合在一起,等到合在一起的数据量足够大了再把整体一起发送出去,这样接收方就会收到粘包的数据。发送方产生截断的原因一般是由于网络故障导致了完整的数据包被分成多个小包发送给接收方,这样接收方收到的数据不能组成一个完成的数据包。接收方的粘包也是由于使用了TCP。因为TCP传送的是数据流,如果接收方不及时处理数据,就会产生粘包。接收方在收到数据后会把数据放在系统的缓冲区中等待用户把它们取出,如果在新的数据到来以前,老的数据还没有被取走的话,则新收到的数据就会和老的数据在系统缓冲区中连接在一起。此刻一旦用户开始取出数据就会把粘在一起的数据全部取走。粘包有两种情况,一种是粘在一起的数据包都是完整的,而另一种是粘在一起的包包含不完整的数据包,这样就会导致数据被截断。假设用户接收缓冲区的的容量是m个字节,系统接收缓冲区的容量是11个字节,图钳显示了粘包和截断同时发生的情况。在系统接收缓冲区中有k个包粘在一起,而用户接收缓冲区没有系统缓冲区容量大,只能提取m个字节,这样提取的前面k-1个包是完整的,第k个包就被截断了。在网络游戏中所有的数据包都是按照游戏协议构成的,所以要对粘包和截断进行处理。系统接收缓冲区1'IIl丁IJ7第l包第2包第k包J一/kt争:E}‘用户接收缓冲区图4_4粘包和截断57 电子科技大学硕十学位论文通过前面的分析,必须要对网络游戏中的粘包和截断现象进行分包和等待处理,处理方式分为三种情况。第一种情况,如图4.5所示,收到的数据流能够组成一个完整的数据包,但剩余部分不能成为多个完整的数据包。这种情况下,我们不能把接收到的所有数据都从缓存区中取出进行处理,如果全部取出来处理,不仅本次处理会出错,后面接收到的数据包也会全部出错。所以只能先把完整的数据包一个一个地取出来,然后截断数据包,将剩余的数据留在缓冲区中,等待下一部分数据到来,以便连接成完整的数据包后再进行处理。第二种情况,实际收到的数据流的长度太短,以至于不能形成一个完整的数据包。这种情况下我们将数据取出进行处理的话,同样会发生类似于第一种情况的错误。仔细观察,这种情况和第一种情况的后期状态非常相似,处理方法也如出一辙。直接不处理此次的数据,将数据留在缓冲区,等待下一部分的的数据到来。接收到下一部分后再通过报文头的信息验证能不能取出一个完整的数据包进行处理。第三种情况,收到的数据流刚好能够组成一个完整的数据包。这种情况则直接取出缓冲区的数据并清空缓冲区。系统接收缓冲区分包后剩下第k包前半部分图4.5分包处理流程如图4-6所示: 第四章游戏协议的设计与实现图%分包流程图(1)检查是否接收到了网络数据。(2)将收到的数据放入缓冲区中,如果缓冲区中还有未处理的数据,则将本次收到的数和其连接到一起。(3)如果收到的数据包格式不正确,则清空缓冲区,并将接收到的数据丢弃,同时返回到程序开始处继续执行。(4)进入完整数据包检测循环,查看收到数据流是否能够根据报文头所声明的信息组成完整的数据包。(5)如果实际收到数据流能够组成完整的数据包,则从缓冲区中取出数据并59 电子科技大学硕十学位论文清除缓冲区中的这部分数据,然后继续进行循环检测。(6)如果实际收到数据流不能组成完整的数据包,则将数据留在缓冲区中。等待下一部分数据到来,以便连接成完整的数据包后再进行处理。经过以上六个步骤,可以将粘在一起的数据包分开,同时通过分包将不完整的数据包留在缓冲区中,等下一次收到数据后再连接成完整的数据包后处理,这样就解决了粘包和截断问题。为了避免粘包现象,可采取通过设置套接字选项关闭Nagle算法的方法来避免发送方引起的粘包现象。Nagle的文档定义了一种称之为小封包问题的解决方法。当某个应用程序每次只产生一字节的数据,就会导致网络由于这样的小封包而过载,从而产生问题。但开启Nagle算法,在某些时候导致小的及时传输的数据包不能被迅速的传递到对方,从而产生粘包,造成应用程序的性能问题。而且这个算法虽然缓解了网络拥塞,但如果不清楚它何时以及如何起作用,反而会增加延迟与降低吞吐量。缺省情况下,大多数TCP/IP实现都会使用Na西e算法,这一算法会在发送端的TCP/IP栈中缓存少量、依次发送的数据流。关闭此算法后,当要发送的数据拷贝到发送缓冲区,系统就立即将所有数据发送出去,而不必等待发送缓冲区满。总的来说,关闭Nagle算法的方法是可以避免发送方引起的粘包问题,如何取舍主要看网络游戏自己的需求。4.3.3游戏数据包的序列化游戏数据包最终都是用来存储需要传送的数据的,在传输前需要提供一个指针给网络通信函数,还需要为游戏数据的各种内置数据类型,标准库类型以及自定义类型提供序列化服务【351。序列化功能可以在数据包的成员函数中实现,也可以新建一个命名空间,并在命名空间中建立一系列函数来实现。剩余的部分就是怎样把数据存进数据包。比较简单的办法是使用动态内存分配,然后再使用内存拷贝函数,把数据拷贝到内存中。但是分配多大的内存缓冲区是个问题,太大了会浪费内存,太小了又需要再分配。本课题在底层使用了C抖标准库的std::vector作为缓冲区来存储游戏数据【36】。如图4-7所示。使用这种设计带来了两种好处,第一,标准库里对小容量内存的申请与释放使用了内存池技术,通过使用内存池,可以大大提高使用频度很高的数据包对象的处理效率;第二,在对数据包进行处理时,可以很方便地使用标准库算法。 第四章游戏协议的设计与实现Packet序列化Packet反序列化图4.7缓冲区序列化类的定义如下所示:classCSerializer{public:CSerializer();CSerializer(constCSerializer&Ibuf);templatevoidget(Wvalue);template>(uint8&value);CSerializer&operator>>(uintl6&value);CSerializer&operator>>(uint32&value);CSerializer&operator>>(uint64&value);//⋯⋯⋯⋯⋯⋯省略部分代码}}反芹琴嗵化CSerializer&operator<<(uint8&value);CSerializer&operator<<(uintl6&value);CSefializer&operator<<(uint32&value);CSerializer&operator<<(uint64&value);∥⋯⋯⋯⋯⋯⋯省略部分代码protected:61 电子科技大学硕士学位论文sizet_rpos,_wpos;std::vectorstorage;);在类的实现中重载了>>和《操作符用于序列化和反序列化对象。为了满足对所有基本类型的序列化操作,还需要为每一个类型定义序列化和反序列化的函数。62 第五章状态机的设计与实现第五章状态机的设计与实现差不多所有的软件都有有限状态机的影子。有限状态机实际上就是一个有向图,包含了标识状态的节点和改变状态的一系列状态转换函数。简单点儿说,有限状态机是一个事件驱动系统的模型,整个模型由有限个状态,一组刺激值和状态之间的转换规则构成。在某一个时刻,有限状态机处在一个当前状态,当它接收一个输入的刺激值后,会根据转换规则,把当前的状态转换为一个新的状态。通过这三个组成部分,游戏状态机有了明显的特点。在游戏服务器设计中,有限状态机常常被用来实现游戏对象的状态检测。5.1有限状态机概述有限状态机由有限个状态组成,在接收到输入刺激值后,会发生状态的转换,并产生输出。有限状态机可以描述软件系统的状态,所以被广泛地应用在很多领域中,包括程序验证、网络游戏等。使用有限状态机时,会提供一系列输入到系统中,然后观察有限状态机的输出来得到测试信息。本文将重点用到其在面向对象软件中类的行为及其游戏开发这个领域的应用。下面给出有限状态机的定义。一个有限状态机M可以表示为一个六元组(S,So,6,九,I,O),其中:S是有限状态集合;So∈S是初态;6是状态转换函数;九是输出函数;I是有限输入字符集;o是有限输出字符集;M处于状态s时接收到输入XEI后,产生输出y=X(s,x)并且迁移到状态s’=6(s,x)即产生一个变迁t,记为t(s,st,x/y),其中,s=head(t),s'=tail(t)。对于一个有限状态机M,如果对每一个状态和每一个输入,6和九都有定义,那么称M是完全定义(CompletelySpecified)的有限状态机。如果一个给定的有限状态机不是完全定义的,可以通过对未完全定义的状态增加相应的6和九定义来达到完全定义,其定义方法为:s是一个未完全定义的状态,对每一个未定义的 电子科技大学硕+学位论文输入x,6(s,x)或者指向一个出错状态或者指向自身,Us,x刖LL。对于一个有限状态机,如果存在一个输入rEX,对于任意一状态S,都满足Us,r)=So(其中So是初态),则称该有限状态机具有重置功能(restcapacity),缺省的,命名这一输入为rest。这样,可以使用rest分割不同的测试子序列从而形成一个测试输入序列,这一输入序列可以一次输入系统测试。从状态Si到状态sj是可达的是指有一个合法的输入序列使机器能从Si到达sj,当没有指明一个开始状态而说某一个状态是可达的,则默认初态为开始状态So。如果一个有限状态机M可以从初始状态So到达每一个状态,则称该有限状态机M是初始化连通的(initiallyconnected)。在状态机验证理论中,如果一个状态不能由初始状态到达,则应该将此状态从状态机中删除。如果对于M中的每一对状态(Si,Si),都存在一个输入序列使得从状态Si能迁移到状态Si,则称M是强连通的(strongconnected)。从上面的定义可知,如果M是初始化连通的并具有重置功能,则M是强联通的。5.1.1简单的状态机有限状态机由一组状态、一个初始状态、输入和根据输入及现有状态转换为下一个状态的转换函数组成。如图5.1所示,使用圆圈来标识每一个状态,此图中有五种可能状态S1,S2,S3,s4以及S5。对于每一个有限状态机,都有一组状态转换函数来实现有限状态机的状态转换,转换函数t1,t2,t3,t4,t5来表示。有限状态机的初始状态是Sl,等到提供了一个和状态转换函数t1相关的刺激值,有限状态机的状态就会转变成S2。从图5.1可以很容易地看出从一种状态转换成另一种状态,是由哪个转换函数所提供的刺激值。在某些情况下只有一种可能,以S3为例,只有t4提供的刺激值才可以改变该状态机的状态。然而,注意到在S2情况下,有两种可能的刺激值可以改变其状态。仁卜』一、√图5-1简单的状态机 第五章状态机的设计与实现可以把游戏中贩卖道具的NPC做为有限状态机的例子。它会在指定的区域里行走,有时候停留,当行走到区域的边界时,会自己转向。状态转换图如图5-2所示。图5.2NPC状态机NPC状态机的转换规则函数如表5-1所示。表5-1NPC状态机转换当前输入状态停留停留的时间到行走行走行走到指定的边界转向行走行走的时间到停留转向行走NPC状态机函数实现如下所示enamSTATE{STOP,WAu(’TURN);classFSM{InitState(actor)m_map;intm_iCurrentState;intrniLastState;>;构造函数FSMClass通过参数iStatelD定义了状态机的初态。GetCurrentState()和GetLastStateO用于返回当前状态和前一次的状态。SetCurrentState0用于设置现在的状态。GetState《)通过状态ID擦找到状态的指针。AddState0和DeleteState()可以添加和删除状态机所拥有的状态。StateTransition0是转换函数,通过调用此7l 电子科技大学硕十学位论文函数就可以通过刺激值来转换状态机的状态。mmap存储着状态机所拥有的所有状态。m—iCurrentState和m—iLastState保存有状态机的当前状态和上一次状态。StateTransitionO是最常用的函数。通过传入的刺激值,StateTransitionO会调用GetStateO函数从re_map中返回状态指针。同样通过刺激值,状态指针所指向的状态对象会调用它的成员函数GetOutPutO来得到应该到达的状态并通知状态机。 第六章结束语第六章结束语随着网络技术的发展,网络游戏逐渐地发展起来了,人们对网络游戏的需求越来越大,这就促使原来越多的人关注网络游戏的开发。作为网络游戏最重要的一部分,网络游戏服务器得到了更多的关注。本文研究的就是网络游戏的服务器端开发。并且利用了所研究的各项服务器端技术,设计了一个网络游戏服务器。论文的主要工作如下:(1)详细介绍了网络游戏的发展现状及其发展趋势,并介绍了网络游戏的分类。(2)研究了网络游戏服务器端采用的关键技术及其发展趋势;分析了游戏服务器的体系结构,并指出存在的不足。(3)学习了游戏服务器端相关的基本理论技术,这些技术包括网络通信技术、多线程编程技术,对象序列化技术以及有限状态机技术,并将这些技术应用到了设计中去。(4)给出了游戏服务器端的总体设计。综合运用相关技术和实例分析得出的结论,详细描述了服务器端的通信模型、多线程工作模型,游戏协议以及游戏中的状态机,并在此基础之上,给出了各个模块的详细设计。由于考虑的不是很完善,在本文的网络游戏设计中,还有一些问题需要改进和研究,也是本人今后努力的工作方向:一是网络游戏的安全性问题,这一直是网络游戏研究的重点;二是负载均衡技术,这对扩展服务器带宽和增加吞吐量非常有效:三是网络游戏同步技术。本文对于游戏设计仅限于初探阶段,加上时间有限,对一些问题并没有能够深入研究,虽然经过几次修改,难免出现错误之处,敬请各位老师、评委和读者给与批评指正。 电子科技大学硕士学位论文致谢时光如梭,光阴似箭,三年时间匆匆而过,一时心中思绪千万,不知从何说起,在我三年的硕士研究生生涯中,许多老师、同学、朋友和家人在学习和生活上给了我很大的帮助,在我即将毕业之际,向他们表示衷心的感谢。首先,需要感谢我的导师黄迪明教授。黄老师具有丰富的实践和教学经验,渊博的专业知识,为人谦逊、和蔼可亲。在整个研究生期间,黄老师的人生态度和对学术的追求一直影响着我。黄老师对教学事业的钟爱,对我的孜孜教诲我会永远铭记于心,并作为我不断学习的动力。特别是在论文撰写期间,黄老师从选题到最后提交论文都对我进行悉心地考察和指导,并尽可能地提供一些意见和建议,为我付出了大量的心血。在此再一次感谢我的恩师。其次,感谢身边的同学们,和你们在研究生期间的生活将会是我人生中一个重要的经历。与你们在一起的日子里,我感到非常愉快,并且在同你们的交流中,我学到了很多东西。感谢实习期间的所有同事,你们不但支持我、鼓励我,还愿意将自己学到的知识和得来不易的经验与我分享,和你们一起度过的时间里,我收获了很多。工作期间的默契配合和团结友好的氛围令我们一直相处得很愉快,真的谢谢你们。在此,我特别需要感谢我年迈的父母。在二十多年的人生中,我一直都生活在父母的关爱之中,父母的言行影响着我这一生,我一直都很幸运可以受到父母那种朴素的人生观熏陶。我很希望能够在你们余下的人生中尽到我做儿子的责任,希望你们可以快乐舒适地安度晚年。最后,衷心地感谢那些为了评阅我的毕业论文的老师们,感谢你们百忙之中能够抽出时间来评阅论文,并提供宝贵的意见。74 参考文献参考文献【1】中国网络游戏行业报告.GameRes游戏开发论坛,http-//www.gamcres.com【2】2009年网游市场数据艾瑞网,http://www.iresearch.cn【3】袁勃.中国网络游戏产业发展的六大趋势.办公自动杂志,2008,5:4-6【4】SteveMcConnell.代码大全.北京:电子工业出版社,2006,61-66【5】云风.游戏之旅一我的编程感悟.北京:电子工业出版社,2006,313-323【6】W.RichardStcvons.UNIXN出vorkProgramming.PrenticeHall,1990,30-45f7】BehrouzA.Forouzan,SophiaChungFegan.TCP/IP协议族.北京:清华大学出版社,2001,220-234【8】AndreLaMothe.Windows游戏编程大师技巧.北京:中国电力出版社2004【9】ScottMeyers.EffectiveC++.Addison-Wesley,1996【10]HerbSuRer.ExceptionalC++.Addison-Wesley,1999【1l】DouglasC.Schmidt,StephenD.Huston.CHNetworkProgramming,Volume2:SystematicReusewithACEandFrameworks.Addison-Wesley,2002,4·13【12】MandelT.TheElementsofUserInterfaceDesign.Addison-Wesley,1997【13】8jameStroustrup.TheC++ProgrammingLanguage:SpecialEdition.Addison-Wesley,2000[14】DouglasC.Schmidt,StephenD.Huston.C.HNetworkProgramming,,Volumel:MasteringComplexity谢thACEandPaRems.Addison-Wesley,2001,33·37【15】求是科技,黄超.Windows网络编程.北京:人民邮电出版社,2003,214-230f16]AnthonyJones.Windows网络编程.北京:机械工业出版社,2000,176-190【17】EricFreeman,ElisabethFreeman,WithKathyierra,BertBates.HeadFirstDesignPaRems.Oq受eillyMedia,2004【18】Stevens,Pooley.UsingUML:SoftwareEngineering谢thObjectsandComponents.Addison-Wesley,1999【19】BennettS,SMcRob,RFarmer.Object-OrientedAnalysisandDesign.McGraw-Hill,2002【20】BrooksETheMythicalMan—Month.Addison—Wesley,1975[21】AndreiAlexandrescu.ModemC++Design.Addison—Wesley,1996【22】ScottMeyers.EffectiveSTL.北京:清华人学出版社,2006,153.16275 电子科技大学硕士学位论文[23】E^chGamma,RicardHelm,RalphJohnson,JohnVlissides.设计模式.北京:机械工业出版社,2005,100.107【24】AlanShalloway,JamesR.Trott.设计模式解析.北京:人民邮电出版社,2006,115.130【25】刘柏.SecondLife架构剖析.程序员,2008,2:83—85【26]吴永明,何迪.基于完成端口的服务器底层通信模块设计.信息技术,2007,3:115.118【27】张静华,张玉.IOCP研究及在大规模网络通信系统中的应用.计算机与现代化,2004,9:4146[28】程杰.大话设计模式.北京:清华大学出版社,2008【291NicolaiM.Josuttis.TheC.HStandardLibrary.Addison-Wesley,1999【30】HerbSutter.C.HCodingStandards.Addison-Wesley,2004【31】DavidHEberly.3DGameEngineDesign.MorganKaufmann,2001【32】DaleRogerson.InsideCOM.MicrosoftPress,1997【33】JimBeveridge,RobertWiener.Win32多线程程序设计.武汉:华中科技大学出版社,2002,107.122【34】JeffreyM.Richter,ChristopheNasarre.Windows核心编程.北京:清华大学出版社,2008,324.339【35】NoelLlopis.C++游戏编程.北京:清华大学出版社,2004,263-274[36】黄勇.MMORPG的数据包系.程序员,2008,5:99.102【37】JonBentley.ProgrammingPearls.Addison-Wesley,1999,33-40【38】DavidVandevoorde.C_HTemplates.Addison-Wesley,2002,45-56【39】JoshuaKerievsky.重构与模式.北京:人民邮电出版社,2003,29.36[40】黄勇.游戏中的状态机.程序员,2008,12:98.101【4l】ScottMeyers.MoreEffectiveC.阡.Addison-Wesley,1996【42】HerbSuRer.MoreExceptionalC++.Addison-Wesley,2001【43】HerbSuRer.ExceptionalC++Style.Addison-Wesley,2004[44】MSDN主页.http://msdn2.microsoft.com/zh-cn/default.aspx76 攻读硕士学位期间取得的研究成果攻读硕士学位期间取得的研究成果发表论文:[1】刘树杰,黄迪明.基于ARM的LINUX平台上的USB驱动设计.电子科技大学研究生学报,2009.1,No.60
还剩80页未读

继续阅读

下载pdf到电脑,查找使用更方便

pdf的实际排版效果,会与网站的显示效果略有不同!!

需要 15 金币 [ 分享pdf获得金币 ] 2 人已下载

下载pdf