首页
关于
Search
1
Lua使用调试库hook函数调用
729 阅读
2
傻瓜式快速搭建l2tp
643 阅读
3
游戏邮件系统数据设计因素
634 阅读
4
傻瓜式安装chatgpt-web工具
586 阅读
5
Linux内核数据结构kfifo小结(TODO)
577 阅读
项目技术
项目思考
开发环境
数据库
编程语言
生活与阅读
哲学
登录
Search
标签搜索
nodejs
npm
资深IT牛马
累计撰写
58
篇文章
累计收到
0
条评论
首页
栏目
项目技术
项目思考
开发环境
数据库
编程语言
生活与阅读
哲学
页面
关于
搜索到
58
篇与
的结果
python访问Lua
安装环境:apt-get install liblua5.4-dev pip3 install lupaPython访问Lua虚拟机获取虚拟机:import lupa.lua54 as lupa from lupa import LuaRuntime lua = LuaRuntime(unpack_returned_tuples=True)访问Lua全局表:lua.globals()["print"] = print执行Lua代码1: lua.execute('print("hello")')执行Lua代码2: ret = lua.eval("_VERSION") , ret 是一个Lua变量封装变量判定Lua变量类型: lupa.lua_type封装变量给Lua访问:ret = lupa.as_itemgetter(py_dict) #Lua索引`ret`时优先取数据 ret = lupa.as_attrgetter(py_dict) #Lua索引`ret`时优先取属性方法Lua访问PythonLua脚本内置Python变量 python.builtinsuserdatapython内置类型,包含print/len/type/isinstance/list/dict等等python.argsfunctiontodopython.as_functionfunctiontodopython.evaluserdata用于执行python字符串代码python.set_overflow_handlerfunctiontodopython.noneuserdata类似lua中的nil, 遍历pyobj时可以直接相等判定python.enumeratefunction类似ipairs,遍历pyobj, 返回index和value。 但是index从0开始,如果是Dict则value其实是keypython.iterfunction遍历pyobj, 一次只返回一个值,list类型的元素值或者dict类型的key值python.as_itemgetterfunction设置pyobj数据在Lua索引时访问优先python.as_attrgetterfunction设置pyobj数据在Lua索引时属性方法优先索引python.iterexfunction类似pairs, 遍历pyobj, 返回key和value
2025年12月27日
6 阅读
0 评论
0 点赞
权重采集算法
游戏内经常遇到权重抽奖算法,之前一直思考有什么高效方法,最近遇到Alias Method算法。实现非常nice,他的时间复杂度O(1)的离散采样方法, 比之前二分查找快多了。从一维概率转化为二维视角实现,回头看好像很简单,但之前一直没有想到 😂 参考下面文章https://www.keithschwarz.com/darts-dice-coins/
2025年09月21日
5 阅读
0 评论
0 点赞
skynet原理
Skynet设计原理Skynet框架是属于一种多核并发编程模型,常见的多核并发模型有:多线程模型在一个进程中开启多线程,为了充分利用多核,一般设置工作线程的个数为 CPU的核心数;Memcached 就是采用这种方式;多线程在一个进程当中,所以数据共享来自进程当中的内存;这里会涉及到很多临界资源的访问,所以需要考虑加锁, 小粒度考虑使用自旋锁;多进程模型在一台机器当中,开启多个进程充分利用多核,一般设置工作进程的个数为 CPU的核心数;Nginx 就是采用这种方式;Nginx 当中的worker进程,通过共享内存来进行共享数据;也需要考虑使用锁;比如ngx_shmtx_t依次尝试使用自旋锁、信号量、文件锁。CSP模型CSP(Communicating Sequential Processes), 以 go 语言为代表,并发实体是协程(用户态线程、轻量级线程), 使用管道channel来通信;内部也是采用多少个核心开启多少个内核线程来充分利用多核;Actor模型Erlang 从语言层面支持 Actor 并发模型,并发实体是 Actor(在 Skynet中称之为服务),他们通过发送消息来相互通信;Skynet采用 C + Lua来实现 Actor 并发模型;底层也是通过采用多少个核心开启多少个内核线程来充分利用多核;总结 :不要通过共享内存来通信,而应该通过通信来共享内存;CSP 和 Actor 都符合这一哲学;通过通信来共享数据,其实是一种解耦合的过程;并发实体之间可以分别开发并进行单独优化,而它们唯一的耦合在于消息;这能让我们快速地进行开发;同时也符合我们开发的思路,将一个大的问题拆分成若干个小问题;Skynet框架主要就是Actor模块的封装。它是一个轻量级游戏服务器框架,而不仅仅用于游戏,还有金融、监控等;轻量级体现在:仅实现 actor 模型,以及相关的脚手架(工具集),如消息队列通信、调度、广播、资源共享等(lualib目录下的功能HTTP、Websocket、DB、Sharedata等也属于此列);实现了服务器框架的基础组件;实现了 reactor 并发网络库;并提供了大量连接的接入方案;基于自身网络库,实现了常用的数据库驱动(异步连接方案),并融合了Lua 数据结构;环境准备centos :yum install -y git gcc readline-devel autoconfubuntu :apt-get install git build-essential readline-dev autoconf # 或者 apt-get install git build-essential libreadline-dev autoconfMac:brew install git gcc readline autoconfActor模型有消息的 Actor 为活跃的 Actor,没有消息为非活跃的 Actor;Actor定义用于并行计算;可以理解为用户态轻量级进程。Actor 是最基本的计算单元和并发单元, 有自己的状态访谈变量和行为;基于消息计算,有自己的Mailbox消息队列、消息处理回调函数;Actor 通过消息进行沟通Actor的组成隔离的环境主要通过 Lua 虚拟机来实现;消息队列用来存放有序(先后到达)的消息;消息处理回调函数用来运行 Actor;从 Actor 的消息队列中取出消息,并作为该回调函数的参数来运行 Actor;Actor 创建skynet启动服务流程函数调用源码路径skynet.newservicelualib/skynet.luacommand.LAUNCHservice/launcher.lua (以下都在launcher服务中调用)skynet.launchlualib/skynet/manager.lualcommandlualib-src/lua-skynet.ccmd_launchskynet-src/skynet_server.cskynet_context_newskynet-src/skynet_server.cActor 底层关键接口// 对于snlua服务来说,用于创建隔离的环境 void * skynet_module_instance_create(struct skynet_module *m); // 用于设置初始回调函数和给自己发送初始化消息 int skynet_module_instance_init(struct skynet_module *m, void * inst, struct skynet_context *ctx, const char * parm); // 用于释放 Actor 对象 void skynet_module_instance_release(struct skynet_module *m, void *inst); // 用于处理 信号 消息,比如中断死循环 void skynet_module_instance_signal(struct skynet_module *m, void *inst, int signal);Actor 运行skynet.start会设置Actor消息回调函数,一个消息执行时会获取一个协程执行它。设置ctx->cb消息回调关键:函数源码路径skynet.startskynet.luac.callback(skynet.dispatch_message)skynet.lualcallbacklualib-src/lua-skynet.cskynet_callbackskynet-src/skynet_server.c一切从thread_worker函数开始运行,每个消息都会有一个协程来执行它。函数源码路径thread_workerskynet-src/skynet_start.cskynet_context_message_dispatchskynet-src/skynet_server.cskynet_mq_popskynet-src/skynet_mq.cdispatch_messageskynet-src/skynet_server.cctx->cbskynet-src/skynet_server.cskynet.dispatch_messageskynet.lua 以Lua消息为例skynet.raw_dispatch_messageskynet.luaco_createskynet.luaskynet.dispatch所注册的协程入口函数skynet.luaLua虚拟机有一个限制,同时只有一个协程在运行。所以写代码时以单线程思维方式在组织代码。内核线程取出消息队列,找到Lua虚拟机,从协程池取出一个协程来执行消息运行Actor 消息Actor 模型基于消息计算,在 Skynet 框架中,消息包含 Actor (之间)消息、网络消息以及定时消息;Actor 之间消息-- addr 对端服务的地址 -- typename 消息类型 actor内部间通常为 lua 类型消息 -- ... 为可变参 -- skynet.send是百分百会到达的, 异步消息,不等待对方反馈 skynet.send(addr, typename, ...) -- addr 对端服务的地址 -- typename 消息类型 actor内部间通常为 lua 类型消息 -- ... 为可变参 -- skynet.call就是发起一次远程调用,调用者会被挂起,等待对方回应后唤醒并处理返回值 -- 注意: -- 对端需要显示调用 skynet.ret(...) 回应 skynet.call 的请求 -- 或者通过调用 skynet.response() 延迟回应 skynet.call 的请求 skynet.call(addr, typename, ...)网络消息Skynet 当中采用一个 socket 线程来处理网络信息;Skynet 基于 reactor 网络模型;问题:网络当中获取数据,怎么知道FD数据传递到哪个服务(Actor)的消息队列当中去?// 在 linux 系统中,采用 epoll 来检测管理网络事件; int epoll_create(int size); int epoll_ctl(int epfd, int op, int fd, struct epoll_event* event); int epoll_wait(int epfd, struct epoll_event* events, int maxevents, int timeout); // epoll常见事件:EPOLLIN,EPOLLOUT,EPOLLHUP,EPOLLERR // 注意右边的就绪队列是事件队列,epoll_wait是从就绪队列中取事件通过epoll_ctl设置struct epoll_event中data.ptr = (struct socket *)ud;来完成fd与Actor绑定。有一个slot池,不和Redis一样用fd来创建大池。通过定义域相对较小的id创建预分配socket池。Lua层只能访问到slot idx来当作FDSkynet的Lua逻辑通过socket.start(fd, func)来完成Actor与FD的绑定定时消息定时器线程发送给Actor的消息。工作线程通过skynet_timeout函数调用 timer_add函数在时间轮加入新定时器。当时间到达时,在dispatch_list处理并调用skynet_context_push给工作线程压入超时消息。Skynet 采用多层级时间轮来解决多线程环境下定时任务的管理;时间复杂度为O(1) ;当定时任务被触发,将会向目标 Actor 发送定时消息,从而驱动 Actor 的运行;网络消息推送到 Actor函数源码备注skynet_socket_pollskynet-src/socket_server.csocket线程循环主要工作socket_server_pollskynet-src/socket_server.c处理外部客户端和内部管道数据forward_messageskynet-src/skynet_socket.c将消息推送到所属Actor消息队列ctrl_cmdskynet-src/socket_server.c处理work线程通过PIPE发送来数据sp_waitskynet-src/socket_epoll.h阻塞处理IO事件report_connectskynet-src/socket_server.c连接第三方服务 建立成功的标识report_acceptskynet-src/socket_server.c接收到客户端的连接,在这里可以绑定不同的client对应不同的服务forward_message_tcpskynet-src/socket_server.c读事件send_bufferskynet-src/socket_server.c写事件,把写缓存区发送出去Actor的调度工作线程流程全局单向队列和Actor消息队列都是先进先出,前者保证不饿死,后者保证顺序处理工作线程从全局队列中 pop 出单个 Actor 消息队列;从 Actor 消息队列中按照规则 pop 出一定数量的消息进行执行;若 Actor 消息队列中仍有消息继续放入全局队列队尾;若 Actor 消息队列中没有消息则不放入全局队列中;全局队列只存活跃的 Actor 消息队列;非活跃的Actor不在全局队列中。这个流程和Nginx线程池工作原理一致工作线程权重工作线程数量是按照 CPU 核心数来设置的;工作线程按照下面工作线程权重图来设置每个工作线程的权重;// 工作线程权重图,32核 static int weight[] = { -1, -1, -1, -1, 0, 0, 0, 0,// 前4个线程只消耗1个,后4个线程全部消耗 1, 1, 1, 1, 1, 1, 1, 1, // 一次消耗1/2个消息 2, 2, 2, 2, 2, 2, 2, 2, // 1/4 3, 3, 3, 3, 3, 3, 3, 3, }; // 1/8工作线程执行规则int i,n=1; for (i=0; i<n; i++) { // 注意: skynet_mq_pop pop出消息则返回0,没有pop消息返回1 if (skynet_mq_pop(q, &msg)) { skynet_context_release(ctx); return skynet_globalmq_pop(); } else if (i==0 && weight >= 0) {// 在执行第1次时就计算出本轮会分发几个消息 n = skynet_mq_length(q); n >>= weight;// n >> 0 = n , n >> 1 = n/2 } ... // 调用 actor 回调函数消费消息 dispatch_message(ctx, &msg); }从上面逻辑可以看出,当工作线程的权重为 -1 时,该工作线程每次只 pop 一条消息;当工作线程的权重为 0 时,该工作线程每次消费完所有的消息(防止消息多的Actor频繁线程切换);当工作线程的权重为 1 时,每次消费消息队列中 1/2的消息;当工作线程的权重为 2 时,每次消费消息队列中1/4 的消息;以此类推;通过这种方式,完成消息队列梯度消费,从而不至于让某些队列过长;这种消息调度的方式不是最优的调度方式(相较于 go 语言,Go可以绑定线程等),云风也在尝试修改更优的方式来调度;但是目前从多年线上实践情况来看,Skynet 运行良好;调度问题关注锁的使用,参考多核并发编程当中Nginx实现多个工作线程从全局消息队列中取次级消息队列,应该采用什么锁?自旋当 Skynet 全局消息队列节点很少的时候,怎么让多余的工作线程得到休眠?互斥+条件变量在问题 2 的基础上,如果此时全局消息队列节点很多后,怎么让休眠的工作线程得到唤醒?定时Skynet多个虚拟机共享函数原型、字符串等,实现一个虚拟机200KB左右内存。总体原理图关于并发和并行并行定义为一个时间点是有多个任务同时被处理。并发定义为一个时间段内多个任务被处理了。类似于一个CPU通过切换任务可以达到在一个时间段内处理多个队列。Skynet框架并发体现在:内部调度Actor时就是并发模型在socket数据分发使用的reactor模型多线程并行处理成千上万的Actor
2025年09月21日
6 阅读
0 评论
0 点赞
vue.js全家桶 读后感
整体感觉因为是带着问题阅读本书,基本感觉整本书都是在罗列非常具体的技术细节,缺少从更高维度看设计和框架性思考。并不是一个对前端新手友好的书。但这也不能否认讲到到一些vue基本知识。认真读后还是有一些收获。(也可能是因为是我找的第一本vue书籍)内容大概本书简单介绍vue环境和vue框架提供的一些关键特性,介绍了使用mock拦截请求来模拟服务器请求并实现了一个图书管理系统。这让我想起各种本科论文都是实现xxx系统的味道。当然也简单的写了项目webpack打包。收获vue就是一个前端框架,它的一切都跑在浏览器端。可配合element ui之类的库使用单页面应用的概念使用webpack进行js代码、png资源等等的打包,对.vue文件使用webpack的vue-loader插件。使用mock拦截自己本地请求,返回模拟的服务器数据。一些vue关键特性和加载流程图看了看豆瓣的连接,居然没有人评论,可见热度真不怎么好。https://book.douban.com/subject/35625681/
2024年04月14日
84 阅读
0 评论
0 点赞
文件bom头
有关于BOM,是Windows程序处理文本是常用,但linux不常用。BOM 是 byte-order mark 的缩写,是 "字节序标记" 的意思, 它常被用来当做标识文件是以 UTF-8、UTF-16 或 UTF-32编码的标记。在 Unicode 编码中有一个叫做 "零宽度非换行空格" 的字符 ( ZERO WIDTH NO-BREAK SPACE ), 用字符 FEFF 来表示对于 UTF-16 ,如果接收到以 FEFF 开头的字节流, 就表明是大端字节序,如果接收到 FFFE, 就表明字节流 是小端字节序UTF-8 没有字节序问题,上述字符只是用来标识它是 UTF-8 文件,而不是用来说明字节顺序的。"零宽度非换行空格" 字符 的 UTF-8 编码是 EF BB BF, 所以如果接收到以 EF BB BF 开头的字节流,就知道这是UTF-8 文件关于文件16进制查看方法,在linux下可以用hexdump $path_to_file命令查,windows下也可以用vim打开后,使用%!xxd 命令,本质上是调用了xxd程序进行转化。windows下也可以用vs code插件,需要安装Hex Editor插件,然后在vscode打开对应文件名页签处右键调用客户端js代码下载函数如下,关键的是在数据前面增加了 FFFE 前缀,浏览器下载后会变更EF BB BF 开头。export function downloadExportUtf8Csv(url, fileName) { request({url: url, method: 'get'}).then(response => { let data = "\ufeff" + response.data let blob = new Blob([data], {type:'text/csv,charset=UTF-8'}) let url = window.URL.createObjectURL(blob) let a = document.createElement("a") a.href = url a.download = fileName a.click() window.URL.revokeObjectURL(url) a.remove() }) }后续在node_module中的客户端库,FileSaver中也有自动转码。auto_bom = function(blob) { // prepend BOM for UTF-8 XML and text/* types (including HTML) // note: your browser will automatically convert UTF-16 U+FEFF to EF BB BF if (/^\s*(?:text\/\S*|application\/xml|\S*\/\S*\+xml)\s*;.*charset\s*=\s*utf-8/i.test(blob.type)) { return new Blob([String.fromCharCode(0xFEFF), blob], {type: blob.type}); } return blob; } FileSaver = function(blob, name, no_auto_bom) { if (!no_auto_bom) { blob = auto_bom(blob); } // ... }
2024年03月27日
68 阅读
0 评论
0 点赞
1
2
...
12