ngxin将磁盘文件作为包体发送

文章介绍了Nginx如何利用sendfile系统调用高效发送文件,避免了将文件全部加载到内存中,同时讨论了如何处理文件句柄的清理以防止资源泄露。此外,文章还阐述了Nginx对HTTPRange头的支持,允许多线程下载和断点续传功能。
摘要由CSDN通过智能技术生成

背景

发送文件时完全可以先把 文件读取到内存中再向用户发送数据,但是这样做会有两个缺点:

·为了不阻塞Nginx,每次只能读取并发送磁盘中的少量数据,需要反复持续多次。

·Linux上高效的sendfile系统调用不需要先把磁盘中的数据读取到用户态内存再发送到 网络中

 ngxin文件

typedef struct ngx_file_s ngx_file_t; struct ngx_file_s {
// 文件句柄描述符
ngx_fd_t fd;
// 文件名称
ngx_str_t name;
// 文件大小等资源信息,实际就是Linux系统定义的stat结构
ngx_file_info_t info;
/*该偏移量告诉
Nginx现在处理到文件何处了,一般不用设置它,
Nginx框架会根据当前发送状态设置它
*/
off_t offset;
// 当前文件系统偏移量,一般不用设置它,同样由Nginx框架设置
off_t sys_offset;
// 日志对象,相关的日志会输出到log指定的日志文件中
ngx_log_t *log;
// 目前未使用
unsigned valid_info:1;
// 与配置文件中的directio配置项相对应,在发送大文件时可以设为1
unsigned directio:1;
};

note:

1.fd是打开文件的句柄描述符,打开文件这一步需要用户自己来做。Nginx简单封装了一个宏用来代替open系统的调用

#define ngx_open_file(name, mode, create, access) \
open((const char *) name, mode|create, access)

文件标志位:

#define NGX_FILE_RDONLY O_RDONLY
#define NGX_FILE_WRONLY O_WRONLY
#define NGX_FILE_RDWR O_RDWR
#define NGX_FILE_CREATE_OR_OPEN O_CREAT
#define NGX_FILE_OPEN 0
#define NGX_FILE_TRUNCATE O_CREAT|O_TRUNC
#define NGX_FILE_APPEND O_WRONLY|O_APPEND
#define NGX_FILE_NONBLOCK O_NONBLOCK
#define NGX_FILE_DEFAULT_ACCESS 0644
#define NGX_FILE_OWNER_ACCESS 0600

示例:

//开辟缓冲区
ngx_buf_t *b;
b = ngx_palloc(r->pool, sizeof(ngx_buf_t)); 

//初始化文件
u_char* filename = (u_char*)"tmptest.txt"; 
b->in_file = 1;
b->file = ngx_pcalloc(r->pool, sizeof(ngx_file_t));
b->file->fd = ngx_open_file(filename, NGX_FILE_RDONLY|NGX_FILE_NONBLOCK, NGX_FILE_OPEN, 0); 
b->file->log = r->connection->log;
b->file->name.data = filename;
b->file->name.len = strlen(filename); 
//文件初始化失败
if (b->file->fd <= 0)
{
return NGX_HTTP_NOT_FOUND;
}

note:

ngx_buf_t有一个标 志位in_file,将in_file置为1就表示这次ngx_buf_t缓冲区发送的是文件而不是内存。调用 ngx_http_output_filter后,若Nginx检测到in_file为1,将会从ngx_buf_t缓冲区中的file成员处获 取实际的文件

设置缓冲区:

//判断是否获取文件信息

if (ngx_file_info(filename, &b->file->info) == NGX_FILE_ERROR) {
return NGX_HTTP_INTERNAL_SERVER_ERROR; }

//设置headers_out的长度
r->headers_out.content_length_n = b->file->info.st_size

//告诉Nginx从文件的file_pos偏移量开始发送文件,一直到达file_last偏移量处截止
b->file_pos = 0;
b->file_last = b->file->info.st_size;

note

1.:ngxin框架封装linux中的文件系统stat方法

typedef struct stat ngx_file_info_t;
#define ngx_file_info(file, sb) stat((const char *) file, sb)

 2.当磁盘中有大量的小文件时,会占用Linux文件系统中过多的inode结构,这 时,成熟的解决方案会把许多小文件合并成一个大文件。在这种情况下,当有需要时,只要 把上面的file_pos和file_last设置为合适的偏移量,就可以只发送合并大文件中的某一块内容 (原来的小文件),这样就可以大幅降低小文件数量

清理文件句柄

Nginx会异步地将整个文件高效地发送给用户,但是我们必须要求HTTP框架在响应发送 完毕后关闭已经打开的文件句柄,否则将会出现句柄泄露问题

使用ngx_pool_cleanup_t(Nginx源码分析--内存池_编程界的谢菲尔德的博客-CSDN博客

1: struct ngx_pool_cleanup_s {
   2:     ngx_pool_cleanup_pt   handler;
   3:     void                 *data;
   4:     ngx_pool_cleanup_t   *next;
   5: };

专用来关闭文件句柄的函数

void ngx_pool_cleanup_file(void *data) 
{
ngx_pool_cleanup_file_t *c = data; 
ngx_log_debug1(NGX_LOG_DEBUG_ALLOC, c->log, 0, "file cleanup: fd:%d",c->fd); 

if (ngx_close_file(c->fd) == NGX_FILE_ERROR) 
{
ngx_log_error(NGX_LOG_ALERT, c->log, ngx_errno, 
ngx_close_file_n " \"%s\" failed", c->name);
}


}

note

从上面的实现中可以看出, ngx_pool_cleanup_file方法需要一个ngx_pool_cleanup_file_t类型的参数

typedef struct {
// 文件句柄
ngx_fd_t fd;
// 文件名称
u_char *name;
// 日志对象
ngx_log_t *log;
} ngx_pool_cleanup_file_t

总体清理:

//ngx_pool_cleanup_add用于告诉HTTP框架,在请求结束时调用cln的handler方法清理资源。

ngx_pool_cleanup_t* cln = ngx_pool_cleanup_add(r->pool, sizeof(ngx_pool_cleanup_file_t)); 
if (cln == NULL) {
return NGX_ERROR;
}
cln->handler = ngx_pool_cleanup_file; 
//初始化clnf 
ngx_pool_cleanup_file_t *clnf = cln->data;
clnf->fd = b->file->fd;
clnf->name = b->file->name.data;
clnf->log = r->pool->log;

支持用户多线程下载和断点续传

r->allow_ranges = 1;

 Nginx对range协议的支持非常好,因为range协议主要增加了一些HTTP头部处理流程,以 及发送文件时的偏移量处理Nginx设计了HTTP过滤模块,每一个请求 可以由许多个HTTP过滤模块处理,而http_range_header_filter模块就是用来处理HTTP请求头 部range部分的,它会解析客户端请求中的range头部,最后告知在发送HTTP响应包体时将会 调用到的ngx_http_range_body_filter_module模块,该模块会按照range协议修改指向文件的 ngx_buf_t缓冲区中的file_pos和file_last成员,以此实现仅发送一个文件的部分内容到客户 端

拓展阅读

range协议

它给出了一种规则使得客户端可以在一次请求中只下 载完整文件的某一部分,这样就可支持客户端在开启多个线程的同时下载一份文件,其中每 个线程仅下载文件的一部分,最后组成一个完整的文件。range也支持断点续传,只要客户端 记录了上次中断时已经下载部分的文件偏移量,就可以要求服务器从断点处发送文件之后的内容

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值