游戏服务端开发面试
专业基础
网络
网络协议
TCP 每次发送一个数据包后都要等待接收方发送一个应答信息,这样TCP才可以确认数据包通过因特网完整地送到了接收方。如果在一段时间内TCP没有收到 接收方的应答,他就会停止发送新的数据包,转而去重新发送没有收到应答的数据包,并且持续这种发送状态,直到收到接收方的应答。所以这会造成网络数据传输的延迟,若网络情况不好,发送方会等待相当长一段时间
UDP 无连接,不可靠,不保证顺序,快
长连接/短连接
长连接
指在一个TCP连接上可以连续发送多个数据包,在TCP连接保持期间,如果没有数据包发送,需要双方发检测包以维持此连接,一般需要自己做在线维 : 连接→数据传输→保持连接(心跳)→数据传输→保持连接(心跳)→……→关闭连接短连接
是指通信双方有数据交互时,就建立一个TCP连接,数据发送完成后,则断开此TCP连接,如Http : 连接→数据传输→关闭连接滑动窗口技术
建立连接的三次握手 与 断开链接的四次挥手
TCP/IP协议的传输效率
解释
DOS
攻击与DRDOS
攻击的基本原理一个
100Byte
的数据包精简到了50Byte
,起传输效率提高了 50%TIMEWAIT
状态怎么解释常用的网络通信模型
Select
常用的网络通信模型
epoll
, 边缘触发与平台触发的区别和应用Select
和epoll
的区别和应用
IO模型
IO分两个阶段:1. 通知内核准备数据。2.数据从内核缓冲区拷贝到应用缓冲区;根据这2点IO类型可以分成:
同时阻塞多个I/O操作。而且可以同时对多个读操作,多个写操作的I/O函数进行检测,直到有数据可读或可写时,才真正调用I/O操作函数
阻塞IO (Blocking I/O Model)
在两个阶段上面都是阻塞的
非阻塞IO (Nonblocking I/O Model)
在第1阶段,程序不断的轮询直到数据准备好,第2阶段还是阻塞的
IO复用 (I/O Multiplexing Model)
在第1阶段,当一个或者多个IO准备就绪时,通知程序,第2阶段还是阻塞的,在第1阶段还是轮询实现的,只是所有的IO都集中在一个地方,这个地方进行轮询
信号驱动IO (Signal-Driven I/O Model)
当数据准备完毕的时候,信号通知程序数据准备完毕,第2阶段阻塞
异步IO (Asynchronous I/O Model)
1,2都不阻塞
Java#Selector
允许套接口进行信号驱动I/O,并安装一个信号处理函数,进程继续运行并不阻塞。当数据准备好时,进程会收到一个SIGIO信号,可以在信号处理函数中调用I/O操作函数处理数据.
Java#NIO2
发出系统调用后,直接返回。通知IO操作完成。
前四种同步IO,最后一种异步IO.二者区别:第二个阶段必须要求进程主动调用recvfrom.而异步io则将io操作全部交给内核完成,完成后发信号通知。此期间,用户不需要去检查IO操作的状态,也不需要主动的去拷贝数据。
行程阻塞的原因
Thread.sleep(),线程放弃CPU,睡眠N秒,然后恢复运行
线程要执行一段同步代码,由于无法获得相关的锁,阻塞。获得同步锁后,才可以恢复运行。
线程执行了一个对象的wait方法,进入阻塞状态,只有等到其他线程执行了该对象的notify、nnotifyAll,才能将其唤醒。
IO操作,等待相关资源,阻塞线程的共同特点是:放弃CPU,停止运行,只有等到导致阻塞的原因消除,才能恢复运行 。或者被其他线程中断,该线程会退出阻塞状态,并抛出InterruptedException.
阻塞/非阻塞/同步/异步
同步/异步关注的是消息如何通知的机制。而阻塞和非阻塞关注的是处理消息。是两组完全不同的概念。
并发编程模式 Reactor 与 Proactor
用于派发/分离IO操作事件,IO事件也就是诸如
read/write
的IO操作,派发/分离
就是将单独的IO事件通知到上层模块Dispatcher 分发器
Notifer 通知器
, 事件到来时,使用Dispatcher
对Handler
进行分派,这个Dispatcher
要对所有注册的Handler
进行维护。同时有一个Demultiplexer 分拣器
对多路的同步事件进行分拣。两个模式的相同点,都是对某个IO事件的事件通知,即告诉某个模块,这个IO操作可以进行或已经完成。
在结构上,两者也有相同点:
demultiplexor
负责提交IO操作(异步)、查询设备是否可操作(同步),然后当条件满足时,就回调handler
Reactor 用于同步IO
同步情况下
Reactor
回调handler
时,表示IO设备可以进行某个操作(can read or can write),handler
这个时候开始提交操作
Proactor 用于异步IO
异步情况下
Proactor
回调handler
时,表示IO操作已经完成
网络通讯框架
TCP Server框架:
Apache MINA(Multipurpose Infrastructure for Network Applications)2.0.4
Netty 3.5.0Final
Grizzly 2.2
Quickserver是一个免费的开源Java库,用于快速创建健壮的多线程、多客户端TCP服务器应用程序。使用QuickServer,用户可以只集中处理应用程序的逻辑/协议
Cindy :强壮,可扩展,高效的异步I/O框架
xSocket:一个轻量级的基于nio的服务器框架用于开发高性能、可扩展、多线程的服务器。该框架封装了线程处理、异步读/写等方面
ACE 6.1.0 C++ADAPTIVE CommunicationEnvironment,
SmaxFoxServer 2.X :专门为Adobe Flash设计的跨平台socket服务器
消息编码协议
AMF/JSON/XML/自定义/ProtocolBuffer
无论是做何种网络应用,必须要解决的问题之一就是应用层从字节流中拆分出消息的问题,也就是对于 TCP 这种字节流协议,接收方应用层能够从字节流中识别发送方传输的消息.
使用特殊字符或者字符串作为消息的边界,应用层解析收到的字节流时,遇见此字符或者字符串则认为收到一个完整的消息
为每个消息定义一个长度,应用层收到指定长度的字节流则认为收到了一个完整的消息: 消息分隔标识(separator)、消息头(header)、消息体(body)
粘包
TCP粘包
是指发送方发送的若干包数据到接收方接收时粘成一包,从接收缓冲区看,后一包数据的头紧接着前一包数据的尾。
发送方引起的粘包
是由TCP协议本身造成的,TCP为提高传输效率,发送方往往要收集到足够多的数据后才发送一包数据。若连续发送几次的数据都很少,通常TCP会根据优化算法把这些数据合成一包后一次发送出去,这样接收方就收到了
粘包数据
。
接收方引起的粘包
是由于接收方用户进程不及时接收数据,从而导致粘包现象。这是因为接收方先把收到的数据放在
系统接收缓冲区
,用户进程从该缓冲区取数据, 若下一包数据到达时前一包数据 尚未被用户进程取走,则下一包数据放到系统接收缓冲区时就接到前一包数据之后,这样就形成了粘包
, 用户进程根据预先设定的缓冲区大小从系统接收缓冲区取数据,这样就一次取到了多包数据
解决措施
对于发送方引起的粘包现象,用户可通过编程设置来避免,TCP提供了强制数据立即传送的操作指令push,TCP软件接收到该操作指令后,就立即将本段数据发送出去,而不必等待发送缓冲区满;TCP-NO-DELAY-关闭了优化算法,不推荐
对于接收方引起的粘包,则可通过优化程序设计、精简接收进程工作量、提高接收进程优先级等措施,使其及时接收数据,从而尽量避免出现粘包现象-当发送频率高时依然可能出现粘包
接收方控制,将一包数据按结构字段,人为控制分多次接收,然后合并,通过这种手段来避免粘包。-效率低
接收方创建一预处理线程,对接收到的数据包进行预处理,将粘连的包分开
分包算法思路
基本思路是首先将待处理的接收数据(长度设为
m
),强行转换成预定的结构数据形式,并从中取出数据结构长度字段,即n
,而后根据n
计算得到第一包数据长度n < m
,则表明数据流包含多包数据,从其头部截取n个字节存入临时缓冲区,剩余部分数据一次继续循环处理,直至结束。n=m
,则表明数据流内容恰好是一完整结构数据,直接将其存入临时缓冲区即可。n>m
,则表明数据流内容尚不够构成一个完整结构数据,需留待与下一包数据合并后再行处理。
存储
计算机系统存储体系
程序运行时内存结构
计算机文件系统,页表结构
内存池与对象池的实现原理,应用场景与区别
关系型数据库
MySQL
的使用共享内存
程序
对
C/C++
有较深的理解深刻理解接口,封装与多态,有实践经验
深刻理解常用的数据结构:数组,链表,二叉树,哈希表
熟悉常用的算法及相关复杂度:冒泡排序 快速排序
清楚宏定义 模板的含义以及使用
灵活使用 STL 模板中的对象
数据结构知识的复习
游戏开发入门
防御式编程
不要相信客户端数据,一定要检验; 作为服务端,是无法确定你的客户端是谁(可能是黑客伪造的请求)
函数内部,对于收到的参数;函数调用,对于函数发返回值;这两者都要进行合法的判断
内部子系统之间不要过于信任,要求高内聚,低耦合;
插件式模块设计,功能模块的健壮性应该是内建的,而不是依赖外部环境的稳定
设计模式
道法自然,不要迷信迷恋设计模式
简化,简化,再简化
网络模型
自己造轮子:
Select
Epoll
,Epoll
一定比Select
高效么?开源框架:
Libevent
Libev
ACE
数据持久化
选择存储系统要考虑的因素:稳定性,性能,可扩展性
自定义文件存储 ,如《梦幻西游》
关系型数据库,如
MySQL
No-SQL数据库, 如
MongoDB
内存管理
使用内存池和对象池,禁止运行期间动态分配内存
对于输入输出的指针参数,严格检查,宁滥勿缺
写内存包含,使用带内存保护的函数
strncpy
memcpy
snprintf
vsnprintf
等严防数组下标越界
防止读内存溢出, 确保字符串以
\0
结束
日志系统
简单高效, 大量日志操作不应该影响程序性能
稳定 做到服务器崩溃,日志不丢失
完备 玩家关键操作一定记录在日志里, 理想状态下,通过日志能够重建任何时刻的玩家数据
开关 开发日志要加级别开关控制
通信协议
PDL(Protocol Design Language) 如
Protobuf
, 可以同时生成前后端代码,减少前后端联调成本,拓展性好JSON 文本协议,简单,自解释,无联调成本,拓展性好,方便包过滤 以及 写入日志
自定义二进制协议,精简,高效的传输性能,完全可控,几乎无拓展性
全局唯一Key GUID
为合服做准备
方便追踪道具,装备流向
每个角色,装备,道具是全局唯一Key GUID
多线程与同步
消息队列进行同步化处理
状态机
强化角色的状态
前置状态的检查校验
数据包操作
合并,同一帧内的数据包进行合并,减少
IO
操作次数单副本,用一个包尽量只保存一份,减少内存复制次数
AOI同步中减少中间过程无用数据包
状态监控
随时监控服务器内部状态
内存池,对象池使用情况
帧处理时间
网络IO
包处理性能
各种业务逻辑的处理次数
包频率控制
基于每个玩家每条协议的包频率控制,瘫痪变速齿轮
开关控制
每个模块都有开关,可以紧急关闭任何出问题的功能模块
反外挂和反作弊
包频率控制可以消灭变速齿轮
包id自增校验,可以消灭
WPE
包校验码可以消灭或者拦截篡改的包
图形识别码,可以踢掉 99% 非人的操作
热更新
核心配置逻辑的热更新,如防沉迷系统,包频率控制,开关控制
代码基本热更新,如
Erlang
Lua
防刷
关键系统资源的产出日志
资源的产出和消耗尽量依赖两个或者两个以上的独立条件的检测
严格检查各项操作的前置条件
检验参数合法性
防崩溃
系统底层与具体业务逻辑无关,可以用大量机器人压力测试,暴露各种BUG,确保稳定
业务逻辑建议使用脚本,系统性的保证游戏不会崩溃
性能优化
IO 操作异步化
IO 操作合并缓写
Cache 机制
减少竞态条件,避免频繁进出切换,尽量减少锁定使用,多线程不一定比单线程快
减少内存复制
自己测试,用数据说话,别猜
运营支持
接口支持: 实时查询,控制指令,数据监控,客服处理
实现考虑提供 http 接口
容灾与故障预案
服务器架构
好的架构
满足业务要求
能迅速的实现策划需求,响应需求变更
系统级的稳定性保障
简化开发。将复杂性控制在架构底层,降低对开发人员的技术要求,逻辑开发不依赖于开发人员本身强大的技术实力,提高开发效率
完善的运营支撑体系
架构实践的思考
简单,满足需求的架构就是好架构
设计性能,抓住重要的20%, 没必要从程序代码里面去抠性能
热更新是必须的
人难免会犯错,尽可能的用一套机制去保障逻辑的健壮性
游戏服务器的发展也由以前的单服结构转变为多服机构,甚至出现了
bigworld
引擎的分布式解决方案,最近了解到Unreal
的服务器解决方案atlas
也是基于集群的方式。
基于功能和场景划分服务器结构
负载均衡是一个很复杂的课题,这里暂不谈
bigworld
和atlas
的这类服务器的设计
思路
分离游戏中占用系统资源(cpu,内存,IO等)较多的功能,独立成服务器。
在同一服务器架构下的不同游戏,应尽可能的复用某些服务器(进程级别的复用)。
以多线程并发的编程方式适应多核处理器。
宁可在服务器之间多复制数据,也要保持清晰的数据流向。
主要按照场景划分进程,若需按功能划分,必须保持整个逻辑足够的简单,并满足以上1,2点。
服务器划分
Gateway 应用网关
主要用于保持和client的连接,该服务器需要2种IO
对client采用高并发连接,低吞吐量的网络模型,如IOCP等
对服务器采用高吞吐量连接,如阻塞或异步IO
分担了网络IO资源,同时,也分担了网络消息包的加解密,压缩解压等cpu密集的操作。
隔离了client和内部服务器组,对client来说,它只需要知道网关的相关信息即可(ip和port)。client由于一直和网关保持常连接,所以切换场景服务器等操作对client来说是透明的。
维护玩家登录状态。
World Server
是一个控制中心,它负责把各种计算资源分布到各个服务器,它具有以下职责
管理和维护多个Scene Server
管理和维护多个功能服务器,主要是同步数据到功能服务器
复杂转发其他服务器和Gateway之间的数据
实现其他需要跨场景的功能,如组队,聊天,帮派等
Phys Server
主要用于玩家移动,碰撞等检测
所有玩家的移动类操作都在该服务器上做检查,所以该服务器本身具备所有地图的地形等相关信息。
具体检查过程是这样的:
Worldserver
收到一个移动信息,WorldServer
收到后向Phys Server
请求检查,Phys Server
检查成功后再返回给world Server
,然后world server
传递给相应的Scene Server
Scene Server
场景服务器,按场景划分,每个服务器负责的场景应该是可以配置的。理想情况下是可以动态调节的
ItemMgr Server
物品管理服务器,负责所有物品的生产过程。在该服务器上存储一个物品掉落数据库,服务器初始化的时候载入到内存。任何需要产生物品的服务器均与该服务器直接通信。
AIServer
又一个功能服务器,负责管理所有
NPC
的AI
。AI Server
通常有2个输入: 一个是Scene Server
发送过来的玩家相关操作信息,另一个时钟Timer驱动在这个设计中,对其他服务器来说,
AI Server
就是一个拥有很多个NPC
的客户端。AI server
需要同步所有与AI
相关的数据,包括很多玩家数据。由于AI Server
的Timer
驱动特性,可在很大程度上使用TBB
程序库来发挥多核的性能。