nginx發送靜態文件,速度極快,Nginx中的x-sendfile機制需要依靠 X-Accel-Redirect 特性實現,不過經過我的測試,不能滿足我的需求,我 要用lua來處理業務邏輯, 然後發送文件內容,一開始用如下方式來實現, 這種方式如果文件小, 到無所謂, 但是當文件很大時, 對性能的影響非常大。 [cpp] 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的框架。 [cpp] 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");//添加的內容 } 上面的代碼裡 lua_pushcfunction 就是把函數的指針壓入堆棧,lua_setglobal 就是用來把 "sendfile"壓入堆棧的, 並且設置的是全局函數,全局函數的話, 在lua裡調用就是直接 sendfile(), 如果是用 lua_setfield 來壓入堆棧的話, 那在lua裡就得用 ngx.sendfile () 這樣的形式來調用了。 反正是兩種都可以, 隨便你了。 下面貼出 ngx_http_lua_sendfile 函數的實現 : [cpp] 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的堆棧壓入任何參數, 而下面的代碼 [cpp] luaL_error(L, "no request object found"); return 1; 就代表壓入了一個參數, 所以返回值為1, 要不然會出現段錯誤。