本次給大家分享一個C語言 實現(xiàn)http的下載 器。比如做OTA升級功能時,我們能直接拿到的往往只是升級包的鏈接,需要我們自己去下載,這時候就需要用到http下載器。
這里分享一個:
功能:
1、支持chunked方式傳輸?shù)南螺d
2、被重定向時能下載重定向頁面
3、要實現(xiàn)的接口 為int http_download (char *url, char *save_path)
思路:
1、解析輸入的URL,分離出主機,端口 號,文件路徑的信息
2、解析主機的DNS
3、填充http請求的頭部,給服務(wù)器發(fā)包
4、解析收到的http頭,提取狀態(tài)碼,Conte nt-length, Transfer-Encoding等字段信息
(1)如果是普通的頭則進行接下來的正常收包流程
(2)如果狀態(tài)碼為302,則從頭里提取出重定向地址,用新的地址重新開始下載動作
(3)如果傳送方式是chunked的,則進行分段讀取數(shù)據(jù)并拼接
(4)如果是404或其他狀態(tài)碼則打印錯誤信息
缺陷:
太多錯誤處理,讓代碼看起來不太舒服
其他:
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
Descripti on:?實現(xiàn)簡單的http下載功能
Author :?Leon
Version:?1.0
Date:?2016-12-2?1032
Function:
History:
?????????
?Leon
************************************************************/
#include?
#include?
#include?
#include?
#include?
#include?
#include?
#include?
#include?
#include?
#define?HOST_NAME_LEN???256
#define?URI_MAX_LEN?????2048
#define?RECV_BUF????????8192
#define?RCV_SND_TIMEOUT?(10*1000)???//收發(fā)數(shù)據(jù)超時時間(ms)
typedef?struct?{
????int?sock;???????????????????????//與服務(wù)器通信 的socket
????FILE?*in;???????????????????????//sock描述符轉(zhuǎn)為文件指針,方便讀寫
????char?host_name[HOST_NAME_LEN];??//主機名
????int?port;???????????????????????//主機端口號
????char?uri[URI_MAX_LEN];??????????//資源路徑
????char?buffer[RECV_BUF];??????????//讀寫緩沖
????int?status_code;????????????????//http狀態(tài)碼
????int?chunked_flag;???????????????//chunked傳輸?shù)臉酥疚?????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ù)的時間
????time_t?end_recv_time;???????????//結(jié)束接受數(shù)據(jù)的時間
}?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ū)分大小寫的strs tr?*/
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?invai ld
");
????????return?-1;??????
????}
????end?=?tmp;
????/*解析端口號和主機*/
????info->port?=?80;???//先附默認值80
????len?=?MIN(end?-?start,?HOST_NAME_LEN?-?1);
????strncpy(info->host_name,?start,?len);
????info->host_name[len]?=?'';
????if((tmp?=?strchr(start,?':'))?&&?tmp?port?=?atoi(tmp?+?1);
????????if(info->port?<=?0?||?info->port?>=?65535)
????????{
????????????lprintf(MSG_ERROR,?"url?port?invaild
");
????????????return?-1;
????????}
????????/*?覆蓋之前的賦值?*/
????????len?=?MIN(tmp?-?start,?HOST_NAME_LEN?-?1);
????????strncpy(info->host_name,?start,?len);
????????info->host_name[len]?=?'';
????}
????/*?復制uri?*/
????start?=?end;
????strncpy(info->uri,?start,?URI_MAX_LEN?-?1);
????lprintf(MSG_INFO,?"parse?url?ok
host:%s,?port:%d,?uri:%s
",?
????????info->host_name,?info->port,?info->uri);
????return?0;
}
/*?dns解析,返回解析到的第一個地址,失敗返回-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
",?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
",?host_name,?inet_ntoa(addr));
????????pp++;
????????return?addr.s_addr;
????}
????return?-1;
}
/*?設(shè)置發(fā)送接收超時?*/
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
",?(int)timeout.tv_sec,?(int)timeout.tv_usec);
????//設(shè)置socket為非阻塞
????//?fcntl(sock?,F_SETFL,?O_NONBLOCK);?//以非阻塞的方式,connect需要重新處理
????//?設(shè)置發(fā)送超時
????if(-1?==?setsockopt(sock,?SOL_SOCKET,?SO_SNDTIMEO,?(char?*)&timeout,?
????????????sizeof(struct?timeval)))
????{
????????lprintf(MSG_ERROR,?"setsockopt?error:?%m
");
????????return?-1;
????}
????//?設(shè)置接送超時
????if(-1?==?setsockopt(sock,?SOL_SOCKET,?SO_RCVTIMEO,?(char?*)&timeout,?
????????????sizeof(struct?timeval)))
????{
????????lprintf(MSG_ERROR,?"setsockopt?error:?%m
");
????????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
");
????????goto?failed;
????}
????if(-1?==?set_socket_option(sockfd))
????{
????????goto?failed;
????}
????if?((addr?=?dns(info->host_name))?==?-1)
????{
????????lprintf(MSG_ERROR,?"Get?Dns?Failed
");
????????goto?failed;
????}
????mems et(&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
");
????????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
"
????????"Ac cept:?*/*
"
????????"User-Agent:?Mozilla/5.0?(compatible;?MSIE?5.01;?Windows?NT?5.0)
"
????????"Host:?%s
"
????????"Connection:?Close
",?info->uri,?info->host_name);
????lprintf(MSG_DEBUG,?"request:
%s
",?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
");
????????return?-1;
????}
????info->status_code?=?atoi(p?+?1);???
????lprintf(MSG_DEBUG,?"http?status?code:?%d
",?info->status_code);
????//?循環(huán)讀取解析http頭
????while(fgets(info->buffer,?RECV_BUF,?info->in))
????{
????????//?判斷頭部是否讀完
????????if(!strcmp(info->buffer,?"
"))
????????{
????????????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
",?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
",?info->location);
????????}
????}
????lprintf(MSG_ERROR,?"bad?http?head
");
????return?-1;??/*?頭解析出錯?*/
}
/*?保存服務(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
",?info->save_path);
????????????return?-1;
????????}
????}
????while(total_len)
????{
????????write_len?=?fwrite(buf,?sizeof(char),?len,?info->save_file);
????????if(write_len?buffer,?sizeof(char),?read_len,?info->in);
????????if(rtn_len?in))
????????????{
????????????????if(errno?==?EINTR)?/*?信號 中斷了讀操作?*/
????????????????{
????????????????????;???/*?不做處理繼續(xù)往下走?*/
????????????????}
????????????????else?if(errno?==?EAGAIN?||?errno?==?EWOULDBLOCK)?/*?超時?*/
????????????????{
????????????????????lprintf(MSG_ERROR,?"socket?recvice?timeout:?%dms
",?RCV_SND_TIMEOUT);
????????????????????total_len?-=?rtn_len;
????????????????????lprintf(MSG_DEBUG,?"read?len:?%d
",?rtn_len);
????????????????????break;
????????????????}
????????????????else????/*?其他錯誤?*/
????????????????{
????????????????????lprintf(MSG_ERROR,?"fread?error:?%m
");
????????????????????break;
????????????????}
????????????}
????????????else????/*?讀到文件尾?*/
????????????{
????????????????lprintf(MSG_ERROR,?"socket?closed?by?peer
");
????????????????total_len?-=?rtn_len;
????????????????lprintf(MSG_DEBUG,?"read?len:?%d
",?rtn_len);
????????????????break;
????????????}
????????}
????????//?lprintf(MSG_DEBUG,?"?%s
",?info->buffer);
????????total_len?-=?rtn_len;
????????lprintf(MSG_DEBUG,?"read?len:?%d
",?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
",?
????????????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{
????????//?獲取這一個部分的長度
????????fgets(info->buffer,?RECV_BUF,?info->in);
????????part_len?=?strtol(info->buffer,?NULL,?16);
????????lprintf(MSG_DEBUG,?"part?len:?%ld
",?part_len);
????????if(-1?==?read_data(info,?part_len))
????????????return?-1;
????????//讀走后面的
兩個字符
????????if(2?!=?fread(info->buffer,?sizeof(char),?2,?info->in))
????????{
????????????lprintf(MSG_ERROR,?"fread?\r\n?error?:?%m
");
????????????return?-1;
????????}
????}while(part_len);
????return?0;
}
/*?計算平均下載速度,單位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,避免計算浮點數(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
");
????????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請求報文
????if(-1?==?send_request(info))
????????goto?failed;
????//?接收響應(yīng)的頭信息
????info->in?=?fdopen(info->sock,?"r");
????if(!info->in)
????{
????????lprintf(MSG_ERROR,?"fdopen?error
");
????????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
");
????????????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
",?info->recv_data_len);
????????????lprintf(MSG_INFO,?"Average?download?speed:?%.2fKB/s
",?
????????????????????calc_download_speed(info)/1000);
????????????break;
????????case?HTTP_REDIRECT:
????????????//?重啟本函數(shù)
????????????lprintf(MSG_INFO,?"redirect:?%s
",?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
");
????????????goto?failed;
????????????break;
????????default:
????????????lprintf(MSG_INFO,?"Not?supported?http?code?%d
",?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)錯誤輸入測試
./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;
}
?
?
審核編輯:湯梓紅
?
評論
查看更多