Visual Studio Code
( VSCODE )是一款功能强大的代码编辑器,它以其高度可定制的界面、强大的扩展生态系统、流畅的性能表现以及对众多编程语言的天然支持而备受开发者青睐。在众多的开发场景中,VSCODE 都展现出了卓越的性能和灵活性。 Lua
作为一种轻量级的脚本语言,在游戏开发、移动应用开发等领域有着广泛的应用。然而,由于 Lua
是一门小众语言,相关的开发工具并不像主流语言那样完善。Android Lua Helper
插件的出现,为开发者提供了一系列强大的功能,极大地提高了 Lua
代码的开发效率和质量。 Android Lua Helper
插件具有多种功能,如符号定义跳转、代码格式化、符号查找、全局引用查找以及智能代码补全、语法错误检测、Lua
代码片段提示等。这些功能使得开发者在编写 Lua
代码时更加高效和准确。例如,代码补全功能可以大大减少开发者的输入时间,提高开发速度;语法错误检测功能可以帮助开发者及时发现并修复代码中的错误,避免在运行时出现问题。Android Lua Helper
插件还拥有低内存消耗和高实时性的优点,即便面对规模庞大的项目,也能流畅运行,毫无卡顿之感。 此外,Android Lua Helper
插件还支持多种 Lua
版本,如 Lua 5.1
和 Lua 5.3
,满足了不同项目的需求。插件的不断更新和改进也为开发者提供了更好的开发体验。Android Lua Helper
支持使用安卓 ADB
工具连接手机,实现远程调试,安装卸载应用,截屏到本地,启动scrcpy
等。 总之,VSCODE
与 Android Lua Helper
的结合为 Android
开发中的 Lua
语言项目提供了强大的支持,使得开发者能够更加高效地进行开发工作。
首先,我们需要安装 VSCODE
,然后安装 Android Lua Helper
插件。 点击此链接直接在VSCODE
中安装,或在 VSCODE
的软件商店安装 Android Lua Helper
插件极为简便。首先打开 VSCODE
,目光聚焦于侧边栏底部,那里有一个扩展商店入口图标,形似方块。轻轻点击此图标,便会开启一个全新视图,其中罗列着众多插件。接着,在搜索栏中输入 Android Lua Helper
并按下回车键。随后,在搜索结果里找到 Android Lua Helper
插件,点击 “安装” 按钮。待安装完成,再点击 “重新加载” 按钮以启用该插件。 插件安装完成后,我们需要配置 VSCODE
,以支持 Android Lua Helper
的调试。
在安装好 Android Lua Helper
插件后,点击运行
按钮,接着点击创建 launch.json
,选择调试器时,我们要选择 AndroidLuaHelper:Debug
。这个过程就像是为我们的调试之旅搭建起了一座桥梁。它为后续的调试工作提供了基础的配置框架,确保我们能够顺利地进行 Lua 代码的调试。 在
launch.json
文件中,我们添加了如下内容:
"connectionPort"
字段用于指定调试端口,默认为 8818
。stopOnEntry
字段尤为关键。通过将其置为 false
,可以避免在程序入口处进行断点,使得调试过程更加流畅。例如,在一些复杂的项目中,如果 stopOnEntry
为 true
,可能会导致调试过程一开始就被中断,难以快速定位到实际的问题点。通过将其置为 false
,我们可以更加灵活地控制调试的起点,提高调试效率。enableRemotePath
字段用于是否启用远程路径,默认为 true
。remotePath
字段用于指定远程路径,默认为 /sdcard/Download/script.zip
。会把本地的工程文件打包成 zip 包,上传到手机的此目录。APP 需要加载此目录的脚本来进行解压,然后运行和调试。LuaPanda.lua
文件到项目中按快捷键Ctrl+Shift+P
,输入AndroidLuaHelper:Copy Debug file
,再按回车键。 选择合适的项目目录后LuaPanda.lua
文件会自动复制到目录下,并命名为 LuaPanda.lua
。
在合适的文件(如:main.lua
)中,按快捷键Ctrl+Shift+P
,输入AndroidLuaHelper:Insert Debugger Code
,再按回车键。 插入如下代码: 默认端口为
8818
,与 launch.json
文件中的"connectionPort"
要一致。
Android Lua Helper
会自动检测手机,如果检测不到,则需要手动输入。 点击边栏上的安卓
按钮,接着点击 Input Device IP Address
按钮,或按快捷键Ctrl+Shift+P
,输入AndroidLuaHelper:Input Device IP Address
,再按回车键,依次输入 IP 地址和端口号。
在需要的位置点入断点,点击运行
按钮,选择启动调试
,调试器会进入监听模式,在手机上运行 App 即可进行调试。
skynet 默认扩展不支持这种模式。 所以想请教下这种怎么改写。 也看了 https://github.com/openresty/lua-resty-mysql 源码。还是一脑袋浆糊。
if auth_plugin == "caching_sha2_password" then token = _compute_caching_sha2_password_token(password, scramble) else token = _compute_token(password, scramble) end local client_flags = 260047 local req = strpack("<I4I4c1c23zs1z", client_flags, self._max_packet_size, strchar(charset), strrep("\0", 23), user, token, database ) local authpacket = _compose_packet(self, req) sockchannel:request(authpacket, dispatch_resp) if on_connect then on_connect(self) end function _compute_caching_sha2_password_token(password, scramble) -- 1. Compute SHA-256 hash of the password local password_hash = sha.sha256(password) -- 2. Concatenate the hash with the scramble local combined = sha.sha256(sha.sha256(password_hash .. scramble)) local token = xor(password_hash, combined) -- 3. Compute SHA-256 hash of the combined value return token end 下边是我检索 chatgpt 来的,但是不对。请教下大家。
]]>背景
我是一名 Unity3D 的前端开发。公司的项目大部分是用 C# + xLua 的框架,MMORPG 类型的游戏。不可否认的 Lua 的技术还是非常成熟的。
疑惑
在工作中项目中,Lua 能用到的什么优化方法呢?我几乎想不到,完全没有思路。也不知道从何下手。平时公司里也没有什么大佬会告诉我。所以想请教下社区的大佬们,请问您知道的 lua 优化有哪些呢?谢谢您
目的
我觉得像我这样的小白应该不少,既然有相同的疑问,我也想通过这个帖子,可能相互学习一下。
谢谢您!
]]>这两个插件是负载均衡和健康检测。组合到一块我不知道怎么写了。
upstream webserver { server 127.0.0.1:12354; server 127.0.0.1:12355; server 127.0.0.1:12356 backup; } upstream webserver { server 127.0.0.1; balancer_by_lua_block { local b = require "ngx.balancer" local swrr_up = package.loaded.my_swrr_up -- Note that SWRR picks the first server randomly local server = swrr_up:find() assert(b.set_current_peer(server)) } }
]]>希望能够将 cpp 文件编译成动态库, 以 lua 作为主要运行逻辑(main 函数)
稍微有点多,感谢耐心观看
#pragma once extern "C" { #include "lua.h" #include "lualib.h" #include "lauxlib.h" } #include <string> class Worker{ public: Worker(); ~Worker(); int SetName(std::string &name); int SetAge(int &age); int SetHight(float &hight); std::string GetName(); int GetAge(); float GetHight(); private: std::string name; int age; float hight; }; extern "C" int luaopen_cstudy(lua_State *L);
#include "classstudy.h" #include <iostream> /* Class Worker 的函数定义省略 */ static int CreateNewWorker(lua_State *L){ //ligth_userdata // int n = luaL_checkany(L, 1); Worker **w1 = (Worker**)lua_newuserdata(L, sizeof(Worker*)); *w1 = new Worker(); if(luaL_getmetatable(L, "WorkerClass") == false){ printf("getmetatable nil\n"); } lua_setmetatable(L, -2); return 1; } static int SetWorkerName(lua_State *L){ luaL_checktype(L, -1, LUA_TSTRING); std::string var_name = lua_tostring(L, -1); printf("%s\n", var_name.c_str()); Worker **w1 = (Worker**)lua_newuserdata(L, sizeof(Worker*)); luaL_argcheck(L, w1 != NULL, 1, "invalid user data"); (*w1)->SetName(var_name); return 0; } static int SetWorkerAge(lua_State *L){ luaL_checktype(L, -1, LUA_TNUMBER); int var_age = lua_tointeger(L, -1); Worker **w1 = (Worker**)lua_newuserdata(L, sizeof(Worker*)); luaL_argcheck(L, w1 != NULL, 1, "invalid user data"); (*w1)->SetAge(var_age); return 0; } static int SetWorkerHight(lua_State *L){ luaL_checktype(L, -1, LUA_TNUMBER); float var_hight = lua_tonumber(L, -1); Worker **w1 = (Worker**)lua_newuserdata(L, sizeof(Worker*)); luaL_argcheck(L, w1 != NULL, 1, "invalid user data"); (*w1)->SetHight(var_hight); return 0; } static int GetWorkerInfo(lua_State *L){ Worker **w1 = (Worker**)lua_newuserdata(L, sizeof(Worker*)); luaL_argcheck(L, w1 != NULL, 1, "invalid user data"); printf("Name: %s\n", ((*w1)->GetName()).c_str()); printf("Age: %d\n", (*w1)->GetAge()); printf("Hight: %f\n", (*w1)->GetHight()); return 0; } static int DestoryInfo(lua_State* L) { // 释放对象 delete *(Worker**)lua_topointer(L, 1); return 0; } const static luaL_Reg mylib[] = { // {"NewWorker", CreateNewWorker}, {"SetName", SetWorkerName}, {"SetAge", SetWorkerAge}, {"SetHight", SetWorkerHight}, {"PrintInfo", GetWorkerInfo}, {NULL,NULL} }; int luaopen_cstudy(lua_State *L){ // C\++对象 = 私有数据 + 类(公共数据 + 公共方法) // Lua Table = 私有数据 + 元表(元数据 + 元函数) // luaL_newlib(L, mylib); lua_pushcfunction(L, CreateNewWorker); // 注册用于创建类的全局函数 lua_setglobal(L, "CWorker"); luaL_newmetatable(L, "WorkerClass"); // 设置自身 lua_pushstring(L, "__gc"); lua_pushcfunction(L, DestoryInfo); lua_settable(L, -3); // 设置元表 lua_pushstring(L, "__index"); // 设置元表为自己 lua_pushvalue(L, -2); lua_settable(L, -3); lua_pushstring(L, "SetName"); lua_pushcfunction(L, SetWorkerName); lua_settable(L, -3); lua_pushstring(L, "SetAge"); lua_pushcfunction(L, SetWorkerAge); lua_settable(L, -3); lua_pushstring(L, "SetHight"); lua_pushcfunction(L, SetWorkerHight); lua_settable(L, -3); lua_pushstring(L, "PrintInfo"); lua_pushcfunction(L, GetWorkerInfo); lua_settable(L, -3); // lua_pop(L, 1); return 1; }
local csl = require("cstudy") local Worker = CWorker() print(debug.getregistry()) Worker.SetName("hello") Worker.PrintInfo() -- csl.SetAge(23) -- csl:SetHight(180.0) -- csl:PrintInfo()
]]>飞书是字节跳动旗下一款企业级协同办公软件,本文将介绍如何基于飞书开放平台的身份验证能力,使用 Lua 实现企业级组织架构的登录认证网关。
让我们首先看一下飞书第三方网站免登的整体流程:
第一步: 网页后端发现用户未登录,请求身份验证; 第二步: 用户登录后,开放平台生成登录预授权码,302 跳转至重定向地址; 第三步: 网页后端调用获取登录用户身份校验登录预授权码合法性,获取到用户身份; 第四步: 如需其他用户信息,网页后端可调用获取用户信息(身份验证)。
function _M:get_app_access_token() local url = "https://open.feishu.cn/open-apis/auth/v3/tenant_access_token/internal/" local body = { app_id = self.app_id, app_secret = self.app_secret } local res, err = http_post(url, body, nil) if not res then return nil, err end if res.status ~= 200 then return nil, res.body end local data = json.decode(res.body) if data["code"] ~= 0 then return nil, res.body end return data["tenant_access_token"] end
function _M:get_login_user(code) local app_access_token, err = self:get_app_access_token() if not app_access_token then return nil, "get app_access_token failed: " .. err end local url = "https://open.feishu.cn/open-apis/authen/v1/access_token" local headers = { Authorization = "Bearer " .. app_access_token } local body = { grant_type = "authorization_code", code = code } ngx.log(ngx.ERR, json.encode(body)) local res, err = http_post(url, body, headers) if not res then return nil, err end local data = json.decode(res.body) if data["code"] ~= 0 then return nil, res.body end return data["data"] end
获取登录用户信息时无法获取到用户的部门信息,故这里需要使用登录用户信息中的 open_id
获取用户的详细信息,同时 user_access_token
也是来自于获取到的登录用户信息。
function _M:get_user(user_access_token, open_id) local url = "https://open.feishu.cn/open-apis/contact/v3/users/" .. open_id local headers = { Authorization = "Bearer " .. user_access_token } local res, err = http_get(url, nil, headers) if not res then return nil, err end local data = json.decode(res.body) if data["code"] ~= 0 then return nil, res.body end return data["data"]["user"], nil end
我们使用 JWT 作为登录凭证,同时用于保存用户的 open_id
和 department_ids
。
-- 生成 token function _M:sign_token(user) local open_id = user["open_id"] if not open_id or open_id == "" then return nil, "invalid open_id" end local department_ids = user["department_ids"] if not department_ids or type(department_ids) ~= "table" then return nil, "invalid department_ids" end return jwt:sign( self.jwt_secret, { header = { typ = "JWT", alg = jwt_header_alg, exp = ngx.time() + self.jwt_expire }, payload = { open_id = open_id, department_ids = json.encode(department_ids) } } ) end -- 验证与解析 token function _M:verify_token() local token = ngx.var.cookie_feishu_auth_token if not token then return nil, "token not found" end local result = jwt:verify(self.jwt_secret, token) ngx.log(ngx.ERR, "jwt_obj: ", json.encode(result)) if result["valid"] then local payload = result["payload"] if payload["department_ids"] and payload["open_id"] then return payload end return nil, "invalid token: " .. json.encode(result) end return nil, "invalid token: " .. json.encode(result) end
ngx.header["Set-Cookie"] = self.cookie_key .. "=" .. token
我们在用户登录时获取用户的部门信息,或者在用户后续访问应用时解析登录凭证中的部门信息,根据设置的部门白名单,判断用户是否拥有访问应用的权限。
-- 部门白名单配置 _M.department_whitelist = {} function _M:check_user_access(user) if type(self.department_whitelist) ~= "table" then ngx.log(ngx.ERR, "department_whitelist is not a table") return false end if #self.department_whitelist == 0 then return true end local department_ids = user["department_ids"] if not department_ids or department_ids == "" then return false end if type(department_ids) ~= "table" then department_ids = json.decode(department_ids) end for i=1, #department_ids do if has_value(self.department_whitelist, department_ids[i]) then return true end end return false end
同时支持 IP 黑名单和路由白名单配置。
-- IP 黑名单配置 _M.ip_blacklist = {} -- 路由白名单配置 _M.uri_whitelist = {} function _M:auth() local request_uri = ngx.var.uri ngx.log(ngx.ERR, "request uri: ", request_uri) if has_value(self.uri_whitelist, request_uri) then ngx.log(ngx.ERR, "uri in whitelist: ", request_uri) return end local request_ip = ngx.var.remote_addr if has_value(self.ip_blacklist, request_ip) then ngx.log(ngx.ERR, "forbided ip: ", request_ip) return ngx.exit(ngx.HTTP_FORBIDDEN) end if request_uri == self.logout_uri then return self:logout() end local payload, err = self:verify_token() if payload then if self:check_user_access(payload) then return end ngx.log(ngx.ERR, "user access not permitted") self:clear_token() return self:sso() end ngx.log(ngx.ERR, "verify token failed: ", err) if request_uri ~= self.callback_uri then return self:sso() end return self:sso_callback() end
本文就不赘述 OpenResty 的安装了,可以参考我的另一篇文章《在 Ubuntu 上使用源码安装 OpenResty 》。
cd /path/to git clone git@github.com:ledgetech/lua-resty-http.git git clone git@github.com:SkyLothar/lua-resty-jwt.git git clone git@github.com:k8scat/lua-resty-feishu-auth.git
lua_package_path "/path/to/lua-resty-feishu-auth/lib/?.lua;/path/to/lua-resty-jwt/lib/?.lua;/path/to/lua-resty-http/lib/?.lua;/path/to/lua-resty-redis/lib/?.lua;/path/to/lua-resty-redis-lock/lib/?.lua;;"; server { access_by_lua_block { local feishu_auth = require "resty.feishu_auth" feishu_auth.app_id = "" feishu_auth.app_secret = "" feishu_auth.callback_uri = "/feishu_auth_callback" feishu_auth.logout_uri = "/feishu_auth_logout" feishu_auth.app_domain = "feishu-auth.example.com" feishu_auth.jwt_secret = "thisisjwtsecret" feishu_auth.ip_blacklist = {"47.1.2.3"} feishu_auth.uri_whitelist = {"/"} feishu_auth.department_whitelist = {"0"} feishu_auth:auth() } }
app_id
用于设置飞书企业自建应用的 App ID
app_secret
用于设置飞书企业自建应用的 App Secret
callback_uri
用于设置飞书网页登录后的回调地址(需在飞书企业自建应用的安全设置中设置重定向 URL )logout_uri
用于设置登出地址app_domain
用于设置访问域名(需和业务服务的访问域名一致)jwt_secret
用于设置 JWT secretip_blacklist
用于设置 IP 黑名单uri_whitelist
用于设置地址白名单,例如首页不需要登录认证department_whitelist
用于设置部门白名单(字符串)本项目已完成且已在 GitHub 上开源:k8scat/lua-resty-feishu-auth,希望大家可以动动手指点个 Star,表示对本项目的肯定与支持!
]]>主要是为了解耦,所谓高内聚,低耦合,如果不采用事件驱动的方式,则会像下面这样来写代码。
比如说,游戏内有多个玩法模块,”玩家“在打造装备的时候,可能触发”装备打造 xx 阶的成就“,也可能达成某种条件获得了时装。
这种情景的话,如果没有采用事件驱动的方式来写代码,则需要在装备模块的”升级装备“函数内调用 成就模块 的检查成就达成的函数,还需要调用 时装模块 的检查获得时装的函数。
function equipStrengthen() -- 装备强化逻辑 checkAchievement() -- 成就模块检查成就 checkObtainFashion() -- 时装模块检查获得时装 end
如果装备关联的模块越来越多的话,就要记得去相关的函数内添加相关的调用。
事件驱动的方式就比较好的处理了这种情况。 如果是用事件驱动的方式来处理以上问题,则我们会这么做,由装备模块发出“装备强化”的事件,成就模块和时装模块只需要监听”装备强化“事件做相应的处理就好了。
在装备强化的模块内只需要一行代码,发出事件,后续如果需要增加关联的模块时,装备模块完全不用动,新模块只要增加监听事件就可以了。
下面我用 lua 实现一个例子:
------------------------------------------------------ 事件触发器 local Listener = {} function Listener:new(channel, callback) local obj = { callback = callback, channel = channel, } setmetatable(obj, self) self.__index = self return obj end local Channel = {} function Channel:new(event) assert(event) local obj = { listeners = {}, event = event, } setmetatable(obj, self) self.__index = self return obj end function Channel:on(callback) listener = Listener:new(self, callback) table.insert(self.listeners, listener) end local EventEmitter = {} function EventEmitter:new() local obj = { events = {}, -- 监听的所有事件 channels = {}, -- event: channel } setmetatable(obj, self) self.__index = self return obj end function EventEmitter:setEvents(events) self.events = events end function EventEmitter:on(event, callback) assert(event) assert(callback) if not self.events[event] then error("not register event: "..event) end local channel = self.channels[event] if not channel then channel = Channel:new(event) self.channels[event] = channel end channel:on(callback) end function EventEmitter:emit(event) if not self.events[event] then error("not register event: "..event) end local channel = self.channels[event] if not channel then return end for _, listener in ipairs(channel.listeners) do listener.callback() end end ----------------------------------------------------- 装备模块 local eventEmitter = EventEmitter:new() eventEmitter:setEvents({ ["equipStrengthen"] = "装备强化", }) function equipStrengthen() -- 装备强化逻辑 eventEmitter:emit("equipStrengthen") end ------------------------------------------------------ 成就模块 function checkAchievement() print('checkAchievement') end eventEmitter:on("equipStrengthen", checkAchievement) -- 成就模块注册监听 ------------------------------------------------------ 时装模块 function checkObtainFashion() print('checkObtainFashion') end eventEmitter:on("equipStrengthen", checkObtainFashion) -- 时装模块注册监听 ------------------------------------------------------------------------------------ function main() equipStrengthen() end main()
最后输出:
checkAchievement checkObtainFashion
下面这个图可以有助于理解,
上述的实现比较简单,主要意思表达出来了,具体的细节可以结合需要再添加就好了。
如果觉得对你有帮助的话请 @程序员杨小哥 点个赞,谢谢!
]]>/**注释*/ let a=1
然后在鼠标移动到任何使用a
变量的地方,都能提示这个变量是什么 那么 lua 有相关插件或者设置吗? 尝试了--
和--[[]]
都没用类似ts
的效果 不知道有没有大牛研究过这个问题 或者推荐一款能显示注释的 ide
—— 大道至简, 返璞归真.
在发表这篇博文的前夕, 还有一些小伙伴在提问一些以下相关的问题:
性能怎么样?
是否容易上手?
开发目标在哪?
如何反馈问题?
对比行业内的 lua 开源项目有何优势?
等等, 以上问题会在本文中一一介绍.
首先来聊聊情怀这个东西! 相信每一个行业内的从业者都或多或少有过一个梦, 这个梦叫做: "我到时候要开发一个 XXX"!其实作者当初也是一样.
每当半夜(凌晨)在加班、看文档、调试的时候, 总会搜索到一些几年前或十几年前的框架或入门 demo。例如: tinyhttp, 链接的源码是一些同学 fork 的镜像站。
每次看到这些内容或多或少都会激起心中那一丝丝快熄灭的热情, 也许这就是最后对技术的渴望?
就是在动手创建项目之前还反复问过自己是否要做? 能坚持下去么?也许被喷都是一种奢望?
在心里一一回答了这些问题后, 在 2018 年末创建了本项目.
说句实话! 一个网络开发框架最难的不是实现某个功能, 而是从零开始一步一步添砖加瓦的造轮子!
作为一个网络开发框架, 最重要的两个功能肯定是需要的! 定时器库、事件驱动库. 如何抉择?选项有 2 个: libev / libuv .
libev 成熟稳定、轻量级、unix like 支持、容易嵌入; libuv 比 libev 更加优秀,增加了许多功能(线程池、信号、同步、锁等等),封装更加完善, 并且增加了 windows 支持;
从 cf 框架开发之初选型来看, libuv 绝对是目前最优解. 但是作者偏偏选择了 libev. 也从此开始, 艰辛的底层开发之路就此展开.
首先, 作者不让使用者 C/C++进行实际业务开发! 这样做会让使用者有较高的开发成本与学习成本, 而选择一门较好的脚本语言就显得尤为重要.
作者对Lua还算是稍微熟悉一点, 所以就选了 Lua 作为业务脚本语言。至于 Lua 语言的优势这里就不说了, 网上大把文章夸它的.
现在既然脚本语言已经选定, 那么就开始写代码吧! Let's Lua.
首先, 我们来看一段 C 封装给 Lua 调用的 API 代码:
LUAMOD_API int luaopen_tcp(lua_State *L){ luaL_checkversion(L); /* 添加 SSL 支持 */ SSL_library_init(); SSL_load_error_strings(); // CRYPTO_set_mem_functions(xmalloc, xrealloc, xfree); // OpenSSL_add_ssl_algorithms(); /* 添加 SSL 支持 */ luaL_newmetatable(L, "__TCP__"); lua_pushstring (L, "__index"); lua_pushvalue(L, -2); lua_rawset(L, -3); lua_pushliteral(L, "__mode"); lua_pushliteral(L, "kv"); lua_rawset(L, -3); luaL_Reg tcp_libs[] = { {"read", tcp_read}, {"write", tcp_write}, {"ssl_read", tcp_sslread}, {"ssl_write", tcp_sslwrite}, {"stop", tcp_stop}, {"start", tcp_start}, {"close", tcp_close}, {"listen", tcp_listen}, {"connect", tcp_connect}, {"ssl_connect", tcp_sslconnect}, {"new", tcp_new}, {"new_ssl", ssl_new}, {"free_ssl", ssl_free}, {"new_server_fd", new_server_fd}, {"new_client_fd", new_client_fd}, {NULL, NULL} }; luaL_setfuncs(L, tcp_libs, 0); luaL_newlib(L, tcp_libs); return 1; }
以上是 TCP 实现的 C 代码的片段, 有兴趣阅读源码的小伙伴请点击这里;
众所周知 Lua 没有原生的 Socket. 那么就需要框架编写者自己抽象底层逻辑重新实现一套 API.
简单的封装 Lua C 库谁都会, 而且也算不上是什么难事. 但是我们的目的是将底层同步阻塞 Socket hook 为非阻塞, 这时候难点就来了!
大家都知道 libev 是基于 react 模型的事件驱动网络库, 所有注册事件后的业务逻辑都是以回调的形式触发. 那不就变成 node-lua 代码了吗?(笑)
这时候, 作者想了个点子来解决这个问题! 执行流程如下:
- 每次需要做一些同步操作的时候, 就调用 C API 注册回调事件.
- 为当前注册的所有事件创建一个 Lua 协程保存上下文并让出当前协程执行权.
- 等到注册事件被触发后, 调用 C API 恢复协程继续执行.
简单来说就是将 C 层次的异步回调逻辑封装为 Lua 层的同步非阻塞, 保证不因为 IO 问题阻塞线程.
下面提供一段 socket 同步非阻塞的伪代码, 经供参考:
function TCP:recv(bytes) local current_co = co_self() self.read_co = read_ev(function() -- do action -- stop timer_ev -- wakeup(current_co) 恢复执行权 end) self.timer_co = self.timer_ev(function() -- do action -- stop read_ev -- wakeup(current_co) 恢复执行权 end) tcp_start(io, EV_READ, self.read_co) timer_start(timer, 3 秒超时, self.timer_co) return co_yield() -- 让出执行权 end
一个 Lua 版的 Socket EV_READ 伪代码大致的处理流程如上, 想看实际处理逻辑请看这里。
同理, Socket write/connect/listen 等等 API 直接照抄就行(UDP 也大同小异). (其实这里有个小插曲就是 SSL SOCKET 的坑, 但是由于篇幅问题就不说了.)
细心的小伙伴可能发现代码同时注册了 Socket 与 Timer 事件, Socket 非阻塞操作不能解决 read 与 connect 超时的问题. 所以 cf 框架干脆就封装彻底一点.
至此, Socket 算是已经算是基本 hook 与封装完成了. 接下来就可以开始写应用层协议了.
现在 Socket 终于能正常使用了, 那么面临的新问题就又来了。
libev 没有自带异步 dns
dns 都还需要使用者自己封装, 这个坑真是填的无比难受! 好在网络上有前辈实现了 Lua 版的异步 dns, 作者稍微看明白之后就借用了过来封装内部使用.
这样 cf 也算是有了深度定制的异步 dns库了吧!(虽然并不完善, 但是足够使用)
一个网络库是否流行, 基本上就得看生态. 那么协议层的轮子又得造起来:
其中一些协议为各位前辈那边借过来适配后定制的, 简单的协议则是直接花 1-2 小时直接手写出来的。
为了不让 API 那么封闭与提升 cf 的可用性, 作者决定将 mysql 与 redis 进行初步封装.
封装包括大家常用的功能, 连接池、面向对象操作、无需手动管理 session 生命周期等等. 简化编程思想包袱来提升开发效率.
至于内部 Socket 更是让框架来解决释放问题确保文件描述数量限制的情况下也是可以正常使用. (其实是不喜欢依赖 gc 被动 close fd 与 free 内存)
如果你耐心看完了第一部分介绍, 那么你就应该对 cf 有了一个大概的了解.
cf 全称为: CoreFramework, 是一个基于 libev 的 Lua 网络开发框架. 在其内部实现了多种网络协议与第三方库用来帮助使用者进行项目原型的快速开发.
cf 在 httpd 使用上尊崇前、后端分离的解决方案, 仅实现了基本的 view 路由并且不支持 rest 风格的 API 路由. 虽然这样可能会引来宇多人的诟病.
cf 的 httpd 内嵌 websocket 支持, 方便使用者在复用端口的同时也可以享受长连接编写的乐趣.
更多的介绍, 请大家项目地址的Wiki
基于容器技术的微服务场景(swarm/kubernetes); —— 推荐
游戏服务器的前端代理层; —— 推荐
内存 /CPU 资源较为紧缺的云服务器; —— 推荐
对性能要求较高的无状态集群; —— 推荐
海量长连接(websocket)Agent 集群; —— 推荐
传输层: TCP/UDP
会话层: SSL Client 支持
协议层: dns/webocket/http/mqtt/redis/mysql/smtp
工具库: Timer/TASK
第三方库: Libev、openssl/libressl、lua-5.3、jemalloc/tcmalloc(可选)
cf 目前支持绝大部分 Unix like 操作系统, 作者是在 Mac 上进行开发, 所以 Mac 支持是必须的.
cf 测试的 Linux 为 Centos, 所以基本上基于 Linux 内核的操作系统编译后的运行也没什么问题(export 增加 /usr/local/lib)
同时,作者还贴心的为大家做了一个简单 Dockerfile. 文件在项目根目录下, 大家下载直接使用即可。
当然, 如果你不想制作 Dockerfile,也可以使用 Docker 命令直接拉去作者制作好放在 docker hub 的镜像. candymi/cfweb
bash#: ./cfadmin
bash#: ./cfadmin
killall cfadmin
ctrl + c
作者为大家贴心的写了一篇详细到不能再详细的文档, 以此来获取大家的点赞与关注.
作者还为喜欢阅读源码的同学准备了充足的中文注释与英文注释, 结合起来方便大家快速了解 CF 工作方式(中 /英注释结合易于理解一些专属词汇).
Q. 性能怎么样?
A. 性能还不错, 但是具体数值请自行测试.
Q. 是否容易上手?
A. 学习 lua 一小时入门 -> cf 一小时入门
Q. 开这个项目的初衷是什么?
A. 其实在前面已经回答过了.
Q. 开发目标在哪?
A. Wiki 里有TODO项
Q. 如何反馈问题?
A. Wiki 里有Q & A项
Q. 对比行业内的 lua 开源项目有何优势?
A. CF 对比其它 lua 开发项目更深入改变用户使用习惯! 简化框架上手难度, 将框架都黑盒子透明化. 无需学习复杂的设计模式与理念.
Q. CF 的开发理念是什么?
A. CF 项目的目标不是竞争, 而是明白明白简单为美. 当你习惯了它, 也许你就会上瘾.
也许你正在使用其它开发框架, 但是这不妨碍你对 cf 的督促.
也许你正在试用它, 这不妨碍你与作者沟通你的想法.
也许你正在吐槽它的缺点,请来issue尽情吐槽.
出于各种业务 /硬件 /软件博弈, 最终不得不面对...
-f image2 frames/%05d.jpg
伪视频
: 功能服务
/frame/last
反馈最新一帧图片名ngx_lua 应用
/frame/last
反馈最新一帧图片内容或是有其它更加优雅方案?
感谢大家任何建议.
]]>POST http://10.0.0.1/cgi-bin/luci/;stok=/login?form=login
出错:
/usr/lib/lua/luci/dispatcher.lua:473: Failed to execute call dispatcher target for entry '/login'. The called action terminated with an exception: /usr/lib/lua/luci/sauth.lua:106: Session data invalid! stack traceback: [C]: in function 'assert' /usr/lib/lua/luci/dispatcher.lua:473: in function 'dispatch' /usr/lib/lua/luci/dispatcher.lua:200: in function </usr/lib/lua/luci/dispatcher.lua:199>
TP-Link 型号:TL-ER3220G
]]>请问各位 V 友有没有好的 Lua 的 IDE 推荐,能够有代码提示和像 VS 一样的错误监测(比如拼错了变量能够红色的下划线提示),断点调试有更好,没有也不强求。
]]>local myPosition = string.find(myStr, myValue) --查找字符所在位置 string.sub(myStr, myPosition) --取出找到的字符以及之后的字符串
PS : myValue 可能是任何字符串(有可能是字母数特殊符号和中文的混合字符串), string.find 这函数没法直接查找,得要各种转义,非常蛋疼。
想问一下还能有什么 lua 能高效的实现 php strstr() 功能的方法?
]]>详细的信息请关注: github 项目地址 和 sol2 主页 。
以及本人写的一篇入门使用教程。
欢迎大家一起来探讨下,这个项目怎么样,能够运用实际项目当中去。
ps : Sol 的作者对 issue 的响应速度还是蛮快的。
]]>然后用 luarocks path 可以看到环境变量应如何设置,自行拷贝出来 export 即可
每次 install 都要从网上下载源文件,若本地已有源代码,可以这样用 luarocks make local-rockspec --local
缺省 luarocks 编译 c 文件是不带调试信息的,这对于 debug (比如生成火焰图缺乏符号信息尤其不方便),可以这样用 luarocks make local-rockspec --local CC="gcc -g"
]]>web 应用层,
md5(IP.文件路径.其他固定信息如 UA 啥的) 存进 redis ,并作为 url 中的 hash 。
nginx 里, ngx.md5(IP.文件路径.其他固定信息如 UA 啥的)对比连接中的 hash 并读取 redis 查到时间戳。
判断时间戳是否失效。
现在想吧 UID 也放进去,不知道有什么好方法能做到避免伪造和验证用户是否登录。只有给 nginx+lua 写 cookie+session 来验证吗?
nginx.lua 上面不方便读 MySQL 数据库的~~.
谢谢诸位,
]]>[岗位职责]
[任职要求]
有意者联系: 18758322614
]]>所以我比较好奇的是,目前是否有什么比较好的 Lua 库,可以引入之后用来解决这些最常见的问题?
]]># nginx.conf http { ... ... lua_shared_dict statics_dict 1M; # 初始化变量 lua_package_path "/etc/nginx/ngx_lua_reqstatus/?.lua"; #路径 log_by_lua_file "/etc/nginx/ngx_lua_reqstatus/hook.lua"; # 添加此句 server { listen 80; server_name www.justforfun.com; location /{ ... } } # http 接口 server { listen 127.0.0.1:6080; location /{ access_by_lua_file "/etc/nginx/ngx_lua_reqstatus/status.lua"; } } }
查看 www.justforfun.com
的运行状况:
curl localhost:6080/?domain=www.justforfun.com
输出
Server Name key: www.justforfun.com Seconds SinceLast: 26.601999998093 Average Req Time Sec: 0.031799983978271 Request Count: 5 Requests Per Secs: 0.18795579281101 5xx num: 0