游戏开发中,客户端、服务端之间的交互是很频繁的,尤其是逻辑玩法的实现,需要大量的交互。

如果所有的交互都按功能构建出不同的协议,这样即繁琐又不方便修改。

通过Lua,使用远程调用可以极大的方便客户端、服务器的通信。

在Lua中,通过C++告诉对方,我要调用哪个函数、传递哪些参数,来执行相关的功能。

这样就不用定义一大串协议,而只需定义一种:远程调用协议。

如果要对方执行相应的函数,需要传递以下信息:

  1. 函数名
  2. 参数

当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);
<span class="k">return</span> <span class="mi">0</span><span class="p">;</span>

}

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);

<span class="k">return</span> <span class="mi">0</span><span class="p">;</span>

}

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);

<span class="k">return</span> <span class="mi">0</span><span class="p">;</span>

}

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);

<span class="k">return</span> <span class="mi">0</span><span class="p">;</span>

}

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");

<span class="n">lua_setmetatable</span><span class="p">(</span><span class="n">L</span><span class="p">,</span> <span class="o">-</span><span class="mi">2</span><span class="p">);</span>

<span class="n">lua_setglobal</span><span class="p">(</span><span class="n">L</span><span class="p">,</span> <span class="s">&#34;server&#34;</span><span class="p">);</span>

<span class="n">lua_pushcfunction</span><span class="p">(</span><span class="n">L</span><span class="p">,</span> <span class="n">handler2</span><span class="p">);</span>
<span class="n">lua_setglobal</span><span class="p">(</span><span class="n">L</span><span class="p">,</span> <span class="s">&#34;rpc&#34;</span><span class="p">);</span>

}

int main() {
lua_State* L = luaL_newstate();

<span class="n">regluafuncs</span><span class="p">(</span><span class="n">L</span><span class="p">);</span>

<span class="k">return</span> <span class="n">luaL_dofile</span><span class="p">(</span><span class="n">L</span><span class="p">,</span> <span class="s">&#34;test.lua&#34;</span><span class="p">);</span>

}

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) *