C语言实现http下载器(附代码)
来源:嵌入式大杂烩 发布时间:2022-10-26
分享至微信

大家好,我是杂烩君。
本次给大家分享一个C语言实现http的下载器。比如做OTA升级功能时,我们能直接拿到的往往只是升级包的链接,需要我们自己去下载,这时候就需要用到http下载器。
这里分享一个:
1、支持chunked方式传输的下载
2、被重定向时能下载重定向页面
3、要实现的接口为int http_download(char *url, char *save_path)
1、解析输入的URL,分离出主机,端口号,文件路径的信息
2、解析主机的DNS
3、填充http请求的头部,给服务器发包
4、解析收到的http头,提取状态码,Content-length, Transfer-Encoding等字段信息
(1)如果是普通的头则进行接下来的正常收包流程
(2)如果状态码为302,则从头里提取出重定向地址,用新的地址重新开始下载动作
(3)如果传送方式是chunked的,则进行分段读取数据并拼接
(4)如果是404或其他状态码则打印错误信息
太多错误处理,让代码看起来不太舒服
1、如何移植到没有文件系统的系统中?
修改sava_data接口里面的保存就好了
2、如何提高下载速度?
增大读写buffer缓冲区 改为多线程,使用Range字段分段读取,最后再拼在一起
/************************************************************
Copyright(C),2016,Leon,AllRightsReserved.
FileName:download.c
coding:UTF-8
Description:实现简单的http下载功能
Author:Leon
Version:1.0
Date:2016-12-210: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>
#defineHOST_NAME_LEN256
#defineURI_MAX_LEN2048
#defineRECV_BUF8192
#defineRCV_SND_TIMEOUT(10*1000)//收发数据超时时间(ms)
typedefstruct{
intsock;//与服务器通信的socket
FILE*in;//sock描述符转为文件指针,方便读写
charhost_name[HOST_NAME_LEN];//主机名
intport;//主机端口号
charuri[URI_MAX_LEN];//资源路径
charbuffer[RECV_BUF];//读写缓冲
intstatus_code;//http状态码
intchunked_flag;//chunked传输的标志位
intlen;//Content-length里的长度
charlocation[URI_MAX_LEN];//重定向地址
char*save_path;//保存内容的路径指针
FILE*save_file;//保存内容的文件指针
intrecv_data_len;//收到数据的总长度
time_tstart_recv_time;//开始接受数据的时间
time_tend_recv_time;//结束接受数据的时间
}http_t;
/*打印宏*/
#defineMSG_DEBUG0x01
#defineMSG_INFO0x02
#defineMSG_ERROR0x04
staticintprint_level=/*MSG_DEBUG|*/MSG_INFO|MSG_ERROR;
#definelprintf(level,format,argv...)do{\
if(levelprint_level)\
printf("[%s][%s(%d)]:"format,#level,__FUNCTION__,__LINE__,##argv);\
}while(0)
#defineMIN(x,y)((x)>(y)?(y):(x))
#defineHTTP_OK200
#defineHTTP_REDIRECT302
#defineHTTP_NOT_FOUND404
/*不区分大小写的strstr*/
char*strncasestr(char*str,char*sub)
{
if(!str||!sub)
returnNULL;
intlen=strlen(sub);
if(len==0)
{
returnNULL;
}
while(*str)
{
if(strncasecmp(str,sub,len)==0)
{
returnstr;
}
++str;
}
returnNULL;
}
/*解析URL,成功返回0,失败返回-1*/
/*http://127.0.0.1:8080/testfile*/
intparser_URL(char*url,http_t*info)
{
char*tmp=url,*start=NULL,*end=NULL;
intlen=0;
/*跳过http://*/
if(strncasestr(tmp,"http://"))
{
tmp+=strlen("http://");
}
start=tmp;
if(!(tmp=strchr(start,'/')))
{
lprintf(MSG_ERROR,"urlinvaild\n");
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]='\0';
if((tmp=strchr(start,':'))tmp<end)
{
info->port=atoi(tmp+1);
if(info->port<=0||info->port>=65535)
{
lprintf(MSG_ERROR,"urlportinvaild\n");
return-1;
}
/*覆盖之前的赋值*/
len=MIN(tmp-start,HOST_NAME_LEN-1);
strncpy(info->host_name,start,len);
info->host_name[len]='\0';
}
/*复制uri*/
start=end;
strncpy(info->uri,start,URI_MAX_LEN-1);
lprintf(MSG_INFO,"parseurlok\nhost:%s,port:%d,uri:%s\n",
info->host_name,info->port,info->uri);
return0;
}
/*dns解析,返回解析到的第一个地址,失败返回-1,成功则返回相应地址*/
unsignedlongdns(char*host_name)
{
structhostent*host;
structin_addraddr;
char**pp;
host=gethostbyname(host_name);
if(host==NULL)
{
lprintf(MSG_ERROR,"gethostbyname%sfailed\n",host_name);
return-1;
}
pp=host->h_addr_list;
if(*pp!=NULL)
{
addr.s_addr=*((unsignedint*)*pp);
lprintf(MSG_INFO,"%saddressis%s\n",host_name,inet_ntoa(addr));
pp++;
returnaddr.s_addr;
}
return-1;
}
/*设置发送接收超时*/
intset_socket_option(intsock)
{
structtimevaltimeout;
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);
//设置socket为非阻塞
//fcntl(sock,F_SETFL,O_NONBLOCK);//以非阻塞的方式,connect需要重新处理
//设置发送超时
if(-1==setsockopt(sock,SOL_SOCKET,SO_SNDTIMEO,(char*)timeout,
sizeof(structtimeval)))
{
lprintf(MSG_ERROR,"setsockopterror:%m\n");
return-1;
}
//设置接送超时
if(-1==setsockopt(sock,SOL_SOCKET,SO_RCVTIMEO,(char*)timeout,
sizeof(structtimeval)))
{
lprintf(MSG_ERROR,"setsockopterror:%m\n");
return-1;
}
return0;
}
/*连接到服务器*/
intconnect_server(http_t*info)
{
intsockfd;
structsockaddr_inserver;
unsignedlongaddr=0;
unsignedshortport=info->port;
sockfd=socket(AF_INET,SOCK_STREAM,0);
if(-1==sockfd)
{
lprintf(MSG_ERROR,"socketcreatefailed\n");
gotofailed;
}
if(-1==set_socket_option(sockfd))
{
gotofailed;
}
if((addr=dns(info->host_name))==-1)
{
lprintf(MSG_ERROR,"GetDnsFailed\n");
gotofailed;
}
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,(structsockaddr*)server,sizeof(structsockaddr)))
{
lprintf(MSG_ERROR,"connectfailed:%m\n");
gotofailed;
}
info->sock=sockfd;
return0;
failed:
if(sockfd!=-1)
close(sockfd);
return-1;
}
/*发送http请求*/
intsend_request(http_t*info)
{
intlen;
memset(info->buffer,0x0,RECV_BUF);
snprintf(info->buffer,RECV_BUF-1,"GET%sHTTP/1.1\r\n"
"Accept:*/*\r\n"
"User-Agent:Mozilla/5.0(compatible;MSIE5.01;WindowsNT5.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);
returnsend(info->sock,info->buffer,strlen(info->buffer),0);
}
/*解析http头*/
intparse_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,"badhttphead\n");
return-1;
}
info->status_code=atoi(p+1);
lprintf(MSG_DEBUG,"httpstatuscode:%d\n",info->status_code);
//循环读取解析http头
while(fgets(info->buffer,RECV_BUF,info->in))
{
//判断头部是否读完
if(!strcmp(info->buffer,"\r\n"))
{
return0;/*头解析正常*/
}
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);
}
elseif(p=strncasestr(info->buffer,"Transfer-Encoding"))
{
if(strncasestr(info->buffer,"chunked"))
{
info->chunked_flag=1;
}
else
{
/*不支持其他编码的传送方式*/
lprintf(MSG_ERROR,"Notsupport%s",info->buffer);
return-1;
}
lprintf(MSG_INFO,"%s",info->buffer);
}
elseif(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,"badhttphead\n");
return-1;/*头解析出错*/
}
/*保存服务器响应的内容*/
intsave_data(http_t*info,constchar*buf,intlen)
{
inttotal_len=len;
intwrite_len=0;
//文件没有打开则先打开
if(!info->save_file)
{
info->save_file=fopen(info->save_path,"w");
if(!info->save_file)
{
lprintf(MSG_ERROR,"fopen%serror:%m\n",info->save_path);
return-1;
}
}
while(total_len)
{
write_len=fwrite(buf,sizeof(char),len,info->save_file);
if(write_len<lenerrno!=EINTR)
{
lprintf(MSG_ERROR,"fwriteerror:%m\n");
return-1;
}
total_len-=write_len;
}
}
/*读数据*/
intread_data(http_t*info,intlen)
{
inttotal_len=len;
intread_len=0;
intrtn_len=0;
while(total_len)
{
read_len=MIN(total_len,RECV_BUF);
//lprintf(MSG_DEBUG,"needreadlen:%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)/*信号中断了读操作*/
{
;/*不做处理继续往下走*/
}
elseif(errno==EAGAIN||errno==EWOULDBLOCK)/*超时*/
{
lprintf(MSG_ERROR,"socketrecvicetimeout:%dms\n",RCV_SND_TIMEOUT);
total_len-=rtn_len;
lprintf(MSG_DEBUG,"readlen:%d\n",rtn_len);
break;
}
else/*其他错误*/
{
lprintf(MSG_ERROR,"freaderror:%m\n");
break;
}
}
else/*读到文件尾*/
{
lprintf(MSG_ERROR,"socketclosedbypeer\n");
total_len-=rtn_len;
lprintf(MSG_DEBUG,"readlen:%d\n",rtn_len);
break;
}
}
//lprintf(MSG_DEBUG,"%s\n",info->buffer);
total_len-=rtn_len;
lprintf(MSG_DEBUG,"readlen:%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,"weneedtoread%dbytes,butread%dbytesnow\n",
len,len-total_len);
return-1;
}
}
/*接收服务器发回的chunked数据*/
intrecv_chunked_response(http_t*info)
{
longpart_len;
//有chunked,contentlength就没有了
do{
//获取这一个部分的长度
fgets(info->buffer,RECV_BUF,info->in);
part_len=strtol(info->buffer,NULL,16);
lprintf(MSG_DEBUG,"partlen:%ld\n",part_len);
if(-1==read_data(info,part_len))
return-1;
//读走后面的\r\n两个字符
if(2!=fread(info->buffer,sizeof(char),2,info->in))
{
lprintf(MSG_ERROR,"fread\\r\\nerror:%m\n");
return-1;
}
}while(part_len);
return0;
}
/*计算平均下载速度,单位byte/s*/
floatcalc_download_speed(http_t*info)
{
intdiff_time=0;
floatspeed=0.0;
diff_time=info->end_recv_time-info->start_recv_time;
/*最小间隔1s,避免计算浮点数结果为inf*/
if(0==diff_time)
diff_time=1;
speed=(float)info->recv_data_len/diff_time;
returnspeed;
}
/*接收服务器的响应数据*/
intrecv_response(http_t*info)
{
intlen=0,total_len=info->len;
if(info->chunked_flag)
returnrecv_chunked_response(info);
if(-1==read_data(info,total_len))
return-1;
return0;
}
/*清理操作*/
voidclean_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);
}
/*下载主函数*/
inthttp_download(char*url,char*save_path)
{
http_t*info=NULL;
chartmp[URI_MAX_LEN]={0};
if(!url||!save_path)
return-1;
//初始化结构体
info=malloc(sizeof(http_t));
if(!info)
{
lprintf(MSG_ERROR,"mallocfailed\n");
return-1;
}
memset(info,0x0,sizeof(http_t));
info->sock=-1;
info->save_path=save_path;
//解析url
if(-1==parser_URL(url,info))
gotofailed;
//连接到server
if(-1==connect_server(info))
gotofailed;
//发送http请求报文
if(-1==send_request(info))
gotofailed;
//接收响应的头信息
info->in=fdopen(info->sock,"r");
if(!info->in)
{
lprintf(MSG_ERROR,"fdopenerror\n");
gotofailed;
}
//解析头部
if(-1==parse_http_header(info))
gotofailed;
switch(info->status_code)
{
caseHTTP_OK:
//接收数据
lprintf(MSG_DEBUG,"recvdatanow\n");
info->start_recv_time=time(0);
if(-1==recv_response(info))
gotofailed;
info->end_recv_time=time(0);
lprintf(MSG_INFO,"recv%dbytes\n",info->recv_data_len);
lprintf(MSG_INFO,"Averagedownloadspeed:%.2fKB/s\n",
calc_download_speed(info)/1000);
break;
caseHTTP_REDIRECT:
//重启本函数
lprintf(MSG_INFO,"redirect:%s\n",info->location);
strncpy(tmp,info->location,URI_MAX_LEN-1);
clean_up(info);
returnhttp_download(tmp,save_path);
caseHTTP_NOT_FOUND:
//退出
lprintf(MSG_ERROR,"Pagenotfound\n");
gotofailed;
break;
default:
lprintf(MSG_INFO,"Notsupportedhttpcode%d\n",info->status_code);
gotofailed;
}
clean_up(info);
return0;
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
****************************************************************************/
intmain(intargc,char*argv[])
{
if(argc<3)
return-1;
http_download(argv[1],argv[2]);
return0;
}
版权声明:本文为CSDN博主「普朗克常量」的原创文章,遵循CC4.0BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/u013401853/article/details/53434167
[ 新闻来源:嵌入式大杂烩,更多精彩资讯请下载icspec App。如对本稿件有异议,请联系微信客服specltkj]
存入云盘 收藏
举报
全部评论
暂无评论哦,快来评论一下吧!


嵌入式大杂烩
专注于嵌入式技术,包括但不限于C/C++、嵌入式、物联网、Linux等编程学习笔记,同时,公众号内包含大量的学习资源。欢迎关注,一同交流学习,共同进步!
查看更多
相关文章
新加坡推出多语言模型MERaLiON
2025-06-04
印度新规要求监控设备商提交源代码
2025-05-28
微软CEO透露:公司近三成代码由AI生成
2025-04-30
中科海光发布新一代服务器处理器C86-5G
2025-05-12
韩国加速布局AI产业,目标打造国家级大型语言模型
2025-06-10
热门搜索