发布于 2015-01-04 09:05:57 | 680 次阅读 | 评论: 0 | 来源: PHPERZ
aLiLua 高性能Web服务器
aLiLua 是一套基于 epoll/kqueue/Lua 构建的网络服务开发框架
aLiLua 使用 epoll 进行网络/文件IO事件读写,对Lua协程进行调度,其性能高效并且简单
Lua 语言就像 PHP 那么简单容易理解
本文为大家提供的是一份aLiLua撰写的入门手册,非常实用,感兴趣的同学参考下。
aLiLua 是一套基于 epoll/kqueue/Lua 构建的网络服务开发框架
aLiLua 使用 epoll 进行网络/文件IO事件读写,对Lua协程进行调度,其性能高效并且简单
Lua 语言就像 PHP 那么简单容易理解
一个页面代码(index.lua):
header('HTTP/1.1 200 OK')
local sock = cosocket.tcp()
local r,e = sock:connect(host, port)
r, e = sock:send(...)
r, e = sock:read(...)
echo(r)
一个Lua脚本例子(cotest.lua):
local L = require('coevent') --同时引入 cosocket
L(function() --切换程序主体到 epoll loop,并执行 Lua 代码片段
-- 异步IO操作
local sock = cosocket.tcp()
local r,e = sock:connect(host, port)
r, e = sock:send(...)
r, e = sock:read(...)
print(r, e)
end)
--如把异步IO操作放在 epoll loop 所运行的代码块之外是错误的
local sock = cosocket.tcp()
local r,e = sock:connect(host, port) -- 错误,并会结束执行
运行该脚本:
$lua cotest.lua
其实 aLiLua 中的 cosocket 也同样依赖 coevent-module 的支持,但因程序架构不一样,aLiLua-coevent-module 模式下因为运行主体在 Lua 上,这样一来 epoll loop 就无法进行异步IO的处理,所以我们专门提供了一个接口方法,以便把程序运行主体切换到 epoll loop 上,如:L = require('coevent') 切换方法就是 L(),当然也可以用其他名字。而操作异步IO的Lua脚本需要 epoll loop 对其进行调度,所以必须把代码片段传递到 epoll loop,让其以协程的形式执行
aLiLua 模式下运行主体是 epoll loop,所有 Lua 代码均以协程的形式执行,所以不需要切换运行主体
$tar zxf alilua-*.tar.gz
$cd alilua-*
$make install clean
如需支持 luajit 请用以下 make 命令 (v0.4 开始已内置 luajit 无需单独安装,并不再支持 Lua 官方版本)
$sudo make install LUAJIT=/usr/local/lib
启动方式
$/usr/local/alilua/alilua process=4 bind=8080 daemon
process 启动多个进程 (建议以 CPU 核心数量进行设置)
bind 绑定IP端口 bind=127.0.0.1:8080
daemon 以守护进程模式运行
log 错误日志文件
accesslog 访问日志文件
host-route 虚拟主机的路由脚本
app 默认主机的脚本
$tar zxf alilua-coevent-module-*.tar.gz
$cd alilua-coevent-module-*
$make install clean
使用方式:
在 Lua 脚本中 require('coevent')
$lua 脚本.lua
也支持 luajit
$luajit 脚本.lua
*注: 以下方法仅在 aLiLua 模式下被支持
语法: header(string|table)
向客户端输出 HTTP 头信息,该方法只支持一个调用参数,但参数类型可以是 string 或一个包含 string 的 table
如:
header('HTTP/1.1 200 OK')
header({'HTTP/1.1 200 OK', 'Content-Type: text/html'})
语法: echo(string|table)
向客户端输出 HTTP 头信息,该方法支持多个调用参数,如只传入一个参数,那么参数类型可以是 string 或一个包含 string 的 table
如:
echo('hello world')
echo('hello', 'world')
echo({'hello', 'world'})
语法: die(string|table)
结束当前页面处理逻辑并输出结果到客户端,如有传入参数将转入 echo 方法处理
语法: sendfile(path)
向客户端发送文件内容,如发送的是文本文件且客户端支持 gzip 压缩协议则会自动压缩后再发送
语法: setcookie( name [, value [, expire = 0 [, path [, domain [, bool secure [, bool httponly]]]]]]
定义一个 cookie 和 header 一起发送到浏览器。如 value 为 nil 则表示删除某个 cookie
语法: t = session()
如果是客户端第一次请求将自动生成一个 session table,可以在该 table 中设置具体内容,并会自动存储于 aLiLua server 的共享内存中。以备第二次请求来访时可使用。如:
local _SESSION = session() --如是第一次访问,会自动创建
print(_SESSION.name) --如是第一次访问,_SESSION.name == nil
_SESSION.name = 'hello' --对 table 进行任何修改都将自动存储于共享内存中
session 数据最长可存储 1800 秒,这具体因缓存大小而决定,如开设的缓存大小不足以存放全部数据,会自动删除少访问或时间过长的数据
注意: aLiLua server 并不对 session 数据在并发设置的情况下提供一致性支持
访问请求的头信息,如:
headers = {
'accept-language'='zh-CN,zh;q=0.8',
'connection'='keep-alive',
'accept'='text/html,application/xhtml+xml',
'accept-encoding'='gzip,deflate,sdch',
'host'='localhost',
'method'='GET',
'uri'='/',
'query'='key=value'
'cache-control'='max-age=0',
'user-agent'='Mozilla/5.0 Chrome/28.0.1500.36 Safari/537.36',
}
访问请求 URL 上的变量参数,如:
-- GET http://localhost/?key=value&key2=2
_GET = {
'key' = 'value',
'key2' = '2',
}
访问请求中的cookies变量参数,如:
-- curl -H 'Cookie: key=value; key2=2;' http://localhost/
_COOKIE = {
'key' = 'value',
'key2' = '2',
}
POST表单中的变量参数,如:
-- curl -d 'key=value&key2=2' http://localhost/
_POST = {
'key' = 'value',
'key2' = '2',
}
_POST 变量参数支持普通表单 x-www-form-urlencoded 格式和 multipart/form-data 文件上传的格式。表单的文件域则以table形式访问,如:
{
'key' = 'value',
'file field' = {
'filename' = 'text.txt',
'type' = 'text/paint',
'size' = 10,
'data' = 'abcdefghij',
}
}
语法: get_post_body()
获取该请求的原始内容(注意: 已被解析的表单请求无法获取原始内容),如:
-- PUT http://localhost/text.txt
local body = get_post_body()
print(body) -- == 'abcdefghij'
语法: jsonrpc_handle(json, api table)
提供 Json RPC 接口服务
例子:
--File: /api-demo.lua
local apis = {
demo = { -- json-rpc v2.0
hello = function(a, b)
return a+b
end,
},
hello = function(a, b) -- json-rpc v1.0
return a+b
end,
}
jsonrpc_handle(json_decode(get_post_body()), apis)
JavaScript 调用例子:
$.jsonRPC.setup({endPoint: '/api-demo'});
$.jsonRPC.request('hello', {
params: [123, 456],
success: function(result) {console.log(result.result);},
error: function(result) {console.log('error');}
});
语法: cok, err = cosocket.tcp([ssl = true])
创建一个TCP连接,并提供以下操作方法:
如需创建 ssl 连接,请传入 boolean 参数 true
语法: ok, err = cok:connect(host, port, [pool_size, 'pool_key'])
连接到服务器,支持IP连接也支持 UNIX Domain Socket 连接。cosocket 默认所有连接是持久的,并支持连接池功能,如需开启连接池可在 connect 方法调用时传入 pool_size 连接池大小即可。但对于 MySQL 类对连接有身份验证要求,则可设置 pool_key 连接池组名来避免错误分配
注: 连接池中的连接生命时间为60秒,会自动关闭过期的连接
语法: ok, err = cok:send(data)
在当前连接发送数据,调用参数支持 string 和 table 或多个 string 参数
语法: data, err = cok:receive(pattern?)
在当前连接读取数据,调用参数可以是 'l', 'a' 或 大小(数字)
'*l': 读取一行数据,以 \n 分割(或者是连接已断开,缓冲区中剩余且不带\n分割的数据)
'*a': 读取缓冲区中的数据,如缓冲区为空则发起一次网络读取的系统操作,协程休眠到数据返回时
num: 读取指定字节大小的数据,如连接已断开则可能返回少于指定的读取大小
**如不指定参数,默认以 'a'读取缓冲区数据的模式执行
语法: ok, err = cok:close()
关闭当前连接,如该连接有设置连接池则会放入连接池内,并不会真正关闭
当然这个操作不是必须的,当该连接所在的协程结束也将自动关闭
语法: cok:settimeout(sec)
设置连接超时(单位: 秒),请在创建连接后设置超时时间,超时时间对网络连接、读取和写入都有效
**注: 连接超时时间是 settimeout 设定值的一半
语法: cok:setkeepalive(size, ['pool key']) 设置长连接的连接池大小和组名
语法: websocket_accept([function])
把当前请求升级为 WebSocket。可传入 function 作为事件循环的协程。
语法: r,err = websocket_send(string [, typ] [,fin])
向客户端发送消息。注意: 检查 err 值,因一个 WebSocket 不允许同时发送消息,如遇到错误,请再次发送
typ 为 boolean 值,typ == true 表示发送二进制数据 fin 为 boolean 值,用于发送连续多帧操作
语法: function on(data ,typ ,fin ) ... end
typ 为 int 值,1 表示 data 是文本数据,2 为二进制数据。 typ = 0 则表示此为中间帧,数据并未完全接收完整,需等待 fin = 1 的结束帧
fin 为 boolean 值, true 表示此帧为结束帧,数据已完成接收
接收客户端传来的消息
function on(data) --接收客户端消息,注意: 必须声明在 websocket_accept 调用之前
websocket_send('['..data..']') --向客户端发送消息
end
function loop() --事件循环,可以不断的向客户端发送消息
while true do
websocket_send('text')
sleep(1)
--发送一个多帧操作
websocket_send(data_part_1, true, false) -- fin = 0
websocket_send(data_part_2)
websocket_send(data_part_3)
-- ... 可重复多次发送
websocket_send(data_part_n, true, true) -- fin = 1 表示结束
end
end
--升级为 WebSocket,注意: 为保障安全,你需要先检验 cookie 或 session 以判断是否应该接收该请求
websocket_accept(loop)
语法: newthread(function)
创建一个协程并运行,使用该方法创建协程才能保障数据交互不会混乱
语法: wait(thread|table)
等待协程结束,并获取返回结果。该方法支持多种协程传递方式,如:
local t1 = newthread(function)
local t2 = newthread(function)
local t2 = newthread(function)
-- 方式 1
local rt, er = wait(t1)
local rt, er = wait(t2)
local rt, er = wait(t3)
-- 方式 2
local rts = wait(t1, t2, t3)
rt, er = rts[1][1], rts[1][2]
-- 方式 3
local rts = wait({t1, t2, t3})
rt, er = rts[2][1], rts[2][2]
-- 对于方式 2 和 3,所获取到的协程结果以 table 的形式返回
语法: swop()
短暂睡眠 (大概 0.001秒),使得大的循环体中可以把 CPU 运行权限交予 epoll loop 处理异步IO事件,以避免堵塞
如:
local j = 0
for i=1,100000 do
swop()
j = j + 1
end
语法: s = trim(string)
语法: s = string:trim()
去除头尾空字符
语法: r = string:startsWith(prefix [, true])
判断字符串是否以某字符串开头。第二个参数为 true 表示不区分大小写
去除头尾空字符
语法: r = string:endsWith(suffix [, true])
判断字符串是否以某字符串结尾。第二个参数为 true 表示不区分大小写
语法: s = strip(html)
去除HTML标记
语法: s = md5(string)
对字符串进行 md5 加密
语法: s = iconv(string, form charset [, to charset])
转换字符串编码(to charset 默认为 utf-8)
语法: len = iconv_strlen(string, charset)
获取字符串长度(charset 默认为 utf-8)
语法: str = iconv_substr(string, start [, length [, charset]])
截取字符串(charset 默认为 utf-8)
如 length 为负数,则表示截取到字符串长度向前移 n 位
转换字符串编码
语法: s = escape(string)
对特殊字符进行编码,NULL (ASCII 0), \n, \r, \, ', ", 和 Control-Z
语法: s = escape_uri(string)
对字符串进行 URL 编码
语法: s = unescape_uri(string)
对字符串进行反 URL 编码
语法: s = base64encode(string)
对字符串进行 base64 编码
语法: s = base64decode(string)
对字符串进行 base64 反编码
语法: s = json_encode(value)
对对象进行 JSON 格式编码
语法: t = json_decode(string)
对字符串进行 JSON 格式反编码
语法: s = nl2br(string)
把 \n 换行符转换为 HTML 代码 <br/>
语法: t = explode(string, regex)
对字符串以 regex 中的字符进行切分,支持多个字符,就像 strtok。如:
local t = explode('aaa:bbb;ccc' , ':;')
-- t = {'aaa', 'bbb', 'ccc'}
语法: s = implode(table, sep)
对 table 中的内容进行拼接。如:
local s = implode({'aaa','bbb', 'ccc'}, ':')
-- s = 'aaa:bbb:ccc';
语法: s = random_string([length])
产生一个随机字符串,如不设置 length 默认产生一个由 32 个 [0~9|a-f] 字符组成的字符串。(length > 0 && length < 4096)
语法: printf(formatstring, [values ...])
输出具有特定格式的字符串, 函数的第一个参数是格式(formatstring), 之后是对应格式中每个代号的各种数据. 由于格式字符串的存在, 使得产生的长字符串可读性大大提高了. 这个函数的格式很像C语言中的printf().函数string.format在用来对字符串进行格式化的时候,特别是字符串输出,是功能强大的工具。 这个函数有两个参数,你完全可以照C语言的printf来使用这个函数。第一个参数为格式化串:由指示符和控制格式的字符组成。指示符后的控制格式的字符 可以为:十进制'd';十六进制'x';八进制'o';浮点数'f';字符串's'。在指示符'%'和控制格式字符之间还可以有其他的选项:用来控制更详 细的格式,比如一个浮点数的小数的位数:
格式字符串可能包含以下的转义码:
%c - 接受一个数字, 并将其转化为ASCII码表中对应的字符
%d, %i - 接受一个数字并将其转化为有符号的整数格式
%o - 接受一个数字并将其转化为八进制数格式
%u - 接受一个数字并将其转化为无符号整数格式
%x - 接受一个数字并将其转化为十六进制数格式, 使用小写字母
%X - 接受一个数字并将其转化为十六进制数格式, 使用大写字母
%e - 接受一个数字并将其转化为科学记数法格式, 使用小写字母e
%E - 接受一个数字并将其转化为科学记数法格式, 使用大写字母E
%f - 接受一个数字并将其转化为浮点数格式
%g(%G) - 接受一个数字并将其转化为%e(%E, 对应%G)及%f中较短的一种格式
%q - 接受一个字符串并将其转化为可安全被Lua编译器读入的格式
%s - 接受一个字符串并按照给定的参数格式化该字符串
语法: s = sprintf(formatstring, [values ...])
生成具有特定格式的字符串,具体可参考上面的 printf
语法: s = dump(obj [,true])
遍历对象并返回构造字符串,或打印输出。
语法: r = cache_set(key, value)
添加一条新缓存记录,返回 true or false。value 值可以是 string / number / boolean 或 table 类型
语法: r = cache_get(key)
获取一条缓存记录,返回内容或 nil
语法: r = cache_del(key)
删除一条缓存记录,返回 true or false
语法: rts, err = dotemplate(html file [, true])
渲染模板文件并输出,如第二参数为 true 则返回 HTML 内容
HTML 模板例子:
<body>
{{if user then}}
Hi, {{=user}}.
{{else}}
<a href="/login">Login</a>
{{end}}
#for loop
{{for k,v in ipairs(datas) do}}
{{=v.name}}<br/>
{{end}}
</body>
{{include footer.html}}
语法: hook(target fun, func)
把一个函数追加到目标函数结束时运行,业务函数中如有返回值将自动替换原始函数的返回内容
例子:
function tf(p1)
return p1*2
end
hook(tf, function(rt1) -- hooker
print(rt1) -- 2
end)
hook(tf, function(rt1) -- filter
return rt1*2
end)
print(tf(1)) -- 4
语法: r = is_dir('/a')
判断路径是否为目录,返回 true 表示是目录,或 false
语法: r = is_file('/a')
判断路径是否为文件,返回 true 表示是文件,或 false
语法: r = mkdir('/a')
创建目录,返回 true 表示创建成功,或 nil
语法: r = rmdie('/a')
删除目录,返回 true 表示删除成功,或 nil
语法: t = readdir('/a')
读取目录下的文件列表,以 table 类型返回
语法: infos = stat('/a')
获取文件信息,以 table 类型返回
语法: r = unlink('/a')
删除文件,返回 true 为成功
具体可参阅扩展官网文档 [http://luacrypto.luaforge.net/manual.html#reference]