以现在2017年的科技力量,顶级ff14服务器人数2017的网游ff14服务器人数2017能够同时支持在线人数是多少

股票/基金&
大菠萝碉堡了 服务器不堪蹂躏网游盘点
  第1页:服务器不稳定成玩家“眼中钉”  前段时间,大菠萝3(Diablo 3)在欧洲的服务器频繁报错,很多玩家抱怨经常遭遇无故掉线、频繁报错影响游戏体验。而暴雪对此评论为“所有的网游服务器都处于满负荷运行状态。”当然,网游运营商的解释永远得不到玩家的谅解,因为在推出这么一款受人喜爱的网游产品的时候,就应该预想到大规模高并发连接并且这种状态会在一段时间内保持居高不下的态势。犹如今年新推出的12306铁路订票系统一样,不仅出现了购票难、“钱被扣,票没订上”的问题,甚至还出现了连网站系统都难以登陆的问题。  其实这些问题的背后,都与服务器系统的设计、部署和软硬件性能息息相关。在网游中,服务器担负的重要角色 非常重要。一方面,服务器需要对外连接并负责数万玩家的同时异地登陆;另一方面,服务器还需要负责同步、广播和游戏运行,同时,它们还需要进行游戏逻辑运算以防止客户端游戏作弊。  网络游戏需要有良好的游戏互动,让玩家在虚拟世界中感受到欢畅淋漓的娱乐体验。因此,网游服务器还需要具有较高的I/O 系统性能和大内存扩展支持。当某款网游获得玩家热情追捧的时候,会出现居高不下的并发连接数,大规模的数据运算需求和大流量的数据吞吐量会对网游服务器的CPU、I/O系统提出严峻挑战。    服务器宕机影响网游体验  而在玩家看来,服务器故障导致的不稳定、不顺畅游戏体验一直备受诟病。在这个网络游戏百家争鸣的年代,每 天都有层出不穷的小运营商加入游戏行业,其中许多公司因资金实力不够雄厚,只能租用较为廉价的服务器运营游戏。要知道,服务器的优劣直接影响到游戏内所能容纳的玩家数量,一旦游戏内的玩家超出的服务器的承载范围,那么延迟、掉线、宕机便成为了家常便饭。  下面,我们将为大家介绍几款著名的因服务器不堪重负而导致首发即出现故障的网游。  第2页:《魔兽世界》大面积宕机 玩家装备尽失  游戏介绍:  《魔兽世界》(World of Warcraft、简称WoW或魔兽)是著名的游戏公司暴雪娱乐(Blizzard Entertainment) 所制作的一款大型多人在线角色扮演游戏(MMORPG),于2004年年中在北美公开。《魔兽世界》在中国大陆的前代理商为第九城市,日下午开始限量测试,日关闭限量测试,日开始 公开测试,日正式商业化运营。日起中国地区运营商变更为网易。魔兽世界(图片来自网络,点击查看高清大图)  魔兽世界在中国的代理商由九城变更为网易,与九城服务器经常宕机不无关系,但是换作网易后服务器也是经常宕机。以下是日魔兽世界服务器故障时官网论坛上游戏玩家的“贺电”截图,看着这些刷屏帖,魔兽世界的火爆程度可见一斑了:玩家吐槽讽刺宕机事件(图片来自网络)  事件回顾:  魔兽世界出现过多次服务器宕机,其中影响最大的有2006年、2008年和2009年等期间发生的宕机事件。2006年6 月,魔兽世界三区24个服务器的玩家几乎同时发现,服务器被关闭了,无法登陆游戏,而如此大规模的服务器当机事件在当时来说还是第一次。随后,9C发表公告称服务器正在进行网络优化。紧接着9C却又称三区服务器硬盘 出现故障。加剧了玩家虚拟财产丢失的担忧。2区大面积宕机(图片来自网络)  2008年4月,魔兽世界第九大区开放,同为网通的2/6区就开始出现服务器大面积宕机。竞技场地图服务器特出现故障,玩家只能进入纳格兰竞技场这一张地图。更有甚者出现严重的卡进度条现象,轻则卡进去后显示竞技场人数不足而关闭,重则直接卡到人物列表。服务器大面积断开  2009年11月,国服魔兽世界服务器大面积断开,众多玩家无法登陆游戏,但在游戏内没有登出的玩家可以正常运行。而当时的网游运营方网易相关负责人表示,这仅是一次紧急的服务器维护,维护期间会造成玩家登陆困难及掉线。官网公告  问题分析:  对于一款受全球玩家热捧的网游来说,《魔兽世界》在各地服务器都有出现过严重故障的情况,而通常暴雪会采取免费延长游戏时间的补偿措施。而奇怪的是,大陆魔兽世界则从未出现过任何补偿措施。这一点,相比服务器系统本身的故障来说,更让玩家难以接受。毕竟,一款优秀的网游,更多的是有赖运营方出色的服务和支持,而不仅仅是前期优秀的设计、制作和推广上线就完事  九城代理魔兽世界的时候,其宕机事件也有很多是自身在硬件投入和服务支持上的问题  ,而这些问题相比其他地区的代理商而言是完全可以避免的。也许,九城  在国内代理网游的发展道路上,背负玩家骂声的同时也为推动网游代理商服务意识的提高做出了牺牲  另外,根据记者的了解,实际运营中会在玩家数量达到负载的80%水平的时候,系统会开启另一组新服务器缓解用户访问的压力。根据这种设计思想的话,单纯通过增加服务器并不能解决宕机问题。因为一旦承载上限都集中在某个场景中(比如银河系统),势必带来单点故障问题的发生。因此,应该充分考虑到玩家涌入不同场景的平衡性问题,尽量避免上千玩家都集中在有限的几个地图  第3页:《梦幻西游》故障不断 玩家无法登陆  游戏介绍:  《梦幻西游》是一款由中国网易公司自行开发并营运的网络游戏。游戏以著名的章回小说《西游记》故事为背景 ,将中国古典建筑的凝厚与Q版的意趣风格糅合在一起,将游戏场景打造成一幅“游戏画卷”。《梦幻西游》拥有注册用户超过2.5亿,一共开设收费服务器达472组。梦幻西游(图片来自网络,点击图片查看高清大图)  相比之前介绍的《魔兽世界》遭遇的各种宕机、卡进度条、虚拟装备丢失等问题的发生,《梦幻西游》更多的是出现服务器故障并且由此导致的用户无法登陆的问题。而在问题发生后,运营商方面的积极态度和服务意识也有明显提高。在该游戏公测开始后的30个小时内先后四次增开新服。并且还以不同颜色来区分服务器的不同工作状态。增开新服、颜色标注服务器工作状态  事件回顾:  2004年,也就是《梦幻西游》发行后的第二年,由于机房出现意外故障,导致常州电信所辖服务器出现暂时无法登陆的情况,并且,网游运营方还将受到影响服务器一一列出。官方公告  2008年,《梦幻西游》“玉龙雪山”服务器出现异外故障造成玩家无法登录,对此运营方还为广大玩家提供了一套补偿方案:运营商提供的补偿方案  值得一提的是,网易正式接手CWOW的时候,九城关闭了所有WOW服务器,但玩家期待的服务器会在网易手上立即开放的情况并没有出现。更为糟糕的是等待的时间越来越长,却依然没有服务器重新开放的消息,而网易一直不出面表态。于是在日,CWOW玩家有组织的冲击了网易旗下另一款游戏“梦幻西游”,造成梦幻西游七个服务器全部瘫痪。这就是著名的国服玩家大规模攻击"梦幻西游"服务器事件,有图为证:WOW国服玩家大规模攻击"梦幻西游"(图片来自网络)  问题分析:  作为国内门户站点之一,网易有能力开发出这款优秀网游,也有能力运营好这款网游。这里说的能力一方面包括  硬性层面的技术和服务器系统,也包括软性层面的服务意识和前瞻性准备工作  《梦幻西游》在公测开始后考虑到周末玩家时期,为了更好地满足玩家的需要,提前做出预测并增加服务器数量。之后的服务器故障,导致玩家无法正常登陆服务器,相比其他宕机事件来说,影响要小得多。这种问题的出现,主要还是因为用户在高峰时间段对服务器造成的巨大压力和网络拥堵  问题的出现所导致。  另外需要说明的是,在网游生命周期内,在不断增加服务器数量的同时,更应该重视服务器利用率的提高。平常的服务器利用率往往只有30%左右,运营商对于服务器的投入更多的是看到依靠增加数量来分流压力,而不是提高服务器利用率。因此,即时有准备充分的增开新服务器计划,也逃脱不了周而复始频繁增加新服务器的宿命  。对于网易最为重要的网游来说,能够抵挡广大WOW玩家的大规模攻击,也体现出运营商在服务器安全方面的实力。  第4页:《暗黑破坏神3》服务器频出错 玩家倍伤心  游戏介绍:  《暗黑破坏神3》即DIABLO (大菠萝3),日暴雪首发。它是著名动作角色扮演游戏 《暗黑破坏神2》的续作,游戏故事发生于《暗黑破坏神2》的20年之后一个黑暗的魔幻世界。大菠萝III(图片来自网络,点击图片查看高清大图)  它被看作是游戏史上最值得期待的电子游戏作品之一。12年的等待,暴雪的《暗黑破坏神3》已经成为迄今为止预订销量最高的游戏作品。但从游戏正式上线的第一刻起开始,游戏的过程演变成了 一场灾难性的闹剧。DRM的缺陷以及数字未来之路的种种问题才开始逐渐暴露出来。  游戏发布不久之后,关于服务器宕机和各种错误信息的报道开始在各大媒体网站上出现。致命的错误37(游戏服务器已满)成为了玩家们的梦魇。  大菠萝3刷爆了?补丁换来服务器狂报错  事件回顾:  5月15日《暗黑破坏神3》亚洲服务器开启,但部分玩家安装好程序后却发生了账号验证错误的问题 。客户端报错提示“315300错误”。台服暗黑3官方公告官方论坛宕机台服暗黑官网玩家刷屏  随后的两天,由于有更多玩家涌入使服务器负荷过大,很多玩家都表示登录服务器时遭遇各种报错 :ERROR_12、ERROR_37、ERROR_24000、ERROR_3004、ERROR_3006、ERROR_3007、ERROR_315300等 。甚至是5月16日的晚上黄金时段,服务器有长达3个多小时的停机维护,严重刺伤了广大玩家的热情。疯狂报错Error 3003使得玩家无法正常连接服务器  补丁出现之后,玩家还是遭遇了服务器Errors 37和73的疯狂报错,甚至有数百玩家会无故被服务器踢出。令玩家抓狂的Error 37(代表服务器繁忙中。。。)  问题分析:  作为一款取代魔兽世界成为全球最风靡的网游来说,对于一家有着多年网游开发和运营经验的暴雪公司来说,出现这种初级游戏公司才会犯的低级错误着实不应该。虽然暴雪公司表示“我们真诚地向那些遇到问题和困扰的玩家表示道歉。我们对游戏发售的准备还不够充分。”,但有着多款著名 网游运营和开发经验,怎么可能会不对《暗黑破坏神3》可能将超过服务器人数的限制做好充分的准备呢?尤其是对《魔兽世界》和《星际争霸2》的运营公司暴雪来说,这令人难以相信。  暴雪的游戏似乎一直都没有压力测试的传统,从《魔兽世界》到《星际争霸2》,测试都采用激活码的方式进行,也就是说都是限制人数的。虽然以前从未出现过这种大量玩家无法正常登陆的现象,但是这次暴雪却令玩家失望了。  第5页:《劲舞团》受黑客攻击 玩家心惊肉跳  游戏介绍:  《劲舞团》是一款舞蹈类休闲音乐网游,它起源于一部韩国漫画《Audition》,游戏由T3 Entertainment开发, Yedang Online负责其全球发行。《劲舞团》最初于2005年在韩国展开测试,后来发展到全球范围。 2005年7月,《劲舞团》在中国成为第一批适合未成年人网络游戏产品。在2005年至2007年间,荣获十大最受欢迎网游、最具绿色创意、最佳境外网游等多项大奖。《劲舞团》游戏画面(图片来自网络)  然而,就是这样一款荣获多项大奖的游戏,也同样经历了其他网游服务器所带来的宕机问题,不同是,它的网游服务器更多的是遭受来自黑客的攻击。  事件回顾:  2010年1月初,《劲舞团》服务器受到多次“DDOS”攻击,接连造成服务器宕机和网络中断等事故。同年国庆期间,《劲舞团》官方服务器一直遭受网络上来历不明的恶意攻击和非法入侵,致使部分游戏大区频繁宕机或掉线,游戏环境及服务器稳定性遭受严重破坏,许多玩家的正常游戏受到致命影响。服务器列表读取出错(图片来自网络)  后来经过调查发现,有黑客网站公开收费攻击《劲舞团》的服务器,甚至在游戏中散布消息寻找合作者。对此,游戏运营方久游网还悬赏100万奖励举报用户打击这一黑客攻击事件。在2010年年底,警方侦破劲舞团黑客攻击案,宣告劲舞团网游服务器完全恢复正常。无法读取服务器列表(图片来自网络)  问题分析:  虽有“人怕出名猪怕壮”一说,但作为一款风靡一时的网游来说,不仅要在硬件投入上做足功课,提供充足的服务器和带宽,满足各地玩家的需要,而且也要在服务器安全上做足功夫  。前面提到的几个网游服务器故障问题, 更多的是自身硬件建设问题,而这个网游案例带来的启发则是安全的保障问题。运营商在推广和维护游戏日常运行的同时,也特别需要加强游戏自身的安全性和强健性,使得游戏能够处理各种异常情况而不死机,而且应该使用专业的安全监控和防火墙系统,做得实时监控实时处理。  第6页:《EVE Online》系统小瑕疵 冰火两重天  游戏介绍:  《EVE Online》由冰岛CCP公司开发,于2003年5月首发。它以宏大的太空为背景,高度融合硬科幻元素,给玩家展现了一个自由的虚拟宇宙世界。玩家驾驶各式船舰在超过五千个行星系中穿梭,在游戏的宇宙中能进行各式的活动,包括采矿、制造 、贸易与战斗。这款游戏获得GDC在内的世界游戏大奖,在欧美最著名游戏网站的榜单排名中也击 败了众多知名网游,成为2011年度世界最佳游戏。游戏最新版本《EVE:新纪元》于2012年第二季度上线与玩家见面。EVE Online游戏画面  EVE的游戏画面堪称世界一流。在规模达7808个恒星系统的EVE空间里,遍布着上百万风格各异、形态万千的星座、星系、行星、卫星、小行星带、空间站和传送门。EVE最核心的特性,是其角色技能系统。所有的角色诞生的时候,都有几项基本的技能,除此之外的所有的技能都是通过训练得来的。作为游戏的核心,飞船是EVE中最关键的物品。数千种飞船装备能根据不同的目的不同的任务进行排列组合,对船的攻击力、防御、性能进行调整。并且,EVE里没有完全相同的两艘船。  相比其他网游,EVE的服务器可靠性还算比较值得信赖,当然,从首发至今也快有十个春秋了,这其中难免会有问题的发生。EVE Online游戏画面  事件回顾:  2007年6月,EVE公测服务器核心敏感部件因温度过高而保护性自动关闭,导致服务中断。后来通过IDC、CCP工程师共同对该问题进行商讨并在1小时后恢复服务。  2010年,欧美《EVE Online》服务器循例停机1小时进行维护,重启之后GM开始陆续接到玩家的报告,报告内容均是部分物品消失了,部分购物订单也无影无踪。CCP在进行了一系列的调查后,决定再次关闭服务器以找出问题的源头。后来发现,导致物品丢失的原因是由于负责从垃圾物品中回收旧物品ID,并将回收ID指派给新物品的数据库脚本出错。  2011年,《EVE Online》开发运营公司CCP通过官方Facebook发布公告,确认遭到不明身份的黑客攻击。攻击导致游戏服务器和官方网站都已瘫痪。随后CCP公司关闭了所有对外服务并对可能存在的漏洞进行检查。  问题分析:  EVE游戏在国内代理商先后经历了光通、中华网和世纪天成。世纪天成采用了电信网通BGP真双线骨干机房、全新IBM至强服务器集群,以及数据吞吐能力超强的SSD固态硬盘存储,在硬件投入方面可谓倾注了大量心血。这一点相比其他很多网游运营公司来说,还是挺有借鉴意义的。  该网游服务器暴露出的问题主要有两个,一个是黑客攻击的安全性问题,另一个则是内部系统的设计问题。前者和之前介绍的《劲舞团》游戏有类似的参考之处;后者则是由服务器日常运行和脚本编写带来的故障。该网游服务器故障给人的警醒,更多的是在设计服务器系统和编写脚本的时候,要充分考虑周到  。而对于影响玩家装备甚至凭空消失的情形,运营商更应该在  事前充分测试、事后积极赔偿  第7页:《岐山》封测即宕机 吓跑潜在玩家  游戏介绍:  《岐山》是由八泽推出的一款3D角色扮演游戏。游戏在别具一格的“岐彩”画风表现下,独创 “年龄”、“轮回”、“炼丹”、“妖魂”、“元婴”、“法阵”六大道教核心系统,让玩家在修真途中感受生老病死、容颜变化;尽享羽化登仙,轮回妙趣。《岐山》游戏画面(图片来自网络,点击查看高清大图)  事件回顾:  2010年11月,《岐山》开启封测的时候由于有大量玩家涌入,服务器不堪重负出现宕机,服务器首次面对度压力出现“休克”状态,致使部分玩家登陆困难,即使进入游戏之后也出现异常情况 。随后,游戏官网发布服务器紧急停服公告,而先前的倒计时也突然变成了30天。《岐山》诚意封测声明(图片来自网络)停机维护公告  随后几天服务器仍处于停机维护状态,开服时间未知。首日封测服务器宕机给了玩家非常不好的印象,大多数参与测试的玩家还没看清庐山真面目便无法登陆。  问题分析:  相比其他网游,这款网游在刚开始进行封闭性测试的时候,就给广大期待已久的玩家泼了冷水,这对于一款游戏产品来说危害极大。在封闭性测试中,游戏运营商完全可以通过内部压力测试系统情况,制定相应的标准,并严格控制测试邀请码。  第8页:网游宕机 伤的不仅仅是玩家的心  近几年,网络游戏的强劲发展,助长了服务器市场的进一步扩大。即使前几年处于经济低迷时期,也未对网游和服务器市场造成很大创伤。根据艾瑞“2011年第一季度网络游戏核心数据”的统计显示,第一季度中国网络游戏市场规模达92.1亿元,环比上升1.8%,同比上升23.1%。国内网游市场规模(图片来自艾瑞研究咨询)  而在网游中服务器的重要性就会显得更加重要了。网游运营商在选择服务器的时候,特别需要关注服务器的运算处理能力和持久可靠性。在运算处理能力方面,优先选择多核多插槽式服务器。在服务器结构方面,可以重点考虑机架式服务器,条件许可的用户也可以考虑选购拥有更高性能和灵活性的刀片服务器。  网游服务器是检验服务提供商和设备商的试金石  通过前面的几大网游服务器宕机的盘点分析,不难发现服务器对玩家的游戏体验起着至关重要的作用,丝毫不亚于网游本身的独特设计和精美效果。从这种程度上来说,网游服务器是检验网游服务提供商的重要手段,也是考验服务器设备商实力的有力途径。  从服务器整个系统设计、容量和集群的规划,乃至存储、网络子系统性能和备份、安全防护等工作,都需要运营商一开始就要考虑周到,要能满足不同地区不同时间段涌现出的突发性高并发连接。  另外网游说到底是服务,是体验。官服还深受盗号、私服、外挂等影响,会直接或者间接影响玩家使用体验,长此以往自然会对网游的声誉带来负面影响。
06/12 18:1606/12 18:1606/12 17:5206/12 15:4306/12 15:0706/12 14:3206/12 14:2206/12 14:00
科技精品推荐
每日要闻推荐
精彩焦点图鉴
  【免责声明】本文仅代表作者本人观点,与和讯网无关。和讯网站对文中陈述、观点判断保持中立,不对所包含内容的准确性、可靠性或完整性提供任何明示或暗示的保证。请读者仅作参考,并请自行承担全部责任。点击查看更多内容
热门手机应用&主题:说起来,现代的网游服务器,最多可支持多少人同时登录玩?
泡网分: 0.937
帖子: 2040
注册: 2014年02月
好像十多年前就是几千人,现在还是几千人?
本帖由安卓客户端发布
微信扫一扫分享
&浏览:417&&回帖:15 &&
泡网分: 11.401
注册: 2008年10月
rodman0215 发表于
好奇的是服务器,每个公司自己配备??借用某特大型公司的机器在里面安装各自的系统程序来多家公司一起运行?是租用天河xx的超算吗?很多数据中心(IDC)提供这种服务器租赁服务的.
泡网分: 0.144
注册: 2011年03月
好奇的是服务器,每个公司自己配备??借用某特大型公司的机器在里面安装各自的系统程序来多家公司一起运行?是租用天河xx的超算吗?
泡网分: 10.156
帖子: 8564
注册: 2012年02月
现在没有用单服务器做网游的服务端吧,那也太脑残了
eve那样的,看起来是一个服,后面也是群集,讲究的就是怎么分割用户,现在的魔兽世界也是那样
泡网分: 3.529
帖子: 3894
注册: 2015年01月
索尼大法好 发表于
得卡出翔了吧 现在的服务器性能带宽可以多少了?其实还好。
ccp出品,eve ,
泡网分: 4.015
帖子: 2210
注册: 2011年06月
索尼大法好 发表于
得卡出翔了吧 现在的服务器性能带宽可以多少了?
...3年前某游戏单服在线19w 那个时候号称世界纪录
本帖由 OnePlus 客户端发布
泡网分: 19.889
帖子: 3099
注册: 2003年02月
sandoo 发表于
看什么游戏,种个菜一百万人都没问题,要是玩战地1,64人就卡了qq超市用i3能拿占用50%
服务器端不见的卡
泡网分: 36.56
帖子: 19255
注册: 2008年09月
本帖由 samsung+SM-N9200 客户端发布
泡网分: 1.533
注册: 2011年07月
多年之前 自己架mu的私服 家里的破速龙都能拖一二百号人加进来玩...当时mu客户端才一两百兆,对服务器性能不要太高。
本帖由安卓客户端发布
泡网分: 22.151
帖子: 3737
注册: 2006年02月
多年之前 自己架mu的私服 家里的破速龙都能拖一二百号人加进来玩
泡网分: 27.037
帖子: 16349
注册: 2009年03月
看什么游戏,种个菜一百万人都没问题,要是玩战地1,64人就卡了
本帖由安卓客户端发布
泡网分: 18.239
主题: 1293
帖子: 27124
注册: 2011年08月
掉线城与虚荣勇士
本帖最后由 2046小马 于
01:00 编辑
泡网分: 0.937
帖子: 2040
注册: 2014年02月
整理 发表于
10年前,某单服游戏,同时在线最高人数是6万得卡出翔了吧 现在的服务器性能带宽可以多少了?
本帖由安卓客户端发布
泡网分: 0.937
帖子: 2040
注册: 2014年02月
zhxichz 发表于
好像没有严格上限
01年玩传奇时,服务器很少,人暴多,连鸡都杀不到,新手村都挤满了人,一个区估计得有四五千人,
这么多年过去了,服务器性...也就是说可以好几万人同时玩?
本帖由安卓客户端发布
泡网分: 3.529
帖子: 3894
注册: 2015年01月
10年前,某单服游戏,同时在线最高人数是6万
泡网分: 12.728
帖子: 2436
注册: 2008年09月
好像没有严格上限
01年玩传奇时,服务器很少,人暴多,连鸡都杀不到,新手村都挤满了人,一个区估计得有四五千人,
这么多年过去了,服务器性能和网络带宽都翻了几十倍,应该更没问题了
&版权所有:&&&&4047人阅读
软件开发(49)
本文作者:sodme
本文出处:
声明:本文可以不经作者同意任意转载、复制、传播,但任何对本文的引用都请保留作者、出处及本声明信息。谢谢!
常见的网络服务器,基本上是7*24小时运转的,对于网游来说,至少要求服务器要能连续工作一周以上的时间并保证不出现服务器崩溃这样的灾难性事件。事 实上,要求一个服务器在连续的满负荷运转下不出任何异常,要求它设计的近乎完美,这几乎是不太现实的。服务器本身可以出异常(但要尽可能少得出),但是, 服务器本身应该被设计得足以健壮,“小病小灾”打不垮它,这就要求服务器在异常处理方面要下很多功夫。
  服务器的异常处理包括的内容非常广泛,本文仅就在网络封包方面出现的异常作一讨论,希望能对正从事相关工作的朋友有所帮助。
  关于网络封包方面的异常,总体来说,可以分为两大类:一是封包格式出现异常;二是封包内容(即封包数据)出现异常。在封包格式的异常处理方面, 我们在最底端的网络数据包接收模块便可以加以处理。而对于封包数据内容出现的异常,只有依靠游戏本身的逻辑去加以判定和检验。游戏逻辑方面的异常处理,是 随每个游戏的不同而不同的,所以,本文随后的内容将重点阐述在网络数据包接收模块中的异常处理。
  为方便以下的讨论,先明确两个概念(这两个概念是为了叙述方面,笔者自行取的,并无标准可言):
  1、逻辑包:指的是在应用层提交的数据包,一个完整的逻辑包可以表示一个确切的逻辑意义。比如登录包,它里面就可以含有用户名字段和密码字段。尽管它看上去也是一段缓冲区数据,但这个缓冲区里的各个区间是代表一定的逻辑意义的。
  2、物理包:指的是使用recv(recvfrom)或wsarecv(wsarecvfrom)从网络底层接收到的数据包,这样收到的一个数据包,能不能表示一个完整的逻辑意义,要取决于它是通过UDP类的“数据报协议”发的包还是通过TCP类的“流协议”发的包。
  我们知道,TCP是流协议,“流协议”与“数据报协议”的不同点在于:“数据报协议”中的一个网络包本身就是一个完整的逻辑包,也就是说,在应 用层使用sendto发送了一个逻辑包之后,在接收端通过recvfrom接收到的就是刚才使用sendto发送的那个逻辑包,这个包不会被分开发送,也 不会与其它的包放在一起发送。但对于TCP而言,TCP会根据网络状况和neagle算法,或者将一个逻辑包单独发送,或者将一个逻辑包分成若干次发送, 或者会将若干个逻辑包合在一起发送出去。正因为TCP在逻辑包处理方面的这种粘合性,要求我们在作基于TCP的应用时,一般都要编写相应的拼包、解包代
  因此,基于TCP的上层应用,一般都要定义自己的包格式。TCP的封包定义中,除了具体的数据内容所代表的逻辑意义之外,第一步就是要确定以何种方式表示当前包的开始和结束。通常情况下,表示一个TCP逻辑包的开始和结束有两种方式:
  1、以特殊的开始和结束标志表示,比如FF00表示开始,00FF表示结束。
  2、直接以包长度来表示。比如可以用第一个字节表示包总长度,如果觉得这样的话包比较小,也可以用两个字节表示包长度。
  下面将要给出的代码是以第2种方式定义的数据包,包长度以每个封包的前两个字节表示。我将结合着代码给出相关的解释和说明。
  函数中用到的变量说明:
  CLIENT_BUFFER_SIZE:缓冲区的长度,定义为:Const int CLIENT_BUFFER_SIZE=4096。
  m_ClientDataBuf:数据整理缓冲区,每次收到的数据,都会先被复制到这个缓冲区的末尾,然后由下面的整理函数对这个缓冲区进行整理。它的定义是:char m_ClientDataBuf[2* CLIENT_BUFFER_SIZE]。
  m_DataBufByteCount:数据整理缓冲区中当前剩余的未整理字节数。
  GetPacketLen(const char*):函数,可以根据传入的缓冲区首址按照应用层协议取出当前逻辑包的长度。
  GetGamePacket(const char*, int):函数,可以根据传入的缓冲区生成相应的游戏逻辑数据包。
  AddToExeList(PBaseGamePacket):函数,将指定的游戏逻辑数据包加入待处理的游戏逻辑数据包队列中,等待逻辑处理线程对其进行处理。
  DATA_POS:指的是除了包长度、包类型等这些标志型字段之外,真正的数据包内容的起始位置。
Bool SplitFun(const char* pData,const int &len)
&&& PBaseGamePacket pGamePacket=NULL;
&&& __int64 startPos=0, prePos=0, i=0;
&&& int packetLen=0;
& //先将本次收到的数据复制到整理缓冲区尾部
&&& startPos = m_DataBufByteC&&
&&& memcpy( m_ClientDataBuf+startPos, pData, len );
&&& m_DataBufByteCount +=&&&
&&& //当整理缓冲区内的字节数少于DATA_POS字节时,取不到长度信息则退出
 //注意:退出时并不置m_DataBufByteCount为0
&&& if (m_DataBufByteCount & DATA_POS+1)
&&& //根据正常逻辑,下面的情况不可能出现,为稳妥起见,还是加上
&&& if (m_DataBufByteCount && 2*CLIENT_BUFFER_SIZE)
&&&&&&& //设置m_DataBufByteCount为0,意味着丢弃缓冲区中的现有数据
&&&&&&& m_DataBufByteCount = 0;
  //可以考虑开放错误格式数据包的处理接口,处理逻辑交给上层
  //OnPacketError()
&&&& //还原起始指针
& && startPos = 0;
&&&& //只有当m_ClientDataBuf中的字节个数大于最小包长度时才能执行此语句
&&& packetLen = GetPacketLen( pIOCPClient-&m_ClientDataBuf );
&&& //当逻辑层的包长度不合法时,则直接丢弃该包
&&& if ((packetLen & DATA_POS+1) || (packetLen & 2*CLIENT_BUFFER_SIZE))
&&&&&&& m_DataBufByteCount = 0;
  //OnPacketError()
&&& //保留整理缓冲区的末尾指针
&&& __int64 oldlen = m_DataBufByteC&
&&& while ((packetLen &= m_DataBufByteCount) && (m_DataBufByteCount&0))
&&&&&&& //调用拼包逻辑,获取该缓冲区数据对应的数据包
&&&&&&& pGamePacket = GetGamePacket(m_ClientDataBuf+startPos, packetLen);&
&&&&&&& if (pGamePacket!=NULL)
&&&&&&&&&&& //将数据包加入执行队列
&&&&&&&&&&& AddToExeList(pGamePacket);
&&&&&&& pGamePacket = NULL;
  //整理缓冲区的剩余字节数和新逻辑包的起始位置进行调整
&&&&&&& m_DataBufByteCount -= packetL
&&&&&&& startPos += packetL&
&&&&&&& //残留缓冲区的字节数少于一个正常包大小时,只向前复制该包随后退出
&&&&&&& if (m_DataBufByteCount & DATA_POS+1)
&&&&&&&&&&& for(i=startP i&startPos+m_DataBufByteC ++i)
&&&&&&&&&&&&&&& m_ClientDataBuf[i-startPos] = m_ClientDataBuf[i];
&&&&&&&&&&&
&&&&&&&&packetLen = GetPacketLen(m_ClientDataBuf + startPos );
&&&&&&&& //当逻辑层的包长度不合法时,丢弃该包及缓冲区以后的包
&&&&&&& if ((packetLen&DATA_POS+1) || (packetLen&2*CLIENT_BUFFER_SIZE))
&&&&&&&&&&& m_DataBufByteCount = 0;
&&&   //OnPacketError()
&&&&&&&&&&&
&&&&&&&& if (startPos+packetLen&=oldlen)
&&&&&&&&&&& for(i=startP i&startPos+m_DataBufByteC ++i)
&&&&&&&&&&&&&&& m_ClientDataBuf[i-startPos] = m_ClientDataBuf[i];&&&&&&&&&&
&&&&&&&&&&&
&&&& }//取所有完整的包
  以上便是数据接收模块的处理函数,下面是几点简要说明:
  1、用于拼包整理的缓冲区(m_ClientDataBuf)应该比recv中指定的接收缓冲区(pData)长度(CLIENT_BUFFER_SIZE)要大,通常前者是后者的2倍(2*CLIENT_BUFFER_SIZE)或更大。
  2、为避免因为剩余数据前移而导致的额外开销,建议m_ClientDataBuf使用环形缓冲区实现。
3、为了避免出现无法拼装的包,我们约定每次发送的逻辑包,其单个逻辑包最大长度不可以超过CLIENT_BUFFER_SIZE的2倍。因为我们的整 理缓冲区只有2*CLIENT_BUFFER_SIZE这么长,更长的数据,我们将无法整理。这就要求在协议的设计上以及最终的发送函数的处理上要加上这 样的异常处理机制。
  4、对于数据包过短或过长的包,我们通常的情况是置m_DataBufByteCount为0,即舍弃当前包的处理。如果此处不设置 m_DataBufByteCount为0也可,但该客户端只要发了一次格式错误的包,则其后继发过来的包则也将连带着产生格式错误,如果设置 m_DataBufByteCount为0,则可以比较好的避免后继的包受此包的格式错误影响。更好的作法是,在此处开放一个封包格式异常的处理接口 (OnPacketError),由上层逻辑决定对这种异常如何处置。比如上层逻辑可以对封包格式方面出现的异常进行计数,如果错误的次数超过一定的值,
则可以断开该客户端的连接。
  5、建议不要在recv或wsarecv的函数后,就紧接着作以上的处理。当recv收到一段数据后,生成一个结构体或对象(它主要含有 data和len两个内容,前者是数据缓冲区,后者是数据长度),将这样的一个结构体或对象放到一个队列中由后面的线程对其使用SplitFun函数进行 整理。这样,可以最大限度地提高网络数据的接收速度,不至因为数据整理的原因而在此处浪费时间。
  代码中,我已经作了比较详细的注释,可以作为拼包函数的参考,代码是从偶的应用中提取、修改而来,本身只为演示之用,所以未作调试,应用时需要你自己去完善。如有疑问,可以我的blog上留言提出。
posted @&&暗夜教父 阅读(97) |&&|&&
本文作者:sodme 本文出处:
版权声明:本文可以不经作者同意任意转载,但转载时烦请保留文章开始前两行的版权、作者及出处信息。
提示:阅读本文前,请先读此文了解文章背景:
  让无数中国玩家为之瞩目的“魔兽世界”,随着一系列内测前期工作的逐步展开,正在一步步地走近中国玩家,但是,“魔兽”的服务器,却着实让我们为它捏了一把汗。
造成一个网游服务器当机的原因有很多,但主要有以下两种:一,服务器在线人数达到上限,服务器处理效率严重迟缓,造成当机;二,由于外挂或其它游戏作弊 工具导致的非正常数据包的出错,导致游戏服务器逻辑出现混乱,从而造成当机。在这里,我主要想说说后者如何尽可能地避免。
  要避免以上 所说到的第二种情况,我们就应该遵循一个基本原则:在网游服务器的设计中,对于具有较强逻辑关系的处理单元,服务器端和客户端应该采用“互不信任原则”, 即:服务器端即使收到了客户端的数据包,也并不是立刻就认为客户端已经达到了某种功能或者状态,客户端到达是否达到了某种功能或者状态,还必须依靠服务器 端上记载的该客户端“以往状态”来判定,也就是说:服务器端的逻辑执行并不单纯地以“当前”的这一个客户端封包来进行,它还应该广泛参考当前封包的上下文 环境,对执行的逻辑作出更进一步地判定,同时,在单个封包的处理上,服务器端应该广泛考虑当前客户端封包所需要的“前置”封包,如果没有收到该客户端应该
发过来的“前置”封包,则当前的封包应该不进行处理或进行异常处理(如果想要性能高,则可以直接忽略该封包;如果想让服务器稳定,可以进行不同的异常处 理)。
  之所以采用“互不信任”原则设计网游服务器,一个很重要的考虑是:防外挂。对于一个网络服务器(不仅仅是游戏服务器,泛指所有 服务器)而言,它所面对的对象既有属于自己系统内的合法的网络客户端,也有不属于自己系统内的非法客户端访问。所以,我们在考虑服务器向外开放的接口时, 就要同时考虑这两种情况:合法客户端访问时的逻辑走向以及非法客户端访问时的逻辑走向。举个简单的例子:一般情况下,玩家登录逻辑中,都是先向服务器发送 用户名和密码,然后再向服务器发送进入某组服务器的数据包;但在非法客户端(如外挂)中,则这些客户端则完全有可能先发进入某组服务器的数据包。当然,这
里仅仅是举个例子,也许并不妥当,但基本的意思我已经表达清楚了,即:你服务器端不要我客户端发什么你就信什么,你还得进行一系列的逻辑验证,以判定我当 前执行的操作是不是合法的。以这个例子中,服务器端可以通过以下逻辑执行验证功能:只有当客户端的用户名和密码通过验证后,该客户端才会进入在线玩家列表 中。而只有在线玩家列表中的成员,才可以在登陆服务器的引导下进入各分组服务器。
  总之,在从事网游服务器的设计过程中,要始终不移地 坚持一个信念:我们的服务器,不仅仅有自己的游戏客户端在访问,还有其它很多他人写的游戏客户端在访问,所以,我们应该确保我们的服务器是足够强壮的,任 它风吹雨打也不怕,更不会倒。如果在开发实践中,没有很好地领会这一点或者未能将这一思路贯穿进开发之中,那么,你设计出来的服务器将是无比脆弱的。
当然,安全性和效率总是相互对立的。为了实现我们所说的“互不信任”原则,难免的,就会在游戏逻辑中加入很多的异常检测机制,但异常检测又是比较耗时 的,这就需要我们在效率和安全性方面作个取舍,对于特别重要的逻辑,我们应该全面贯彻“互不信任”原则,一步扣一步,步步为营,不让游戏逻辑出现一点漏 洞。而对于并非十分重要的场合,则完全可以采用“半信任”或者根本“不须信任”的原则进行设计,以尽可能地提高服务器效率。
  本文只是对自己长期从事游戏服务器设计以来的感受加以总结,也是对魔兽的服务器有感而发。欢迎有相同感受的朋友或从事相同工作的朋友一起讨论。
posted @&&暗夜教父 阅读(122) |&&|&&
本文作者:sodme 本文出处:
版权声明:本文可以不经作者同意任意转载,但转载时烦请保留文章开始前两行的版权、作者及出处信息。
  QQ游戏于前几日终于突破了百万人同时在线的关口,向着更为远大的目标迈进,这让其它众多传统的棋牌休闲游戏平台黯然失色,相比之下,联众似乎 已经根本不是QQ的对手,因为QQ除了这100万的游戏在线人数外,它还拥有3亿多的注册量(当然很多是重复注册的)以及QQ聊天软件900万的同时在线 率,我们已经可以预见未来由QQ构建起来的强大棋牌休闲游戏帝国。
那么,在技术上,QQ游戏到底是如何实现百万人同时在线并保持游戏高效率的呢?
事实上,针对于任何单一的网络服务器程序,其可承受的同时连接数目是有理论峰值的,通过C++中对TSocket的定义类型:word,我们可以判定 这个连接理论峰值是65535,也就是说,你的单个服务器程序,最多可以承受6万多的用户同时连接。但是,在实际应用中,能达到一万人的同时连接并能保证 正常的数据交换已经是很不容易了,通常这个值都在之间,据说QQ的单台服务器同时连接数目也就是在这个值这间。
如果要实现用户的单服务器同时在线,是不难的。在windows下,比较成熟的技术是采用IOCP--完成端口。与完成端口相关的 资料在网上和CSDN论坛里有很多,感兴趣的朋友可以自己搜索一下。只要运用得当,一个完成端口服务器是完全可以达到2K到5K的同时在线量的。但,5K 这样的数值离百万这样的数值实在相差太大了,所以,百万人的同时在线是单台服务器肯定无法实现的。
要实现百万人同时在线,首先要实现一个比较完善的完成端口服务器模型,这个模型要求至少可以承载2K到5K的同时在线率(当然,如果你MONEY多, 你也可以只开发出最多允许100人在线的服务器)。在构建好了基本的完成端口服务器之后,就是有关服务器组的架构设计了。之所以说这是一个服务器组,是因 为它绝不仅仅只是一台服务器,也绝不仅仅是只有一种类型的服务器。
简单地说,实现百万人同时在线的服务器模型应该是:登陆服务器+大厅服务器+房间服务器。当然,也可以是其它的模型,但其基本的思想是一样的。下面,我将逐一介绍这三类服务器的各自作用。
登陆服务器:一般情况下,我们会向玩家开放若干个公开的登陆服务器,就如QQ登陆时让你选择的从哪个QQ游戏服务器登陆一样,QQ登陆时让玩家选择的 六个服务器入口实际上就是登陆服务器。登陆服务器主要完成负载平衡的作用。详细点说就是,在登陆服务器的背后,有N个大厅服务器,登陆服务器只是用于为当 前的客户端连接选择其下一步应该连接到哪个大厅服务器,当登陆服务器为当前的客户端连接选择了一个合适的大厅服务器后,客户端开始根据登陆服务器提供的信 息连接到相应的大厅上去,同时客户端断开与登陆服务器的连接,为其他玩家客户端连接登陆服务器腾出套接字资源。在设计登陆服务器时,至少应该有以下功
能:N个大厅服务器的每一个大厅服务器都要与所有的登陆服务器保持连接,并实时地把本大厅服务器当前的同时在线人数通知给各个登陆服务器,这其中包括:用 户进入时的同时在线人数增加信息以及用户退出时的同时在线人数减少信息。这里的各个大厅服务器同时在线人数信息就是登陆服务器为客户端选择某个大厅让其登 陆的依据。举例来说,玩家A通过登陆服务器1连接到登陆服务器,登陆服务器开始为当前玩家在众多的大厅服务器中根据哪一个大厅服务器人数比较少来选择一个 大厅,同时把这个大厅的连接IP和端口发给客户端,客户端收到这个IP和端口信息后,根据这个信息连接到此大厅,同时,客户端断开与登陆服务器之间的连
接,这便是用户登陆过程中,在登陆服务器这一块的处理流程。
大厅服务器:大厅服务器,是普通玩家看不到的服务器,它的连接IP和端口信息是登陆服务器通知给客户端的。也就是说,在QQ游戏的本地文件中,具体的 大厅服务器连接IP和端口信息是没有保存的。大厅服务器的主要作用是向玩家发送游戏房间列表信息,这些信息包括:每个游戏房间的类型,名称,在线人数,连 接地址以及其它如游戏帮助文件URL的信息。从界面上看的话,大厅服务器就是我们输入用户名和密码并校验通过后进入的游戏房间列表界面。大厅服务器,主要 有以下功能:一是向当前玩家广播各个游戏房间在线人数信息;二是提供游戏的版本以及下载地址信息;三是提供各个游戏房间服务器的连接IP和端口信息;四是
提供游戏帮助的URL信息;五是提供其它游戏辅助功能。但在这众多的功能中,有一点是最为核心的,即:为玩家提供进入具体的游戏房间的通道,让玩家顺利进 入其欲进入的游戏房间。玩家根据各个游戏房间在线人数,判定自己进入哪一个房间,然后双击服务器列表中的某个游戏房间后玩家开始进入游戏房间服务器。
游戏房间服务器:游戏房间服务器,具体地说就是如“斗地主1”,“斗地主2”这样的游戏房间。游戏房间服务器才是具体的负责执行游戏相关逻辑的服务 器。这样的游戏逻辑分为两大类:一类是通用的游戏房间逻辑,如:进入房间,离开房间,进入桌子,离开桌子以及在房间内说话等;第二类是游戏桌子逻辑,这个 就是各种不同类型游戏的主要区别之处了,比如斗地主中的叫地主或不叫地主的逻辑等,当然,游戏桌子逻辑里也包括有通用的各个游戏里都存在的游戏逻辑,比如 在桌子内说话等。总之,游戏房间服务器才是真正负责执行游戏具体逻辑的服务器。
这里提到的三类服务器,我均采用的是完成端口模型,每个服务器最多连接数目是5000人,但是,我在游戏房间服务器上作了逻辑层的限定,最多只允许 300人同时在线。其他两个服务器仍然允许最多5000人的同时在线。如果按照这样的结构来设计,那么要实现百万人的同时在线就应该是这样:首先是大 厅,0=200。也就是说,至少要200台大厅服务器,但通常情况下,考虑到实际使用时服务器的处理能力和负载情况,应该至少准备 250台左右的大厅服务器程序。另外,具体的各种类型的游戏房间服务器需要多少,就要根据当前玩各种类型游戏的玩家数目分别计算了,比如斗地主最多是十万
人同时在线,每台服务器最多允许300人同时在线,那么需要的斗地主服务器数目就应该不少于:=333,准备得充分一点,就要准备 350台斗地主服务器。
除正常的玩家连接外,还要考虑到:
对于登陆服务器,会有250台大厅服务器连接到每个登陆服务器上,这是始终都要保持的连接;
而对于大厅服务器而言,如果仅仅有斗地主这一类的服务器,就要有350多个连接与各个大厅服务器始终保持着。所以从这一点看,我的结构在某些方面还存在着需要改进的地方,但核心思想是:尽快地提供用户登陆的速度,尽可能方便地让玩家进入游戏中。
posted @&&暗夜教父 阅读(108) |&&|&&
本文作者:sodme
本文出处:
声明:本文可以不经作者同意任意转载、复制、引用。但任何对本文的引用,均须注明本文的作者、出处以及本行声明信息。
  之前,我分析过QQ游戏(特指QQ休闲平台,并非QQ堂,下同)的通信架构(),分析过魔兽世界的通信架构(),
似乎网络游戏的通信架构也就是这些了,其实不然,在网络游戏大家庭中,还有一种类型的游戏我认为有必要把它的通信架构专门作个介绍,这便是如泡泡堂、QQ 堂类的休闲类竞技游戏。曾经很多次,被网友们要求能抽时间看看泡泡堂之类游戏的通信架构,这次由于被逼交作业,所以今晚抽了一点的时间截了一下泡泡堂的 包,正巧昨日与网友就泡泡堂类游戏的通信架构有过一番讨论,于是,将这两天的讨论、截包及思考总结于本文中,希望能对关心或者正在开发此类游戏的朋友有所 帮助,如果要讨论具体的技术细节,请到我的BLOG()加我的MSN讨论..
  总体来说,泡泡堂类游戏(此下简称泡泡堂)在大厅到房间这一层的通信架构,其结构与QQ游戏相当,甚至要比QQ游戏来得简单。所以,在房间这一层的通信架构上,我不想过多讨论,不清楚的朋友请参看我对QQ游戏通信架构的分析文章()。可以这么说,如果采用与QQ游戏相同的房间和大厅架构,是完全可以组建起一套可扩展的支持百万人在线的游戏系统的。也就是说,通过负载均衡+大厅+游戏房间对游戏逻辑的分摊,完全可以实现一个可扩展的百万人在线泡泡堂。
  但是,泡泡堂与斗地主的最大不同点在于:泡泡堂对于实时性要求特别高。那么,泡泡堂是如何解决实时性与网络延迟以及大用户量之间矛盾的呢?
  阅读以下文字前,请确认你已经完全理解TCP与UDP之间的不同点。
  我们知道,TCP与UDP之间的最大不同点在于:TCP是可靠连接的,而UDP是无连接的。如果通信双方使用TCP协议,那么他们之前必须事先 通过监听+连接的方式将双方的通信管道建立起来;而如果通信双方使用的是UDP通信,则双方不用事先建立连接,发送方只管向目标地址上的目标端口发送 UDP包即可,不用管对方到底收没收到。如果要说形象点,可以用这样一句话概括:TCP是打电话,UDP是发电报。TCP通信,为了保持这样的可靠连接, 在可靠性上下了很多功夫,所以导致了它的通信效率要比UDP差很多,所以,一般地,在地实时性要求非常高的场合,会选择使用UDP协议,比如常见的动作射
击类游戏。
  通过载包,我们发现泡泡堂中同时采用了TCP和UDP两种通信协议。并且,具有以下特点:
1.当玩家未进入具体的游戏地图时,仅有TCP通信存在,而没有UDP通信;
2.进入游戏地图后,TCP的通信量远远小于UDP的通信量
3.UDP的通信IP个数,与房间内的玩家成一一对应关系(这一点,应网友疑惑而加,此前已经证实)
  以上是几个表面现象,下面我们来分析它的本质和内在。^&^
  泡泡堂的游戏逻辑,简单地可以归纳为以下几个方面:
1.玩家移动
2.玩家埋地雷(如果你觉得这种叫法比较土,你也可以叫它:下泡泡,呵呵)
3.地雷爆炸出道具或者地雷爆炸困住另一玩家
4.玩家捡道具或者玩家消灭/解救一被困的玩家
  与MMORPG一样,在上面的几个逻辑中,广播量最大的其实是玩家移动。为了保持玩家画面同步,其他玩家的每一步移动消息都要即时地发给其它玩家。
  通常,网络游戏的逻辑控制,绝大多数是在服务器端的。有时,为了保证画面的流畅性,我们会有意识地减少服务器端的逻辑判断量和广播量,当然,这 个减少,是以“不危及游戏的安全运行”为前提的。到底如何在效率、流畅性和安全性之间作取舍,很多时候是需要经验积累的,效率提高的过程,就是逻辑不断优 化的过程。不过,有一个原则是可以说的,那就是:“关键逻辑”一定要放在服务器上来判断。那么,什么是“关键逻辑”呢?
  拿泡泡堂来说,下面的这个逻辑,我认为就是关键逻辑:玩家在某处埋下一颗地雷,地雷爆炸后到底能不能炸出道具以及炸出了哪些道具,这个信息,需要服务器来给。那么,什么又是“非关键逻辑”呢?
  “非关键逻辑”,在不同的游戏中,会有不同的概念。在通常的MMORPG中,玩家移动逻辑的判断,是算作关键逻辑的,否则,如果服务器端不对客 户端发过来的移动包进行判断那就很容易造成玩家的瞬移以及其它毁灭性的灾难。而在泡泡堂中,玩家移动逻辑到底应不应该算作关键逻辑还是值得考虑的。泡泡堂 中的玩家可以取胜的方法,通常是确实因为打得好而赢得胜利,不会因为瞬移而赢得胜利,因为如果外挂要作泡泡堂的瞬移,它需要考虑的因素和判断的逻辑太多 了,由于比赛进程的瞬息万变,外挂的瞬移点判断不一定就比真正的玩家来得准确,所在,在玩家移动这个逻辑上使用外挂,在泡泡堂这样的游戏中通常是得不偿失
的(当然,那种特别变态的高智能的外挂除外)。从目前我查到的消息来看,泡泡堂的外挂多数是一些按键精灵脚本,它的本质还不是完全的游戏机器人,并不是通 过纯粹的协议接管实现的外挂功能。这也从反面验证了我以上的想法。
  说到这里,也许你已经明白了。是的!TCP通信负责“关键逻辑”,而UDP通信负责“非关键逻辑”,这里的“非关键逻辑”中就包含了玩家移动。 在泡泡堂中,TCP通信用于本地玩家与服务器之间的通信,而UDP则用于本地玩家与同一地图中的其他各玩家的通信。当本地玩家要移动时,它会同时向同一地 图内的所有玩家广播自己的移动消息,其他玩家收到这个消息后会更新自己的游戏画面以实现画面同步。而当本地玩家要在地图上放置一个炸弹时,本地玩家需要将 此消息同时通知同一地图内的其他玩家以及服务器,甚至这里,可以不把放置炸弹的消息通知给服务器,而仅仅通知其他玩家。当炸弹爆炸后,要拾取物品时才向服
务器提交拾取物品的消息。
  那么,你可能会问,“地图上某一点是否存在道具”这个消息,服务器是什么时候通知给客户端的呢?这个问题,可以有两种解决方案:
1.客户端如果在放置炸弹时,将放置炸弹的消息通知给服务器,服务器可以在收到这个消息后,告诉客户端炸弹爆炸后会有哪些道具。但我觉得这种方案不好,因为这样作会增加游戏运行过程中的数据流量。
2.而这第2种方案就是,客户端进入地图后,游戏刚开始时,就由服务器将本地图内的各道具所在点的信息传给各客户端,这样,可以省去两方面的开 销:a.客户端放炸弹时,可以不通知服务器而只通知其它玩家;b.服务器也不用在游戏运行过程中再向客户端传递有关某点有道具的信息。
但是,不管采用哪种方案,服务器上都应该保留一份本地图内道具所在点的信息。因为服务器要用它来验证一个关键逻辑:玩家拾取道具。当玩家要在某点拾取道具时,服务器必须要判定此点是否有道具,否则,外挂可以通过频繁地发拾取道具的包而不断取得道具。
  至于泡泡堂其它游戏逻辑的实现方法,我想,还是要依靠这个原则:首先判断这个逻辑是关键逻辑吗?如果不全是,那其中的哪部分是非关键逻辑呢?对 于非关键逻辑,都可以交由客户端之间(UDP)去自行完成。而对于关键逻辑,则必须要有服务器(TCP)的校验和认证。这便是我要说的。
  以上仅仅是在理论上探讨关于泡泡堂类游戏在通信架构上的可能作法,这些想法是没有事实依据的,所有结论皆来源于对封包的分析以及个人经验,文章 的内容和观点可能跟真实的泡泡堂通信架构实现有相当大的差异,但我想,这并不是主要的,因为我的目的是向大家介绍这样的TCP和UDP通信并存情况下,如 何对游戏逻辑的进行取舍和划分。无论是“关键逻辑”的定性,还是“玩家移动”的具体实施,都需要开发者在具体的实践中进行总结和优化。此文全当是一个引子 罢,如有疑问,请加Msn讨论。
posted @&&暗夜教父 阅读(142) |&&|&&
本文作者:sodme
本文出处:
声明:本文可以不经作者同意任意转载,但任何对本文的引用都须注明作者、出处及此声明信息。谢谢!!
  要了解此篇文章中引用的本人写的另一篇文章,请到以下地址:
以上的这篇文章是早在去年的时候写的了,当时正在作休闲平台,一直在想着如何实现一个可扩充的支持百万人在线的游戏平台,后来思路有了,就写了那篇总结。文章的意思,重点在于阐述一个百万级在线的系统是如何实施的,倒没真正认真地考察过QQ游戏到底是不是那样实现的。
  近日在与业内人士讨论时,提到QQ游戏的实现方式并不是我原来所想的那样,于是,今天又认真抓了一下QQ游戏的包,结果确如这位兄弟所言,QQ 游戏的架构与我当初所设想的那个架构相差确实不小。下面,我重新给出QQ百万级在线的技术实现方案,并以此展开,谈谈大型在线系统中的负载均衡机制的设 计。
  从QQ游戏的登录及游戏过程来看,QQ游戏中,也至少分为三类服务器。它们是:
第一层:登陆/账号服务器(Login Server),负责验证用户身份、向客户端传送初始信息,从QQ聊天软件的封包常识来看,这些初始信息可能包括“会话密钥”此类的信息,以后客户端与后续服务器的通信就使用此会话密钥进行身份验证和信息加密;
第二层:大厅服务器(估且这么叫吧, Game Hall Server),负责向客户端传递当前游戏中的所有房间信息,这些房间信息包括:各房间的连接IP,PORT,各房间的当前在线人数,房间名称等等。
第三层:游戏逻辑服务器(Game Logic Server),负责处理房间逻辑及房间内的桌子逻辑。
  从静态的表述来看,以上的三层结构似乎与我以前写的那篇文章相比并没有太大的区别,事实上,重点是它的工作流程,QQ游戏的通信流程与我以前的设想可谓大相径庭,其设计思想和技术水平确实非常优秀。具体来说,QQ游戏的通信过程是这样的:
  1.由Client向Login Server发送账号及密码等登录消息,Login Server根据校验结果返回相应信息。可以设想的是,如果Login Server通过了Client的验证,那么它会通知其它Game Hall Server或将通过验证的消息以及会话密钥放在Game Hall Server也可以取到的地方。总之,Login Server与Game Hall Server之间是可以共享这个校验成功消息的。一旦Client收到了Login Server返回成功校验的消息后,Login Server会主动断开与Client的连接,以腾出socket资源。Login
Server的IP信息,是存放在QQGame\config\QQSvrInfo.ini里的。
  2.Client收到Login Server的校验成功等消息后,开始根据事先选定的游戏大厅入口登录游戏大厅,各个游戏大厅Game Hall Server的IP及Port信息,是存放在QQGame\Dirconfig.ini里的。Game Hall Server收到客户端Client的登录消息后,会根据一定的策略决定是否接受Client的登录,如果当前的Game Hall Server已经到了上限或暂时不能处理当前玩家登录消息,则由Game Hall Server发消息给Client,以让Client重定向到另外的Game
Hall Server登录。重定向的IP及端口信息,本地没有保存,是通过数据包或一定的算法得到的。如果当前的Game Hall Server接受了该玩家的登录消息后,会向该Client发送房间目录信息,这些信息的内容我上面已经提到。目录等消息发送完毕后,Game Hall Server即断开与Client的连接,以腾出socket资源。在此后的时间里,Client每隔30分钟会重新连接Game Hall Server并向其索要最新的房间目录信息及在线人数信息。
  3.Client根据列出的房间列表,选择某个房间进入游戏。根据我的抓包结果分析,QQ游戏,并不是给每一个游戏房间都分配了一个单独的端口 进行处理。在QQ游戏里,有很多房间是共用的同一个IP和同一个端口。比如,在斗地主一区,前50个房间,用的都是同一个IP和Port信息。这意味着, 这些房间,在QQ游戏的服务器上,事实上,可能是同一个程序在处理!!!QQ游戏房间的人数上限是400人,不难推算,QQ游戏单个服务器程序的用户承载 量是2万,即QQ的一个游戏逻辑服务器程序最多可同时与2万个玩家保持TCP连接并保证游戏效率和品质,更重要的是,这样可以为腾讯省多少money
呀!!!哇哦!QQ确实很牛。以2万的在线数还能保持这么好的游戏品质,确实不容易!QQ游戏的单个服务器程序,管理的不再只是逻辑意义上的单个房间,而 可能是许多逻辑意义上的房间。其实,对于服务器而言,它就是一个大区服务器或大区服务器的一部分,我们可以把它理解为一个庞大的游戏地图,它实现的也是分 块处理。而对于每一张桌子上的打牌逻辑,则是有一个统一的处理流程,50个房间的50*100张桌子全由这一个服务器程序进行处理(我不知道QQ游戏的具 体打牌逻辑是如何设计的,我想很有可能也是分区域的,分块的)。当然,以上这些只是服务器作的事,针对于客户端而言,客户端只是在表现上,将一个个房间单
独罗列了出来,这样作,是为便于玩家进行游戏以及减少服务器的开销,把这个大区中的每400人放在一个集合内进行处理(比如聊天信息,“向400人广播” 和“向2万人广播”,这是完全不同的两个概念)。
  4.需要特别说明的一点。进入QQ游戏房间后,直到点击某个位置坐下打开另一个程序界面,客户端的程序,没有再创建新的socket,而仍然使 用原来大厅房间客户端跟游戏逻辑服务器交互用的socket。也就是说,这是两个进程共用的同一个socket!不要小看这一点。如果你在创建桌子客户端 程序后又新建了一个新的socket与游戏逻辑服务器进行通信,那么由此带来的玩家进入、退出、逃跑等消息会带来非常麻烦的数据同步问题,俺在刚开始的时 候就深受其害。而一旦共用了同一个socket后,你如果退出桌子,服务器不涉及释放socket的问题,所以,这里就少了很多的数据同步问题。关于多个
进程如何共享同一个socket的问题,请去google以下内容:WSADuplicateSocket。
  以上便是我根据最新的QQ游戏抓包结果分析得到的QQ游戏的通信流程,当然,这个流程更多的是客户端如何与服务器之间交互的,却没有涉及到服务器彼此之间是如何通信和作数据同步的。关于服务器之间的通信流程,我们只能基于自己的经验和猜想,得出以下想法:
  1.Login Server与Game Hall Server之前的通信问题。Login Server是负责用户验证的,一旦验证通过之后,它要设法让Game Hall Server知道这个消息。它们之前实现信息交流的途径,我想可能有这样几条:a. Login Server将通过验证的用户存放到临时数据库中;b. Login Server将验证通过的用户存放在内存中,当然,这个信息,应该是全局可访问的,就是说所有QQ的Game Hall Server都可以通过服务器之间的数据包通信去获得这样的信息。
  2.Game Hall Server的最新房间目录信息的取得。这个信息,是全局的,也就是整个游戏中,只保留一个目录。它的信息来源,可以由底层的房间服务器逐级报上来,报给谁?我认为就如保存的全局登录列表一样,它报给保存全局登录列表的那个服务器或数据库。
  3.在QQ游戏中,同一类型的游戏,无法打开两上以上的游戏房间。这个信息的判定,可以根据全局信息来判定。
  以上关于服务器之间如何通信的内容,均属于个人猜想,QQ到底怎么作的,恐怕只有等大家中的某一位进了腾讯之后才知道了。呵呵。不过,有一点是 可以肯定的,在整个服务器架构中,应该有一个地方是专门保存了全局的登录玩家列表,只有这样才能保证玩家不会重复登录以及进入多个相同类型的房间。
  在前面的描述中,我曾经提到过一个问题:当登录当前Game Hall Server不成功时,QQ游戏服务器会选择让客户端重定向到另位的服务器去登录,事实上,QQ聊天服务器和MSN服务器的登录也是类似的,它也存在登录重定向问题。
  那么,这就引出了另外的问题,由谁来作这个策略选择?以及由谁来提供这样的选择资源?这样的处理,便是负责负载均衡的服务器的处理范围了。由QQ游戏的通信过程分析派生出来的针对负责均衡及百万级在线系统的更进一步讨论,将在下篇文章中继续。
  在此,特别感谢网友tilly及某位不便透露姓名的网友的讨论,是你们让我决定认真再抓一次包探个究竟。
posted @&&暗夜教父 阅读(116) |&&|&&
一直以来,flash就是我非常喜爱的平台,
因为他简单,完整,但是功能强大,
很适合游戏软件的开发,
只不过处理复杂的算法和海量数据的时候,
速度慢了一些,
但是这并不意味着flash不能做,
我们需要变通的方法去让flash做不善长的事情,
这个贴子用来专门讨论用flash作为客户端来开发网络游戏,
持续时间也不会很长,在把服务器端的源代码公开完以后,
就告一段落,
注意,仅仅用flash作为客户端,
服务器端,我们使用vc6,
我将陆续的公开服务器端的源代码和大家共享,
并且将讲解一些网络游戏开发的原理,
希望对此感兴趣的朋友能够使用今后的资源或者理论开发出完整的网络游戏。
我们从简单到复杂,
从棋牌类游戏到动作类的游戏,
从2个人的游戏到10个人的游戏,
因为工作忙的关系,我所做的一切仅仅起到抛砖引玉的作用,
希望大家能够热情的讨论,为中国的flash事业垫上一块砖,添上一片瓦。
现在的大型网络游戏(mmo game)都是基于server/client体系结构的,
server端用c(windows下我们使用vc.net+winsock)来编写,
客户端就无所谓,
在这里,我们讨论用flash来作为客户端的实现,
实践证明,flash的xml socket完全可以胜任网络传输部分,
在别的贴子中,我看见有的朋友谈论msn中的flash game
他使用msn内部的网络接口进行传输,
这种做法也是可以的,
我找很久以前对于2d图形编程的说法,&给我一个打点函数,我就能创造整个游戏世界&,
而在网络游戏开发过程中,&给我一个发送函数和一个接收函数,我就能创造网络游戏世界.&
我们抽象一个接口,就是网络传输的接口,
对于使用flash作为客户端,要进行网络连接,
一个网络游戏的客户端,
可以简单的抽象为下面的流程
1.与远程服务器建立一条长连接
2.用账号密码登陆
我们可以直接使用flash 的xml socket,也可以使用类似msn的那种方式,
这些我们先不管,我们先定义接口,
Connect( &127.0.0.1&, 20000 ); 连接远程服务器,建立一条长连接
Send( data, len ); 向服务器发送一条消息
Recv( data, len ); 接收服务器传来的消息
项目开发的基本硬件配置
一台普通的pc就可以了,
安装好windows 2000和vc6就可以了,
然后连上网,局域网和internet都可以,
接下去的东西我都简化,不去用晦涩的术语,
既然是网络,我们就需要网络编程接口,
服务器端我们用的是winsock 1.1,使用tcp连接方式,
[tcp和udp]
tcp可以理解为一条连接两个端子的隧道,提供可靠的数据传输服务,
只要发送信息的一方成功的调用了tcp的发送函数发送一段数据,
我们可以认为接收方在若干时间以后一定会接收到完整正确的数据,
不需要去关心网络传输上的细节,
而udp不保证这一点,
对于网络游戏来说,tcp是普遍的选择。
[阻塞和非阻塞]
在通过socket发送数据时,如果直到数据发送完毕才返回的方式,也就是说如果我们使用send( buffer, 100.....)这样的函数发送100个字节给别人,我们要等待,直到100个自己发送完毕,程序才往下走,这样就是阻塞的,
而非阻塞的方式,当你调用send(buffer,100....)以后,立即返回,此时send函数告诉你发送成功,并不意味着数据已经向目的地发送完 毕,甚至有可能数据还没有开始发送,只被保留在系统的缓冲里面,等待被发送,但是你可以认为数据在若干时间后,一定会被目的地完整正确的收到,我们要充分 的相信tcp。
阻塞的方式会引起系统的停顿,一般网络游戏里面使用的都是非阻塞的方式,
[有状态服务器和无状态服务器]
在c/s体系中,如果server不保存客户端的状态,称之为无状态,反之为有状态,
在这里要强调一点,
我们所说的服务器不是一台具体的机器,
而是指服务器应用程序,
一台具体的机器或者机器群组可以运行一个或者多个服务器应用程序,
我们的网络游戏使用的是有状态服务器,
保存所有玩家的数据和状态,
一些有必要了解的理论和开发工具
[开发语言]
我们首先要熟练的掌握一门开发语言,
学习c++是非常有必要的,
而vc是windows下面的软件开发工具,
为什么选择vc,可能与我本身使用vc有关,
而且网上可以找到许多相关的资源和源代码,
[操作系统]
我们使用windows2000作为服务器的运行环境,
所以我们有必要去了解windows是如何工作的,
同时对它的编程原理应该熟练的掌握
[数据结构和算法]
要写出好的程序要先具有设计出好的数据结构和算法的能力,
好的算法未必是繁琐的公式和复杂的代码,
我们要找到又好写有满足需求的算法,
有时候,最笨的方法同时也是很好的方法,
很多程序员沉迷于追求精妙的算法而忽略了宏观上的工程,
花费了大量的精力未必能够取得好的效果,
举个例子,
我当年进入游戏界工作,学习老师的代码,
发现有个函数,要对画面中的npc位置进行排序,
确定哪个先画,那个后画,
他的方法太“笨”,
任何人都会想到的冒泡,
一个一个去比较,没有任何的优化,
我当时想到的算法就有很多,
而且有一大堆优化策略,
可是,当我花了很长时间去实现我的算法时,
发现提升的那么一点效率对游戏整个运行效率而言几乎是没起到什么作用,
或者说虽然算法本身快了几倍,
可是那是多余的,老师的算法虽然“笨”,
可是他只花了几十行代码就搞定了,
他的时间花在别的更需要的地方,
这就是他可以独自完成一个游戏,
而我可以把一个函数优化100倍也只能打杂的原因
[tcp/ip的理论]
推荐数据用tcp/ip进行网际互连,tcp/ip详解,
这是两套书,共有6卷,
都是国外的大师写的,
可以说是必读的,
网络传输中的“消息”
消息是个很常见的术语,
在windows中,消息机制是个十分重要的概念,
我们在网络游戏中,也使用了消息这样的机制,
一般我们这么做,
一个数据块,头4个字节是消息名,后面接2个字节的数据长度,
再后面就是实际的数据
为什么使用消息??
我们来看看例子,
在游戏世界,
一个玩家想要和别的玩家聊天,
那么,他输入好聊天信息,
客户端生成一条聊天消息,
并把聊天的内容打包到消息中,
然后把聊天消息发送给服务器,
请求服务器把聊天信息发送给另一个玩家,
服务器接收到一条消息,
此刻,服务器并不知道当前的数据是什么东西,
对于服务器来讲,这段数据仅仅来自于网络通讯的底层,
不加以分析的话,没有任何的信息,
因为我们的通讯是基于消息机制的,
我们认为服务器接收到的任何数据都是基于消息的数据方式组织的,
4个字节消息名,2字节长度,这个是不会变的,
通过消息名,服务器发现当前数据是一条聊天数据,
通过长度把需要的数据还原,校验,
然后把这条消息发送给另一个玩家,
大家注意,消息是变长的,
关于消息的解释完全在于服务器和客户端的应用程序,
可以认为与网络传输低层无关,
比如一条私聊消息可能是这样的,
MsgID:4 byte
Length:2 byte
TargetPlayerID:2 byte
String:anybyte & 256
一条移动消息可能是这样的,
MsgID:4 byte
Length:2 byte
TargetPlayerID:2 byte
TargetPosition:4 byte (x,y)
编程者可以自定义消息的内容以满足不同的需求
队列是一个很重要的数据结构,
比如说消息队列,
服务器或者客户端,
发送的消息不一定是立即发送的,
而是等待一个适当时间,
或者系统规定的时间间隔以后才发送,
这样就需要创建一个消息队列,以保存发送的消息,
消息队列的大小可以按照实际的需求创建,
队列又可能会满,
当队列满了,可以直接丢弃消息,
如果你觉得这样不妥,
也可以预先划分一个足够大的队列,
可以使用一个系统全局的大的消息队列,
也可以为每个对象创建一个消息队列,
这个我们的一个数据队列的实现,
开发工具vc.net,使用了C++的模板,
关于队列的算法和基础知识,我就不多说了,
DataBuffer.h
#ifndef __DATABUFFER_H__
#define __DATABUFFER_H__
#include &windows.h&
#include &assert.h&
#include &g_assert.h&
#include &stdio.h&
#ifndef HAVE_BYTE
#endif // HAVE_BYTE
//数据队列管理类
template &const int _max_line, const int _max_size&
class DataBufferTPL
bool Add( byte *data ) // 加入队列数据
G_ASSERT_RET( data, false );
m_ControlStatus =
if( IsFull() )&
//assert( false );
memcpy( m_s_ptr, data, _max_size );
NextSptr();
m_NumData++;
m_ControlStatus =
bool Get( byte *data ) // 从队列中取出数据
G_ASSERT_RET( data, false );
m_ControlStatus =
if( IsNull() )&
memcpy( data, m_e_ptr, _max_size );
NextEptr();
m_NumData--;
m_ControlStatus =
bool CtrlStatus() // 获取操作成功结果
return m_ControlS
int GetNumber() // 获得现在的数据大小
return m_NumD
DataBufferTPL()
m_NumData = 0;
m_start_ptr = m_DataTeam[0];
m_end_ptr = m_DataTeam[_max_line-1];
m_s_ptr = m_start_
m_e_ptr = m_start_
~DataBufferTPL()
m_NumData = 0;
m_s_ptr = m_start_
m_e_ptr = m_start_
bool IsFull() // 是否队列满
G_ASSERT_RET( m_NumData &=0 && m_NumData &= _max_line, false );
if( m_NumData == _max_line )&
bool IsNull() // 是否队列空
G_ASSERT_RET( m_NumData &=0 && m_NumData &= _max_line, false );
if( m_NumData == 0 )
void NextSptr() // 头位置增加
assert(m_start_ptr);
assert(m_end_ptr);
assert(m_s_ptr);
assert(m_e_ptr);
m_s_ptr += _max_
if( m_s_ptr & m_end_ptr )
m_s_ptr = m_start_
void NextEptr() // 尾位置增加
assert(m_start_ptr);
assert(m_end_ptr);
assert(m_s_ptr);
assert(m_e_ptr);
m_e_ptr += _max_
if( m_e_ptr & m_end_ptr )
m_e_ptr = m_start_
byte m_DataTeam[_max_line][_max_size]; //数据缓冲
int m_NumD //数据个数
bool m_ControlS //操作结果
byte *m_start_ //起始位置
byte *m_end_ //结束位置
byte *m_s_ //排队起始位置
byte *m_e_ //排队结束位置
//////////////////////////////////////////////////////////////////////////
// 放到这里了!
//ID自动补位列表模板,用于自动列表,无间空顺序列表。
template &const int _max_count&
class IDListTPL
// 清除重置
void Reset()&
for(int i=0;i&_max_i++)
m_dwList[i] = G_ERROR;
m_counter = 0;
int MaxSize() const { return _max_ }
int Count() const { return m_ }
const DWORD operator[]( int iIndex ) {&
G_ASSERTN( iIndex &= 0 && iIndex & m_counter );
return m_dwList[ iIndex ];&
bool New( DWORD dwID )
G_ASSERT_RET( m_counter &= 0 && m_counter & _max_count, false );
//ID 唯一性,不能存在相同ID
if ( Find( dwID ) != -1 )&
m_dwList[m_counter] = dwID;
m_counter++;
// 没有Assert的加入ID功能
bool Add( DWORD dwID )
if( m_counter &0 || m_counter &= _max_count )&
//ID 唯一性,不能存在相同ID
if ( Find( dwID ) != -1 )&
m_dwList[m_counter] = dwID;
m_counter++;
bool Del( int iIndex )
G_ASSERT_RET( iIndex &=0 && iIndex & m_counter, false );
for(int k=iIk&m_counter-1;k++)
m_dwList[k] = m_dwList[k+1];
m_dwList[k] = G_ERROR;
m_counter--;
int Find( DWORD dwID )
for(int i=0;i&m_i++)
if( m_dwList[i] == dwID )&
return -1;
IDListTPL():m_counter(0)&
for(int i=0;i&_max_i++)
m_dwList[i] = G_ERROR;
virtual ~IDListTPL()&
DWORD m_dwList[_max_count];
//////////////////////////////////////////////////////////////////////////
#endif //__DATABUFFER_H__
我们采用winsock作为网络部分的编程接口,
接下去编程者有必要学习一下socket的基本知识,
不过不懂也没有关系,我提供的代码已经把那些麻烦的细节或者正确的系统设置给弄好了,
编程者只需要按照规则编写游戏系统的处理代码就可以了,
这些代码在vc6下编译通过,
是通用的网络传输底层,
这里是socket部分的代码,
我们需要安装vc6才能够编译以下的代码,
因为接下去我们要接触越来越多的c++,
所以,大家还是去看看c++的书吧,
// socket.h
#ifndef _socket_h
#define _socket_h
#pragma once
//定义最大连接用户数目 ( 最大支持 512 个客户连接 )
#define MAX_CLIENTS 512
//#define FD_SETSIZE MAX_CLIENTS
#pragma comment( lib, &wsock32.lib& )
#include &winsock.h&
class CSocketCtrl
void SetDefaultOpt();
CSocketCtrl(): m_sockfd(INVALID_SOCKET){}
BOOL StartUp();
BOOL ShutDown();
BOOL IsIPsChange();
BOOL CanWrite();
BOOL HasData();
int Recv( char* pBuffer, int nSize, int nFlag );
int Send( char* pBuffer, int nSize, int nFlag );
BOOL Create( UINT uPort );
BOOL Create(void);
BOOL Connect( LPCTSTR lpszHostAddress, UINT nHostPort );
void Close();
BOOL Listen( int nBackLog );
BOOL Accept( CSocketCtrl& sockCtrl );
BOOL RecvMsg( char *sBuf );
int SendMsg( char *sBuf,unsigned short stSize );
SOCKET GetSockfd(){ return m_ }
BOOL GetHostName( char szHostName[], int nNameLength );
protected:
static DWORD m_dwConnectO
static DWORD m_dwReadO
static DWORD m_dwWriteO
static DWORD m_dwAcceptO
static DWORD m_dwReadB
static DWORD m_dwWriteB
// socket.cpp
#include &stdio.h&
#include &msgdef.h&
#include &socket.h&
// 吊线时间
#define ALL_TIMEOUT 120000
DWORD CSocketCtrl::m_dwConnectOut = 60000;
DWORD CSocketCtrl::m_dwReadOut = ALL_TIMEOUT;
DWORD CSocketCtrl::m_dwWriteOut = ALL_TIMEOUT;
DWORD CSocketCtrl::m_dwAcceptOut = ALL_TIMEOUT;
DWORD CSocketCtrl::m_dwReadByte = 0;
DWORD CSocketCtrl::m_dwWriteByte = 0;
// 接收数据
BOOL CSocketCtrl::RecvMsg( char *sBuf )
if( !HasData() )
return FALSE;
int nbRead = this-&Recv( (char*)&header, sizeof( header ), MSG_PEEK );
if( nbRead == SOCKET_ERROR )
return FALSE;
if( nbRead & sizeof( header ) )
this-&Recv( (char*)&header, nbRead, 0 );
printf( &\ninvalid msg, skip %ld bytes.&, nbRead );
return FALSE;
if( this-&Recv( (char*)sBuf, header.stLength, 0 ) != header.stLength )
return FALSE;
return TRUE;
// 发送数据
int CSocketCtrl::SendMsg( char *sBuf,unsigned short stSize )
static char sSendBuf[ 4000 ];
memcpy( sSendBuf,&stSize,sizeof(short) );
memcpy( sSendBuf + sizeof(short),sBuf,stSize );
if( (sizeof(short) + stSize) != this-&Send( sSendBuf,stSize+sizeof(short),0 ) )
return -1;
return stS
// 启动winsock
BOOL CSocketCtrl::StartUp()
WSADATA wsaD
WORD wVersionRequested = MAKEWORD( 1, 1 );
int err = WSAStartup( wVersionRequested, &wsaData );
if ( err != 0 )&
return FALSE;
return TRUE;
// 关闭winsock
BOOL CSocketCtrl::ShutDown()
WSACleanup();
return TRUE;
// 得到主机名
BOOL CSocketCtrl::GetHostName( char szHostName[], int nNameLength )
if( gethostname( szHostName, nNameLength ) != SOCKET_ERROR )
return TRUE;
return FALSE;
BOOL CSocketCtrl::IsIPsChange()
return FALSE;
static int iIPNum = 0;
char sHost[300];
hostent *pH
if( gethostname(sHost,299) != 0 )
return FALSE;
pHost = gethostbyname(sHost);
psHost = pHost-&h_addr_list[i++];
if( psHost == 0 )
}while(1);
if( iIPNum != i )
return TRUE;
return FALSE;
// socket是否可以写
BOOL CSocketCtrl::CanWrite()
tout.tv_sec = 0;
tout.tv_usec = 0;
FD_ZERO(&set);
FD_SET(m_sockfd,&set);
e=::select(0,NULL,&set,NULL,&tout);
if(e==SOCKET_ERROR) return FALSE;
if(e&0) return TRUE;
return FALSE;
// socket是否有数据
BOOL CSocketCtrl::HasData()
tout.tv_sec = 0;
tout.tv_usec = 0;
FD_ZERO(&set);
FD_SET(m_sockfd,&set);
e=::select(0,&set,NULL,NULL,&tout);
if(e==SOCKET_ERROR) return FALSE;
if(e&0) return TRUE;
return FALSE;
int CSocketCtrl::Recv( char* pBuffer, int nSize, int nFlag )
return recv( m_sockfd, pBuffer, nSize, nFlag );
int CSocketCtrl::Send( char* pBuffer, int nSize, int nFlag )
return send( m_sockfd, pBuffer, nSize, nFlag );
BOOL CSocketCtrl::Create( UINT uPort )
m_sockfd=::socket(PF_INET,SOCK_STREAM,0);
if(m_sockfd==INVALID_SOCKET) return FALSE;
SOCKADDR_IN SockA
memset(&SockAddr,0,sizeof(SockAddr));
SockAddr.sin_family = AF_INET;
SockAddr.sin_addr.s_addr = INADDR_ANY;
SockAddr.sin_port = ::htons( uPort );
if(!::bind(m_sockfd,(SOCKADDR*)&SockAddr, sizeof(SockAddr)))&
SetDefaultOpt();
return TRUE;
return FALSE;
void CSocketCtrl::Close()
::closesocket( m_sockfd );
m_sockfd = INVALID_SOCKET;
BOOL CSocketCtrl::Connect( LPCTSTR lpszHostAddress, UINT nHostPort )
if(m_sockfd==INVALID_SOCKET) return FALSE;
SOCKADDR_IN sockA
memset(&sockAddr,0,sizeof(sockAddr));
LPSTR lpszAscii=(LPSTR)lpszHostA
sockAddr.sin_family=AF_INET;
sockAddr.sin_addr.s_addr=inet_addr(lpszAscii);
if(sockAddr.sin_addr.s_addr==INADDR_NONE)
lphost = ::gethostbyname(lpszAscii);
if(lphost!=NULL)
sockAddr.sin_addr.s_addr = ((IN_ADDR *)lphost-&h_addr)-&s_
else return FALSE;
sockAddr.sin_port = htons((u_short)nHostPort);
int r=::connect(m_sockfd,(SOCKADDR*)&sockAddr,sizeof(sockAddr));
if(r!=SOCKET_ERROR) return TRUE;
e=::WSAGetLastError();
if(e!=WSAEWOULDBLOCK) return FALSE;
tout.tv_sec = 0;
tout.tv_usec = 100000;
while( n& CSocketCtrl::m_dwConnectOut)
FD_ZERO(&set);
FD_SET(m_sockfd,&set);
e=::select(0,NULL,&set,NULL, &tout);
if(e==SOCKET_ERROR) return FALSE;
if(e&0) return TRUE;
if( IsIPsChange() )
return FALSE;
n += 100;
return FALSE;
// 设置监听socket
BOOL CSocketCtrl::Listen( int nBackLog )
if( m_sockfd == INVALID_SOCKET ) return FALSE;
if( !listen( m_sockfd, nBackLog) ) return TRUE;
return FALSE;
// 接收一个新的客户连接
BOOL CSocketCtrl::Accept( CSocketCtrl& ms )
if( m_sockfd == INVALID_SOCKET ) return FALSE;
if( ms.m_sockfd != INVALID_SOCKET ) return FALSE;
tout.tv_sec = 0;
tout.tv_usec = 100000;
while(n& CSocketCtrl::m_dwAcceptOut)
//if(stop) return FALSE;
FD_ZERO(&set);
FD_SET(m_sockfd,&set);
e=::select(0,&set,NULL,NULL, &tout);
if(e==SOCKET_ERROR) return FALSE;
n += 100;
if( n&= CSocketCtrl::m_dwAcceptOut ) return FALSE;
ms.m_sockfd=accept(m_sockfd,NULL,NULL);
if(ms.m_sockfd==INVALID_SOCKET) return FALSE;
ms.SetDefaultOpt();
return TRUE;
BOOL CSocketCtrl::Create(void)
m_sockfd=::socket(PF_INET,SOCK_STREAM,0);
if(m_sockfd==INVALID_SOCKET) return FALSE;
SOCKADDR_IN SockA
memset(&SockAddr,0,sizeof(SockAddr));
SockAddr.sin_family = AF_INET;
SockAddr.sin_addr.s_addr = INADDR_ANY;
SockAddr.sin_port = ::htons(0);
//if(!::bind(m_sock,(SOCKADDR*)&SockAddr, sizeof(SockAddr)))&
SetDefaultOpt();
return TRUE;
return FALSE;
// 设置正确的socket状态,
// 主要是主要是设置非阻塞异步传输模式
void CSocketCtrl::SetDefaultOpt()
ling.l_onoff=1;
ling.l_linger=0;
setsockopt( m_sockfd, SOL_SOCKET, SO_LINGER, (char *)&ling, sizeof(ling));
setsockopt( m_sockfd, SOL_SOCKET, SO_REUSEADDR, 0, 0);
int bKeepAlive = 1;
setsockopt( m_sockfd, SOL_SOCKET, SO_KEEPALIVE, (char*)&bKeepAlive, sizeof(int));
BOOL bNoDelay = TRUE;
setsockopt( m_sockfd, IPPROTO_TCP, TCP_NODELAY, (char*)&bNoDelay, sizeof(BOOL));
unsigned long nonblock=1;
::ioctlsocket(m_sockfd,FIONBIO,&nonblock);
今天晚上写了一些测试代码,
想看看flash究竟能够承受多大的网络数据传输,
我在flash登陆到服务器以后,
每隔3毫秒就发送100次100个字符的串 &6789& 给flash,
然后在flash里面接收数据的函数里面统计数据,
var g_nTotalRecvByte = 0;
var g_time = new Date();&
var g_nStartTime = g_time.getTime();
var g_nCounter = 0;
mySocket.onData=function(xmlDoc)
g_nTotalRecvByte += xmlDoc.
// 每接收超过1k字节的数据,输出一次信息,
if( g_nTotalRecvByte-g_nCounter & 1024 )
g_time = new Date();
var nPassedTime = g_time.getTime()-g_nStartT
trace( &花费时间:&+nPassedTime+&毫秒& );
g_nCounter = g_nTotalRecvB
trace( &接收总数:&+g_nTotalRecvByte+&字节& );
trace( &接收速率:&+g_nTotalRecvByte*1000/nPassedTime+&字节/秒& );
结果十分令我意外,
这是截取的一段调试信息,
花费时间:6953毫秒
接收总数:343212字节
接收速率:8988字节/秒
花费时间:7109毫秒
接收总数:344323字节
接收速率:534字节/秒
花费时间:7109毫秒
接收总数:345434字节
接收速率:3878字节/秒
花费时间:8125毫秒
接收总数:400984字节
接收速率:0769字节/秒
花费时间:8125毫秒
接收总数:402095字节
接收速率:6154字节/秒
花费时间:8125毫秒
接收总数:403206字节
接收速率:1538字节/秒
我检查了几遍源程序,没有发现逻辑错误,
如果程序没有问题的话,
那么我们得出的结论是,flash的xml socket每秒可以接收至少40K的数据,
这还没有计算xmlSocket.onData事件的触发,调试代码、信息输出占用的时间。
比我想象中快了一个数量级,
flash网络游戏我们可以继续往下走了,
有朋友问到lag的问题,
问得很好,不过也不要过于担心,
lag的产生有的是因为网络延迟,
有的是因为服务器负载过大,
对于游戏的设计者和开发者来说,
首先要从设计的角度来避免或者减少lag产生的机会,
如果lag产生了,
也不要紧,找到巧妙的办法骗过玩家的眼睛,
这也有很多成熟的方法了,
比如航行预测法,路径插值等等,
都可以产生很好的效果,
还有最后的绝招,就是提高服务器的配置和网络带宽,
从我开发网络游戏这段时间的经验来看,
我们的服务器是vc开发的,
普通pc跑几百个玩家,几百个怪物是没有问题的,
又作了一个flash发送的测试,
网络游戏的特点是,
出去的信息比较少,
进来的信息比较多,
这个很容易理解,
人操作游戏的速度是很有限的,
控制指令的产生也是随机的,
但是多人游戏的话,
因为人多,信息的流量也就区域均匀分布了,
在昨天接收数据的基础上,
我略加修改,
我在_root.enterFrame写了如下代码,
_root.onEnterFrame = function()&
for( i = 0; i & 10; i++ )
mySocket.send( ConvertToMsg( &89& ) );
服务器端要做的是,
把所有从flash客户端收到的信息原封不动的返回来,
这样,我又可以通过昨天onData里面的统计算法来从侧面估算出flash发送数据的能力,
这里是输出的数据
花费时间:30531毫秒
接收总数:200236字节
接收速率:5468字节/秒
花费时间:30937毫秒
接收总数:201290字节
接收速率:6811字节/秒
花费时间:31140毫秒
接收总数:202344字节
接收速率:9904字节/秒
花费时间:31547毫秒
接收总数:203398字节
接收速率:7208字节/秒
可以看出来,发送+接收同时做,
发送速率至少可以达到5k byte/s
有一点要注意,要非常注意,
不能让flash的网络传输满载,
所谓满载就是flash在阻塞运算的时候,
不断的有数据从网络进来,
而flash又无法在预计的时间内处理我这些信息,
或者flash发送数据过于频繁,
导致服务器端缓冲溢出导致错误,
对于5k的传输速率,
已经足够了,
因为我也想不出来有什么产生这么大的数据量,
而且如果产生了这么大的数据量,
也就意味着服务器每时每刻都要处理所有的玩家发出的海量数据,
还要把这些海量数据转发给其他的玩家,
已经引起数据爆炸了,
所以,5k的上传从设计阶段就要避免的,
我想用flash做的网络游戏,
除了动作类游戏可能需要恒定1k以内的上传速率,
其他的200个字节/秒以内就可以了,
使用于Flash的消息结构定义
我们以前讨论过,
通过消息来传递信息,
消息的结构是
struct msg
short nL // 2 byte
DWORD dwId;

我要回帖

更多关于 2017doulci解锁服务器 的文章

 

随机推荐