当前位置: 首页 > news >正文

网站设计论文结束语上海发乐门网站建设公司

网站设计论文结束语,上海发乐门网站建设公司,简单详细搭建网站教程,乐至seo文章目录 游戏模块基础功能模块定时器模块日志模块通用模块 游戏模块 游戏从逻辑方面可以分为下面几个模块#xff1a; 注册和登录网络协议数据库玩法逻辑其他通用模块 除了逻辑划分#xff0c;还有几个重要的工具类模块#xff1a; Excel 配置导表工具GM 指令测试机器人… 文章目录 游戏模块基础功能模块定时器模块日志模块通用模块 游戏模块 游戏从逻辑方面可以分为下面几个模块 注册和登录网络协议数据库玩法逻辑其他通用模块 除了逻辑划分还有几个重要的工具类模块 Excel 配置导表工具GM 指令测试机器人服务器打包部署工具 本节先来实现几个通用的基础功能模块。 基础功能模块 定时器模块 在什么场景下我们会要使用到定时器 每日任务的重置比如游戏在每天的 0 点需要定时进行刷新登录流程的超时机制对于长时间未通过验证的连接需要踢客户端下线避免占用服务端资源活动结算在定期活动结束后需要给所有用户发放结算奖励 服务端和客户端都可以实现定时器逻辑一般涉及到全服玩家的定时器需要服务器来实现针对个人玩家的定时器可以交给客户端来实现。 在 skynet 中通过 skynet.timeout(time, func) 实现定时任务skynet 基本的时间单位是 10ms即会在 0.01s 后执行一次 func 函数。 参考https://github.com/cloudwu/skynet/wiki/LuaAPI skynet.timeout(ti, func) 让框架在 ti 个单位时间后调用 func 这个函数。这不是一个阻塞 API 当前 coroutine 会继续向下运行而 func 将来会在新的 coroutine 中执行。 skynet 的定时器实现的非常高效所以一般不用太担心性能问题。不过如果你的服务想大量使用定时器的话可以考虑一个更好的方法即在一个service里尽量只使用一个 skynet.timeout 用它来触发自己的定时事件模块。这样可以减少大量从框架发送到服务的消息数量。毕竟一个服务在同一个单位时间能处理的外部消息数量是有限的。 由此考虑自己实现一个用于定时触发事件的定时器模块并且不需要这么高的精度采用以秒为单位实现定时器。 定时器模块实现架构 skynet.timeout 实现循环定时器每秒循环一次查看并执行这一秒对应的回调函数。定时器 id 采用自增唯一映射每个定时回调函数。注册回调函数时会计算将要执行的秒数存入对应的回调函数表。 基础变量以及模块初始化 local _M {} local is_init false -- 标记模块是否初始化 local timer_inc_id 1 -- 定时器的自增 ID local cur_frame 0 -- 当前帧一帧对应一秒 local cur_timestamp 0 -- 当前时间戳运行到的秒数 local timer_size 0 -- 定时器数量 local frame_size 0 -- 帧数量local timer2frame {} -- 定时器ID 映射 帧 local frame2cbs {} -- 帧 映射 多个回调任务 --[[frame: {timers: {timerid: { sec, cb, args, is_repeat },timerid: { sec, cb, args, is_repeat }}, size: 1} ]]if not is_init then is_init true -- 初始化定时器模块skynet.timeout(100, main_loop) end return _M is_init用于标记模块是否初始化即生成一次循环定时器定时每秒执行 main_loop 函数timer_inc_id定时器的唯一标识 ID每个定时器创建都会自增cur_frame记录当前循环是对应哪一帧随着循环自增cur_timestamp当前循环时间戳timer_size、frame_size维护的定时器数量和帧数量timer2frame定时器 ID 对应的帧frame2cbs帧对应的回调函数表 回调函数表的结构 frame2cbs frame: {timers: {timerid: { sec, cb, args, is_repeat },timerid: { sec, cb, args, is_repeat }}, size: 1 }每帧对应回调函数表有 timers 和 size 两个字段size 维护当前回调函数个数timers 则是实际的回调函数表以定时器 ID 映射对应的回调函数。 每个回调函数都存储 sec、cb、args、is_repeat 四个字段表示 sec 秒后执行 cb 函数携带 args 参数 is_repeat 表示是否是一个循环任务。 下面看每帧执行的函数 main_loop local function now() return skynet.time() // 1 -- 截断小数.0 end -- 逐帧执行 local function main_loop()skynet.timeout(100, main_loop)cur_timestamp now()cur_frame cur_frame 1-- 当前没有定时器任务if timer_size 0 then return end -- 当前帧对应的回调任务local cbs frame2cbs[cur_frame]if not cbs then return end -- 当前帧的回调任务数量为0if cbs.size 0 then frame2cbs[cur_frame] nil frame_size frame_size - 1 -- 该帧执行完毕return end -- task: {sec, cb, args, is_repeat}for timerid, task in pairs(cbs.timers) do local f task[2] local args task[3]local ok, err xpcall(f, traceback, unpack(args, 1, args.n))if not ok then logger.error(timer, crontab is run in error:, err)end del_timer(timerid) -- 执行成功与否都需要删掉当前这个定时器local is_repeat task[4]if is_repeat then local sec task[1]init_timer(timerid, sec, f, args, is_repeat)end end -- 当前这一帧所有任务执行完并且这一帧没有删(双重保障del_timer)删掉当前帧if frame2cbs[cur_frame] then frame2cbs[cur_frame] nil frame_size frame_size - 1end end 这里在入口处我们就立即需要执行 skynet.timeout(100, main_loop)实现循环定时并且没有多余其他操作保证一下秒定时的准确。 skynet.time()当前 UTC 时间单位是秒, 精度是 ms 主要逻辑判断当前帧是否有任务有则执行 frame2cbs[cur_frame].timers 回调函数表中的回调函数执行完后进行删除和判断该回调是否是循环定时任务是则重新创建该回调的新定时器。 再来看定时器的创建和删除逻辑 init_timer local function init_timer(id, sec, f, args, is_repeat)-- 第一步定时器 id 映射 帧local offset_frame sec -- sec 帧后开始当前任务 -- 矫正帧数if now() cur_timestamp then offset_frame offset_frame 1end -- 实际计算执行帧local fix_frame cur_frame offset_frame-- 第二步该帧 映射 定时器任务local cbs frame2cbs[fix_frame]if not cbs then -- 创新当前帧的任务集cbs { timers {}, size 1 }frame2cbs[fix_frame] cbs frame_size frame_size 1 else cbs.size cbs.size 1end cbs.timers[id] {sec, f, args, is_repeat}timer2frame[id] fix_frametimer_size timer_size 1if timer_size 500 then logger.warn(timer, timer is too many!)end end 创建定时器任务对应需要修改 frame2cbs 和 timer2frame 表。回调函数加入当前帧的回调表中回调的定时器ID映射当前帧一并维护一下定时器和帧的数量统计。 在函数的开始我们进行了对帧的校正。保证回调任务在未来帧中执行而不会在当前帧中继续添加任务。 del_timer -- 删除定时器 local function del_timer(id) -- 获取定时器id 映射 帧local frame timer2frame[id]if not frame then return end -- 获取该帧对应的任务local cbs frame2cbs[frame]if not cbs or not cbs.timers then return end -- 如果这个帧中的定时器任务存在if cbs.timers[id] then cbs.timers[id] nil -- 删除该定时器任务cbs.size cbs.size - 1 -- 当前帧的任务数 -1end -- 当前删掉了这一帧的最后一个定时器任务if cbs.size 0 then frame2cbs[frame] nil -- 置空frame_size frame_size - 1 -- 帧数 -1 end -- 当前定时器id对应的帧置空且定时器数量 -1timer2frame[id] nil timer_size timer_size - 1 end 删除定时器逻辑很好理解传入定时器 ID找到 ID 对应的帧看该帧中是否存在这个任务存在就删除并维护帧数和定时器数量。 接口实现 -- 新增定时器 timersec 秒后执行函数 f -- 返回定时器 ID function _M.timeout(sec, f, ...)assert(sec 0)timer_inc_id timer_inc_id 1init_timer(timer_inc_id, sec, f, pack(...), false)return timer_inc_id end function _M.timeout_repeat(sec, f, ...) assert(sec 0)timer_inc_id timer_inc_id 1init_timer(timer_inc_id, sec, f, pack(...), true)return timer_inc_id end -- 取消定时器任务 function _M.cancel(id)del_timer(id) end -- 检查定时器是否存在 function _M.exist(id)if timer2frame[id] then return true end return false end -- 获取定时器还有多久执行 function _M.get_remain(id)local frame timer2frame[id] if frame then return frame - cur_frameend return -1 end 完整代码timer.lua 日志模块 日志系统一般分为 4 个等级 DEBUG调试用的日志线上运行时屏蔽不输出INFO普通日志线上运行时输出流程的关键步骤都需要有 INFO 日志WARN数据异常但不影响正常流程的时候输出ERROR数据异常且需要人工处理的时候输出 日志服务模块配置如下 -- log conf logger log logservice snlua logpath log logtag game -- debug | info | warn | error log_level debug参考官方 wiki logger 它决定了 skynet 内建的 skynet_error 这个 C API 将信息输出到什么文件中。如果 logger 配置为 nil 将输出到标准输出。你可以配置一个文件名来将信息记录在特定文件中。 logservice 默认为 logger 你可以配置为你定制的 log 服务比如加上时间戳等更多信息。可以参考 service_logger.c 来实现它。注如果你希望用 lua 来编写这个服务可以在这里填写 snlua 然后在 logger 配置具体的 lua 服务的名字。在 examples 目录下有 config.userlog 这个范例可供参考。 配置中指定 logger 是 log.lua 这个日志服务logservice 是 snlua 表示这个日志服务是 lua 服务。其余的三个参数作为键值对存储在配置中用于实现服务模块时取出使用。logpath 指定为日志存放的目录路径logtag 指定为日志进程标识log_level 可选四种日志级别。 这里先来看日志模块lualib/logger.lua local skynet require skynetlocal loglevel {debug 0,info 1,warn 2,error 3, }local logger {_level nil,_fmt [%s] [%s] %s, -- [info] [label] msg_fmt2 [%s] [%s %s] %s, --[info] [label labeldata] msg }local function init_log_level()if not logger._level thenlocal level skynet.getenv log_levellocal default_level loglevel.debuglocal valif not level or not loglevel[level] thenval default_levelelseval loglevel[level]endlogger._level valend endfunction logger.set_log_level(level)local val loglevel.debugif level and loglevel[level] thenval loglevel[level]endlogger._level val endlocal function formatmsg(loglevel, label, labeldata, args)local args_len #argsif args_len 0 thenfor k, v in pairs(args) dov tostring(v)args[k] vendargs table.concat(args, )elseargs endlocal msglocal fmt logger._fmtif labeldata ~ nil thenfmt logger._fmt2msg string.format(fmt, loglevel, label, labeldata, args)elsemsg string.format(fmt, loglevel, label, args)endreturn msg end--[[ logger.debug(map, user, 1024, entered this map) logger.debug2(map, 1, user, 2048, leaved this map) ]] function logger.debug(label, ...)if logger._level loglevel.debug thenlocal args {...}local msg formatmsg(debug, label, nil, args)skynet.error(msg)end end function logger.debug2(label, labeldata, ...)if logger._level loglevel.debug thenlocal args {...}local msg formatmsg(debug, label, labeldata, args)skynet.error(msg)end endfunction logger.info(label, ...)if logger._level loglevel.info thenlocal args {...}local msg formatmsg(info, label, nil, args)skynet.error(msg)end end function logger.info2(label, labeldata, ...)if logger._level loglevel.info thenlocal args {...}local msg formatmsg(info, label, labeldata, args)skynet.error(msg)end endfunction logger.warn(label, ...)if logger._level loglevel.warn thenlocal args {...}local msg formatmsg(warn, label, nil, args)skynet.error(msg)end end function logger.warn2(label, labeldata, ...)if logger._level loglevel.warn thenlocal args {...}local msg formatmsg(warn, label, labeldata, args)skynet.error(msg)end endfunction logger.error(label, ...)if logger._level loglevel.error thenlocal args {...}local msg formatmsg(error, label, nil, args, debug.traceback())skynet.error(msg)end end function logger.error2(label, labeldata, ...)if logger._level loglevel.error thenlocal args {...}local msg formatmsg(error, label, labeldata, args, debug.traceback())skynet.error(msg)end endskynet.init(init_log_level)return logger这个日志模块主要暴露的四个接口分别对应四个日志等级并且只有当前日志等级 log_level 低于当前 API 对应的等级才可以输出。如果程序测试阶段那么指定 debug 级就会获得所有日志。如果程序上线指定 error 级那么只会关注到最高级别的错误日志。 error 等级日志额外输出了调用堆栈方便查看错误问题所在的位置。 skynet.init若服务尚未初始化完成则注册一个函数等服务初始化阶段再执行若服务已经初始化完成则立刻运行该函数。 下面再来看一下日志服务代码service/log.lua local skynet require skynet require skynet.manager local time require utils.time-- 日志目录 local logpath skynet.getenv(logpath) or log -- 日志文件名 local logtag skynet.getenv(logtag) or game local logfilename string.format(%s/%s.log, logpath, logtag) local logfile io.open(logfilename, a)-- 写文件 local function write_log(file, str) file:write(str, \n)file:flush()print(str) end -- 切割日志文件重新打开日志 local function reopen_log() -- 下一天零点再次执行local future time.get_next_zero() - time.get_current_sec()skynet.timeout(future * 100, reopen_log)if logfile then logfile:close() end local date_name os.date(%Y%m%d%H%M%S, time.get_current_sec())local newname string.format(%s/%s-%s.log, logpath, logtag, date_name)os.rename(logfilename, newname) -- logfilename文件内容剪切到newname文件logfile io.open(logfilename, a) -- 重新持有logfilename文件 end -- 注册日志服务处理函数 skynet.register_protocol {name text, id skynet.PTYPE_TEXT, unpack skynet.tostring, dispatch function(_, source, str)local now time.get_current_time()str string.format([%08x][%s] %s, source, now, str)write_log(logfile, str)end }-- 捕捉sighup信号kill -l 执行安全关服逻辑 skynet.register_protocol {name SYSTEM, id skynet.PTYPE_SYSTEM, unpack function(...) return ... end,dispatch function()-- 执行必要服务的安全退出操作skynet.sleep(100)skynet.abort()end }local CMD {} skynet.start(function()skynet.register(.log)skynet.dispatch(lua, function(_, _, cmd, ...)local f CMD[cmd]if f then skynet.ret(skynet.pack(f(...)))else skynet.error(string.format(invalid command: [%s], cmd))endend)local ok, msg pcall(reopen_log)if not ok then print(msg)end end)日志服务已经在配置中指定logger log、logservice snlua不需要自行启动这个日志服务。且项目中所有 skynet.error API 输出的内容都被定向到了日志文件中而不是输出在控制台。便于调试write_log 写日志函数最后调用了 print 打印日志到了终端。 通过注册 skynet.PTYPE_TEXT 文本类型消息那么项目中的 skynet.error 输出的日志都会经过本日志服务进行分发处理由此在分发函数 dispatch function(_, source, str) end 中处理日志消息对所有的日志消息进行格式化的美观输出。 日志服务工作原理可以参考文章https://www.jianshu.com/p/351ac2cfd98c/ 本系列 skynet 偏原理性的东西不做深入讲解。 日志服务如果不做切割全部放在一个文件中会导致日志文件日益增大这里实现 reopen_log 函数通过 skynet.timeout 定时每天零点对日志进行切割包括在服务重启时也会对上次的日志文件 game.log 进行分割处理。 日志服务还注册了一种消息类型skynet.PTYPE_SYSTEM用来接收 kill -1 命令的信号触发保存数据的逻辑待后续实现了缓存模块在完善。 通用模块 同样在本章节继续实现几个通用模块细心的小伙伴应该注意到了在实现日志模块、日志服务时都有导入 utils.time 这个处理时间的一个模块。 下图是目前的模块 lualib 文件夹的结构 time.lua local skynet require skynetlocal _M {}-- 一秒只转一次时间戳 local last_sec local current_str -- 获取当前时间戳 function _M.get_current_sec()return math.floor(skynet.time()) end-- 获取下一天零点的时间戳 function _M.get_next_zero(cur_time, zero_point)zero_point zero_point or 0 cur_time cur_time or _M.get_current_sec() local t os.date(*t, cur_time) if t.hour zero_point then t os.date(*t, cur_time 24 * 3600) end local zero_date {year t.year, month t.month, day t.day, hour zero_point,min 0, sec 0,}return os.time(zero_date) end -- 获取当前可视化时间 function _M.get_current_time() local cur _M.get_current_sec()if last_sec ~ cur then current_str os.date(%Y-%m-%d %H:%M:%S, cur)last_sec cur endreturn current_str endreturn _M 目前实现了三个接口 get_current_sec获取当前时间戳get_current_time获取当前可视化时间get_next_zero获取下一天零点时间戳可以自定义项目的刷新时间 zero_point 其中有一个小优化是 last_seccurrent_str 设置上一秒时间戳与当前可视化时间变量保证一秒只会转换一次。 os.time、os.date 的使用参考 lua 手册 table.lua local string require stringlocal _M {}function _M.dump(t) local print_r_cache {}local function sub_print_table(t, indent)if (print_r_cache[tostring(t)]) thenprint(indent .. * .. tostring(t))elseprint_r_cache[tostring(t)] trueif (type(t) table) thenfor pos, val in pairs(t) doif (type(val) table) thenprint(indent .. [ .. pos .. ] .. tostring(t) .. {)sub_print_table(val, indent .. string.rep( , string.len(pos) 8))print(indent .. string.rep( , string.len(pos) 6) .. })elseif (type(val) string) thenprint(indent .. [ .. pos .. ] .. val .. )elseprint(indent .. [ .. pos .. ] .. tostring(val))endendelseprint(indent .. tostring(t))endendendif (type(t) table) thenprint(tostring(t) .. {)sub_print_table(t, )print(})elsesub_print_table(t, )endprint() endreturn _M string.lua local string require stringlocal _M {}function _M.split(str, sep) local arr {}local i 1for s in string.gmatch(str, ([^ .. sep .. ])) doarr[i] si i 1endreturn arr endreturn _M 目前 table 模块仅实现了 dump 接口对表的美化输出 string 模块仅实现了对字符串的分割转表有需求在自定义添加更多的功能。
http://www.hkea.cn/news/14271855/

相关文章:

  • 网站建设素材网页无法访问此页面
  • 做企业网站代码那种好网站建设 青少年宫
  • 阳高网站建设百度搜索引擎
  • 手机上如何制作网站欢迎访问中国建设银行官网
  • 网站维护是什么工作sem和seo都包括什么
  • 曲靖市住房和城乡建设局网站做网站哪里比较好
  • 建立网站的服务器公众微信绑定网站帐号
  • 网站建设公司排名及费用pc网站 公众号数据互通
  • 即墨网站建设哪家好网站开发报价表格
  • 网站建设与推广公司网页设计制作个人简历代码
  • 浙江省国有建设用地出让网站河北seo推广平台
  • 深圳做网站什么公司好清苑住房和城乡建设局网站
  • 天津工程建设协会网站镇江新区
  • 马可波罗网站做外贸地图设计网站
  • 网站建设教程(任务2签订网站建设合同)题库公众号登录不上
  • 域名没到期 网站打不开免费房屋设计装修
  • 深圳餐饮网站建设wordpress怎样连接数据库连接
  • 网站10月份可以做哪些有意思的专题网站开发域名注册
  • 泉州网站建设定制无锡网站建设方案优化
  • 网站优化塔山双喜中学网站建设方案计划
  • 上海建设银行网站莘庄ps制作个人网站
  • 印度vps汕头自动seo
  • 保定定兴网站建设郑州网站建设推广渠道
  • 重庆智能网站建设费用那些网站可以做淘宝店铺推广
  • 新手学做网站编程软件园
  • 怎么用phpcmf做网站做网站能设置关键词在百度中搜索到
  • .net 快速网站开发wordpress影视主题模板免费下载
  • 网站推广工作流程图什么是网络营销的渠道策略
  • 北京中燕建设公司网站网页无法访问6
  • 网站内链代码虎林网站建设