基于C語言實(shí)現(xiàn)http下載器
C語言實(shí)現(xiàn)http的下載器。
例:做OTA升級功能時(shí),我們能直接拿到的往往只是升級包的鏈接,需要我們自己去下載,這時(shí)候就需要用到http下載器。
這里分享一個(gè):
功能
1、支持chunked方式傳輸?shù)南螺d
2、被重定向時(shí)能下載重定向頁面
3、要實(shí)現(xiàn)的接口為int http_download(char *url, char *save_path)
思路
1、解析輸入的URL,分離出主機(jī),端口號,文件路徑的信息
2、解析主機(jī)的DNS
3、填充http請求的頭部,給服務(wù)器發(fā)包
4、解析收到的http頭,提取狀態(tài)碼,Content-length, Transfer-Encoding等字段信息
(1)如果是普通的頭則進(jìn)行接下來的正常收包流程
(2)如果狀態(tài)碼為302,則從頭里提取出重定向地址,用新的地址重新開始下載動作
(3)如果傳送方式是chunked的,則進(jìn)行分段讀取數(shù)據(jù)并拼接
(4)如果是404或其他狀態(tài)碼則打印錯(cuò)誤信息
缺陷
太多錯(cuò)誤處理,讓代碼看起來不太舒服
其他
1、如何移植到?jīng)]有文件系統(tǒng)的系統(tǒng)中?
修改sava_data接口里面的保存就好了
2、如何提高下載速度?
增大讀寫buffer緩沖區(qū)
改為多線程,使用Range字段分段讀取,最后再拼在一起
代碼
/************************************************************
Copyright (C), 2016, Leon, All Rights Reserved.
FileName: download.c
coding: UTF-8
Description: 實(shí)現(xiàn)簡單的http下載功能
Author: Leon
Version: 1.0
Date: 2016-12-2 10:49:32
Function:
History:
<author> <time> <version> <description>
Leon
************************************************************/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <arpa/inet.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <fcntl.h>
#include <unistd.h>
#include <netdb.h>
#include <errno.h>
#define HOST_NAME_LEN 256
#define URI_MAX_LEN 2048
#define RECV_BUF 8192
#define RCV_SND_TIMEOUT (10*1000) //收發(fā)數(shù)據(jù)超時(shí)時(shí)間(ms)
typedef struct {
int sock; //與服務(wù)器通信的socket
FILE *in; //sock描述符轉(zhuǎn)為文件指針,方便讀寫
char host_name[HOST_NAME_LEN]; //主機(jī)名
int port; //主機(jī)端口號
char uri[URI_MAX_LEN]; //資源路徑
char buffer[RECV_BUF]; //讀寫緩沖
int status_code; //http狀態(tài)碼
int chunked_flag; //chunked傳輸?shù)臉?biāo)志位
int len; //Content-length里的長度
char location[URI_MAX_LEN]; //重定向地址
char *save_path; //保存內(nèi)容的路徑指針
FILE *save_file; //保存內(nèi)容的文件指針
int recv_data_len; //收到數(shù)據(jù)的總長度
time_t start_recv_time; //開始接受數(shù)據(jù)的時(shí)間
time_t end_recv_time; //結(jié)束接受數(shù)據(jù)的時(shí)間
} http_t;
/* 打印宏 */
#define MSG_DEBUG 0x01
#define MSG_INFO 0x02
#define MSG_ERROR 0x04
static int print_level = /*MSG_DEBUG |*/ MSG_INFO | MSG_ERROR;
#define lprintf(level, format, argv...) do{ \
if(level & print_level) \
printf("[%s][%s(%d)]:"format, #level, __FUNCTION__, __LINE__, ##argv); \
}while(0)
#define MIN(x, y) ((x) > (y) ? (y) : (x))
#define HTTP_OK 200
#define HTTP_REDIRECT 302
#define HTTP_NOT_FOUND 404
/* 不區(qū)分大小寫的strstr */
char *strncasestr(char *str, char *sub)
{
if(!str || !sub)
return NULL;
int len = strlen(sub);
if (len == 0)
{
return NULL;
}
while (*str)
{
if (strncasecmp(str, sub, len) == 0)
{
return str;
}
++str;
}
return NULL;
}
/* 解析URL, 成功返回0,失敗返回-1 */
/* http://127.0.0.1:8080/testfile */
int parser_URL(char *url, http_t *info)
{
char *tmp = url, *start = NULL, *end = NULL;
int len = 0;
/* 跳過http:// */
if(strncasestr(tmp, "http://"))
{
tmp += strlen("http://");
}
start = tmp;
if(!(tmp = strchr(start, '/')))
{
lprintf(MSG_ERROR, "url invaild\n");
return -1;
}
end = tmp;
/*解析端口號和主機(jī)*/
info->port = 80; //先附默認(rèn)值80
len = MIN(end - start, HOST_NAME_LEN - 1);
strncpy(info->host_name, start, len);
info->host_name[len] = '\0';
if((tmp = strchr(start, ':')) && tmp < end)
{
info->port = atoi(tmp + 1);
if(info->port <= 0 || info->port >= 65535)
{
lprintf(MSG_ERROR, "url port invaild\n");
return -1;
}
/* 覆蓋之前的賦值 */
len = MIN(tmp - start, HOST_NAME_LEN - 1);
strncpy(info->host_name, start, len);
info->host_name[len] = '\0';
}
/* 復(fù)制uri */
start = end;
strncpy(info->uri, start, URI_MAX_LEN - 1);
lprintf(MSG_INFO, "parse url ok\nhost:%s, port:%d, uri:%s\n",
info->host_name, info->port, info->uri);
return 0;
}
/* dns解析,返回解析到的第一個(gè)地址,失敗返回-1,成功則返回相應(yīng)地址 */
unsigned long dns(char* host_name)
{
struct hostent* host;
struct in_addr addr;
char **pp;
host = gethostbyname(host_name);
if (host == NULL)
{
lprintf(MSG_ERROR, "gethostbyname %s failed\n", host_name);
return -1;
}
pp = host->h_addr_list;
if (*pp!=NULL)
{
addr.s_addr = *((unsigned int *)*pp);
lprintf(MSG_INFO, "%s address is %s\n", host_name, inet_ntoa(addr));
pp++;
return addr.s_addr;
}
return -1;
}
/* 設(shè)置發(fā)送接收超時(shí) */
int set_socket_option(int sock)
{
struct timeval timeout;
timeout.tv_sec = RCV_SND_TIMEOUT/1000;
timeout.tv_usec = RCV_SND_TIMEOUT%1000*1000;
lprintf(MSG_DEBUG, "%ds %dus\n", (int)timeout.tv_sec, (int)timeout.tv_usec);
//設(shè)置socket為非阻塞
// fcntl(sock ,F_SETFL, O_NONBLOCK); //以非阻塞的方式,connect需要重新處理
// 設(shè)置發(fā)送超時(shí)
if(-1 == setsockopt(sock, SOL_SOCKET, SO_SNDTIMEO, (char *)&timeout,
sizeof(struct timeval)))
{
lprintf(MSG_ERROR, "setsockopt error: %m\n");
return -1;
}
// 設(shè)置接送超時(shí)
if(-1 == setsockopt(sock, SOL_SOCKET, SO_RCVTIMEO, (char *)&timeout,
sizeof(struct timeval)))
{
lprintf(MSG_ERROR, "setsockopt error: %m\n");
return -1;
}
return 0;
}
/* 連接到服務(wù)器 */
int connect_server(http_t *info)
{
int sockfd;
struct sockaddr_in server;
unsigned long addr = 0;
unsigned short port = info->port;
sockfd = socket(AF_INET, SOCK_STREAM, 0);
if (-1 == sockfd)
{
lprintf(MSG_ERROR, "socket create failed\n");
goto failed;
}
if(-1 == set_socket_option(sockfd))
{
goto failed;
}
if ((addr = dns(info->host_name)) == -1)
{
lprintf(MSG_ERROR, "Get Dns Failed\n");
goto failed;
}
memset(&server, 0, sizeof(server));
server.sin_family = AF_INET;
server.sin_port = htons(port);
server.sin_addr.s_addr = addr;
if (-1 == connect(sockfd, (struct sockaddr *)&server, sizeof(struct sockaddr)))
{
lprintf(MSG_ERROR, "connect failed: %m\n");
goto failed;
}
info->sock = sockfd;
return 0;
failed:
if(sockfd != -1)
close(sockfd);
return -1;
}
/* 發(fā)送http請求 */
int send_request(http_t *info)
{
int len;
memset(info->buffer, 0x0, RECV_BUF);
snprintf(info->buffer, RECV_BUF - 1, "GET %s HTTP/1.1\r\n"
"Accept: */*\r\n"
"User-Agent: Mozilla/5.0 (compatible; MSIE 5.01; Windows NT 5.0)\r\n"
"Host: %s\r\n"
"Connection: Close\r\n\r\n", info->uri, info->host_name);
lprintf(MSG_DEBUG, "request:\n%s\n", info->buffer);
return send(info->sock, info->buffer, strlen(info->buffer), 0);
}
/* 解析http頭 */
int parse_http_header(http_t *info)
{
char *p = NULL;
// 解析第一行
fgets(info->buffer, RECV_BUF, info->in);
p = strchr(info->buffer, ' ');
//簡單檢查http頭第一行是否合法
if(!p || !strcasestr(info->buffer, "HTTP"))
{
lprintf(MSG_ERROR, "bad http head\n");
return -1;
}
info->status_code = atoi(p + 1);
lprintf(MSG_DEBUG, "http status code: %d\n", info->status_code);
// 循環(huán)讀取解析http頭
while(fgets(info->buffer, RECV_BUF, info->in))
{
// 判斷頭部是否讀完
if(!strcmp(info->buffer, "\r\n"))
{
return 0; /* 頭解析正常 */
}
lprintf(MSG_DEBUG, "%s", info->buffer);
// 解析長度 Content-length: 554
if(p = strncasestr(info->buffer, "Content-length"))
{
p = strchr(p, ':');
p += 2; // 跳過冒號和后面的空格
info->len = atoi(p);
lprintf(MSG_INFO, "Content-length: %d\n", info->len);
}
else if(p = strncasestr(info->buffer, "Transfer-Encoding"))
{
if(strncasestr(info->buffer, "chunked"))
{
info->chunked_flag = 1;
}
else
{
/* 不支持其他編碼的傳送方式 */
lprintf(MSG_ERROR, "Not support %s", info->buffer);
return -1;
}
lprintf(MSG_INFO, "%s", info->buffer);
}
else if(p = strncasestr(info->buffer, "Location"))
{
p = strchr(p, ':');
p += 2; // 跳過冒號和后面的空格
strncpy(info->location, p, URI_MAX_LEN - 1);
lprintf(MSG_INFO, "Location: %s\n", info->location);
}
}
lprintf(MSG_ERROR, "bad http head\n");
return -1; /* 頭解析出錯(cuò) */
}
/* 保存服務(wù)器響應(yīng)的內(nèi)容 */
int save_data(http_t *info, const char *buf, int len)
{
int total_len = len;
int write_len = 0;
// 文件沒有打開則先打開
if(!info->save_file)
{
info->save_file = fopen(info->save_path, "w");
if(!info->save_file)
{
lprintf(MSG_ERROR, "fopen %s error: %m\n", info->save_path);
return -1;
}
}
while(total_len)
{
write_len = fwrite(buf, sizeof(char), len, info->save_file);
if(write_len < len && errno != EINTR)
{
lprintf(MSG_ERROR, "fwrite error: %m\n");
return -1;
}
total_len -= write_len;
}
}
/* 讀數(shù)據(jù) */
int read_data(http_t *info, int len)
{
int total_len = len;
int read_len = 0;
int rtn_len = 0;
while(total_len)
{
read_len = MIN(total_len, RECV_BUF);
// lprintf(MSG_DEBUG, "need read len: %d\n", read_len);
rtn_len = fread(info->buffer, sizeof(char), read_len, info->in);
if(rtn_len < read_len)
{
if(ferror(info->in))
{
if(errno == EINTR) /* 信號中斷了讀操作 */
{
; /* 不做處理繼續(xù)往下走 */
}
else if(errno == EAGAIN || errno == EWOULDBLOCK) /* 超時(shí) */
{
lprintf(MSG_ERROR, "socket recvice timeout: %dms\n", RCV_SND_TIMEOUT);
total_len -= rtn_len;
lprintf(MSG_DEBUG, "read len: %d\n", rtn_len);
break;
}
else /* 其他錯(cuò)誤 */
{
lprintf(MSG_ERROR, "fread error: %m\n");
break;
}
}
else /* 讀到文件尾 */
{
lprintf(MSG_ERROR, "socket closed by peer\n");
total_len -= rtn_len;
lprintf(MSG_DEBUG, "read len: %d\n", rtn_len);
break;
}
}
// lprintf(MSG_DEBUG, " %s\n", info->buffer);
total_len -= rtn_len;
lprintf(MSG_DEBUG, "read len: %d\n", rtn_len);
if(-1 == save_data(info, info->buffer, rtn_len))
{
return -1;
}
info->recv_data_len += rtn_len;
}
if(total_len != 0)
{
lprintf(MSG_ERROR, "we need to read %d bytes, but read %d bytes now\n",
len, len - total_len);
return -1;
}
}
/* 接收服務(wù)器發(fā)回的chunked數(shù)據(jù) */
int recv_chunked_response(http_t *info)
{
long part_len;
//有chunked,content length就沒有了
do{
// 獲取這一個(gè)部分的長度
fgets(info->buffer, RECV_BUF, info->in);
part_len = strtol(info->buffer, NULL, 16);
lprintf(MSG_DEBUG, "part len: %ld\n", part_len);
if(-1 == read_data(info, part_len))
return -1;
//讀走后面的\r\n兩個(gè)字符
if(2 != fread(info->buffer, sizeof(char), 2, info->in))
{
lprintf(MSG_ERROR, "fread \\r\\n error : %m\n");
return -1;
}
}while(part_len);
return 0;
}
/* 計(jì)算平均下載速度,單位byte/s */
float calc_download_speed(http_t *info)
{
int diff_time = 0;
float speed = 0.0;
diff_time = info->end_recv_time - info->start_recv_time;
/* 最小間隔1s,避免計(jì)算浮點(diǎn)數(shù)結(jié)果為inf */
if(0 == diff_time)
diff_time = 1;
speed = (float)info->recv_data_len / diff_time;
return speed;
}
/* 接收服務(wù)器的響應(yīng)數(shù)據(jù) */
int recv_response(http_t *info)
{
int len = 0, total_len = info->len;
if(info->chunked_flag)
return recv_chunked_response(info);
if(-1 == read_data(info, total_len))
return -1;
return 0;
}
/* 清理操作 */
void clean_up(http_t *info)
{
if(info->in)
fclose(info->in);
if(-1 != info->sock)
close(info->sock);
if(info->save_file)
fclose(info->save_file);
if(info)
free(info);
}
/* 下載主函數(shù) */
int http_download(char *url, char *save_path)
{
http_t *info = NULL;
char tmp[URI_MAX_LEN] = {0};
if(!url || !save_path)
return -1;
//初始化結(jié)構(gòu)體
info = malloc(sizeof(http_t));
if(!info)
{
lprintf(MSG_ERROR, "malloc failed\n");
return -1;
}
memset(info, 0x0, sizeof(http_t));
info->sock = -1;
info->save_path = save_path;
// 解析url
if(-1 == parser_URL(url, info))
goto failed;
// 連接到server
if(-1 == connect_server(info))
goto failed;
// 發(fā)送http請求報(bào)文
if(-1 == send_request(info))
goto failed;
// 接收響應(yīng)的頭信息
info->in = fdopen(info->sock, "r");
if(!info->in)
{
lprintf(MSG_ERROR, "fdopen error\n");
goto failed;
}
// 解析頭部
if(-1 == parse_http_header(info))
goto failed;
switch(info->status_code)
{
case HTTP_OK:
// 接收數(shù)據(jù)
lprintf(MSG_DEBUG, "recv data now\n");
info->start_recv_time = time(0);
if(-1 == recv_response(info))
goto failed;
info->end_recv_time = time(0);
lprintf(MSG_INFO, "recv %d bytes\n", info->recv_data_len);
lprintf(MSG_INFO, "Average download speed: %.2fKB/s\n",
calc_download_speed(info)/1000);
break;
case HTTP_REDIRECT:
// 重啟本函數(shù)
lprintf(MSG_INFO, "redirect: %s\n", info->location);
strncpy(tmp, info->location, URI_MAX_LEN - 1);
clean_up(info);
return http_download(tmp, save_path);
case HTTP_NOT_FOUND:
// 退出
lprintf(MSG_ERROR, "Page not found\n");
goto failed;
break;
default:
lprintf(MSG_INFO, "Not supported http code %d\n", info->status_code);
goto failed;
}
clean_up(info);
return 0;
failed:
clean_up(info);
return -1;
}
/****************************************************************************
測試用例:
(1)chunked接收測試
./a.out "http://www.httpwatch.com/httpgallery/chunked/chunkedimage.aspx" test.aspx
(2)重定向測試
./a.out "192.168.10.1/main.html" test.txt
(3)錯(cuò)誤輸入測試
./a.out "32131233" test.txt
(4)根目錄輸入測試
./a.out "www.baidu.com/" test.txt
(5)端口號訪問測試
./a.out "192.168.0.200:8000/FS_AC6V1.0BR_V15.03.4.12_multi_TD01.bin" test.txt
****************************************************************************/
int main(int argc, char *argv[])
{
if(argc < 3)
return -1;
http_download(argv[1], argv[2]);
return 0;以上就是基于C語言實(shí)現(xiàn)http下載器的詳細(xì)內(nèi)容,更多關(guān)于C語言http下載器的資料請關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
C++ 詳解數(shù)據(jù)結(jié)構(gòu)中的搜索二叉樹
搜索二叉樹是一種具有良好排序和查找性能的二叉樹數(shù)據(jù)結(jié)構(gòu),包括多種操作,本篇只介紹插入,排序(遍歷),和刪除操作,重點(diǎn)是刪除操作比較復(fù)雜2022-04-04
剖析C++中的常量表達(dá)式與省略號的相關(guān)作用
這篇文章主要介紹了C++中的常量表達(dá)式與省略號的相關(guān)作用,以及表達(dá)式中的可變參數(shù)模板示例,需要的朋友可以參考下2016-01-01
Linux中使用C語言的fork()函數(shù)創(chuàng)建子進(jìn)程的實(shí)例教程
fork是一個(gè)在Linux系統(tǒng)環(huán)境下專有的函數(shù),現(xiàn)有的進(jìn)程調(diào)用fork后將會創(chuàng)建一個(gè)新的進(jìn)程,這里我們就來看一下Linux中使用C語言的fork()函數(shù)創(chuàng)建子進(jìn)程的實(shí)例教程2016-06-06
C++使用宏函數(shù)實(shí)現(xiàn)單例模板詳解
在我們?nèi)粘i_發(fā)中,無可避免需要使用單例模式進(jìn)行設(shè)計(jì)類對象。這篇文章主要介紹了如何使用宏函數(shù)實(shí)現(xiàn)單例模板,感興趣的小伙伴可以了解一下2023-02-02
通過c語言調(diào)用系統(tǒng)curl動態(tài)庫的示例詳解
這篇文章中我們將通過一個(gè)簡單的示例來講解如何在Ubuntu系統(tǒng)中通過C語言調(diào)用動態(tài)庫(共享庫)的方法,我們將使用libcurl庫,這是一個(gè)基于客戶端的URL傳輸庫,廣泛用于各種程序和應(yīng)用中以訪問網(wǎng)頁和服務(wù)器數(shù)據(jù),需要的朋友可以參考下2024-03-03
C 程序?qū)崿F(xiàn)密碼隱秘輸入的實(shí)例 linux系統(tǒng)可執(zhí)行
下面小編就為大家?guī)硪黄狢 程序?qū)崿F(xiàn)密碼隱秘輸入的實(shí)例 linux系統(tǒng)可執(zhí)行。小編覺得挺不錯(cuò)的,現(xiàn)在就分享給大家,也給大家做個(gè)參考。一起跟隨小編過來看看吧2017-11-11
C++實(shí)現(xiàn)LeetCode(79.詞語搜索)
這篇文章主要介紹了C++實(shí)現(xiàn)LeetCode(79.詞語搜索),本篇文章通過簡要的案例,講解了該項(xiàng)技術(shù)的了解與使用,以下就是詳細(xì)內(nèi)容,需要的朋友可以參考下2021-07-07

