nginx发送静态文件,速度极快,Nginx中的x-sendfile机制需要依靠 X-Accel-Redirect 特性实现,不过经过我的测试,不能满足我的需求,我 要用lua来处理业务逻辑, 然后发送文件内容,一开始用如下方式来实现, 这种方式如果文件小, 到无所谓, 但是当文件很大时, 对性能的影响非常大。
local file = io.open(filePath, "rb") local size = file:seek("end") ngx.header["Content-Length"] = size file:seek("set", 0) data = file:read("*a") ngx.print(data) ngx.flush(true) file:close()
众所周知, linux内核里有sendfile函数,可以0拷贝发送文件,于是在网上找资料,找到的都是同样的一个文章,都是介绍Nginx的 X-Accel-Redirect 如何使用的, 经过测试 X-Accel-Redirect ,不能满足我的需求。
介绍 X-Accel-Redirect 的官方文章地址为 : http://wiki.nginx.org/XSendfile
最后没办法, 只能从源码入手了。
参考了 ngx_http_static_module.c 这个发送静态文件的模块源码, 决定实现一个lua的接口, 可以让lua直接调用sendfile函数, 发送文件内容。
print 函数在 ngx_http_lua_log.c 中实现, 我也把sendfile函数放这里吧, 直接用 ngx_lua的框架。
void ngx_http_lua_inject_log_api(lua_State *L) { ngx_http_lua_inject_log_consts(L); lua_pushcfunction(L, ngx_http_lua_ngx_log); lua_setfield(L, -2, "log"); lua_pushcfunction(L, ngx_http_lua_print); lua_setglobal(L, "print"); lua_pushcfunction(L, ngx_http_lua_sendfile); //添加的内容 lua_setglobal(L, "sendfile");//添加的内容 }
下面贴出 ngx_http_lua_sendfile 函数的实现 :
static int ngx_http_lua_sendfile(lua_State *L) { u_char *last, *location; size_t root, len; ngx_http_request_t *r; ngx_str_t path; ngx_int_t rc; ngx_uint_t level; ngx_log_t *log; ngx_buf_t *b; ngx_chain_t out; ngx_open_file_info_t of; ngx_http_core_loc_conf_t *clcf; int offset; int bytes; char *filename; int nargs; lua_pushlightuserdata(L, &ngx_http_lua_request_key); lua_rawget(L, LUA_GLOBALSINDEX); r = lua_touserdata(L, -1); lua_pop(L, 1); if (r == NULL) { luaL_error(L, "no request object found"); return 1; } nargs = lua_gettop(L); filename = (char *) lua_tolstring(L, 1, &len); offset = lua_tonumber(L, 2); bytes = lua_tonumber(L, 3); log = r->connection->log; path.len = ngx_strlen(filename); path.data = ngx_pnalloc(r->pool, path.len + 1); if (path.data == NULL) { return 0; } (void) ngx_cpystrn(path.data, (u_char *) filename, path.len + 1); ngx_log_debug1(NGX_LOG_DEBUG_HTTP, log, 0, "ngx send lua filename: \"%s\"", filename); clcf = ngx_http_get_module_loc_conf(r, ngx_http_core_module); ngx_memzero(&of, sizeof(ngx_open_file_info_t)); of.read_ahead = clcf->read_ahead; of.directio = clcf->directio; of.valid = clcf->open_file_cache_valid; of.min_uses = clcf->open_file_cache_min_uses; of.errors = clcf->open_file_cache_errors; of.events = clcf->open_file_cache_events; if (ngx_http_set_disable_symlinks(r, clcf, &path, &of) != NGX_OK) { return 0;//NGX_HTTP_INTERNAL_SERVER_ERROR; } if (ngx_open_cached_file(clcf->open_file_cache, &path, &of, r->pool) != NGX_OK) { switch (of.err) { case 0: return 0;//NGX_HTTP_INTERNAL_SERVER_ERROR; case NGX_ENOENT: case NGX_ENOTDIR: case NGX_ENAMETOOLONG: level = NGX_LOG_ERR; rc = NGX_HTTP_NOT_FOUND; break; case NGX_EACCES: #if (NGX_HAVE_OPENAT) case NGX_EMLINK: case NGX_ELOOP: #endif level = NGX_LOG_ERR; rc = NGX_HTTP_FORBIDDEN; break; default: level = NGX_LOG_CRIT; rc = NGX_HTTP_INTERNAL_SERVER_ERROR; break; } if (rc != NGX_HTTP_NOT_FOUND || clcf->log_not_found) { ngx_log_error(level, log, of.err, "%s \"%s\" failed", of.failed, path.data); } return 0;//rc; } r->root_tested = !r->error_page; ngx_log_debug1(NGX_LOG_DEBUG_HTTP, log, 0, "http static fd: %d", of.fd); if (offset < 0) { offset = 0; } if (bytes <= 0) { bytes = of.size - offset; } #if !(NGX_WIN32) /* the not regular files are probably Unix specific */ if (!of.is_file) { ngx_log_error(NGX_LOG_CRIT, log, 0, "\"%s\" is not a regular file", path.data); return 0;//NGX_HTTP_NOT_FOUND; } #endif if (r->method & NGX_HTTP_POST) { return 0;//NGX_HTTP_NOT_ALLOWED; } rc = ngx_http_discard_request_body(r); if (rc != NGX_OK) { return 0;//rc; } log->action = "sending response to client"; len = (offset + bytes) >= of.size ? of.size : (offset + bytes); r->headers_out.status = NGX_HTTP_OK; r->headers_out.content_length_n = len - offset; r->headers_out.last_modified_time = of.mtime; if (ngx_http_set_content_type(r) != NGX_OK) { return 0;//NGX_HTTP_INTERNAL_SERVER_ERROR; } if (r != r->main && of.size == 0) { ngx_http_send_header(r); return 0;// } r->allow_ranges = 1; /* we need to allocate all before the header would be sent */ b = ngx_pcalloc(r->pool, sizeof(ngx_buf_t)); if (b == NULL) { return 0;//NGX_HTTP_INTERNAL_SERVER_ERROR; } b->file = ngx_pcalloc(r->pool, sizeof(ngx_file_t)); if (b->file == NULL) { return 0;//NGX_HTTP_INTERNAL_SERVER_ERROR; } rc = ngx_http_send_header(r); if (rc == NGX_ERROR || rc > NGX_OK || r->header_only) { return 0;//rc; } b->file_pos = offset; b->file_last = (offset + bytes) >= of.size ? of.size : (offset + bytes); b->in_file = 1; b->last_buf = (r == r->main) ? 1: 0; b->last_in_chain = 1; b->file->fd = of.fd; b->file->name = path; b->file->log = log; b->file->directio = of.is_directio; out.buf = b; out.next = NULL; ngx_http_output_filter(r, &out); return 0;// }
sendfile函数的参数共有三个, 第一个参数为文件名filename。 第二个参数为文件偏移量offset, offset<0代表从文件头开始发送。 第三个参数为bytes, 要发送的字节数,如果bytes<0, 代表发送到文件尾。
这样在 lua 脚本里就可以这样调用了:
sendfile("/opt/f1.ts", -1,-1) 发送整个文件
或者
sendfile("/opt/f1.ts", 104857600,104857600) 从100MB开始的地方发送100MB的数据
经过测试, 速度和直接nginx发送静态文件的速度一致。
在添加该功能时,碰到过一些小问题,记录下来。
ngx_lua里的C函数返回值代表压入堆栈的返回值的个数, 看我上面的代码, 基本都返回0, 代表我没有给lua的堆栈压入任何参数, 而下面的代码
luaL_error(L, "no request object found"); return 1;就代表压入了一个参数, 所以返回值为1, 要不然会出现段错误。
另外需要明白 lua 与 C交换数据的堆栈原理, 可以参考下面的文章 :
http://blog.csdn.net/yhhwatl/article/details/9303675
这篇文章把堆栈介绍的很明白。
谢谢诸位,晚安。