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

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

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

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