【原创代码分享】如何正确的嵌入lua到c服务端中工作。(linux服务端方向)
回馈一下互联网事业,因为lua, erlang(接下来我要啃它)这些优秀的语言都是有鲜明特色的,在服务端方向持续钻研跟潮的同学都会从这些语言中得到思想的启迪,但这些语言又很少有优秀的资源分享出来供大家学习,因为我掌握了一些语言灵魂上的东西,所以分享到互联网上让其他爱好者能够更快的掌握。
如题, 代码做的事情: 将lua嵌入到了c语言编写的网络服务端中,支持yield异步API的编写,安全的管理会话与lua资源。
工作原理:
一,c服务端框架:
1)一个session对应一个coroutine,所有session共享同一个main thread。coroutine的引用计数在main thread的一个table中维持, 即fd->coroutine的映射。
2)负责网络I/O,负责request的解析并将其加入到所属session的request-queue尾部。根据session(连接,会话)的request-queue是否为空并处于coroutine yield的状态(wait_req标记位)决定是否需要调用lua_resume唤醒lua处理request-queue。
3)每个session都有独立的定时器检查coroutine状态,如果coroutine因为语法等任何错误而死亡,能够及时的探测并踢除session的连接。
二,lua扩展框架中:
1)用户编写main.lua,填写业务回调callback函数, 之后调用start_consume即进入了请求处理循环。
2)start_consume将检查request queue来获取一个request,如果没有request则yield挂起等待c框架唤醒,否则将request传给用户callback进行处理。
3)并提供给了用户一些lua api进行编程, 现在只提供了一个发送报文的接口以及定时器, 后续会提供 建立连接/读/写 等异步编程接口.
特别说明:
为了展示lua给c服务端开发带来了什么改变, 我开发了一个lua api叫做block_alarm,其作用是阻塞N毫秒之后执行回调函数,这对于lua开发者来说是看似阻塞的,但对于c服务端框架来说是完全异步的,并不会阻塞其他连接的并发处理,其中透露出lua coroutine的鲜明特色。
读者群体:
lua的协程(coroutine)的益处, 对于拥有编写过复杂状态机服务端(例如nginx, lighttpd等类似插件与busy loop架构服务端)经验的程序员会有极大的共鸣,协程带来的不仅是更少的cpu损耗, 而是将c语言中的维护状态机的工作转移给了coroutine,让程序扩展性与简洁性都得到了极大的提高, 解放了复杂状态机服务的复杂度。
本代码对上述经历的程序员将有不少指导意义,而对于很多希望涉足游戏开发职位的同学也是必不可少的编程技能之一,争取做一个开发lua server框架而不是只会lua语法的专业游戏开发人员吧。
程序说明:
开发人员在此框架下,只需要关注请求的处理与应答,即使用lua语法编写请求处理脚本,需要使用框架提供的lua api实现功能(即完成与c服务框架的交互)。
这里举了一个最基本的例子,演示了lua coroutine对于慢处理请求的异步化能力:
sapi = require('sapi') require('os') function cb(sess, req) first = "curtime=" .. tostring(os.time()) .. " req=" .. req sapi.write_response(sess, first) function alarm_cb(arg) second = "curtime=" .. tostring(os.time()) .. " res=" .. req sapi.write_response(sess, second) end sapi.block_alarm(sess, 4000, alarm_cb, req) end sapi.start_consume(session, cb)
我编写了一个请求处理函数叫做cb,传入给sapi.start_consume就可以由框架完成剩余的处理。 在cb里,我先向客户端写回了first这行应答,然后调用阻塞定时器睡眠4000毫秒,并在定时器结束时向客户端写回second这行应答。其中,block_alarm虽然将lua脚本的执行挂起了,但对于c框架来说这是完全异步的,不会阻塞其他客户端请求的处理(coroutine的灵魂在这里)。
可以看到上面使用到了sapi这个module,当前只实现了lua入口接口,一个应答接口,以及一个演示coroutine特性的block_alarm接口。
module('sapi', package.seeall) --session api local capi = require('capi') --get a request from session->reqs if not empty, --otherwise it will do yield. --you have to notice that we are already in coroutine here function get_req(session) req = capi._get_req(session) while req == nil do coroutine.yield() req = capi._get_req(session) end return req end function write_response(session, content) return capi._write_response(session, content) end function block_alarm(session, timeout, callback, arg) capi._block_alarm(session, timeout) coroutine.yield() callback(arg) end function start_consume(session, cb) while true do req = get_req(session) cb(session, req) end end
可以看到这个lua module中又使用了capi module,这是一个c语言编写的lua module,用于与c服务端进行交互,而sapi module是一个lua编写的module,作为capi的wrapper(技术限制:因为在lua中yield可以保存堆栈,而在c里做yield只在5.2后实现, 并且会破坏C堆栈),这是市面上lua与c交互基本公知的组织形式。
协议的解析在C中实现,目前支持telnet的行请求形式,即只要通过telnet连接server即可进行交互。另外,编译代码前别忘了改一下Makefile里的lua相关的依赖路径,别忘了编译liblua.a时修改Makefile增加-fPIC编译选项(写lua扩展必备),另外也别忘了在启动服务前export一下你的LUA_PATH和LUA_CPATH,可以参考envrion.sh。
上面我编写的cb实际执行效果是什么样呢,看个截图吧:
终端1
终端2
终端3
继续在telnet上折腾它吧,下载地址:http://pan.baidu.com/share/link?shareid=431706&uk=2686094642
即便你不懂lua,但对服务端开发有兴趣,读一下util下的simple_io网络事件库吧:)
文件组织:
[root@down lua_frame]# find . -type d
.
./script
./script/src
./src
./include
./util
./util/queue
./util/queue/test
./util/queue/src
./util/queue/include
./util/minheap
./util/minheap/test
./util/minheap/src
./util/minheap/include
./util/simple_io
./util/simple_io/test
./util/simple_io/src
./util/simple_io/include