cocos2d-x C++与Lua交互

Cocos版本: 3.10

Lua版本: 5.1.4

环境: window Visual Studio 2013


Lua


Lua作为一种脚本语言, 它的运行需要有宿主的存在,通过Lua虚拟栈进行数据交互。

它的底层实现是C语言,C语言封装了很多的API接口,使得C/C++与Lua之间可以很方便的通信交互。

Lua的官网: https://www.lua.org/

在cocos2dx中, Lua与C++的交互是通过**tolua++**进行的,**tolua++**实质上是对Lua C API的一层封装。

通过tolua++ 设定的接口,使得Lua很方便的调用C++提供的 cocos API接口。


Lua的运行需要有宿主的存在,在cocos2d-x中,C++可以作为Lua的宿主, 而Lua运行的时候需要通过虚拟栈进行数据交互,这个运行的环境,通常被称为Lua_State

Lua虚拟栈

栈的特点是先进后出的, 在Lua的虚拟栈中它有着如下的特点:

  • 栈中数据通过索引进行获取数据
  • 索引的数值可以为正数,也可为负数; 正数为1的永远表示栈底,负数为-1的永远表示栈顶

如下图所示(图片来源:Lua调用原理展示):

pic_1)

假设我们想使用C++访问Lua文件中的数据

-- test.lua
str = "Get Lua Data Sucess!!!"function Add(num1, num2)return num1 + num2
end 

C++获取Lua文件中的字符串为例,其数据交互的流程是:

  1. C/C++将参数str放入Lua栈的栈顶

  2. Lua从栈中获取参数str,并将栈顶置为空

  3. Lua从全局表中查找参数str对应的数据

  4. 全局表将参数str的数据反馈给Lua

  5. Lua将参数str的返回值放入堆栈中,此时返回值位于栈顶

  6. C++从栈中获取数据


C++调用Lua需要进行环境配置:

1. 新建项目,选择Empty Project,在项目的Source Files新增.cpp文件
2. 若有Lua的相关环境,可将Lua/5.1目录下的include,lib文件夹拷贝到与.cpp文件同目录下
若无,则推荐LuaForWindows
其网址为:http://files.luaforge.net/releases/luaforwindows/luaforwindows
它会自动配置lua的环境,并安装SciTE工具相关,以后就可以在控制台,SciTE输入lua相关代码进行调试属性配置,打开项目属性:
1. C/C++ -> General -> Additional Include Directories 将include目录添加进去
2. Linker -> General -> Additional Library Directories 将lib目录添加进去
3. 再通过Linker -> Input -> Additional Dependencies 添加lua5.1.lib, lua51.lib

示例代码:

#include <iostream>
#include <string.h>extern "C" {
// 提供了Lua的基本函数,在lua.h中的函数均已"lua_"为前缀
#include "lua.h"
// 定义lua的标准库函数,比如table, io, math等
#include "lualib.h"
// 提供了辅助库相关,以"luaL_"为前缀
#include "lauxlib.h"    
}void main(){// 创建lua环境,并加载标准库lua_State* pL = lua_open();luaL_openlibs(pL);// 加载lua文件,返回0表示成功int code = luaL_loadfile(pL, "test.lua");if (code != 0){return;}// 执行lua文件,参数分别为,lua环境,输入参数个数,返回值个数lua_call(pL, 0, 0);// 重置栈顶索引,设置为0表示栈清空lua_settop(pL, 0);// ------------- 读取变量 -------------//lua_getglobal 主要做了这么几件事: 将参数压入栈中,lua获取参数的值后再将返回的结果压入栈中lua_getglobal(pL, "str");// 判定栈顶值类型是否为string,返回1表示成功,0表示失败int isStr = lua_isstring(pL, 1);if (isStr == 1) {// 获取栈顶值,并将lua值转换为C++类型std::string str = lua_tostring(pL, 1);std::cout << "str = " << str.c_str() << std::endl;}// ------------- 读取函数 -------------lua_getglobal(pL, "Add");// 将函数所需要的参数入栈lua_pushnumber(pL, 1);            // 压入第一个参数lua_pushnumber(pL, 2);            // 压入第二个参数/*lua_pcall与lua_call类似,均用于执行lua文件,其方法分别为:void lua_call(lua_State *L, int nargs, int nresults);int lua_pcall(lua_State *L, int nargs, int nresults, int errfunc);两者的区别在于:前者在出现错误,程序会崩溃。后者多了一个errfunc索引,用于准确定位错误处理函数。函数执行成功返回0,失败后可通过获取栈顶信息获取错误数据两者的共同之处在于:会根据nargs将参数按次序入栈,并根据nresults将返回值按次序填入栈中若返回值结果数目大于nresults时,多余的将被丢弃;若小于nresults时,则按照nil补齐。*/int result = lua_pcall(pL, 2, 1, 0);if (result != 0) {const char *pErrorMsg = lua_tostring(pL, -1);std::cout << "ERROR:" << pErrorMsg << std::endl;lua_close(pL);return;}/*此处的栈中情况:------------- 栈顶 -------------正索引 负索引   类型       返回值2     -1      number    31     -2      string    "Get Lua Data Sucess!!!"------------- 栈底 -------------因此如下的索引获取数字索引可以使用-1或者2*/int isNum = lua_isnumber(pL, -1);if (isNum == 1) {double num = lua_tonumber(pL, -1);std::cout << "num = " << num << std::endl;}// 关闭state环境,即销毁Lua_State对象,并释放Lua动态分配的空间lua_close(pL);system("pause");
}

注意:

  • C++在获取不同文件下的方法时,在通过include引用后,然后就直接调用
  • Lua通过luaL_loadfile进行加载,然后等 lua_call/lua_pcall执行后才能获取数据

Lua这么处理的原因在于: 全局变量表中是不会存储相关数据的


Lua C API


这里简要说明下常用的API相关,有助于对了解后面的**tolua++**有帮助。 主要方法有:

/*
获取栈顶索引即栈中元素的个数,因为栈底为1,所以栈顶索引为多少,就代表有多少个元素
*/
int lua_gettop(lua_State *L);/*
将栈顶索引设置为指定的数值
若设置的index比原栈顶高,则以nil补足。若index比原栈顶低,高出的部分舍弃。
比如: 栈中有8个元素,若index为7,则表示删除了一个栈顶的元素。若index为0,表示清空栈
注意: index为正数表示相对于栈底设置的,若为负数则相对于栈顶而设置的
*/
void lua_settop(lua_State *L, int index);/*
将栈中索引元素的副本压入栈顶
比如:从栈底到栈顶,元素状态为10,20,30,40;若索引为3则元素状态为:10,20,30,40,30
类似的还有:
lua_pushnil: 压入一个nil值
lua_pushboolean: 压入一个bool值
lua_pushnumber: 压入一个number值
*/
void lua_pushvalue(lua_State *L, int index);/*
删除指定索引元素,并将该索引之上的元素填补空缺
比如:从栈底到栈顶,元素状态为10,20,30,40;若索引为-3则元素状态为10,30,40
*/
void lua_remove(lua_State *L, int index);/*
将栈顶元素替换索引位置的的元素
比如:从栈底到栈顶,元素状态为10,20,30,40,50;若索引为2则,元素状态为10,50,30,40
即索引为2的元素20被栈顶元素50替换
*/
void lua_replace(lua_State *L, int index);/*
获取栈中指定索引元素的类型,若失败返回类型LUA_TNONE。其它类型有:
LUA_TBOOLEAN, LUA_TNUMBER, LUA_TSTRING, LUA_TTABLE
LUA_TFUNCTION, LUA_USERDATA等
*/
int lua_type(lua_State *L, int idx);/*
检测栈中元素是否为某个类型,成功返回1,失败返回0; 类似的还有:
lua_isnumber, lua_isstring, lua_iscfunction, lua_isuserdata
*/
int lua_isXXX(lua_State *L, int index);// 将栈中元素转换为C语言指定类型
lua_Number lua_tonumber(lua_State *L, int idx);
lua_Integer lua_tointeger(lua_State *L, int idx);
int lua_toboolean(lua_State *L, int idx);
const char* lua_tolstring(lua_State *L, int idx, size_t *len);
lua_CFunction lua_tocfunction(lua_State *L, int idx);
void* lua_touserdata(lua_State *L, int idx);

下面我们说下cocos2d-x 对Lua的封装相关。


cocos Lua框架

Lua在cocos引擎封装相关,它主要被放在cocos引擎的libluacocos2d

  • auto: 使用tolua++工具自动生成的C++代码相关

  • **manual:**放置了cocos扩展的一些功能,比如LuaEngine, LuaStack, LuaBridge等

  • luajit: 高效版的lua库,额外添加了lua没有的cocos库,并在对浮点计算,循环等进行了优化

  • luasocket: 网络库相关

  • tolua: tolua++库相关,实质是对Lua C库进行的再封装

  • xxtea: cocos2d-x 自带的加密相关,目前项目使用的较少,很容易被破解

Lua的在引擎中的封装,主要是:

  • LuaEngine 封装的对Lua的管理引擎类
  • Lua_Stack 对Lua运行环境Lua_State的封装,LuaEngine主要管理的就是Lua_Stack

项目启动

关于LuaEngine的初始化,主要在项目启动的时候初始化的,它仅仅是项目启动的一个小的环节。

如果想了解引擎的整个启动流程,可参考博客:cocos2dx 的启动和结束流程

而对于LuaEngine的启动,可以简单的分为三个步骤:

  1. 初始化LuaEngine,通过 LuaStack 获取 LuaState 运行环境
  2. 通过**tolua++**提供的接口,将C++ 不同的模块进行注册
  3. 执行Lua脚本

其主要代码实现在:

bool AppDelegate::applicationDidFinishLaunching() {// 初始化LuaEngine// 在getInstance中会初始化LuaStack,LuaStack初始化Lua环境相关auto engine = LuaEngine::getInstance();// 将LuaEngine添加到脚本引擎管理器ScriptEngineManager中ScriptEngineManager::getInstance()->setScriptEngine(engine);// 获取Lua环境lua_State* L = engine->getLuaStack()->getLuaState();// 注册额外的=C++提供的API模块相关lua_module_register(L);register_all_packages();// 设置cocos自带的加密相关LuaStack* stack = engine->getLuaStack();stack->setXXTEAKeyAndSign("2dxLua", strlen("2dxLua"), "XXTEA", strlen("XXTEA"));// 执行Lua脚本文件if (engine->executeScriptFile("main.lua")) {return false;}return true;
}

LuaEngine初始化

通过LuaEngine::getInstance(),我们了解下**LuaStack::init()**的相关实现

extern "C" {
#include "lua.h"             
#include "tolua++.h"    
#include "lualib.h"         
#include "lauxlib.h"
}bool LuaStack::init(void)
{// 初始化Lua环境并打开标准库_state = lua_open();     luaL_openlibs(_state);toluafix_open(_state);// 注册全局函数print到lua中,它会覆盖lua库中的print方法const luaL_reg global_functions [] = {{"print", lua_print},{"release_print",lua_release_print},{nullptr, nullptr}};// 注册全局变量luaL_register(_state, "_G", global_functions);// 注册cocos2d-x引擎的API到lua环境中g_luaType.clear();register_all_cocos2dx(_state);// ...#if (CC_TARGET_PLATFORM == CC_PLATFORM_ANDROID)// 导入android下调用java相关APILuaJavaBridge::luaopen_luaj(_state);
#endifaddLuaLoader(cocos2dx_lua_loader);return true;
} 

针对于addLuaLoader,它是Lua的加载器,主要将 cocos2dx_lua_loader 方法添加到Lua全局变量package下的loaders成员中。

当Lua通过 requires加载脚本时,Lua会借助package下的loaders中的加载器 cocos2dx_lua_loader 加载。

该加载器支持我们自定义设置搜索路径相关,且拓展实现对脚本的加密解密相关。看下它的实现:

extern "C" {int cocos2dx_lua_loader(lua_State *L) {// 后缀为luac和luastatic const std::string BYTECODE_FILE_EXT    = ".luac";static const std::string NOT_BYTECODE_FILE_EXT = ".lua";// require传入的要加载的文件名,比如:require "cocos.init" 下的"cocos.init"std::string filename(luaL_checkstring(L, 1));// 去掉后缀名".luac"或“.lua”size_t pos = filename.rfind(BYTECODE_FILE_EXT);if (pos != std::string::npos) {filename = filename.substr(0, pos);} else { pos = filename.rfind(NOT_BYTECODE_FILE_EXT);if (pos == filename.length() - NOT_BYTECODE_FILE_EXT.length()) {filename = filename.substr(0, pos);}}// 将"."替换为"/"pos = filename.find_first_of(".");while (pos != std::string::npos) {filename.replace(pos, 1, "/");pos = filename.find_first_of(".");}Data chunk;std::string chunkName;FileUtils* utils = FileUtils::getInstance();// 获取package.path的变量lua_getglobal(L, "package");lua_getfield(L, -1, "path");// 通过package.path获取搜索路径相关,该路径为模版路径,格式类似于:// ?; ?.lua; c:\windows\?;  /usr/local/lua/lua/?/?.lua 以“;”作为分割符std::string searchpath(lua_tostring(L, -1));lua_pop(L, 1);size_t begin = 0;size_t next = searchpath.find_first_of(";", 0);// 遍历package.path中的所有路径,查找文件是否存在,存在则通过getDataFromFile读取数据do {if (next == std::string::npos)next = searchpath.length();std::string prefix = searchpath.substr(begin, next);if (prefix[0] == '.' && prefix[1] == '/') {prefix = prefix.substr(2);}pos = prefix.find("?.lua");// 将?替换为文件名,获取搜索路径名,比如:?.lua替换为cocos/init.luachunkName = prefix.substr(0, pos) + filename + BYTECODE_FILE_EXT;if (utils->isFileExist(chunkName)) {chunk = utils->getDataFromFile(chunkName);break;} else {chunkName = prefix.substr(0, pos) + filename + NOT_BYTECODE_FILE_EXT;if (utils->isFileExist(chunkName)) {chunk = utils->getDataFromFile(chunkName);break;}}// 指定搜素路径下不存在该文件,则下一个begin = next + 1;next = searchpath.find_first_of(";", begin);} while (begin < (int)searchpath.length());// 判定文件内容是否获取成功if (chunk.getSize() > 0) {// 加载文件LuaStack* stack = LuaEngine::getInstance()->getLuaStack();stack->luaLoadBuffer(L, reinterpret_cast<const char*>(chunk.getBytes()),static_cast<int>(chunk.getSize()), chunkName.c_str());} else {CCLOG("can not get file data of %s", chunkName.c_str());return 0;}return 1;}
}    

这个的实现其实就是Lua语言关于require实现的本质,比如:

  • Cocos2dx 是如何实现搜索指定的Lua文件的
  • Lua通过require是如何检索引用文件的
  • 关于Lua文件查找不到,报错的路径信息是如何获取的

想了解更多内容,参考:lua 之 require

继续代码研究,看下luaLoadBuffer的实现:

nt LuaStack::luaLoadBuffer(lua_State *L, const char *chunk, int chunkSize, const char *chunkName) {int r = 0;// 判定是否加密,若lua脚本加密,则解密后在加载脚本文件// luaL_loadbuffer 用于加载并编译Lua代码,并将其压入栈中if (_xxteaEnabled && strncmp(chunk, _xxteaSign, _xxteaSignLen) == 0) {// decrypt XXTEAxxtea_long len = 0;unsigned char* result = xxtea_decrypt((unsigned char*)chunk + _xxteaSignLen,(xxtea_long)chunkSize - _xxteaSignLen,(unsigned char*)_xxteaKey,(xxtea_long)_xxteaKeyLen,&len);skipBOM((const char*&)result, (int&)len);r = luaL_loadbuffer(L, (char*)result, len, chunkName);free(result);} else {skipBOM(chunk, chunkSize);r = luaL_loadbuffer(L, chunk, chunkSize, chunkName);}// 判定内容是否存在错误#if defined(COCOS2D_DEBUG) && COCOS2D_DEBUG > 0if (r) {switch (r) {case LUA_ERRSYNTAX: // 语法错误CCLOG("[LUA ERROR] load \"%s\", error: syntax error during pre-compilation.", chunkName);break;case LUA_ERRMEM:// 内存分配错误CCLOG("[LUA ERROR] load \"%s\", error: memory allocation error.", chunkName);break;case LUA_ERRRUN: // 运行错误CCLOG("[LUA ERROR] load \"%s\", error: run error.", chunkName);break;case LUA_ERRFILE:// 文件错误CCLOG("[LUA ERROR] load \"%s\", error: cannot open/read file.", chunkName);break;case LUA_ERRERR:           // 运行错误处理函数时发生错误CCLOG("[LUA ERROR] load \"%s\", while running the error handler function.", chunkName);default:// 未知错误CCLOG("[LUA ERROR] load \"%s\", error: unknown.", chunkName);}// 通过lua的堆栈,获取栈顶的错误信息,将错误日志打印出来(-1永远表示栈顶)const char* error = lua_tostring(L, -1);CCLOG("[LUA ERROR] Error Result: %s", error);lua_pop(L, 1);}#endifreturn r;
}

这里对Lua做的事情主要是:

  • 检测Lua是否加密,如果是,则进行解密; 否则加载并编译Lua代码
  • 如果是测试版本,会对Lua的内容进行安全检测。

最后我们梳理下关于LuaEngine对Lua的操作流程:

  • 获取LuaEngine的接口,调用LuaStack对Lua所需要的环境进行初始化
  • 通过addLuaLoader将Lua的变量等信息添加到加载器中进行解析
  • 解析文件后,通过loadBuffer加载Lua文件数据并进行安全检测。

到这里Lua的文件数据相关算是初始化成功了。


tolua++

**tolua++**是cocos官方提供的一个将C++代码相关转换为指定格式文件的工具,用于实现Lua对C++的调用, 简单的看下引擎在项目启动中关于tolua++封装的接口相关:

TOLUA_API int register_all_cocos2dx(lua_State* tolua_S)
{tolua_open(tolua_S);tolua_module(tolua_S,"cc",0);tolua_beginmodule(tolua_S,"cc");lua_register_cocos2dx_Ref(tolua_S);lua_register_cocos2dx_Node(tolua_S);// 省略...tolua_endmodule(tolua_S);return 1;
}int lua_register_cocos2dx_Ref(lua_State* tolua_S)
{tolua_usertype(tolua_S,"cc.Ref");tolua_cclass(tolua_S,"Ref","cc.Ref","",nullptr);// tolua_function 表示对应的Ref所持有的public接口相关tolua_beginmodule(tolua_S,"Ref");tolua_function(tolua_S,"release",lua_cocos2dx_Ref_release);tolua_function(tolua_S,"retain",lua_cocos2dx_Ref_retain);tolua_function(tolua_S,"getReferenceCount",lua_cocos2dx_Ref_getReferenceCount);tolua_endmodule(tolua_S);std::string typeName = typeid(cocos2d::Ref).name();g_luaType[typeName] = "cc.Ref";g_typeCast["Ref"] = "cc.Ref";return 1;
}

**tolua++**的特点就是开头必带前缀: tolua_

  • tolua_usertype 用于声明一个自定义的类型,将其注册到Lua中
  • tolua_cclass 声明一个C++类,并将其注册到Lua中。参数中的第一个是Lua中的类名,第二个是C++类名,第三个是父类名,第四个是模板参数(可选),第五个是模板名(可选)
  • tolua_beginmodule/tolua_endmodule 用于定义一个模块,并将接口函数注册到该模块中
  • tolua_function 将一个C++类的成员函数或静态函数注册为Lua中的函数, 方便调用

任意看一段函数的实现:

int lua_cocos2dx_Ref_getReferenceCount(lua_State* tolua_S) {int argc = 0;cocos2d::Ref* cobj = nullptr;bool ok  = true;#if COCOS2D_DEBUG >= 1tolua_Error tolua_err;#endif// 从Lua栈中获取cocos对象类型,是否为cc.Ref#if COCOS2D_DEBUG >= 1if (!tolua_isusertype(tolua_S,1,"cc.Ref",0,&tolua_err)) goto tolua_lerror;#endif// 将数据转换为Ref对象,若失败则提示:无效的对象cobj = (cocos2d::Ref*)tolua_tousertype(tolua_S,1,0);#if COCOS2D_DEBUG >= 1if (!cobj) {tolua_error(tolua_S,"invalid 'cobj' in function 'lua_cocos2dx_Ref_getReferenceCount'", nullptr);return 0;}#endif// 获取参数数目,-1的原因在于对象类型Ref也在栈中argc = lua_gettop(tolua_S)-1;if (argc == 0) {if(!ok){tolua_error(tolua_S,"invalid arguments in function 'lua_cocos2dx_Ref_getReferenceCount'", nullptr);return 0;}unsigned int ret = cobj->getReferenceCount();tolua_pushnumber(tolua_S,(lua_Number)ret);return 1;}luaL_error(tolua_S, "%s has wrong number of arguments: %d, was expecting %d \n", "cc.Ref:getReferenceCount",argc, 0);return 0;
}

其他的cocos2d-x提供的Lua可调用方法不再赘述,与之类似。


结语

前面的内容将cocos2dx对Lua的封装,以及tolua++的使用,说了很多,主要原因在于:

  • 官方提供的一些接口,仅支持在C++中使用,不支持在Lua中使用,比如骨骼动画中的一些复杂逻辑处理
  • 项目的拓展需要有底层的支持
  • 项目如果将cocosStudio替换为FairyGUI,需要了解这些。

最后祝大家学习生活愉快!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/103373.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

【Qt上位机】打开本地表格文件并获取其中全部数据

前言 其实本文所实现的功能并非博主要实现的全部功能&#xff0c;只是全部功能中的一小部分&#xff0c;这里只是为了记录一下实现方法&#xff0c;防止后续忘记&#xff0c;仅供参考。 文章目录 一、实现效果二、UI设计三、程序设计3.1 选择本地表格文件3.2 获取表格总行列数3…

排序算法-冒泡排序法(BubbleSort)

排序算法-冒泡排序法&#xff08;BubbleSort&#xff09; 1、说明 冒泡排序法又称为交换排序法&#xff0c;是从观察水中的气泡变化构思而成的&#xff0c;原理是从第一个元素开始&#xff0c;比较相邻元素的大小&#xff0c;若大小顺序有误&#xff0c;则对调后再进行下一个…

安装Zookeeper

ZooKeeper是一个开源的分布式协调服务&#xff0c;它主要用于解决分布式系统中的一致性、可靠性和协调性等问题。 选择版本 去archive.apache.org/dist/zookeeper/&#xff0c;选择Zookeeper版本&#xff0c;我选择3.4.6 上传服务器 复制地址&#xff0c;通过wget下载 wget…

400电话申请办理:为企业提供高效沟通的必备工具

在当今竞争激烈的商业环境中&#xff0c;企业需要与客户保持紧密联系&#xff0c;提供高效沟通渠道。而400电话作为一种便捷的客服热线&#xff0c;成为越来越多企业的首选。本文将介绍400电话的申请办理过程&#xff0c;帮助企业了解如何获得这一重要的沟通工具。 首先&#…

MySQL常用脚本

&#x1f389;&#x1f389;欢迎来到我的CSDN主页&#xff01;&#x1f389;&#x1f389; &#x1f3c5;我是Java方文山&#xff0c;一个在CSDN分享笔记的博主。&#x1f4da;&#x1f4da; &#x1f31f;推荐给大家我的专栏《ELement》。&#x1f3af;&#x1f3af; &#x1…

史上最强,Jmeter性能测试-性能场景设计实例(详全)

目录&#xff1a;导读 前言一、Python编程入门到精通二、接口自动化项目实战三、Web自动化项目实战四、App自动化项目实战五、一线大厂简历六、测试开发DevOps体系七、常用自动化测试工具八、JMeter性能测试九、总结&#xff08;尾部小惊喜&#xff09; 前言 1、性能测试需求 …

微信小程序支持h5实现webrtc h264 h265低延迟传输渲染

微信小程序自成体系&#xff0c;自身也带了很强的rtc音视频能力&#xff0c;但是他捆绑了他自己的服务&#xff0c;开发也相对受限于他的api。基于以前的了解可以采webview的方式内嵌h5网址来实现自定义的webrtc.但实践起来并不轻松&#xff0c;主要是小程序的严格限制&#xf…

微信页面公众号页面 安全键盘收起后键盘下方页面留白

微信浏览器打开H5页面和公众号页面&#xff0c;输入密码时调起安全键盘&#xff0c;键盘收起后 键盘下方页面留白 解决办法&#xff1a; 1、&#xff08;简单&#xff09;只有在调起安全键盘&#xff08;输入密码&#xff09;的时候会出现这种情况&#xff0c;将input属性改为n…

Hadoop分布式集群搭建教程

目录 前言环境准备一、创建虚拟机二、虚拟机网络配置三、克隆虚拟机四、Linux系统配置五、Hadoop的部署配置六、Hadoop集群的启动Bug解决参考文章 前言 大数据课程需要搭建Hadoop分布式集群&#xff0c;在这里记录一下搭建过程 环境准备 搭建Haoop分布式集群所需环境&#x…

kafka属性说明

kafka中关于一些字段说明 groupId :标识消费者分组id&#xff0c;如果多个消费者id相同&#xff0c;就表示这几个消费者是一组&#xff0c;当一组多个消费者消费同一个topic时&#xff0c;一组中只会有一个成功消费 代码如下 这时只会有一条消息被消费

轻松搭建个人web站点:OpenWRT教程结合内网穿透技术实现公网远程访问

&#x1f525;博客主页&#xff1a; 小羊失眠啦 &#x1f516;系列专栏&#xff1a; C语言、Linux &#x1f325;️每日语录&#xff1a;山不让尘&#xff0c;川不辞盈。 ❤️感谢大家点赞&#x1f44d;收藏⭐评论✍️ 前言 uhttpd 是 OpenWrt/LuCI 开发者从零开始编写的 Web …

视频美颜SDK,提升企业视频通话质量与形象

在今天的数字时代&#xff0c;视频通话已经成为企业与客户、员工之间不可或缺的沟通方式。然而&#xff0c;由于网络环境、设备性能等因素的影响&#xff0c;视频通话中的画面质量往往难以达到预期效果。为了提升视频通话的质量与形象&#xff0c;美摄美颜SDK应运而生&#xff…

【C语言】预处理详解

前言 在上一篇博客中&#xff0c;我们了解了代码是如何执行的&#xff0c;简单介绍了编译中预处理步骤&#xff0c;在这篇博客中我们将详细了解预处理。 文章目录 一、预定义符号二、#define定义2.1 定义常量2.2 定义宏2.3 创建代码片段 三、#和##运算符3.1 字符串化操作符#3.2…

python关联分析实践学习笔记

曾经有个沃尔玛超市&#xff0c;它将啤酒与尿布这样两个奇怪的东西放在一起进行销售&#xff0c;并且最终让啤酒与尿布这两个看起来没有关联的东西的销量双双增加。 我们关注的是在这样的场景下&#xff0c;如何找出物品之间的关联规则。接下来就来介绍下如何使用Apriori算法&…

RabbitMQ与springboot整合

1、基本概念 Server&#xff1a;接收客户端的连接&#xff0c;实现AMQP实体服务&#xff1b;Connection&#xff1a;连接&#xff0c;应用程序与Server的网络连接&#xff0c;TCP连接&#xff1b;Channel&#xff1a;信道&#xff0c;消息读写等操作在信道中进行。客户端可以建…

读书笔记-《ON JAVA 中文版》-摘要26[第二十三章 注解]

文章目录 第二十三章 注解1. 基本语法1.1 基本语法1.2 定义注解1.3 元注解 2. 编写注解处理器2.1 编写注解处理器2.2 注解元素2.3 默认值限制 3. 使用javac处理注解4. 基于注解的单元测试5. 本章小结 第二十三章 注解 注解&#xff08;也被称为元数据&#xff09;为我们在代码…

【Overload游戏引擎细节分析】从视图投影矩阵提取视锥体及overload对视锥体的封装

overoad代码中包含一段有意思的代码&#xff0c;可以从视图投影矩阵逆推出摄像机的视锥体&#xff0c;本文来分析一下原理 一、平面的方程 视锥体是用平面来表示的&#xff0c;所以先看看平面的数学表达。 平面方程可以由其法线N&#xff08;A, B, C&#xff09;和一个点Q(x0,…

让 Visual Studio 用上 ChatGPT

一、简介 Visual chatGPT Studio 是 Visual Studio 的一个免费扩展&#xff0c;它直接在 IDE 中添加了 chatGPT 功能。它允许用户以可以根据菜单功能的方式使用 chatGPT。 二、功能介绍 该扩展提供了一组使用 ChatGPT 命令&#xff0c;可以在编辑器中选择你需要处理的代码或…

k8s修改集群IP--不重置集群

正常在用集群想要更换ip master 节点ip192.168.10.138 改为192.168.10.148 node1节点ip192.168.10.139 改为192.168.10.149 node2节点ip192.168.10.140 改为192.168.10.150 master 节点 1)执行脚本1233.sh 1233.sh 内容如下&#xff1a; # master 节点 export oldip1192.168.…

可完全替代FTP的文件传输工具大集合

在当今的信息时代&#xff0c;文件传输是我们日常工作和生活中不可或缺的一项功能。无论是企业内部还是与外部合作伙伴之间&#xff0c;我们都需要频繁地进行各种类型和大小的文件的交换和共享。然而&#xff0c;传统的文件传输方式&#xff0c;如FTP、HTTP、CIFS等&#xff0c…