课程简介
本课程为机械工业出版社出版的图书《百万在线:大型游戏服务端开发》的电子版。
第一部分:学以致用(第1~4章),这部分介绍了Skynet引擎的使用方法及注意事项,以《球球大作战》的案例贯穿其中,全面又详尽地剖析了服务端结构设计、通信协议格式、数据表结构设计、断线重连等方案的核心技术。
第二部分:入木三分(第5~7章),这部分揭示了在多核时代采用现代C++编写多线程TCP网络服务器的高效做法,以C++重写Skynet的案例贯穿其中,使用大量图表,生动翔实地描述Linux环境下的编程技术。
第三部分:各个击破(第8~11章),这部分列举了同步算法、热更新、防外挂等实际工程难题,并对其逐一击破,非常具有实用价值。尽管本书以Skynet为例,但其同样适用于使用C++自研引擎的项目组,甚至是选用Erlang、Golang、Java的开发者。
关键词:
C/C++/Lua | Socket、TCP/IP、Protobuf | Linux开发环境 | 多线程、数据库、分布式 | 高并发、内存泄漏、热更新
完整版本,可以前往UWA学堂查看《百万在线:大型游戏服务端开发》。
第1.1节 我们简单介绍了从玩家的角度来看,一款网络游戏大都会涉及的流程,在此过程中,藏在幕后的服务端做了很多事情,那究竟做了哪些?此服务端系统又是如何开发的呢?
服务端程序要承载很多玩家,性能是必须要考虑的问题。那么,1.2.4节的程序能够承载多少玩家同时在线呢?
1.3小节介绍服务端程序要承载很多玩家,性能是必须要考虑的问题。
对于中大型商业游戏来说,往往出现全服爆满的现象(如图1-12),1000多人的承载量远远不够。根据游戏厂商的新闻稿可知,2012年《梦幻西游》最高同时在线玩家达到了270多万人;2016年《王者荣耀》的同时在线玩家超过了300万人。既然单个程序的承载量有限,最直接的办法就是开启多个程序来提高承载量。
图1-12 玩家爆满的游戏画面示意图
1.4.1 多个程序协同工作
图1-13展示了一种由多个程序共同协作的服务端模型,图中程序A和程序B分别处理客户端消息,程序C作为中转站,负责程序A和程序B之间的通信。每个程序均独立运行,可以将其部署在不同的物理机上,形成天然的分布式系统。
图1-13 多进程服务端示意图
说明:为统一术语,本书中“服务端”代表整个游戏服务端系统;“程序”“进程”或“节点”代表一个操作系统进程;“物理机”代表服务器,涵盖了实体服务器和云服务器。
尽管单个程序还是最多承载1000余人,但是只需开启1000个程序,并将其布置在数百台物理机上,理论上就可以支撑100万玩家,总承载量得以提高。
1.4.2 三个层次的交互
在分布式结构中,数据的交互被分成了三个层次,如表1-2所示。这就要求开发者能对游戏业务功能做出合理的切分。在游戏中,有些功能是强交互的,有些功能是弱交互的。以MMORPG为例,同一个场景的角色交互很强,每走一步都要让对方知道,可以在同一个程序中处理同一个场景逻辑;不同场景的角色交互较弱,只有聊天、好友、公会这些功能需要交互,可以将同一个服务器的玩家都放在同一台物理机上处理;不同服务器的玩家交互很少,可以放到不同的物理机上。
表1-2 不同交互场景的区别
1.4.3 搭个简单的分布式服务端
理论归理论,实践出真知。实现1.2.4节的“走路”程序是场景服务器的一项主要功能,尽管一个场景只能支撑数十人,只要多开几个场景就能够支持更多玩家。本节将实现图1-14所示的分布式程序,系统中有两个“走路”程序,分别代表兽人村落和森林两个游戏场景,客户端直接连接角色所在的场景,玩家只能看到所在场景的角色,不同场景角色可以全服聊天。该程序可分成三个步骤实现。
图1-14 简单的分布式系统
第一步,编写聊天服务器。聊天服务器其实是转发服务器,它管理着场景服务器发来的连接(见代码1-5中的scenes),只要收到场景服务器的消息,它就会广播给所有的场景服务器。聊天服务器会监听8010端口,等待场景服务器连接。
代码1-5 聊天服务器(Node.js)
(资源:Chapter1/3_chat_server.js)
var net = require('net');
var scenes = new Map();
var server = net.createServer(function(socket){
scenes.set(socket, true) //新连接
socket.on('data', function(data) { //收到数据
for (let s of scenes.keys()) {
s.write(data);
}
});
});
server.listen(8010);
第二步,让场景服务器(“走路”程序)连接聊天服务器。场景服务器即是服务端又是客户端,对于玩家来说,它是服务端,对于聊天服务器来说,它又是客户端。在“走路”程序的基础上,让场景服务器连接聊天服务器(见代码1-6中的net.connect),当场景服务器收到聊天服务器发来的数据时,就会把它原封不动地广播给客户端。
代码1-6 场景服务器的部分代码,用于连接聊天服务器(Node.js)
(资源:Chapter1/3_walk_server.js)
var net = require('net');
//"走路"程序略 server.listen(8001);
var chatSocket = net.connect({port: 8010}, function() {});
chatSocket.on('data', function(data){
for (let s of roles.keys()) {
s.write(data);
}
});
第三步,给场景服务器添加聊天功能(见代码1-7)。假设客户端除了发送“left”“right”等指令外,还会发送聊天文字,那么在收到聊天消息后它会把消息原样发给聊天服务器。整个消息流程是:①场景服务器将聊天消息发送给聊天服务器;②聊天服务器把消息广播给所有场景服务器;③各个场景服务器分别将聊天消息广播给场景中的所有玩家。
代码1-7 场景服务器处理聊天消息的部分代码(Node.js)
(资源:Chapter1/3_walk_server.js)
//接收到数据
socket.on('data', function(data){
……
//更新位置
if(cmd == "left\r\n") role.x--;
……
else {
chatSocket.write(data);
return;
};
……
});
现在可以进行测试了,先运行聊天服务器,再依次运行两个场景服务器(假设监听的端口分别为8001和8002)。如图1-15所示,客户端A和B连接第一个场景服务器,客户端C连接第二个场景服务器,服务器中的小方块代表各个程序,方块中的数字代表该程序的监听端口。当客户端A走动时,因为A、B同在一个场景中,所以它们会收到移动消息,而客户端C不在同一场景中,因此它不会收到;若客户端A发送聊天信息“战神公会招人”,三个客户端都能收到。
图1-15 测试分布式服务端
1.4.4 一致性问题
分布式程序要处理很多异常情况。如果程序部署在不同物理机上,连接不太稳定,需要处理好断线重连、断线期间的消息重发,以及断线后进程间状态不一致的问题。图1-16展示的是因网络不畅通导致的异常情形,假如客户端A的玩家向客户端B的玩家购买道具,消息需要通过程序C中转,因程序A和程序C之间的网络连接出现异常,出现了客户端B的玩家被扣除了道具,客户端A的玩家却没得到道具的情况。程序A与程序C的网络连接异常,游戏功能受到了影响,就算一段时间后重新连接上,两个进程的状态也可能会不一致。
图1-16 分布式程序的异常情形
一致性问题是分布式系统的一大难题,在游戏业务中,开发者一般会把一致性问题抛给具体业务去处理。对于图1-16所示的异常情况,需要给每个交易赋予唯一编号。程序C除了转发消息,还需要记录程序A对每个交易的执行状态,如果转发失败,程序C要在稍后重发交易消息,直到程序A成功执行。而程序A也需要记录每个交易的状态,如果某个交易已经成功执行,则不再响应程序C发来的消息,避免重复添加道具。
另外,管理数百台物理机、成百上千个程序也不容易,第一,物理机多了,某一台出故障的可能性很大;第二,开启或关闭全部程序要花费很长时间。
本篇为《百万在线:大型游戏服务端开发》其中部分内容,完整版本可以前往UWA学堂查看。
适合读者
1、刚入行的服务端工程师
2、游戏公司开发岗位的求职者
3、对游戏开发感兴趣的高校学生
4、游戏开发爱好者
你将获得
1、充分了解业务逻辑和底层框架的设计意图
2、立足实践的服务端学习思路,深入浅出
3、用实际案例贯穿各知识点,在实践中学习
4、了解商业游戏的设计思路和实现方法
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。