寻找良好的服务器端语言,允许玩家上传可执行的代码

发布于 2024-08-08 00:52:35 字数 715 浏览 1 评论 0原文

我有一个想要编写的程序的想法,但哪种语言最好是我的问题。

如果我有一款赛车游戏,并且希望允许用户提交新的交互式 3D 赛道(想想 Speed Racer 电影中的赛道)、车辆及其自动驾驶车辆的代码,那么他们将创建 AI对于他们的汽车来说,这将使汽车能够确定如何处理危险。

因此,我需要一种运行速度快的语言,并且作为服务器拥有的所有可能的种族及其各种状态的世界地图的一部分。

例如,我很好奇这是否是考虑在 Scala 中创建 DSL 的好理由?

我不想重新启动应用程序来加载新的 dll 或 jar 文件,因此许多编译语言将成为问题。

我对 Linux 或 Windows 持开放态度,对于大多数脚本语言、F#、Scala、Erlang 或我可以编程的大多数 OOP 持开放态度。

用户将能够监控他们的车辆的运行情况,以及是否有多个为那辆车上传的人工智能,当它遇到某些障碍时,他们应该能够根据需要将一个人工智能程序交换为另一个。

更新:到目前为止,解决方案是 javascript、使用 V8 和 Lua。

我很好奇这是否适合 DSL(实际上是 3 个独立的 DSL)。第一个用于创建赛道,另一个用于控制赛车,第三个用于创建新车。

如果是这样,Haskell、F# 或 Scala 是不错的选择吗?

更新:让不同的部分以不同的语言结束是否有意义?例如,如果 Erlang 用于控制汽车,Lua 用于汽车本身,以及动画赛道?

I had an idea of a program I want to write, but which language would be best is my problem.

If I have a car racing game and I want to allow users to submit code for new interactive 3D race tracks (think of tracks such as found in the Speed Racer movie), vehicles and for their autonomous vehicles, so, they would create the AI for their car that will enable the car to determine how to handle hazards.

So, I need a language that will run fast, and as part of a world map that the server has of all the possible races available, and their various states.

I am curious if this would be a good reason to look at creating a DSL in Scala, for example?

I don't want to have to restart an application to load new dlls or jar files so many compiled languages would be a problem.

I am open to Linux or Windows, and for the languages, most scripting languages, F#, Scala, Erlang or most OOP I can program in.

The user will be able to monitor how their vehicle is doing, and if they have more than one AI uploaded for that car, when it gets to certain obstacles they should be able to swap one AI program for another on demand.

Update: So far the solutions are javascript, using V8, and Lua.

I am curious if this may be a good use for a DSL, actually 3 separate ones. 1 for creating a racetrack, another for controlling a racecar and the third for creating new cars.

If so, would Haskell, F# or Scala be good choices for this?

Update: Would it make sense to have different parts end up in different languages? For example, if Erlang was used for the controlling of the car and Lua for the car itself, and also for the animated racetrack?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(5

懵少女 2024-08-15 00:52:35

你的情况听起来很适合 Lua。

  • 你需要沙箱:这在 Lua 中很容易做到。例如,您只需通过覆盖或删除 os.execute 命令来初始化用户环境,然后用户就无法再访问该功能。
  • 如果你想要快速:查看一些 针对其他语言的 Lua 基准测试< /a>.
  • 假设您需要与另一种语言进行互操作。至少 Lua 很容易(在我看来)嵌入到 C 或 C++ 中。我没有使用 LuaInterface,但这是 C# 绑定。
  • Lua 具有一阶函数,因此应该很容易即时交换函数。
  • Lua 在某种程度上支持 OOP 的元表。
  • Lua 的主要数据结构是 table (关联数组),它非常适合稀疏数据结构,例如与世界地图结合。
  • Lua 有非常规则的语法。分号或缩进没有什么有趣的技巧,因此用户在学习你的语言时就少了一件需要学习的事情——更不用说,使用记录良好的语言会带走你必须做的一些工作。自己记录的条款。

此外,正如 @elviejo 在评论中指出的那样,Lua 已经在许多游戏中用作脚本语言。如果不出意外的话,按照您所描述的方式使用 Lua 肯定有一些先例。而且,正如 @gmonc 提到的,您的用户有可能已经在另一款游戏中使用过 Lua。


As far as how to integrate with Lua: generally, your users should simply need to upload a Lua script file. To grossly oversimplify, you might provide the users with available functions such as TurnLeft, TurnRight, Go, and Stop. Then, the users would upload a script like

Actions = {} -- empty table, but you might want to provide default functions 
function Actions.Cone()
    TurnLeft()
end

function Actions.Wall()
    Stop()
    TurnRight()
    TurnRight()
    Go()
end

然后在服务器端,您可以使用 Go() 启动它们。然后,当他们的汽车到达圆锥体时,您调用他们的 Actions.Cone() 函数;一堵墙通向 Actions.Wall() 函数,等等。此时,您(希望)已经对 Lua 环境进行了沙箱处理,因此您可以简单地执行他们的脚本,而无需考虑错误检查——如果他们的脚本导致错误,您没有理由不能将错误直接传递给用户。如果没有任何错误,服务器代码中的 lua_State 应该包含汽车的最终状态。


更好的例子

这是一个独立的 C 文件,它从 stdin 获取 Lua 脚本并像我上面解释的那样运行它。游戏中你会遇到地面、栅栏或树枝,你必须分别奔跑、跳跃或躲避才能通过。您通过 stdin 输入 Lua 脚本来决定如何反应。源代码有点长,但希望它很容易理解(除了 Lua API 需要一段时间才能习惯)。这是我在过去 30 分钟内的原创作品,希望对您有所帮助:

#include <stdio.h>
#include <stdlib.h>
#include <time.h>
#include "lua.h"
#include "lauxlib.h"
#include "lualib.h"

#define FAIL 0
#define SUCCESS 1

/* Possible states for the player */
enum STATE {
    RUNNING,
    JUMPING,
    DUCKING
};

/* Possible obstacles */
enum OBSTACLE {
    GROUND,
    FENCE,
    BRANCH
};

/* Using global vars here for brevity */
enum STATE playerstate = RUNNING;
enum OBSTACLE currentobstacle = GROUND;

/* Functions to be bound to Lua */
int Duck(lua_State *L)
{
    playerstate = DUCKING;
    return 0; /* no return values to Lua */
}

int Run(lua_State *L)
{
    playerstate = RUNNING;
    return 0;
}

int Jump(lua_State *L)
{
    playerstate = JUMPING;
    return 0;
}

/* Check if player can pass obstacle, offer feedback */
int CanPassObstacle()
{
    if ( (playerstate == RUNNING && currentobstacle == GROUND) )
    {
        printf("Successful run!\n");
        return SUCCESS;
    }
    if (playerstate == JUMPING && currentobstacle == FENCE)
    {
        printf("Successful jump!\n");
        return SUCCESS;
    }
    if (playerstate == DUCKING && currentobstacle == BRANCH)
    {
        printf("Successful duck!\n");
        return SUCCESS;
    }
    printf("Wrong move!\n");
    return FAIL;
}

/* Pick a random obstacle */
enum OBSTACLE GetNewObstacle()
{
    int i = rand() % 3;
    if (i == 0) { return GROUND; }
    if (i == 1) { return FENCE; }
    else { return BRANCH; }
}

/* Execute appropriate function defined in Lua for the next obstacle */
int HandleObstacle(lua_State *L)
{
    /* Get the table named Actions */
    lua_getglobal(L, "Actions");
    if (!lua_istable(L, -1)) {return FAIL;}
    currentobstacle = GetNewObstacle();

    /* Decide which user function to call */
    if (currentobstacle == GROUND)
    {
        lua_getfield(L, -1, "Ground");
    }
    else if (currentobstacle == FENCE)
    {
        lua_getfield(L, -1, "Fence");
    }
    else if (currentobstacle == BRANCH)
    {
        lua_getfield(L, -1, "Branch");
    }

    if (lua_isfunction(L, -1))
    {
        lua_call(L, 0, 0); /* 0 args, 0 results */
        return CanPassObstacle();
    }
    return FAIL;
}

int main()
{
    int i, res;
    srand(time(NULL));
    lua_State *L = lua_open();

    /* Bind the C functions to Lua functions */
    lua_pushcfunction(L, &Duck);
    lua_setglobal(L, "Duck");

    lua_pushcfunction(L, &Run);
    lua_setglobal(L, "Run");

    lua_pushcfunction(L, &Jump);
    lua_setglobal(L, "Jump");

    /* execute script from stdin */
    res = luaL_dofile(L, NULL); 
    if (res)
    {
        printf("Lua script error: %s\n", lua_tostring(L, -1));
        return 1;
    }

    for (i = 0 ; i < 5 ; i++)
    {
        if (HandleObstacle(L) == FAIL)
        {
            printf("You failed!\n");
            return 0;
        }
    }

    printf("You passed!\n");

    return 0;
}

使用 gcc runner.c -o runner -llua5.1 -I/usr/include/lua5.1 在 GCC 上构建上述内容。

几乎每次都能成功通过的 Lua 脚本是:

Actions = {}

function Actions.Ground() Run() end
function Actions.Fence() Jump() end
function Actions.Branch() Duck() end

也可以写成

Actions = {}
Actions.Ground = Run
Actions.Fence = Jump
Actions.Branch = Duck

使用好的脚本,您将看到如下输出:

Successful duck!
Successful run!
Successful jump!
Successful jump!
Successful duck!
You passed!

如果用户尝试恶意操作,程序将简单地提供一个错误

$ echo "Actions = {} function Actions.Ground() os.execute('rm -rf /') end" | ./runner 
PANIC: unprotected error in call to Lua API (stdin:1: attempt to index global 'os' (a nil value))

:错误的移动脚本,用户将看到他执行了错误的移动:

$ echo "Actions = {} Actions.Ground = Jump; Actions.Fence = Duck; Actions.Branch = Run" | ./runner 
Wrong move!
You failed!

Your situation sounds like a good candidate for Lua.

  • You need sandboxing: This is easy to do in Lua. You simply initialize the users' environment by overwriting or deleting the os.execute command, for instance, and there is no way for the user to access that function anymore.
  • You want fast: Check out some of the Lua benchmarks against other languages.
  • Assumably you need to interoperate with another language. Lua is very easy (IMO) to embed in C or C++, at least. I haven't used LuaInterface, but that's the C# binding.
  • Lua has first-order functions, so it should be easy to swap functions on-the-fly.
  • Lua supports OOP to some extent with metatables.
  • Lua's primary data structure is the table (associative array) which is well-suited to sparse data structures like integrating with a world map.
  • Lua has a very regular syntax. There are no funny tricks with semicolons or indentation, so that's one less thing for your users to learn when they are picking up your language -- not to mention, using a well-documented language takes away some of the work you have to do in terms of documenting it yourself.

Also, as @elviejo points out in a comment, Lua is already used as a scripting language in many games. If nothing else, there's certainly some precedent for using Lua in the way you've described. And, as @gmonc mentions, there is a chance that your users have already used Lua in another game.


As far as how to integrate with Lua: generally, your users should simply need to upload a Lua script file. To grossly oversimplify, you might provide the users with available functions such as TurnLeft, TurnRight, Go, and Stop. Then, the users would upload a script like

Actions = {} -- empty table, but you might want to provide default functions 
function Actions.Cone()
    TurnLeft()
end

function Actions.Wall()
    Stop()
    TurnRight()
    TurnRight()
    Go()
end

Then server-side, you would might start them off with a Go(). Then, when their car reaches a cone, you call their Actions.Cone() function; a wall leads to the Actions.Wall() function, etc. At this point, you've (hopefully) already sandboxed the Lua environment, so you can simply execute their script without even much regard for error checking -- if their script results in an error, no reason you can't pass the error on directly to the user. And if there aren't any errors, the lua_State in your server's code should contain the final state of their car.


Better example

Here's a standalone C file that takes a Lua script from stdin and runs it like I explained above. The game is that you'll encounter Ground, a Fence, or a Branch, and you have to respectively Run, Jump, or Duck to pass. You input a Lua script via stdin to decide how to react. The source is a little long, but hopefully it's easy to understand (besides the Lua API which takes a while to get used to). This is my original creation over the past 30 minutes, hope it helps:

#include <stdio.h>
#include <stdlib.h>
#include <time.h>
#include "lua.h"
#include "lauxlib.h"
#include "lualib.h"

#define FAIL 0
#define SUCCESS 1

/* Possible states for the player */
enum STATE {
    RUNNING,
    JUMPING,
    DUCKING
};

/* Possible obstacles */
enum OBSTACLE {
    GROUND,
    FENCE,
    BRANCH
};

/* Using global vars here for brevity */
enum STATE playerstate = RUNNING;
enum OBSTACLE currentobstacle = GROUND;

/* Functions to be bound to Lua */
int Duck(lua_State *L)
{
    playerstate = DUCKING;
    return 0; /* no return values to Lua */
}

int Run(lua_State *L)
{
    playerstate = RUNNING;
    return 0;
}

int Jump(lua_State *L)
{
    playerstate = JUMPING;
    return 0;
}

/* Check if player can pass obstacle, offer feedback */
int CanPassObstacle()
{
    if ( (playerstate == RUNNING && currentobstacle == GROUND) )
    {
        printf("Successful run!\n");
        return SUCCESS;
    }
    if (playerstate == JUMPING && currentobstacle == FENCE)
    {
        printf("Successful jump!\n");
        return SUCCESS;
    }
    if (playerstate == DUCKING && currentobstacle == BRANCH)
    {
        printf("Successful duck!\n");
        return SUCCESS;
    }
    printf("Wrong move!\n");
    return FAIL;
}

/* Pick a random obstacle */
enum OBSTACLE GetNewObstacle()
{
    int i = rand() % 3;
    if (i == 0) { return GROUND; }
    if (i == 1) { return FENCE; }
    else { return BRANCH; }
}

/* Execute appropriate function defined in Lua for the next obstacle */
int HandleObstacle(lua_State *L)
{
    /* Get the table named Actions */
    lua_getglobal(L, "Actions");
    if (!lua_istable(L, -1)) {return FAIL;}
    currentobstacle = GetNewObstacle();

    /* Decide which user function to call */
    if (currentobstacle == GROUND)
    {
        lua_getfield(L, -1, "Ground");
    }
    else if (currentobstacle == FENCE)
    {
        lua_getfield(L, -1, "Fence");
    }
    else if (currentobstacle == BRANCH)
    {
        lua_getfield(L, -1, "Branch");
    }

    if (lua_isfunction(L, -1))
    {
        lua_call(L, 0, 0); /* 0 args, 0 results */
        return CanPassObstacle();
    }
    return FAIL;
}

int main()
{
    int i, res;
    srand(time(NULL));
    lua_State *L = lua_open();

    /* Bind the C functions to Lua functions */
    lua_pushcfunction(L, &Duck);
    lua_setglobal(L, "Duck");

    lua_pushcfunction(L, &Run);
    lua_setglobal(L, "Run");

    lua_pushcfunction(L, &Jump);
    lua_setglobal(L, "Jump");

    /* execute script from stdin */
    res = luaL_dofile(L, NULL); 
    if (res)
    {
        printf("Lua script error: %s\n", lua_tostring(L, -1));
        return 1;
    }

    for (i = 0 ; i < 5 ; i++)
    {
        if (HandleObstacle(L) == FAIL)
        {
            printf("You failed!\n");
            return 0;
        }
    }

    printf("You passed!\n");

    return 0;
}

Build the above on GCC with gcc runner.c -o runner -llua5.1 -I/usr/include/lua5.1.

And pretty much the only Lua script that will pass successfully every time is:

Actions = {}

function Actions.Ground() Run() end
function Actions.Fence() Jump() end
function Actions.Branch() Duck() end

which could also be written as

Actions = {}
Actions.Ground = Run
Actions.Fence = Jump
Actions.Branch = Duck

With the good script, you'll see output like:

Successful duck!
Successful run!
Successful jump!
Successful jump!
Successful duck!
You passed!

If the user tries something malicious, the program will simply provide an error:

$ echo "Actions = {} function Actions.Ground() os.execute('rm -rf /') end" | ./runner 
PANIC: unprotected error in call to Lua API (stdin:1: attempt to index global 'os' (a nil value))

With an incorrect move script, the user will see that he performed the wrong move:

$ echo "Actions = {} Actions.Ground = Jump; Actions.Fence = Duck; Actions.Branch = Run" | ./runner 
Wrong move!
You failed!
始终不够爱げ你 2024-08-15 00:52:35

为什么不使用 JavaScript 或 EcmaScript? Google 的 V8 是一种非常好的沙盒方式来做到这一点。我记得这真的非常非常容易。当然,您必须为其编写一些绑定。

Why not JavaScript or EcmaScript? Google's V8 is a really nice sandboxed way to do this. I remember it being really really easy. Of course, you will have to write some bindings for it.

夜雨飘雪 2024-08-15 00:52:35

我推荐 Dot Net 有几个原因:

玩家可以选择用哪种语言来实现他们的解决方案:C#、IronPython、VB.NET、Boo 等,但您的运行时不会关心 - 它只是将 dot Net 程序集动态加载到其解决方案中沙箱。但这使您的玩家可以选择自己喜欢的语言。这会鼓励玩家享受体验,而不是一些玩家因为不喜欢您选择的单一语言而决定不参与。您的整体框架可能采用 C#,但玩家的代码可以采用任何 Dot Net 语言。

沙盒和动态加载在 Dot Net 中已经非常成熟。您可以将玩家的程序集加载到您自己的以部分信任方式运行的沙箱 AppDomain 中。您无需重新启动容器进程即可加载和卸载这些播放器 AppDomain。

我们鼓励玩家“玩”这个游戏,因为该语言(无论他们选择哪种 Dot Net 语言)不仅对游戏脚本有用,而且可以在该行业带来真正的职业生涯。例如,搜索“C#”的职位搜索结果比“Lua”或“Haskell”的搜索结果要多很多。因此,这款游戏不仅有趣,而且特别是对于年轻玩家来说,实际上可以帮助他们学习真正有用的、适销对路的技能,这些技能可以让他们以后赚钱。这对参加这个游戏是一个很大的鼓励。

执行速度非常快。与 Lua 等替代方案不同,这是一种编译代码,即使在实时游戏中也以出色的性能而闻名。 (例如,请参见 Unity3d)。

玩家可以在 Mac 和 Linux 上使用 MonoDevelop,也可以使用 Microsoft 免费的 Visual Studio Express,或者使用优秀的记事本和命令行。与此处替代方案的区别在于,如果玩家选择使用成熟、现代的 IDE,则可以使用它们。

对于问题的人工智能部分来说,DSL 似乎不是一个好主意,因为为车辆实现人工智能需要玩家创造性地解决问题。使用 DSL,您将它们锁定为您在思考问题时定义问题的方式。拥有像 Dot Net(或提到的其他一些选择)这样的完整平台的聪明玩家可能会对一些您从未预见到的人工智能问题拥有全新的创新解决方案。有了正确的工具,这些玩家可以实施疯狂的学习程序或小型神经网络或谁知道什么来实施他们的人工智能。但如果你将它们锁定在简化的 DSL 中,不同玩家的 AI 实现可能不会有太多变化(因为他们可用的想法表达集要小得多)。

对于问题的其他部分(例如定义轨道),DSL 可能没问题。不过,我还是倾向于使用一种更简单的 Dot Net 语言,例如 Boo,这样您就可以为整个项目提供统一的技术堆栈。

I would recommend Dot Net for several reasons:

Players can choose which language they implement their solutions in: C#, IronPython, VB.NET, Boo, etc. but your runtime wouldn't care - it is just dynamically loading dot net assemblies into its sandbox. But this gives your players a choice of their own favorite language. This encourages players to enjoy the experience, rather than some players deciding not to participate because they simply don't like the single language that you chose. Your overall framework would probably be in C#, but players' code could be in any Dot Net language.

Sandboxing and dynamically loading are very mature in Dot Net. You could load the players' assemblies into your own sandboxed AppDomains that are running with Partial Trust. You would not have to restart the container process to load and unload these player AppDomains.

Players are encouraged to "play" this game because the language (whichever Dot Net language they choose) is not only useful for game scripting, but can lead to a real career in the industry. Doing a job search for "C#" gives a lot more hits than for "Lua" or "Haskell", for example. Therefore, the game is not only fun, but especially for younger players, is actually helping them to learn genuinely useful, marketable skills that can earn them money later. That is big encouragement to participate in this game.

Execution speed is blazing. Unlike some alternatives like Lua, this is compiled code that is well known for excellent performance, even in real-time games. (See Unity3d, for example).

Players can use MonoDevelop on Mac and Linux or they can use Visual Studio Express for free from Microsoft, or they can use good ol' notepad and the command line. The difference from the alternatives here is that mature, modern IDE's are available if players should choose to use them.

A DSL doesn't seem like a good idea for the AI parts of the problem simply because implementing AI for the vehicles is going to require a lot of creative problem solving on the part of the players. With a DSL, you are locking them into only the way that you defined the problem when you thought about it. Smart players with a complete platform like Dot Net (or some of the other choices mentioned) might have radically new and innovative solutions to some of the AI problems that you never foresaw. With the right tools, these players could implement crazy learning programs or small neural networks or who knows what in order to implement their AI. But if you lock them into a simplified DSL, there might not be much variety in different players' AI implementations (because their set of available expressions of ideas is so much smaller).

For the other parts of the problem such as defining the tracks, a DSL might be fine. Again, though, I would lean toward one of the simpler Dot Net languages like Boo simply so that you can have a unified tech stack for your entire project.

像你 2024-08-15 00:52:35

我之前在MMO中​​做过,你知道,NPC响应脚本使用的是python,而它是在C++框架中,也就是说任何与NPC相关的动作都会触发框架运行python脚本(当然是C-python接口,不是shell 调用,例如“python /script/run.py”)。这些脚本是可替换的运行时,虽然需要玩家或游戏管理员发出命令进行刷新,但无论如何游戏服务器程序不需要重新启动。

实际上我忘记了新脚本运行时是否需要“通过发出命令进行刷新”..2年前...但我认为它适合你。

I had done in MMO before, you know, NPC response scripts were using python, while it is in a framework of C++, say any NPC related action will trigger the framework to run a python script (a C-python interface of course, not a shell call such as "python /script/run.py"). The scripts are replaceable runtime, though need the player or game admin to issue a command to do a refresh, but anyway the game server program is not required to restart.

Actually I forgot that whether "do a refresh by issuing a command" was required or not for a new script runtime..2 years before...but I think it suitable for you.

疯狂的代价 2024-08-15 00:52:35

考虑 Erlang:

  • 您需要沙箱/DSL:您可以编写“解析器生成器”来清除对关键/易受攻击的系统调用的访问。使用此功能可以“轻松”增强库存编译器。

  • 您需要细粒度的调度:您对此有一些控制还提供您在单独的模拟器中运行每个“用户”。也许你可以做得更好,但我必须挖掘更多。请记住,调度是 O(1) :-)

  • 您需要在“玩家”之间进行资源分区(我想如果我理解正确的话):Erlang 没有共享状态,因此这从一开始就有帮助。您可以轻松地制作一些监督者来观察玩家的资源消耗等。另请参阅上面的链接(有很多旋钮来控制模拟器)。

  • 您需要代码热插拔:Erlang 从一开始就是为此设计的

  • 您需要扩展:Erlang 可以扩展可以很好地使用 SMP,并且因为它基于具有无缝机器间通信的消息传递,因此您可以水平扩展

  • 您可以使用 C 驱动程序优化关键路径

  • 集成的“主管”功能,用于优雅地重新启动“用户”

Ulf Wiger 谈并发

Consider Erlang:

  • You need sandboxing / DSL: you can write "parser generators" to scrub access to critical/vulnerable system calls. The stock compiler can be "easily" enhanced with this functionality.

  • You need fine-grained scheduling : you have some control over this also provided you run each "user" in separate emulators. Maybe you can do better but I'd have to dig more. Remember the scheduling is O(1) :-)

  • You need resource partitioning between your "players" ( I guess if I understood correctly): Erlang has no shared-state so this helps from the on-start. You can easily craft some supervisors that watch resource consumption of the players etc. See also link on above point (lots of knobs to control the emulator).

  • You need code hot-swapping: Erlang was designed for this from the on-start

  • You need scaling: Erlang scales with SMP nicely and since it is based on message passing with seamless inter-machine communication, you can scale horizontally

  • You can optimize the critical paths using C drivers

  • Integrated "supervisor" functionality for restarting gracefully "users"

Ulf Wiger on Concurrency

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文