• 设为首页
  • 点击收藏
  • 手机版
    手机扫一扫访问
    迪恩网络手机版
  • 关注官方公众号
    微信扫一扫关注
    公众号

基于Lua的游戏服务端框架简介

原作者: [db:作者] 来自: [db:来源] 收藏 邀请

以下内容转载自

http://blog.csdn.net/lalate/article/details/51498869


本文所述内容,并不涉及服务器集群的进程划分与拓扑结构.
为理解方便,我们假定服务器集群划分为如下的这些进程(跟鹅厂其他游戏项目大同小异):

-            router: 数据转发,多进程按负载分担,支持点对点,广播,主从,哈希等几种常见的数据转发逻辑.

-            gamesvr: 提供客户端接入逻辑,以及常规的游戏逻辑(如道具,商城,等等...), 多实例按负载分担.

-            dbagent: 提供数据库访问服务,多进程按哈希分布.

-            matchsvr: 提供战斗匹配服务,多进程主从备份.

-            其他服务进程,不再列举.

本文所述框架以C++为基础层,以lua为业务层,旨在解决以下3个问题.

2.1 如何方便的在进程之间通信?

假想一个情景:我们要从gamesvr向matchsvr发一个请求,将一个玩家队伍加入匹配队列.
请求中包含的信息: gamesvr的id, 匹配的模式(几对几),是否接受机器人,各玩家的id,各玩家的段位(elo).
我们的程序员要干些什么事情呢?

在协议描述的XML中定义这个消息结构,呃,可能还要嵌套结构.

转换XML,生成对应的.h,.c,.tdr之类的.

在gamesvr中编写发送消息代码.

在matchsvr编写消息处理逻辑代码.

呃,对了,可能还要在派发消息的地方注册一下消息

而上面这些,跟业务逻辑有关的,其实只有3,4,其他都是累赘.
我们能不能只关注业务逻辑,不要这些累赘呢?
当然可以,这就是基于lua的远程调用: 无需额外的协议定义,直接编写业务代码


如下所示,gamesvr发起调用:

local mode = 3-- 3v3

local team = {...}--队伍成员列表

local robot = false;

--从gamesvr调用matchsvr的消息函数: OnMatchRequest

remote.CallMatchSvr("OnMatchRequest", app.iId, mode, robot, team);

 

下面为matchsvr的响应代码:

--远程调用OnMatchRequest的实现

--约定所有远程调用都必须定义在全局表s2s中

--s2s含义为: server to server

function s2s.OnMatchRequest(svr, mode, robot, team)   

    -- 加入匹配池...

end

2.2 如何使得我们的开发过程更加顺畅,运维响应更加及时.

2.2.1 开发过程

继续上面的2.1节的场景,在传统的C++实现中,想想,程序员写完两边的消息代码,要继续干什么?

1. 关掉服务器,嗯,如果共用服务器,还得吼一下: 我要关服了.

2. make ...

3. 启动服务器.

4. 呃,客户端联调的兄弟,你重新登录一下,对了,记得要开几个客户端重新组队哦.

真繁琐啊,能不能简单点?
是的,在新框架下,写完业务逻辑,你需要做的只是Ctrl+S,代码立即生效,自动!
这就是新框架下的代码自动热更新,它让上面的1,2,3,4都成多余.

2.2.2 运维事件

假定现在运营环境发现一个严重Bug,而我们知道只要简单的改一行代码就好了.
又或者,谁都不知道咋回事,还不能上GDB暂停,只能先在某函数处加行日志看看.
我们要经历怎样的过程? 嗯,谁经历谁知道啊.
有了代码热更新技术,我们对在线Bug的修复不再头疼,甚至秒修.

2.3 如何彻底摆脱空指针,野指针,内存越界等顽疾,提供更加稳定的服务?

嗯,这个就属于lua语言本身的特性了.
连指针都没有,所谓空指针,野指针,内存越界,就无从谈起.
即便是新手程序员,也没机会犯这种错误.
即便是除零之类的错误,也不过是当前这一条消息出错,下一条消息照常处理.
另外,lua本身的实现,也属于公认的高质量代码,值得信赖.

3. 历史

lua在游戏领域的应用,大概是从<魔兽世界>火起来的.
本文要介绍的技术基础,沿袭自<剑网三>(一些业界同行也有类似的实现).
笔者在2004年至2011年期间,负责<剑网三>的服务端团队.
<剑网三>服务端架构中,虽然一开始就引入了lua支持,但是早期只是作为业务粘合层而存在.
在研发阶段的中后期,引入了两个技术点来加速开发速度:

-            远程调用: 从此摆脱C++层面的协议定义,数据组织编码,编译更新等繁琐的过程.

-            数据存储: C++层面无需关心数据存储结构,无需再写大量的DB操作代码(MySQL);

 

其实这两个点都是基于lua序列化&反序列化的.
到09年上线时,<剑网三>服务端的lua逻辑大概占比在30%左右,并不算高.
这是因为:

-            <剑网三>的服务端属于计算密集型(3D场景逻辑,战斗技能,AI,等等).

-            作为笔者入行的第一个项目,出于对性能的谨慎,限制了lua在服务端的应用广度.

 

2011年初,我离开服务了6年多的<剑网三>团队,出去创业.
这时,我们已经不再担心性能问题,而更需要的是快速实现,快速响应,于是lua开始大行其道.
特别是12年我们开始做手游时,C++层面差不多只剩下了网络层,客户端也是底层基于cocos2d-x,逻辑都在lua.
顺便提一下,也差不多是那个时候,从网易出来创业的云风也推出了基于lua的skynet开源框架.

2015年的春天,怀着对创业的绝望,我来到了腾讯.
嗯,惊奇的发现,腾讯的游戏服务端实现中,lua应用得非常少.
于是,便有了此文,介绍一下我们正在使用的基于lua的技术框架.

4. 技术基础

4.1 lua的C++绑定

4.1.1 实现原理

1. 为每个导出的class创建了一个table(lazy模式), 其中存放了class的成员函数指针以及成员变量偏移.

2. 在上面的class专属table中,我们将表中的__index, __newindex指向我们的定制的C++函数.

3. 对象首次被push到lua时,会创建一个table与之绑定,称为影子对象,该table中记录了对象指针,并以第1步的table作为其元表.

4. 当在脚本中通过影子对象访问C++对象的成员时,通过元表的__index, __newindex法定向到C++对象成员.

5. C++对象上也记录了影子对象的引用,在对象析构的时候将清除影子对象中存放的指针.

4.1.2 C++对象导出示例

.h 中的class 声明代码:

// class 声明中需要插入一行 DECLARE_LUA_CLASS

class CPlayer
{

    char m_szName[32];

    int m_iLevel;

    int luaSay(lua_State* L);

    DECLARE_LUA_CLASS(CPlayer); // 声明导出CPlayer类

};

 

.cpp 中的实现代码:

// 在 CPP 中增加如下的导出声明

IMPL_LUA_CLASS_BEGIN(CPlayer)

EXPORT_LUA_STRING(m_szName)

EXPORT_LUA_INT(m_iLevel)

EXPORT_LUA_FUNCTION(luaSay)

IMPL_LUA_CLASS_END()

 

int CPlayer::luaSay(lua_State* L)

{

    // ...

    return 0;

}

注意,这不是实际存在的代码;对于业务逻辑都在 lua 中实现的项目而言,真正需要导出的 C++ 代码极少.

4.1.3 主要特性

-            在lua中读写对象的C++成员变量(也可声明为只读).

-            在lua中调用对象的C++成员函数.

-            在lua中对影子对象添加新的"成员变量","成员函数".

-            在lua中覆盖对象中导出的C++函数.

-            在C++中调用影子对象上的lua函数.

4.1.4 实际使用示例

C++部分代码: 在玩家登陆时调用login.lua中定义的lua函数.

void OnPlayerLogin(lua_State* L, int iConnIdx, CPlayer* player)

{

    CSafeStack guard(L);

    // 除了获取文件中的函数,还有其他的相关的API

    // 用来获取影子对象上的函数,以及全局 table 中的函数等等

    Lua_GetFileFunction(L, "login.lua""OnPlayerLogin");

    lua_pushinteger(L, iConnIdx);

    Lua_PushObject(L, player);

    Lua_XCall(L, 20);

}

 

lua部分代码: 响应上面C++代码触发的玩家登陆事件.

function OnPlayerLogin(connIdx, player)

    --访问成员变量: 读

    log_debug("player login, name="..player.szName);

    --访问成员变量: 写

    player.iLevel = 1;

    --调用成员函数

    player.Say("皇上吉祥");

    --在player对象上加入新的函数/变量

    player.OnExit = function()

        -- do something !

    end

end

4.1.5 另一个实现

关于lua的C++绑定,其实还有另一个基于C++14的实现(还在完善中,欢迎提意见:).
主要特性在于函数参数操作不再需要写一堆的lua_to***, lua_push***之类的代码,可直接导出一般C++函数.
遗憾的是,我司的编译器版本并不支持c++14标准,即便是tlinux2.0也不支持(GCC 4.8.2,其实C++11也只是部分支持).
也许某天Docker的普及可以让项目自己指定编译器版本.
C++14版本的实现

4.2 lua文件沙盒

4.2.1 代码示例

关键函数: import

在main.lua中import另外两个文件: a.lua, b.lua

--main.lua

a = import("a.lua");

b = import("b.lua");

 

print("a.txt="..a.txt); --输出: A

a.txt = "X"--修改 a 中的变量,不影响 b.

print("b.txt="..b.txt); --输出: B

 

a.lua,注意它也import了b.lua,但是b.lua在main.lua中已经加载了,两处会引用同一份实例.

txt = "A";

b = import("b.lua");

print("b.txt="..b.txt); --输出: B

 


鲜花

握手

雷人

路过

鸡蛋
该文章已有0人参与评论

请发表评论

全部评论

专题导读
上一篇:
用lua实现ByteArray和ByteArrayVarint发布时间:2022-07-22
下一篇:
通信编码解码c11实现[lua专用版]发布时间:2022-07-22
热门推荐
热门话题
阅读排行榜

扫描微信二维码

查看手机版网站

随时了解更新最新资讯

139-2527-9053

在线客服(服务时间 9:00~18:00)

在线QQ客服
地址:深圳市南山区西丽大学城创智工业园
电邮:jeky_zhao#qq.com
移动电话:139-2527-9053

Powered by 互联科技 X3.4© 2001-2213 极客世界.|Sitemap