Lua远程调用的实现方法
游戏开发中,客户端、服务端之间的交互是很频繁的,尤其是逻辑玩法的实现,需要大量的交互。
如果所有的交互都按功能构建出不同的协议,这样即繁琐又不方便修改。
通过Lua,使用远程调用可以极大的方便客户端、服务器的通信。
在Lua中,通过C++告诉对方,我要调用哪个函数、传递哪些参数,来执行相关的功能。
这样就不用定义一大串协议,而只需定义一种:远程调用协议。
如果要对方执行相应的函数,需要传递以下信息:
- 函数名
- 参数
当C++获得函数名、参数后,就可以构建RPC数据包,实现远程调用了。
本文讲述如何在C++中获得Lua远程调用的函数名、参数。
例如,我想实现:
客户端登录的功能,我就可以在客户端调用位于服务端的loginreq函数。
具体的形式有如下两种:
rpc("loginreq", "username", "password")
server.loginreq("username", "password")
第一种,是直接导出一个全局函数rpc,并将函数名作为第一个参数传递过去。
第二种,是导出一个全局table,以函数名作为key。
第一种实现比较简单:
直接导出全局函数,依次获取栈的值:函数名、参数列表。
int handler2(lua_State* L) {
const char* funcname = lua_tostring(L, 1);
cout << "[handler2] Call function <" << funcname << "> with " << lua_gettop(L) << " params:" << endl;
printstack(L);
return 0;
}
lua_pushcfunction(L, handler2);
lua_setglobal(L, "rpc");
第二种相对复杂一些:
导出全局table,设置metatable.__index处理table访问,Lua会将key作为第二个参数,传给__index(__index(table, key)),由此可以获得函数名。 将函数名存储到处理函数的upvalue中,以便后续使用。
int __index(lua_State* L) {
const char* funcname = lua_tostring(L, -1);
lua_pushstring(L, funcname);
lua_pushcclosure(L, handler1, 1);
return 1;
}
int handler1(lua_State* L) {
const char* funcname = lua_tostring(L, lua_upvalueindex(1));
cout << "[handler1] Call function <" << funcname << "> with " << lua_gettop(L) << " params:" << endl;
printstack(L);
return 0;
}
lua_newtable(L); // server
lua_newtable(L); // metatable
lua_pushcfunction(L, __index);
lua_setfield(L, -2, "__index");
lua_setmetatable(L, -2);
lua_setglobal(L, "server");
完整代码如下:
test.cpp
#include <iostream>
using namespace std;
#include "lua.hpp"
void printstack(lua_State *L) {
int n = lua_gettop(L);
for (int i = 1; i <= n; ++i) {
int t = lua_type(L, i);
cout << i << ": (" << lua_typename(L, t) << ") ";
switch (t) {
case LUA_TNIL:
cout << "nil";
break;
case LUA_TBOOLEAN:
cout << lua_toboolean(L, i);
break;
case LUA_TNUMBER:
cout << lua_tonumber(L, i);
break;
case LUA_TSTRING:
cout << lua_tostring(L, i);
break;
case LUA_TTABLE:
cout << "table";
break;
default:
cout << "*";
break;
}
cout << endl;
}
}
int handler1(lua_State* L) {
const char* funcname = lua_tostring(L, lua_upvalueindex(1));
cout << "[handler1] Call function <" << funcname << "> with " << lua_gettop(L) << " params:" << endl;
printstack(L);
return 0;
}
int handler2(lua_State* L) {
const char* funcname = lua_tostring(L, 1);
cout << "[handler2] Call function <" << funcname << "> with " << lua_gettop(L) << " params:" << endl;
printstack(L);
return 0;
}
int __index(lua_State* L) {
const char* funcname = lua_tostring(L, -1);
lua_pushstring(L, funcname);
lua_pushcclosure(L, handler1, 1);
return 1;
}
void regluafuncs(lua_State* L) {
lua_newtable(L); // server
lua_newtable(L); // metatable
lua_pushcfunction(L, __index);
lua_setfield(L, -2, "__index");
lua_setmetatable(L, -2);
lua_setglobal(L, "server");
lua_pushcfunction(L, handler2);
lua_setglobal(L, "rpc");
}
int main() {
lua_State* L = luaL_newstate();
regluafuncs(L);
return luaL_dofile(L, "test.lua");
}
test.lua
server.test(nil, 1, true, false, "abc", {1,2,3,a="hello"}, function() end)
rpc("test", nil, 1, true, false, "abc", {1,2,3,a="hello"}, function() end)
执行输出:
[handler1] Call function <test> with 7 params:
1: (nil) nil
2: (number) 1
3: (boolean) 1
4: (boolean) 0
5: (string) abc
6: (table) table
7: (function) *
[handler2] Call function <test> with 8 params:
1: (string) test
2: (nil) nil
3: (number) 1
4: (boolean) 1
5: (boolean) 0
6: (string) abc
7: (table) table
8: (function) *