热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

NodeJS开发多人实时对战游戏服务器(一)

从一个游戏情怀说起接触的第一款多人对战游戏是帝国时代,依稀记得那时候上学每周最期待的就是冲到电脑课撸一把罗马复兴,高中开始接触《魔兽争霸3》ÿ

从一个游戏情怀说起

接触的第一款多人对战游戏是帝国时代,依稀记得那时候上学每周最期待的就是冲到电脑课撸一把罗马复兴,高中开始接触《魔兽争霸3》,一款真正让我迷恋十多年的游戏,怀念那时候的《魔兽争霸十大经典战役》还有到图书馆翻 《大众软件》找各种电子游戏相关的新闻的日子,之后和很多人的经历一样,有了 Dota 有了王者荣耀,打一款MOBA游戏几乎成家常便饭,最近也没忍住撸到王者六十多星 ╮(╯_╰)╭。

帝国时代

魔兽争霸3

阴差阳错成为了一名码农,但不幸的是从来没有机会真正去涉足游戏开发者行业。去年魔兽3重制版出来,没忍住交出了一笔情怀税,算是弥补这么多年对暴雪的亏欠,然后转念一想,码农快十载了难道还任由自己继续堕落下去吗?对战类游戏最大的乐趣就是 “与人斗主宰一切的感觉”,“Triple Kill” “Monster Kill” 缭绕于耳,然而再想想那个真正在虚拟世界主宰一切的其实是制定游戏规则的人,也就是游戏创作者,那种当作者的感觉不是2.5D视角的而是真正的上帝视角,所以去年年中开始决定转行求变,从零起步了解下游戏设计,先从技术入手,啰嗦很多,当然不是为了给自己沉迷网络游戏找借口啦。


一个简单的聊天室

若要问一个能集合多人互动又需要实时同步的简单场景是什么?答案就是聊天室,这也是很多游戏框架的入门demo,不例外,我也是从聊天室开始学习的,很快,写这篇文章的现在我大概花了那么丁点时间快速撸了一个,顺带凭着这么多年积累的前端美感对界面稍微加了点样式,代码地址。

聊天室

如果你是一个前端从业者,相信你很快会想到使用 socket.io, 如果你不是,相信你也听过 Websocket。是的,因为简单,我们不用花时间去理解 TCP 的三次握手,拿来即用。为什么聊天室需要Websocket,答案是长连接,在聊天室里,一个房间的任何消息变化都要通过服务端实时广播推送给各个客户端,如下,client1 发送一条消息,其他的 client 都需要收到服务端的消息,而这个的前提是服务端需要知道有多少客户端连接着。

C/S

对比下 http 请求,client1 发送完消息 (Request) 服务端接收后并返回 (Response) 即断开,如此服务端是无法获得其他客户端的连接状态并推送,不过 http 可以使用轮询 (Polling),每个客户端隔一段时间发送一个请求到服务端,如果有发现别的客户端的发送聊天室消息就返回数据,消息延迟跟轮询时间间隔有关, 如此也能做一个聊天室,想想任务也就完成了,但是如果这个聊天室是马化腾发起的呢,目标是做成微信呢?

性能,才是一款优秀的游戏服务器追寻的目标,一条消息服务端广播的数量和客户端数量成正比,n 条消息就是 n * n, 如果再配上轮询,想想王者荣耀 460ms 的延迟是一个玩家能忍受的吗。回到 Websocket 同样会带来性能瓶颈,早期的网络游戏服务器大多是单台服务器单进程架构,所有逻辑都写在一起,同时长连接也需要比短连接带来更多的内存开销,如存储所有客户端Session信息,且内部其实也是通过某种轮询去实现的,这些总总,当我们想去打造一个 “企业级的游戏框架” (这个说法来自 eggjs =。=) 的时候,简单的使用 socket.io, 在面临大量的在线客户端时候,我们可能就到此就止步了,这也是这篇文章的一个背景和初衷,我想聊聊游戏服务器为了性能到底能做什么,可能经验不足,但至少搞下来收获满满。


分布式多进程模型设计

我在 Github 搜了很多游戏框架并对比,最终映入眼帘的就是网易的 pomelo。一是网易的大厂背景,想想当年的梦幻西游,二是它的文档架构的完备性,所以我花了很多时间把它的代码几乎都看完,但是由于它的代码年久失修几乎不维护,同时秉承前端造论圈的坏风气,我重新参考它的代码以更现代化的方式写了一个游戏框架 Regax,并美化了下架构图:

我们回顾上节所讲的性能瓶颈:


  • 单进程单服务器无法承载更多的客户端。
  • 长连接广播带来的开销巨大,特别是游戏场景很频繁需要推送消息。

再看下上边的图到底做了什么:


  • 第一点,所有业务逻辑都以进程服务器粒度拆分,拆分越细越好,提升伸缩性,进程间通过RPC调用,如此可保证进程可跨集群服务器调用,这是分布式架构的基本。
  • 第二点,Socket连接服务器单独拆分,这是最关键的,它只负责连接及广播,不负责任何其他的业务逻辑,保证其性能最大化。

除了解决上节问题再进一步优化:


  • 第三点,协议层更加灵活,不再只是Websocket,由于连接服务器的隔离加纯粹性,服务器可支持多种连接方式共存,如此我们能承载的客户端更多,还可支持灵活切换,如真正的业务场景tcp和udp可根据客户端支持情况自动切换。
  • 第四点,引入网关层,网关层用来控制连接的路由算法,想想农药里的服务器分区策略,再比如地理位置就近原则,分配就近的服务器,进一步提升网络传输效率。
  • 第五点,进程支持权重,权重越高,分配的进程越多机会越大,这也是伸缩性的一种提升。

其他模块就是大众服务器所通用的扩展,如监控及存储等,这里不赘述,真正去理解专研一款优秀的框架设计时候,真的会爱不释手。

一切准备就绪,设计完框架后急需一个业务场景去试炼一番,以此来反哺框架,想想现在能做的太多了,撸一个页游传奇Online渣渣灰绰绰有余,在我所在的支付宝小程序团队也很需要创新场景,框架本身也能给业务带来更多的可能性更多的玩法,最终敲定做了一款简单的多人实时对战贪吃蛇, 可支持和好友一起玩,这时候才是体会开发游戏的乐趣所在。


多人实时对战贪吃蛇

我们参照了王者荣耀的好友匹配+对战的模式设计了下贪吃蛇,如下:

贪吃蛇房间匹配页面

贪吃蛇对战页面

贪吃蛇游戏结束排名

首先按上节的架构,我对服务器做了拆分:


  1. 连接服务器 (ConnectorServer):负责和客户端的Websocket连接及通知,同时校验登陆Token,如果Token不合法直接关闭连接,连接后通过token再去数据库拿用户的昵称等信息。

class ConnectorServer {enter({ token }){// 1. 校验 Token// 2. 通过 Token从数据库获取用户信息, 并创建 Session// 3. 监听 Socket关闭this.ctx.session.on('disconnect', () => {// 4. 如果当前用户在某个房间,发送RPC通知房间服务器踢掉用户this.ctx.rpc.room.kickUser(this.ctx.session.uid)})}
}

 

2. 房间服务器 (RoomServer): 负责房间的创建及加入,并通知房间里所有的用户信息

class RoomServer {kickUser() {// 1. 踢掉用户// 2. 发送 RPC 给 ConnectorServer 广播给客户端房间信息, 这里channel内部封装了rpcthis.ctx.channel.room.pushMessage('onRoomChange', roomData)}joinUser() {// 1. 加入用户// 2. 发送 RPC 给 ConnectorServer 广播给客户端房间信息, 这里channel内部封装了rpcthis.ctx.channel.room.pushMessage('onRoomChange', roomData)}startGame() {// 1. 发送RPC给 BattleServer 开始游戏this.ctx.rpc.battle.start(roomMembers)}
}

3. 对战服务器 (BattleServer): 贪吃蛇开始游戏后,会在服务端建立 帧同步 模式,并定时推送消息, 帧同步会再之后介绍:

class BattleServer {start() {// 模拟帧同步,真正实现会比这个复杂setInterval(() => {// 按每秒三十帧的频率发送帧数据给所有客户端this.ctx.channel.battle.pushMessage('onBattleFrame', currentFrame)},1000 / 30)},syncFrameAction() {// 从客户端接收到贪吃蛇的操作动作并插入到当前帧数据里}
}

而在客户端:

import { Client } from '@regax/client-websocket'const client = new Client({ url: 'ws://localhost:8002', reconnect: true })// 监听服务端断线
client.on('disconnect', () => {})// 1. 创建 WebSocket 连接
await client.connect()// 2. 监听房间成员变化,这里会通过服务端广播接收到
client.on('onRoomChange', ( roomData) => {} )// 3. 监听游戏开始后的帧数据变化
client.on('onBattleFrame', ( frame) => {// 每接收到一帧,就驱动贪吃蛇渲染引擎渲染一次
})// 4. 登陆并校验token
await client.request('connector.enter', { token })
// 5. 加入房间
await client.request('room.joinUser')
// 6. 点击开始游戏
await client.request('room.startGame')
// 7. 操作贪吃蛇时候发送操作行为
await client.request('battle.syncFrameAction', { action })

这样一款多人对战版的贪吃蛇算是基本完成了,但是真正实现的时候遇到不少的坑,如卡顿严重,另外为什么要使用帧同步,帧同步和状态同步的区别在哪,再下一章我会聊一聊这个话题。


最后

如果大家想体验可以到支付宝搜下 `福利贪吃蛇`, 目前集群机器还比较少请轻虐,最后,不忘记招聘,如果你有兴趣,可以私信我, 阿里系能给你的自由度及想象空间挺大。

转自https://zhuanlan.zhihu.com/p/114150098


推荐阅读
  • 云原生应用最佳开发实践之十二原则(12factor)
    目录简介一、基准代码二、依赖三、配置四、后端配置五、构建、发布、运行六、进程七、端口绑定八、并发九、易处理十、开发与线上环境等价十一、日志十二、进程管理当 ... [详细]
  • 本文总结了初学者在使用dubbo设计架构过程中遇到的问题,并提供了相应的解决方法。问题包括传输字节流限制、分布式事务、序列化、多点部署、zk端口冲突、服务失败请求3次机制以及启动时检查。通过解决这些问题,初学者能够更好地理解和应用dubbo设计架构。 ... [详细]
  • 关于我们EMQ是一家全球领先的开源物联网基础设施软件供应商,服务新产业周期的IoT&5G、边缘计算与云计算市场,交付全球领先的开源物联网消息服务器和流处理数据 ... [详细]
  • Java在运行已编译完成的类时,是通过java虚拟机来装载和执行的,java虚拟机通过操作系统命令JAVA_HOMEbinjava–option来启 ... [详细]
  • 如何在php文件中添加图片?
    本文详细解答了如何在php文件中添加图片的问题,包括插入图片的代码、使用PHPword在载入模板中插入图片的方法,以及使用gd库生成不同类型的图像文件的示例。同时还介绍了如何生成一个正方形文件的步骤。希望对大家有所帮助。 ... [详细]
  • SpringBoot整合SpringSecurity+JWT实现单点登录
    SpringBoot整合SpringSecurity+JWT实现单点登录,Go语言社区,Golang程序员人脉社 ... [详细]
  • Servlet多用户登录时HttpSession会话信息覆盖问题的解决方案
    本文讨论了在Servlet多用户登录时可能出现的HttpSession会话信息覆盖问题,并提供了解决方案。通过分析JSESSIONID的作用机制和编码方式,我们可以得出每个HttpSession对象都是通过客户端发送的唯一JSESSIONID来识别的,因此无需担心会话信息被覆盖的问题。需要注意的是,本文讨论的是多个客户端级别上的多用户登录,而非同一个浏览器级别上的多用户登录。 ... [详细]
  • 微信官方授权及获取OpenId的方法,服务器通过SpringBoot实现
    主要步骤:前端获取到code(wx.login),传入服务器服务器通过参数AppID和AppSecret访问官方接口,获取到OpenId ... [详细]
  • Sleuth+zipkin链路追踪SpringCloud微服务的解决方案
    在庞大的微服务群中,随着业务扩展,微服务个数增多,系统调用链路复杂化。Sleuth+zipkin是解决SpringCloud微服务定位和追踪的方案。通过TraceId将不同服务调用的日志串联起来,实现请求链路跟踪。通过Feign调用和Request传递TraceId,将整个调用链路的服务日志归组合并,提供定位和追踪的功能。 ... [详细]
  • 本文讨论了在使用Git进行版本控制时,如何提供类似CVS中自动增加版本号的功能。作者介绍了Git中的其他版本表示方式,如git describe命令,并提供了使用这些表示方式来确定文件更新情况的示例。此外,文章还介绍了启用$Id:$功能的方法,并讨论了一些开发者在使用Git时的需求和使用场景。 ... [详细]
  • 本文介绍了解决Netty拆包粘包问题的一种方法——使用特殊结束符。在通讯过程中,客户端和服务器协商定义一个特殊的分隔符号,只要没有发送分隔符号,就代表一条数据没有结束。文章还提供了服务端的示例代码。 ... [详细]
  • 云原生边缘计算之KubeEdge简介及功能特点
    本文介绍了云原生边缘计算中的KubeEdge系统,该系统是一个开源系统,用于将容器化应用程序编排功能扩展到Edge的主机。它基于Kubernetes构建,并为网络应用程序提供基础架构支持。同时,KubeEdge具有离线模式、基于Kubernetes的节点、群集、应用程序和设备管理、资源优化等特点。此外,KubeEdge还支持跨平台工作,在私有、公共和混合云中都可以运行。同时,KubeEdge还提供数据管理和数据分析管道引擎的支持。最后,本文还介绍了KubeEdge系统生成证书的方法。 ... [详细]
  • Android中高级面试必知必会,积累总结
    本文介绍了Android中高级面试的必知必会内容,并总结了相关经验。文章指出,如今的Android市场对开发人员的要求更高,需要更专业的人才。同时,文章还给出了针对Android岗位的职责和要求,并提供了简历突出的建议。 ... [详细]
  • 阿,里,云,物,联网,net,core,客户端,czgl,aliiotclient, ... [详细]
  • 单点登录原理及实现方案详解
    本文详细介绍了单点登录的原理及实现方案,其中包括共享Session的方式,以及基于Redis的Session共享方案。同时,还分享了作者在应用环境中所遇到的问题和经验,希望对读者有所帮助。 ... [详细]
author-avatar
_Terr1鄭x宜_F
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有