C语言 HTTP协议下载文件,实现断点续传,socket通讯,目前只支持ip和port方式连接,有兴趣的读者可完善域名方式。
代码分为 http.c: 实现http协议下载文件 ,socket.c: 封装linux socket函数,移植时只需修改socket.c中的函数即可。
希望对大家有帮助,本人亲测可用!
http.c
socket.c
实现:
测试代码:
代码分为 http.c: 实现http协议下载文件 ,socket.c: 封装linux socket函数,移植时只需修改socket.c中的函数即可。
希望对大家有帮助,本人亲测可用!
http.c
点击(此处)折叠或打开
-
//http.c
-
//作者:王振
-
#include <stdio.h>
-
#include <stdlib.h>
-
#include <errno.h>
-
#include <string.h>
-
-
#include <sys/types.h>
-
#include <sys/stat.h>
-
#include <fcntl.h>
-
-
#include "socket.h"
-
#include "http.h"
-
-
-
#define MAX_RECV_SIZE 1440//硬件单包最大的接收字节数
-
char g_host[URL_LEN];
-
char g_ip[URL_LEN+1];//ip/域名
-
char g_port[5+1];
-
-
char g_buf_send[4*1024];//发送数据暂存区
-
char g_buf_recv[10*1024];//接收数据暂存区
-
-
BreakPoint_ST g_break_point;
-
-
/*
-
功能:判断断点有效性,现在校验url是否一致
-
参数:
-
返回:
-
>0---------有效,已下载文件大小
-
-1----------无效
-
*/
-
int Get_Breakpoint_Available(BreakPoint_ST *breakpoint,char *url,char *file_crc)
-
{
-
-
//判断断点是否有效,后续加入文件校验码
-
if((memcmp(breakpoint->url,url,strlen(url))== 0)&&(breakpoint->recv_size== MAX_RECV_SIZE))
-
return breakpoint->download_size;
-
else
-
{
-
return -1;
-
}
-
-
}
-
-
-
/*
-
功能:判断要下载文件是否存在断点
-
参数:
-
filename---要下载的文件名
-
file_crc----服务器返回下载文件的校验码
-
返回:
-
0---------无断点
-
>0--------有断点,已下载文件大小
-
*/
-
int Get_Breakpoint(char *url,char *filename,char *file_crc)
-
{
-
char filename_bp[64];
-
int fd = -1;
-
int ret;
-
BreakPoint_ST break_point;
-
-
//断点文件名 filename+bp
-
sprintf(filename_bp,"%s.bp",filename);
-
-
//检测是否存在filename断点文件
-
fd = open(filename_bp,O_RDONLY,S_IRUSR|S_IWUSR);
-
if(fd == -1)
-
{
-
#ifdef DEBUG_HTTP
-
printf("no exsit %s\n",filename_bp);
-
#endif
-
return 0;
-
}
-
-
//存在断点
-
ret = read(fd,&break_point,sizeof(break_point));
-
if(ret != sizeof(break_point))
-
{
-
perror("ERR:Get_Breakpoint read");
-
exit(-1);
-
}
-
-
close(fd);
-
-
//判断断点是否有效
-
ret = Get_Breakpoint_Available(&break_point,url,file_crc);
-
if(ret > 0)
-
return ret;
-
else
-
{
-
-
printf("%s not available\n",filename_bp);
-
remove(filename);
-
remove(filename_bp);
-
return 0;
-
-
}
-
}
-
-
/*
-
功能:保存断点信息,文件名filename.bp
-
参数:
-
filename---要下载的文件名
-
file_crc----服务器返回下载文件的校验码
-
返回:
-
0---------成功
-
>0--------有断点,已下载文件大小
-
*/
-
int Save_Breakpoint(char *url,char *filename,int download_size,char *file_crc)
-
{
-
int fd;
-
BreakPoint_ST breakpoint;
-
char filename_bp[128];//断点信息文件名,包含路径
-
-
sprintf(filename_bp,"%s.bp",filename);
-
/* 创建目的文件 */
-
if((fd=open(filename_bp,O_WRONLY|O_CREAT,S_IRUSR|S_IWUSR))==-1)
-
{
-
fprintf(stderr,"Open %s Error:%s\n",filename_bp,strerror(errno));
-
exit(1);
-
}
-
memset(&breakpoint,0x0,sizeof(breakpoint));
-
strcpy(breakpoint.url,url);
-
//strcpy(breakpoint.crc,file_crc);
-
strcpy(breakpoint.filename,filename);
-
breakpoint.download_size = download_size;
-
breakpoint.recv_size= MAX_RECV_SIZE;
-
-
//xu tioa zheng wei fen ci xie ru
-
if(write(fd,&breakpoint,sizeof(breakpoint)) != sizeof(breakpoint))
-
{
-
perror("ERR:Save_Breakpoint");
-
exit(1);
-
}
-
-
close(fd);
-
-
return 0;
-
-
-
-
}
-
-
/*
-
功能:保存文件,追加写
-
参数:
-
返回:
-
0---------成功
-
*/
-
-
int Save_File(char *filebuf,int filelength,char *filename)
-
{
-
int fd;
-
/* 创建目的文件追加写 */
-
if((fd=open(filename,O_WRONLY|O_CREAT|O_APPEND,S_IRUSR|S_IWUSR))==-1)
-
{
-
fprintf(stderr,"Open %s Error:%s\n",filename,strerror(errno));
-
exit(1);
-
}
-
//xu tioa zheng wei fen ci xie ru
-
if(write(fd,filebuf,filelength) != filelength)
-
{
-
perror("ERR:Save_File");
-
exit(1);
-
}
-
-
close(fd);
-
-
return 0;
-
-
-
}
-
-
-
int HTTP_GetResponseCode(void)
-
{
-
-
-
}
-
-
/*
-
功能:读取http返回的协议实体主体长度
-
参数:
-
revbuf--------接收到的返回值
-
返回值:
-
>=0---------内容(实体主体)的长度
-
-1-----------数据返回错误
-
*/
-
int HTTP_GetRecvLength(char *revbuf)
-
{
-
char *p1 = NULL;
-
int HTTP_Body = 0;//内容体长度
-
int HTTP_Head = 0;//HTTP
协议头长度
-
-
-
HTTP_Body = HTTP_GetContentLength(revbuf);
-
if(HTTP_Body == -1)
-
return -1;
-
-
p1=strstr(revbuf,"\r\n\r\n");
-
if(p1==NULL)
-
return -1;
-
else
-
{
-
HTTP_Head = p1- revbuf +4;// 4是\r\n\r\n的长度
-
return HTTP_Body+HTTP_Head;
-
}
-
-
-
}
-
-
-
/*
-
功能:读取http返回的Content-Length长度
-
参数:
-
revbuf--------接收到的数据
-
返回值:
-
>=0---------Content-Length长度
-
-1-----------数据返回错误
-
*/
-
int HTTP_GetContentLength(char *revbuf)
-
{
-
char *p1 = NULL, *p2 = NULL;
-
int HTTP_Body = 0;//内容体长度
-
-
p1 = strstr(revbuf,"Content-Length");
-
if(p1 == NULL)
-
return -1;
-
else
-
{
-
p2 = p1+strlen("Content-Length")+ 2;
-
HTTP_Body = atoi(p2);
-
return HTTP_Body;
-
}
-
-
}
-
-
/*
-
功能:
-
参数:
-
sockfd--------接收到的返回值
-
返回值:
-
>0---------接收到长度
-
-1----------失败
-
=0---------服务端断开连接
-
注:内部接收缓冲10k
-
*/
-
-
int HTTP_Recv(int sockfd,char *buf_recv)
-
{
-
int ret;
-
int recvlen=0;
-
int downloadlen = 0;
-
//int contentlen=0;
-
char buf_recv_tmp[10*1024+1];
-
-
memset(buf_recv_tmp,0x0,sizeof(buf_recv_tmp));
-
while(1)
-
{
-
ret = Recv(sockfd,buf_recv_tmp+recvlen,sizeof(buf_recv_tmp)-1,0);
-
-
if(ret <= 0)//下载失败
-
{
-
perror("ERR:recv fail");
-
return ret;
-
}
-
-
-
if(recvlen == 0)
-
{
-
#ifdef DEBUG_HTTP_RECV
-
printf("recv len = %d\n", ret);
-
printf("recv = %s\n", buf_recv_tmp);
-
#endif
-
//获取需要下载长度;
-
downloadlen = HTTP_GetRecvLength(buf_recv_tmp);
-
-
-
#ifdef DEBUG_HTTP_RECV
-
printf("downloadlen = %d\n",downloadlen);
-
#endif
-
}
-
-
recvlen += ret;
-
#ifdef DEBUG_HTTP_RECV
-
printf("total recvlen = %d\n",recvlen);
-
#endif
-
-
if(downloadlen == recvlen)//下载完成
-
break;
-
-
-
}
-
memcpy(buf_recv,buf_recv_tmp,downloadlen);
-
return recvlen;
-
-
}
-
-
/*
-
功能:获取下载url中的文件名,最后一个/后的字符
-
参数:
-
返回值:
-
0-----------成功
-
-1----------失败
-
注:内部接收缓冲10k
-
*/
-
-
int HTTP_GetFileName(char *url,char *filename)
-
{
-
//提取url中最后一个/后的内容
-
int len;
-
int i;
-
-
len = strlen(url);
-
for(i=len-1;i>0;i--)
-
{
-
if(url[i] == '/')
-
break;
-
}
-
if(i == 0)//下载地址错误
-
{
-
printf("url not contain '/'\n");
-
return -1;
-
}
-
else
-
{
-
-
strcpy(filename,url+i+1);
-
#ifdef DEBUG_HTTP
-
printf("filename=%s\n",filename);
-
#endif
-
return 0;
-
}
-
}
-
-
/*
-
功能:获取下载url中的路径,第一个/后的字符
-
参数:
-
返回值:
-
0-----------成功
-
-1----------失败
-
注:url ex "http://host:port/path"
-
*/
-
int HTTP_GetPath(char *url,char *path)
-
{
-
char *p;
-
-
p = strstr(url,"http://");
-
if(p == NULL)
-
{
-
p = strchr(url,'/');
-
if(p == NULL)
-
return -1;
-
else
-
{
-
strcpy(path,p);
-
return 0;
-
}
-
}
-
else
-
{
-
p = strchr(url+strlen("http://"),'/');
-
if(p == NULL)
-
return -1;
-
else
-
{
-
strcpy(path,p);
-
return 0;
-
}
-
}
-
-
}
-
/*
-
功能:获取下载url中的ip和port,ip支持域名,端口默认为80
-
参数:
-
返回值:
-
1-----------域名式
-
2-----------ip
port式
-
-1----------失败
-
注:url ex "http://host:port/path"
-
*/
-
-
int HTTP_Get_IP_PORT(char *url,char *ip,char *port)
-
{
-
char *p = NULL;
-
int offset = 0;
-
char DOMAIN_NAME[128];
-
-
p = strstr(url,"http://");
-
if(p == NULL)
-
{
-
offset = 0;
-
}
-
else
-
{
-
offset = strlen("http://");
-
}
-
-
p = strchr(url+offset,'/');
-
if(p == NULL)
-
{
-
printf("url:%s format error\n",url);
-
return -1;
-
-
}
-
else
-
{
-
-
memset(DOMAIN_NAME,0x0,sizeof(DOMAIN_NAME));
-
memcpy(DOMAIN_NAME,url+offset,(p-url-offset));
-
p = strchr(DOMAIN_NAME,':');
-
if(p == NULL)
-
{
-
strcpy(ip,DOMAIN_NAME);
-
strcpy(port,"80");
-
//printf("ip
%p,port %p\n",ip,port);
-
-
#ifdef DEBUG_HTTP
-
printf("ip=%s,port=%s\n",ip,port);//debug
info
-
#endif
-
return 1;
-
-
}
-
else
-
{
-
*p = '\0';
-
-
strcpy(ip,DOMAIN_NAME);
-
strcpy(port,p+1);
-
-
#ifdef DEBUG_HTTP
-
printf("ip=%s,port=%s\n",ip,port);//debug
info
-
#endif
-
return 2;
-
-
}
-
-
-
return 0;
-
}
-
-
}
-
void Package_Url_Get_File(char *path, char *range)
-
{
-
char buf[64];
-
memset(g_buf_send,0x0,sizeof(g_buf_send));
-
sprintf(g_buf_send, "GET %s",path);
-
-
-
//HTTP/1.1\r\n
前面需要一个空格
-
strcat(g_buf_send," HTTP/1.1\r\n");
-
strcat(g_buf_send, "Host: ");
-
strcat(g_buf_send, g_host);
-
//strcat(g_buf_send, ":");
-
//strcat(g_buf_send, PORT);
-
-
sprintf(buf, "\r\nRange: bytes=%s",range);
-
strcat(g_buf_send,buf);
-
strcat(g_buf_send, "\r\nKeep-Alive: 200");
-
strcat(g_buf_send,"\r\nConnection: Keep-Alive\r\n\r\n");
-
-
-
}
-
-
int Package_Url_Get_FileSize(char *url)
-
{
-
-
memset(g_buf_send,0x0,sizeof(g_buf_send));
-
sprintf(g_buf_send, "HEAD %s",url);
-
-
//HTTP/1.1\r\n
前面需要一个空格
-
strcat(g_buf_send," HTTP/1.1\r\n");
-
strcat(g_buf_send, "Host: ");
-
strcat(g_buf_send, g_host);
-
//strcat(g_buf_send, ":");
-
//strcat(g_buf_send, PORT);
-
strcat(g_buf_send,"\r\nConnection: Keep-Alive\r\n\r\n");
-
-
return 0;
-
}
-
-
-
int HTTP_GetFileSize(int sockfd,char *path)
-
{
-
int ret = -1;
-
char buf_recv_tmp[10*1024+1];
-
-
Package_Url_Get_FileSize(path);
-
#ifdef DEBUG_HTTP
-
printf("send = %s \n",g_buf_send);
-
#endif
-
-
Send(sockfd, g_buf_send, strlen(g_buf_send), 0);
-
-
memset(buf_recv_tmp,0x0,sizeof(buf_recv_tmp));
-
ret = Recv(sockfd,buf_recv_tmp,sizeof(buf_recv_tmp)-1,0);
-
#ifdef DEBUG_HTTP
-
printf("recv len = %d\n", ret);
-
printf("recv = %s\n", buf_recv_tmp);
-
#endif
-
if(ret <= 0)
-
{
-
perror("ERR:recv fail GetFileSize()");
-
return -1;
-
-
}
-
ret = HTTP_GetContentLength(buf_recv_tmp);
-
if(ret <= 0)
-
return -1;
-
else
-
return ret;
-
-
-
}
-
-
-
-
-
/*
-
功能:分段下载文件
-
参数:
-
返回值:
-
>0----------已下载文件大小(不包含上次下载)
-
-1----------失败
-
*/
-
int HTTP_GetFile(int sockfd,char *path,int filelength,int download_size,char *filebuf)
-
{
-
int count;
-
char range[32];
-
int i;
-
int j = 0;//成功下载次数
-
int ret = -1;
-
char *p = NULL;
-
int download_index;//下载开始索引
-
-
count = (filelength%MAX_RECV_SIZE)?(filelength/MAX_RECV_SIZE +1):(filelength/MAX_RECV_SIZE);
-
-
download_index = download_size/MAX_RECV_SIZE;
-
-
for(i=download_index;i<count;i++)
-
{
-
//if(i == 20)//测试断点
-
//break;
-
-
-
if((i == (count-1))&&(filelength%MAX_RECV_SIZE))
-
sprintf(range,"%d-%d",i*MAX_RECV_SIZE,filelength-1);
-
else
-
sprintf(range,"%d-%d",i*MAX_RECV_SIZE,(i+1)*MAX_RECV_SIZE-1);
-
-
-
Package_Url_Get_File(path,range);
-
#ifdef DEBUG_HTTP
-
printf("send = %s \n",g_buf_send);
-
#endif
-
Send(sockfd, g_buf_send, strlen(g_buf_send), 0);
-
-
/*需改为提取http 返回协议头和协议体总长,然后定长接收*/
-
memset(g_buf_recv,0x0,sizeof(g_buf_recv));
-
ret = HTTP_Recv(sockfd,g_buf_recv);
-
if(ret < 0)
-
break;
-
if(ret == 0 )//服务端断开连接
-
{
-
sockfd = Socket_Connect(g_ip,g_port);
-
i--;
-
continue;
-
}
-
/*提取协议体数据,保存在filebuf中*/
-
p = strstr(g_buf_recv,"\r\n\r\n");
-
if(p == NULL)//jia
ru duan dian baocun
-
{
-
printf("ERR:g_buf_recv not contain end flag\n");
-
break;
-
}
-
else
-
{
-
memcpy(filebuf+j*MAX_RECV_SIZE,p+4,MAX_RECV_SIZE);
-
j++;
-
-
}
-
}
-
if(i == count)
-
return (filelength-download_size);
-
else
-
return (i*MAX_RECV_SIZE-download_size);
-
}
-
-
/*
-
功能:HTTP下载文件
-
参数:
-
返回值:
-
0----------下载完成
-
-1---------失败
-
-2---------部分下载完成
-
注:保存文件到bin所在目录
-
*/
-
int HTTP_DownloadFile(char *url,char *save_path)
-
{
-
int ret;
-
int sockfd;
-
int filesize;
-
int download_size;
-
char filename[FILENAME_LEN+1];
-
char filename_bp[FILENAME_LEN+3+1];
-
char *filebuf;
-
char save_file_path[FILENAME_LEN+1];//保存下载文件的路径+文件名
-
-
char path[PATH_LEN+1];//url中的path
-
-
//提取ip和port或url(url 暂不实现,需要gethostbyname
linux)
-
ret = HTTP_Get_IP_PORT(url,g_ip,g_port);
-
if(ret == -1)
-
return -1;
-
else
-
{
-
sprintf(g_host,"%s:%s",g_ip,g_port);
-
}
-
//提取下载文件名
-
ret = HTTP_GetFileName(url,filename);
-
if(ret == -1)
-
return -1;
-
-
ret = HTTP_GetPath(url,path);
-
if(ret == -1)
-
return -1;
-
//sleep(3);//debug
info
-
//建立连接
-
sockfd = Socket_Connect(g_ip,g_port);
-
-
//获取下载文件总大小
-
filesize = HTTP_GetFileSize(sockfd,path);
-
if(filesize == -1)
-
return -1;
-
//#ifdef DEBUG_HTTP
-
printf("http need download size %d\n",filesize);
-
//#endif
-
//malloc分配存储文件空间
-
filebuf = (char *)malloc(filesize);
-
if(filebuf == NULL)
-
{
-
perror("malloc filebuf fail");
-
return -1;
-
}
-
else
-
memset(filebuf,0x0,filesize);
-
-
download_size = Get_Breakpoint(url,filename,NULL);
-
#ifdef DEBUG_HTTP
-
printf("breakpoint download_size=%d\n",download_size);//debug
info
-
sleep(3);//debug
info
-
#endif
-
//分段下载文件
-
ret = HTTP_GetFile(sockfd,path,filesize,download_size,filebuf);
-
Close(sockfd);
-
if(ret < 0)
-
{
-
free(filebuf);
-
return -1;
-
}
-
else
-
{
-
-
sprintf(save_file_path,"%s%s",save_path,filename);
-
-
#ifdef DEBUG_HTTP
-
printf("save_path=%s\n",save_path);
-
printf("filename=%s\n",filename);
-
printf("save_file_path=%s\n",save_file_path);
-
printf("download_size = %d\n",ret);
-
#endif
-
Save_File(filebuf,ret,save_file_path);
-
free(filebuf);
-
if((ret+download_size) == filesize)//全部下载完成
-
{
-
sprintf(filename_bp,"%s.bp",filename);
-
remove(filename_bp);
-
-
printf("download success\n");
-
return 0;
-
}
-
else//部分下载完成
-
{
-
printf("part download success\n");
-
//保存断点信息
-
Save_Breakpoint(url,save_file_path,ret+download_size,NULL);
-
return -2;
-
}
-
}
-
- }
点击(此处)折叠或打开
-
//socket.c
-
//作者:王振
-
-
#include <stdio.h>
-
#include <stdlib.h>
-
#include <errno.h>
-
#include <string.h>
-
#include <sys/types.h>
-
#include <netinet/in.h>
-
#include <sys/socket.h>
-
#include <sys/wait.h>
-
-
#include "socket.h"
-
-
int Connect(int fd, struct
sockaddr *addr, socklen_t len)
-
{
-
int result;
-
if ((result = connect(fd, addr, len)) == -1) {
-
perror("connect");
-
exit(EXIT_FAILURE);
-
}
-
return 0;
-
}
-
-
int Socket_Connect(char *ip,char *port)
-
{
-
struct sockaddr_in addr;
-
-
int sockfd;
-
int len;
-
-
-
addr.sin_family = AF_INET;
-
addr.sin_addr.s_addr = inet_addr(ip);//127.0.0.1为本机ip
-
addr.sin_port = htons(atoi(port));
-
len = sizeof(addr);
-
-
sockfd = socket(AF_INET, SOCK_STREAM, 0);
-
Connect(sockfd, (struct sockaddr*)&addr, len);
-
return sockfd;
-
-
}
-
-
-
/*
-
功能:向socketfd发送数据,内部实现了循环发送len长度
-
参数:
-
sockfd 是代表你与远程程序连接的套接字描述符。
-
msg 是一个指针,指向你想发送的信息的地址。
-
len 是你想发送信息的长度。
-
flags 发送标记。一般都设为0
-
返回:
-
0-------- 成功
-
退出---失败
-
修改:
-
备注:
-
王振
-
*/
-
int Send(int sockfd, char *sendbuf, int len, int flags)
-
{
-
int sendlen = 0;
-
int ret = -1;
-
-
while(sendlen < len)
-
{
-
ret = send(sockfd, sendbuf+sendlen, len-sendlen, flags);
-
if(-1 == ret)
-
{
-
perror("send");
-
exit(EXIT_FAILURE);
-
}
-
else
-
sendlen += ret;
-
-
}
-
-
return 0;
-
-
}
-
-
int Close(int sockfd)
-
{
-
return close(sockfd);
-
-
-
}
-
int Recv(int sockfd, char *recvbuf, int len, int flags)
-
{
-
int recv_len;
-
-
if ((recv_len = recv(sockfd, recvbuf, len, flags)) < 0)
-
{
-
perror("recv error");
-
exit(EXIT_FAILURE);
-
}
-
-
return recv_len;
- }
很早就有这个想法了,知道今天才实现了一下,参考了周立发的《《C语言实现HTTP下载例子》》和林超旗整理的《《HTTP协议详解》》,特此谢谢!由于今天时间有限,所以大概的实现了一下(目前只在ubuntu环境下测试的),跨平台部分还需要完善,后面会陆续整理出完整的版本。代码如下:
头文件
/*
$Id: httpdownload.h, v1.0.0, 2011.6.28, YellowBug $
$@@: http下载
*/
#ifndef PENQ_HTTP_DOWNLOAD_H_
#define PENQ_HTTP_DOWNLOAD_H_
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#if defined(__GNUC__)
#include <sys/types.h>
#include <sys/socket.h>
#include <unistd.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <netdb.h>
#include <time.h>
#define PATH_SPLIT '/'
typedef int HSOCKET;
#define closesocket(s) close(s)
#elif defined(_WIN32)
#include <windows.h>
#include <winsock2.h>
#define PATH_SPLIT '//'
typedef SOCKET HSOCKET;
#endif
#define DEF_HTTP_PORT 80
#define HTTP_MAX_PATH 260
#define HTTP_MAX_REQUESTHEAD 1024
#define HTTP_MAX_RESPONSEHEAD 2048
#define HTTP_DOWN_PERSIZE 128
#define HTTP_FLUSH_BLOCK 1024
/*
$@@ 下载状态
$@ HDL_URLERR: 提供的url非法
$@ HDL_SERVERFAL: 根据host地址无法找到主机
$@ HDL_TIMEOUT: 连接下载地址超时
$@ HDL_READYOK: 连接下载地址成功,准备下载
$@ HDL_DOWNING: 正在下载
$@ HDL_DISCONN: 断开连接了
$@ HDL_FINISH: 下载完成
*/
enum
{
HDL_URLERR = 0xe0,
HDL_SERVERFAL = 0xe1,
HDL_SOCKFAL = 0xe2,
HDL_SNDREQERR = 0xe3,
HDL_SERVERERR = 0xe4,
HDL_CRLOCFILEFAL = 0xe5,
HDL_TIMEOUT = 0x100,
HDL_NORESPONSE = 0x101,
HDL_READYOK = 0x104,
HDL_DOWNING = 0x105,
HDL_DISCONN = 0x106,
HDL_FINISH = 0x107
};
/*
$@@ 下载回调函数,主要用于监听下载过程
$@ 第一个参数: 下载标识符号(上面枚举中的值)
$@ 第二个参数: 需要下载的总字节数
$@ 第三个参数: 已经下载的总字节数
$@ 第四个参数: 距离上一次下载的时间戳(毫秒)
$@ 返回值: 下次下载的字节数(0: 表示默认值, 小于0则下载中断, 大于0则位下次下载指定的字节)
*/
typedef int (*HTTPDOWNLOAD_CALLBACK)(int, unsigned int, unsigned int, unsigned int);
/*
$@@ 通过指定的url下载资源
$@ url: 下载资源地址
$@ filepath: 存储在本地的路径(如果为NULL则存储在当前目录)
$@ filename: 存储的文件名(如果为NULL则位url中分析的文件名)
$@ http_func: 下载过程监听函数(如果为NULL则不监听下载过程)
*/
void pq_http_download(const char *url, const char *filepath, const char *filename, HTTPDOWNLOAD_CALLBACK http_func);
#endif /* #ifndef PENQ_HTTP_DOWNLOAD_H_ */
/* END */
实现:
/*
$Id: httpdownload.c, v1.0.0, 2011.6.28, YellowBug $
$@@: http下载
*/
#include "httpdownload.h"
#define HERROR(exp, err) do { /
if (exp) { /
printf("(%s,%d) %s/n", __FILE__, __LINE__, #exp); /
if(S_http_callback) S_http_callback((err), 0, 0, 0); /
exit(1); /
} /
} while(0)
#define HCALLBACK(a, b, c, d) (S_http_callback ? S_http_callback((a), (b), (c), (d)) : 0)
/*
$@@ 文件域的局部变量
*/
static HTTPDOWNLOAD_CALLBACK S_http_callback = NULL;
static const char * S_request_head = "GET %s HTTP/1.1/r/n" /
"Accept: */*/r/n" /
"Accept-Language: zh-cn/r/n" /
"User-Agent: Molliza/4.0(compatible;MSIE6.0;windows NT 5.0)/r/n" /
"Host: %s/r/n" /
"Connection: close/r/n/r/n";
#if defined(__GNUC__)
unsigned int get_tick()
{
struct timeval tv;
gettimeofday(&tv, NULL);
return (unsigned int)(tv.tv_sec*1000+tv.tv_usec/1000);
}
#elif defined(_WIN32)
#define get_tick() GetTickCount()
#endif
/*
$@@ 从指定长度(len0)的字符串的右端开始找到第一个位ch的字符
*/
static char * r_strchr(const char *p, int len, char ch)
{
int last_pos = -1;
int i = 0;
while ( *(p+i) != '/0' ) {
if ( *(p+i) == ch ) last_pos = i;
if ( ++i >= len ) break;
}
return last_pos >= 0 ? (char *)(p+last_pos) : NULL;
}
/*
$@@ 解析url,分析出web地址,端口号,下载的资源名称
*/
static void parse_url(const char *url, char *web, int *port, char *filepath, char *filename)
{
int web_len = 0;
int web_start = 0;
int file_len = 0;
int file_start = 0;
int path_len = 0;
int path_start = 0;
char *pa = NULL;
char *pb = NULL;
if ( !strncmp(url, "http://", 7) ) web_start = 7;
else if ( !strncmp(url, "https://", 8) ) web_start = 8;
pa = strchr(url+web_start, '/');
pb = strchr(url+web_start, ':');
/* 获取web */
if ( pa ) {
web_len = (int)(pa - (url+web_start));
} else {
if ( pb ) web_len = pb - (url+web_start);
else web_len = strlen(url+web_start);
}
HERROR(0 == web_len, HDL_URLERR);
memcpy(web, url+web_start, web_len);
web[web_len] = '/0';
/* 获取filename */
if ( pb ) {
pa = r_strchr(url+web_start, (int)(pb-(url+web_start)), '/');
file_len = (int)(pb - pa - 1);
file_start = (int)(pa + 1 - url);
*port = atoi(pb+1);
} else {
pa = r_strchr(url+web_start, strlen(url+web_start), '/');
HERROR(NULL == pa, HDL_URLERR);
file_len = strlen(pa+1);
file_start = (int)(pa + 1 - url);
*port = DEF_HTTP_PORT;
}
HERROR(NULL == pa, HDL_URLERR);
memcpy(filename, url+file_start, file_len);
filename[file_len] = '/0';
/* 获取filepath */
path_start = web_start + web_len;
path_len = file_start - path_start + file_len;
memcpy(filepath, url+web_start+web_len, file_start - path_start + file_len);
filepath[path_len] = '/0';
}
/*
$@@ 封装request报头
*/
int package_request(char *request, const char *web, const char *filepath)
{
int n = sprintf(request, S_request_head, filepath, web);
request[n] = '/0';
return n;
}
/*
$@@ 发送请求报头
*/
int send_request(HSOCKET sock, const char *buf, int n)
{
int already_bytes = 0;
int per_bytes = 0;
while ( already_bytes < n ) {
per_bytes = send(sock, buf+already_bytes, n-already_bytes, 0);
if ( per_bytes < 0 ) {
break;
} else if ( 0 == per_bytes ) {
HCALLBACK(HDL_DISCONN, 0, 0, 0);
break;
} else
already_bytes += per_bytes;
}
return already_bytes;
}
/*
$@@ 获取响应报头(状态行和消息报头)
*/
int get_response_head(HSOCKET sock, char *response, int max)
{
int total_bytes = 0;
int per_bytes = 0;
int over_flag = 0;
char ch;
do {
per_bytes = recv(sock, &ch, 1, 0);
if ( per_bytes < 0 ) break;
else if ( 0 == per_bytes ) {
HCALLBACK(HDL_DISCONN, 0, 0, 0);
break;
} else {
if ( '/r' == ch || '/n' == ch ) over_flag++;
else over_flag = 0;
response[total_bytes] = ch;
if ( total_bytes >= max ) break;
else total_bytes += per_bytes;
}
} while ( over_flag < 4 );
response[total_bytes] = '/0';
return total_bytes;
}
void parse_response_head(char *response, int n, int *server_status, unsigned int *down_total)
{
int i = 1;
char *pstatus = strstr(response, "HTTP/1.1");
char *plen = strstr(response, "Content-Length:");
printf("%s/n", response);
if ( pstatus ) {
pstatus += strlen("HTTP/1.1");
i = 1;
while ( *(pstatus+i) == ' ' ) ++i;
*server_status = atoi(pstatus+i);
} else
*server_status = 0;
if ( plen ) {
plen += strlen("Content-Length:");
i = 1;
while ( *(plen+i) == ' ' ) ++i;
*down_total = (unsigned int)atoi(plen+i);
} else
*down_total = 0;
printf("status: %d/n", *server_status);
printf("total: %u/n", *down_total);
}
/*
$@@ 创建本地文件
*/
FILE * create_local_file(const char *local_path, const char *local_file, const char *remote_file)
{
int len = strlen(local_path);
static char filename[HTTP_MAX_PATH+1];
if ( local_path[len-1] != PATH_SPLIT ) {
if ( local_file ) len = sprintf(filename, "%s%c%s", local_path, PATH_SPLIT, local_file);
else len = sprintf(filename, "%s%c%s", local_path, PATH_SPLIT, remote_file);
} else {
if ( local_file ) len = sprintf(filename, "%s%s", local_path, local_file);
else len = sprintf(filename, "%s%s", local_path, remote_file);
}
filename[len] = '/0';
printf("ready to create local file: %s/n", filename);
return fopen(filename, "wb+");
}
/*
$@@ 开始下载
*/
void start_download(HSOCKET sock, unsigned int total_bytes, FILE *stream)
{
unsigned int already_bytes;
int per_bytes;
int flush_bytes;
unsigned int total_tim;
unsigned int per_tim;
static char buf[HTTP_DOWN_PERSIZE];
do {
per_tim = get_tick();
per_bytes = recv(sock, buf, HTTP_DOWN_PERSIZE, 0);
per_tim = get_tick() - per_tim;
if ( per_bytes < 0 ) break;
else if ( 0 == per_bytes ) {
HCALLBACK(HDL_DISCONN, total_bytes, already_bytes, 0);
break;
} else {
fwrite(buf, 1, per_bytes, stream);
already_bytes += (unsigned int)per_bytes;
flush_bytes += per_bytes;
if ( flush_bytes >= HTTP_FLUSH_BLOCK ) {
fflush(stream);
flush_bytes = 0;
}
HCALLBACK(HDL_DOWNING, total_bytes, already_bytes, per_tim);
}
} while ( already_bytes < total_bytes );
if ( flush_bytes > 0 )
fflush(stream);
HCALLBACK(HDL_FINISH, total_bytes, already_bytes, total_tim);
}
void pq_http_download(const char *url, const char *local_path, const char *local_file, HTTPDOWNLOAD_CALLBACK http_func)
{
int port;
char web[HTTP_MAX_PATH+1];
char remote_path[HTTP_MAX_PATH+1];
char remote_file[HTTP_MAX_PATH+1];
char request[HTTP_MAX_REQUESTHEAD+1];
char response_head[HTTP_MAX_RESPONSEHEAD+1];
int request_len = 0;
int response_len = 0;
unsigned int down_total = 0;
int server_status = 404;
HSOCKET sock = -1;
struct hostent *host = NULL;
struct sockaddr_in server_addr;
FILE *stream = NULL;
S_http_callback = http_func;
parse_url(url, web, &port, remote_path, remote_file);
request_len = package_request(request, web, remote_path);
host = gethostbyname(web);
HERROR(NULL==host, HDL_SERVERFAL);
sock = socket(AF_INET, SOCK_STREAM, 0);
HERROR(-1==sock, HDL_SOCKFAL);
memset(&server_addr, 0, sizeof(server_addr));
server_addr.sin_family = AF_INET;
server_addr.sin_port = htons(port);
server_addr.sin_addr = *((struct in_addr *)host->h_addr);
HERROR(-1==connect(sock, (struct sockaddr *)&server_addr, sizeof(struct sockaddr)), HDL_TIMEOUT);
HERROR(request_len != send_request(sock, request, request_len), HDL_SNDREQERR);
response_len = get_response_head(sock, response_head, HTTP_MAX_RESPONSEHEAD);
HERROR(response_len <= 0, HDL_NORESPONSE);
parse_response_head(response_head, response_len, &server_status, &down_total);
switch ( server_status ) {
case 200:
if ( down_total > 0 ) {
stream = create_local_file(local_path, local_file, remote_file);
HERROR(NULL == stream, HDL_CRLOCFILEFAL);
HCALLBACK(HDL_READYOK, down_total, 0, 0);
start_download(sock, down_total, stream);
fclose(stream);
} else {
HCALLBACK(HDL_FINISH, 0, 0, 0);
}
default:
HCALLBACK(HDL_SERVERERR, 0, 0, 0);
}
closesocket(sock);
}
/* END */
测试代码:
#include <stdio.h>
#include "httpdownload.h"
int http_callback(int status, unsigned int total_bytes, unsigned int already_bytes, unsigned int tim)
{
switch ( status ) {
case HDL_READYOK:
printf("ready to download, total.bytes=%u/n", total_bytes);
break;
case HDL_DOWNING:
printf("downloading... (%u/%u) bytes/n", already_bytes, total_bytes);
break;
case HDL_FINISH:
printf("download finish! download total.bytes=%u/n", already_bytes);
break;
default:
printf("status: %#x/n", status);
}
return 0;
}
int main(int argc, char *argv[])
{
pq_http_download(argv[1], argv[2], NULL, http_callback);
}
一个http下载程序的c实现
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <errno.h>
#include <unistd.h>
#include <netinet/in.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <limits.h>
#include <netdb.h>
#include <arpa/inet.h>
#include <ctype.h>
#define RECVBUFSIZE 2048
#define READBUFSIZE 2048
/********************************************
功能:搜索字符串右边起的第一个匹配字符
********************************************/
char * Rstrchr(char * s, char x) {
int i = strlen(s);
if(!(*s)) return 0;
while(s[i-1]) if(strchr(s + (i - 1), x)) return (s + (i - 1)); else i--;
return 0;
}
/********************************************
功能:把字符串转换为全小写
********************************************/
void ToLowerCase(char *s) {
while(*s) {
*s=tolower(*s);
s++;
}//while
}
/**************************************************************
功能:从字符串src中分析出网站地址和端口,并得到用户要下载的文件
***************************************************************/
void GetHost(char * src, char * web, char * file, int * port) {
char * pA;
char * pB;
memset(web, 0, sizeof(web));
memset(file, 0, sizeof(file));
*port = 0;
if(!(*src)) return;
pA = src;
if(!strncmp(pA, "http://", strlen("http://"))) pA = src+strlen("http://");
else if(!strncmp(pA, "https://", strlen("https://"))) pA = src+strlen("https://");
pB = strchr(pA, '/');
if(pB) {
memcpy(web, pA, strlen(pA) - strlen(pB));
if(pB+1) {
memcpy(file, pB + 1, strlen(pB) - 1);
file[strlen(pB) - 1] = 0;
}
}
else memcpy(web, pA, strlen(pA));
if(pB) web[strlen(pA) - strlen(pB)] = 0;
else web[strlen(pA)] = 0;
pA = strchr(web, ':');
if(pA) *port = atoi(pA + 1);
else *port = 80;
}
int find_start(char *buf, int size)
{
int i;
char *p;
p = strstr(buf, "\r\n\r\n");
if(p) {
i = p - buf + 4;
if(i >= size)
i = -1;
}
else
i = -1;
return i;
}
//唯一的参数就是网址网址
int main(int argc, char **argv) {
int sockfd;
int fd;
struct hostent *host;
int portnumber;
char url[128];
char host_addr[256];
char host_file[1024];
char request[1024];
struct sockaddr_in server_sockaddr;
char recvbuf[RECVBUFSIZE];
int sendbytes, recvbytes, totalsend, totalsended;
int file_start_pos;
sprintf(url, "%s", argv[1]);
ToLowerCase(url);
GetHost(url, host_addr, host_file, &portnumber);
host = gethostbyname(host_addr);
sockfd = socket(PF_INET, SOCK_STREAM, 0);
server_sockaddr.sin_family = PF_INET;
server_sockaddr.sin_port = htons(portnumber);
server_sockaddr.sin_addr = *((struct in_addr *)host->h_addr);
bzero(&(server_sockaddr.sin_zero), 8);
connect(sockfd, (struct sockaddr *)&server_sockaddr, sizeof(struct sockaddr));
sprintf(request, "GET /%s HTTP/1.1\r\nAccept: */*\r\nAccept-Language: zh-cn\r\n\
User-Agent: Mozilla/4.0 (compatible; MSIE 5.01; Windows NT 5.0)\r\n\
Host: %s:%d\r\nConnection: Keep-Alive\r\n\r\n", host_file, host_addr, portnumber);
sendbytes = 0;
totalsended = 0; //already send
totalsend = strlen(request); //will send
while(totalsended < totalsend) {
sendbytes = send(sockfd, request+totalsended, totalsend - totalsended, 0);
totalsended += sendbytes;
}//while
fd = open("download", O_CREAT | O_TRUNC | O_RDWR, 0777);
recvbytes = recv(sockfd, recvbuf, RECVBUFSIZE, 0); //delete the head
file_start_pos = find_start(recvbuf, recvbytes);
write(fd, recvbuf+file_start_pos, recvbytes-file_start_pos);
while((recvbytes = recv(sockfd, recvbuf, RECVBUFSIZE, 0)) > 0) {
write(fd, recvbuf, recvbytes);
}//while
close(fd);
close(sockfd);
return 0;
}//main()