使用libevent实现高速文件服务.
当收到HTTP请求后, 启动文件发送作业:
// 定义数据结构.
typedef struct http_file_sender_t http_file_sender_t;
struct http_file_sender_t
{
struct evhttp_request* req; ///< http请求句柄
struct evbuffer* evb; ///< 数据缓存
int fd; ///< 文件句柄
int eof; ///< 是否到达文件尾
int64_t start_time; ///< 开始发送的时间.
ssize_t data_offset; ///< 数据块偏移位置.
ssize_t data_bytes; ///< 从文件读取的数据长度
size_t data_cache_size; ///< data_cache 的长度
size_t length; ///< 总长度.
char* data_cache; ///< 数据块缓存. 最大长度为 FILE_CACHE_SIZE
char* filename; ///< 文件名.
};
// 准备文件发送任务.
http_file_sender_t* sender;
sender= (mdf_http_file_sender_t*)malloc(sizeof(mdf_http_file_sender_t));
sender->evb = evbuffer_new();
sender->fd = fd;
sender->req = req;
sender->data_bytes = 0L;
sender->data_offset = 0L;
sender->eof = FALSE;
// range_start和range_end分别是文件要发送数据的开始和结束位置
sender->length = range_end - range_start;
sender->start_time = start_time;
sender->filename = strdup(url);
// cache最大限定 (默认4MB)
if (sender->length > FILE_CACHE_SIZE)
{
sender->data_cache_size = FILE_CACHE_SIZE;
}
else
{
sender->data_cache_size = sender->length;
}
sender->data_cache = (char*)malloc(sender->data_cache_size);
assert(sender->data_cache);
lseek(fd, (off_t)range_start, SEEK_SET);
// 开始发送数据
if (range_start > 0L)
{
evhttp_send_reply_start(req, 206, "Partial Content");
}
else
{
evhttp_send_reply_start(req, HTTP_OK, NULL);
}
// 发送数据
http_file_send_chunk(NULL, sender);
其中http_file_send_chunk函数:
static void http_file_send_chunk(struct evhttp_connection* conn, void* arg)
{
http_file_sender_t* sender = arg;
// 没有数据就要读取.
if (sender->data_offset >= sender->data_bytes)
{
if (sender->eof)
{
http_file_sender_close(sender);
return;
}
// 从文件读取数据.
sender->data_bytes = read(sender->fd, sender->data_cache, sender->data_cache_size);
if (sender->data_bytes <= 0)
{
http_file_sender_close(sender);
return;
}
// 如果读取的长度不足一个CHUNK长度, 则任务已读完所有数据.
if (sender->data_bytes < FILE_CACHE_SIZE)
{
sender->eof = TRUE;
}
// 从0开始.
sender->data_offset = 0L;
}
if (sender->data_offset + FILE_CHUNK_SIZE <= sender->data_bytes)
{
evbuffer_add_reference(sender->evb,
sender->data_cache + sender->data_offset, // data
FILE_CHUNK_SIZE, // size
NULL, NULL);
sender->data_offset += FILE_CHUNK_SIZE;
}
else
{
evbuffer_add_reference(sender->evb,
sender->data_cache + sender->data_offset, // data
(size_t)(sender->data_bytes - sender->data_offset), // size
NULL, NULL);
sender->data_offset = sender->data_bytes;
}
assert(evhttp_request_get_connection(sender->req));
evhttp_send_reply_chunk_with_cb(sender->req, sender->evb, &mdf_http_file_send_chunk, sender);
}
一个HTTP服务线程可同时支持多个文件下载任务