【HTTP】C语言写的简单的HTTP下载器
原帖链接:http://www.0xaa55.com/thread-1093-1-1.html转载请注明出处。
别指望这东西有多强大的功能——它就是用来下载一个直链资源。给一个URL然后就能完成下载。目前只支持HTTP协议(以http://开头的链接)至于什么ED2K、种子、磁力链,或者ftp、https等,它是不支持的。
这个程序使用了Winsock库,TCP/IP协议。
原理就是建立一个SOCKET套接字(调用socket(AF_INET,SOCK_STREAM,IPPROTO_TCP)得到一个基于TCP/IP协议的SOCKET),分析要下载的文件的URL(比如http://www.baidu.com/img/bdlogo.png),取得协议(http://)、主机名(www.baidu.com)、路径(/img/bdlogo.png),然后建立一个http请求头,将其发送到服务器。服务器会回复一些内容,通过分析服务器发回的内容决定进一步的处理。
HTTP请求头是文本格式,一行一个描述符,然后以两个换行符为结尾。我直接参考了IE浏览器的HTTP请求头(用fiddler抓包)
通过参考了IE浏览器的HTTP请求头,我的下载器的请求头是这个样子的:GET /路径 HTTP/1.1
Host: 域名
Connection: Keep-Alive
Accept: */*
Accept-Language: zh-CN
User-Agent: Mozilla/5.0 (Windows NT 6.1; WOW64; Trident/7.0; rv:11.0) like Gecko
如果我要从文件的中间开始下载,我的请求头会在这里添加Range: 开始字节-结束字节(其中结束字节是可选的)
我并不打算让我的下载器支持gzip、deflate,否则我就需要再给自己的代码整合一个zlib。
然后经过我的测试,当我下载一个7z文件的时候,服务器会返回这么一个HTTP头:HTTP/1.1 200 OK
Date: Mon, 27 Oct 2014 14:10:09 GMT
Server: Apache
Last-Modified: Mon, 27 Oct 2014 07:32:24 GMT
Accept-Ranges: bytes
Content-Length: 内容大小
Connection: close
Content-Type: application/x-7z-compressed
如果Accept-Ranges的值是none,那么表示这个文件不能从中间开始下载,只能从头下载。
Content-Length大概是文件的大小,但是并不是每种MIME类型都会提供这个域,如果我是要下载一个纯文本、html文件,可能就没有Content-Length域。
而有些服务器因为资源被转移了,它会返回一个重定向信息。HTTP/1.1 301 Moved Permanently
Date: Thu, 11 Dec 2014 10:53:19 GMT
Server: Apache/2.2.15 (CentOS)
Location: 新URL
Content-Length: 错误页面的大小
Connection: close
Content-Type: text/html; charset=字符集
这个时候就要注意第一行的HTTP/1.1后面的数字了,200是正常,3XX是重定向,4XX是无法访问特定资源,5XX一般是服务器错误或者其它。
如果是3XX重定向,我们就要注意Location域的内容,它指定了新的URL。
根据如上的经验,我写了这个简单的下载器。
经过测试,它能正确下载文件。
http://www.0xaa55.com/data/attachment/forum/201412/11/201523x119h129rzq97o12.png
http://www.0xaa55.com/data/attachment/forum/201412/11/201542y8ffsabysvqay74n.png
其中下载的文件是可以被正常打开的,有图为证:
http://www.0xaa55.com/data/attachment/forum/201412/10/195935pxqze88dccgm8xmg.png
而且对于3XX重定向的链接,它也能正确下载:
http://www.0xaa55.com/data/attachment/forum/201412/11/202315hvxkteaxptaat0fr.png
http://www.0xaa55.com/data/attachment/forum/201412/11/202338qll0uwwfdmwkujww.png
我这个程序使用了三个文件,为了使代码可复用。
download.c
download.h
entry.c
其中最重要的函数是DownFile,它的原型如下://=============================================================================
//函数:DownFile
//描述:根据指定的URL下载一个文件,返回下载的文件大小。
//用法:提供网络资源地址,以及一些可选的参数,然后使用两个回调函数取得下载到的
// 内容。下载到的内容通过fnOnGetData回调函数返回。
//-----------------------------------------------------------------------------
DownLDFunc(FileSize,DownFile)
(
char*szURL, //网络资源地址
int Limited, //是否限制大小
FileSize cbStartByte, //开始字节
FileSize cbEndByte, //结束字节
fnOnGetSize pfnGetSize, //取得下载到的数据的大小时调用的函数
fnOnGetData pfnGetData, //取得下载到的数据时调用的函数
fnErrReporter ErrReport,//报告错误的函数
void*pUserData //传递给用户回调函数的用户自定义参数
);全部代码如下:
download.h//=============================================================================
//作者:0xAA55
//网站:http://0xaa55.com
//版权所有(C) 技术宅的结界
//请保留原作者信息,否则视为侵权
//
//download.h:
//下载器的头文件
//-----------------------------------------------------------------------------
#ifndef _Download_
#define _Download_
#include<stddef.h> //取得size_t的定义
#ifndef DownLDImpExp //符号的导出或导入前缀
# ifdef __cplusplus
# define DownLDImpExp extern"C"
# else
# define DownLDImpExp extern
# endif // !__cplusplus
#endif // !DownLDType
#ifndef DownLDCall //下载器的调用约定
#define DownLDCall _cdecl
#endif // !DownLDCall
#ifndef DownLDCBCall //下载器的回调函数调用约定
#define DownLDCBCall _cdecl
#endif // !DownLDCBCall
#ifndef DownLDInternal //内部符号,不导出
#define DownLDInternal static
#endif // !DownLDInternal
#ifndef DownLDFunc //下载器的函数定义
#define DownLDFunc(r,f) DownLDImpExp r DownLDCall f
#endif // !DownLDFunc
#ifndef DownLDCBFunc //下载器的函数定义
#define DownLDCBFunc(r,f) DownLDImpExp r DownLDCBCall f
#endif // !DownLDFunc
typedef unsigned long long FileSize;
//=============================================================================
//函数类型:fnErrReporter
//描述:报告错误的函数
//-----------------------------------------------------------------------------
typedef void(DownLDCBCall*fnErrReporter)(char*szFormat,...);
//=============================================================================
//函数类型:fnOnGetFileLen
//描述:取得下载到的数据的大小时调用的函数
// 用户使用此回调函数取得要下载的文件的大小。文件的大小通过FileSize返回
// 当无法取得大小时,FileSize返回Size_Unknown
// 用户如果要拒绝下载,使此回调函数返回零即可,否则返回非零
//-----------------------------------------------------------------------------
typedef int(DownLDCBCall*fnOnGetSize)
(
FileSize, //内容大小
void*pUserData //用户自定义参数
);
#define Size_Unknown ((size_t)~0)
//=============================================================================
//函数类型:fnOnGetData
//描述:取得下载到的数据时调用的函数
// 用户如果要拒绝下载,使此回调函数返回零即可,否则返回非零
//-----------------------------------------------------------------------------
typedef int(DownLDCBCall*fnOnGetData)
(
FileSize Position, //位置
void *pData, //数据指针
size_t cbData, //数据大小
void *pUserData //用户自定义参数
);
//=============================================================================
//函数:DownFile
//描述:根据指定的URL下载一个文件,返回下载的文件大小。
//用法:提供网络资源地址,以及一些可选的参数,然后使用两个回调函数取得下载到的
// 内容。下载到的内容通过fnOnGetData回调函数返回。
//-----------------------------------------------------------------------------
DownLDFunc(FileSize,DownFile)
(
char*szURL, //网络资源地址
int Limited, //是否限制大小
FileSize cbStartByte, //开始字节
FileSize cbEndByte, //结束字节
fnOnGetSize pfnGetSize, //取得下载到的数据的大小时调用的函数
fnOnGetData pfnGetData, //取得下载到的数据时调用的函数
fnErrReporter ErrReport,//报告错误的函数
void*pUserData //传递给用户回调函数的用户自定义参数
);
//=============================================================================
//函数:DefErrReport
//描述:默认的报告错误的函数。
//-----------------------------------------------------------------------------
DownLDCBFunc(void,DefErrReport)(char*szFormat,...);
#endif // !_AA55Download_download.c//=============================================================================
//作者:0xAA55
//网站:http://0xaa55.com
//版权所有(C) 技术宅的结界
//请保留原作者信息,否则视为侵权
//
//download.c:
//下载器的源码
//-----------------------------------------------------------------------------
#include"download.h"
#include<stdio.h>
#include<stdarg.h>
#include<string.h>
#include<WinSock2.h>
#include<WS2tcpip.h>
#define MaxSendBuf 0x2000 /*最大发送缓冲区大小*/
#define MaxBuf 0x2000 /*最大缓冲区大小*/
#define MaxPath 0x400 /*最大路径长度*/
#define MaxHost 0x100 /*最大主机名长度*/
#define MaxServ 0x40 /*最大服务号长度*/
#define MaxWaitTime 3000 /*最长等待时间,3秒*/
#define MaxZeroByteRecv 3 //能接受的最大0字节包裹数
//=============================================================================
//函数:DoNothingErrReport
//描述:什么也不做的错误报告程序,当用户不提供错误报告程序的时候调用这个。
//-----------------------------------------------------------------------------
DownLDCBFunc(void,DoNothingErrReport)(char*szFormat,...)
{
//什么也不做
}
//=============================================================================
//函数:DefErrReport
//描述:报告错误。
//-----------------------------------------------------------------------------
DownLDCBFunc(void,DefErrReport)(char*szFormat,...)
{
va_list ap;
va_start(ap,szFormat);
vfprintf(stderr,szFormat,ap);
va_end(ap);
}
//=============================================================================
//函数:h_error_String
//描述:打印h_errno的内容
//-----------------------------------------------------------------------------
DownLDInternal
char*DownLDCall h_error_String()
{
switch(h_errno)
{
case WSAEACCES:
return"WSAEACCES";
case WSAEADDRINUSE:
return"WSAEADDRINUSE";
case WSAEADDRNOTAVAIL:
return"WSAEADDRNOTAVAIL";
case WSAEAFNOSUPPORT:
return"WSAEAFNOSUPPORT";
case WSAEALREADY:
return"WSAEALREADY";
case WSAECONNABORTED:
return"WSAECONNABORTED";
case WSAECONNREFUSED:
return"WSAECONNREFUSED";
case WSAECONNRESET:
return"WSAECONNRESET";
case WSAEDESTADDRREQ:
return"WSAEDESTADDRREQ";
case WSAEFAULT:
return"WSAEFAULT";
case WSAEHOSTDOWN:
return"WSAEHOSTDOWN";
case WSAEHOSTUNREACH:
return"WSAEHOSTUNREACH";
case WSAEINPROGRESS:
return"WSAEINPROGRESS";
case WSAEINTR:
return"WSAEINTR";
case WSAEINVAL:
return"WSAEINVAL";
case WSAEISCONN:
return"WSAEISCONN";
case WSAEMFILE:
return"WSAEMFILE";
case WSAEMSGSIZE:
return"WSAEMSGSIZE";
case WSAENETDOWN:
return"WSAENETDOWN";
case WSAENETRESET:
return"WSAENETRESET";
case WSAENETUNREACH:
return"WSAENETUNREACH";
case WSAENOBUFS:
return"WSAENOBUFS";
case WSAENOPROTOOPT:
return"WSAENOPROTOOPT";
case WSAENOTCONN:
return"WSAENOTCONN";
case WSAENOTSOCK:
return"WSAENOTSOCK";
case WSAEOPNOTSUPP:
return"WSAEOPNOTSUPP";
case WSAEPFNOSUPPORT:
return"WSAEPFNOSUPPORT";
case WSAEPROCLIM:
return"WSAEPROCLIM";
case WSAEPROTONOSUPPORT:
return"WSAEPROTONOSUPPORT";
case WSAEPROTOTYPE:
return"WSAEPROTOTYPE";
case WSAESHUTDOWN:
return"WSAESHUTDOWN";
case WSAESOCKTNOSUPPORT:
return"WSAESOCKTNOSUPPORT";
case WSAETIMEDOUT:
return"WSAETIMEDOUT";
case WSATYPE_NOT_FOUND:
return"WSATYPE_NOT_FOUND";
case WSAEWOULDBLOCK:
return"WSAEWOULDBLOCK";
case WSAHOST_NOT_FOUND:
return"WSAHOST_NOT_FOUND";
case WSAEINVALIDPROCTABLE:
return"WSAEINVALIDPROCTABLE";
case WSAEINVALIDPROVIDER:
return"WSAEINVALIDPROVIDER";
case WSANOTINITIALISED:
return"WSANOTINITIALISED";
case WSANO_DATA:
return"WSANO_DATA";
case WSANO_RECOVERY:
return"WSANO_RECOVERY";
case WSAEPROVIDERFAILEDINIT:
return"WSAEPROVIDERFAILEDINIT";
case WSASYSCALLFAILURE:
return"WSASYSCALLFAILURE";
case WSASYSNOTREADY:
return"WSASYSNOTREADY";
case WSATRY_AGAIN:
return"WSATRY_AGAIN";
case WSAVERNOTSUPPORTED:
return"WSAVERNOTSUPPORTED";
case WSAEDISCON:
return"WSAEDISCON";
default:
return"";
}
}
//=============================================================================
//函数:GetLineLen
//描述:取得一行字符串的长度
//-----------------------------------------------------------------------------
size_t GetLineLen(char*pLine)
{
char*pLineEnd;
pLineEnd=strstr(pLine,"\r\n");
if(!pLineEnd)
pLineEnd=strchr(pLine,'\n');
if(!pLineEnd)
pLineEnd=&pLine;
return(size_t)pLineEnd-(size_t)pLine;
}
//=============================================================================
//函数:LineOut
//描述:使用报错函数打印一行内容
//-----------------------------------------------------------------------------
DownLDInternal
void DownLDCall LineOut
(
char*pLine, //行起始
fnErrReporter ErrReport //报错函数
)
{
char*pLineEnd;
char ChrEnd;
pLineEnd=strstr(pLine,"\r\n");
if(!pLineEnd)
pLineEnd=strchr(pLine,'\n');
if(!pLineEnd)
pLineEnd=&pLine;
ChrEnd=*pLineEnd;
*pLineEnd='\0';
ErrReport("%s\n",pLine);
*pLineEnd=ChrEnd;
}
//=============================================================================
//函数:ParseURL
//描述:分析一个URL,取得域名、路径等信息。
//-----------------------------------------------------------------------------
DownLDInternal
void DownLDCall ParseURL
(
char*szURL, //URL
char*szProtocol,//协议
char*szHostOut, //主机名
char*szServOut, //服务名、端口号
char*szPathOut //路径
)
{
char*pChr;
size_t cbStr;
char*pPath;
char*pHost;
pChr=strchr(szURL,':');//从开头到第一个冒号之间的字符串是协议,比如http,ftp
if(!pChr)
return;
//如果需要返回协议类型
if(szProtocol)
{
cbStr=(size_t)pChr-(size_t)szURL;
memset(szProtocol,0,cbStr+1);
memcpy(szProtocol,szURL,cbStr);
}
//准备处理主机名和服务名
pHost=pChr+3;//冒号往后3字节开始
pPath=strchr(pHost,'/');//路径
if(!pPath)
pPath=&pHost;//如果没有路径则指向字符串最后一个字节(0)
pChr=strchr(pHost,':');//找端口号
if(pChr && (pPath?(size_t)pChr<(size_t)pPath:1))//如果指定了端口号
{
if(szHostOut)//如果需要返回主机名
{
size_t cbHost=(size_t)pChr-(size_t)pHost;//主机名字符串长度
strncpy(szHostOut,pHost,cbHost);
szHostOut='\0';
}
if(szServOut)//如果需要返回服务名
{
size_t cbServ=(size_t)pPath-(size_t)pChr-1;//服务名字符串长度
strncpy(szServOut,pChr+1,cbServ);
szServOut='\0';
}
}
else
{
if(szHostOut)//如果需要返回主机名
{
size_t cbHost=(size_t)pPath-(size_t)pHost;//主机名字符串长度
strncpy(szHostOut,pHost,cbHost);
szHostOut='\0';
}
if(szServOut)//如果需要返回服务名
strcpy(szServOut,"80");
}
if(szPathOut)//如果需要返回路径
{
size_t LineLen=GetLineLen(pPath);
strncpy(szPathOut,pPath,LineLen);
szPathOut='\0';
}
}
//=============================================================================
//函数:ConnectToHost
//描述:让一个Socket连接到一个域名,成功返回非零
//-----------------------------------------------------------------------------
DownLDInternal
int DownLDCall ConnectToHost
(
SOCKET sock, //套接字
char*szHost, //主机名
char*szServ, //服务名
fnErrReporter ErrReport //报错函数
)
{
struct addrinfo Hints; //用于解析域名的Hint
struct addrinfo*pAddrInfoFirst=NULL;//解析取得的域名
struct addrinfo*pAddrInfo; //用于走链表
//解析域名
Hints.ai_flags=0;
Hints.ai_family=AF_INET;
Hints.ai_socktype=SOCK_STREAM;
Hints.ai_protocol=IPPROTO_TCP;
Hints.ai_addrlen=0;
Hints.ai_canonname=NULL;
Hints.ai_addr=NULL;
Hints.ai_next=NULL;
if(getaddrinfo(szHost,szServ,&Hints,&pAddrInfoFirst))
{
ErrReport("Unable to resolve host name:%s.%s\n",
szHost,h_error_String());
goto Cleanup;
}
//遍历列表直到能连接。
pAddrInfo=pAddrInfoFirst;
do
{
# ifdef _DEBUG
ErrReport("Trying:%u.%u.%u.%u:%u\n",
((SOCKADDR_IN*)(pAddrInfo->ai_addr))->sin_addr.s_net,
((SOCKADDR_IN*)(pAddrInfo->ai_addr))->sin_addr.s_host,
((SOCKADDR_IN*)(pAddrInfo->ai_addr))->sin_addr.s_lh,
((SOCKADDR_IN*)(pAddrInfo->ai_addr))->sin_addr.s_impno,
htons(((SOCKADDR_IN*)(pAddrInfo->ai_addr))->sin_port));
# endif
if(!connect(sock,pAddrInfo->ai_addr,(int)(pAddrInfo->ai_addrlen)))
break;//连接上了就跳出循环
pAddrInfo=pAddrInfo->ai_next;//寻到链表下一个
}while(pAddrInfo);
if(!pAddrInfo)//链表到头了
{
ErrReport("Unable to retrieve the host name:%s.%s\n",
szHost,h_error_String());
goto Cleanup;
}
//释放整个链表
freeaddrinfo(pAddrInfoFirst);
return 1;
Cleanup:
if(pAddrInfoFirst)
freeaddrinfo(pAddrInfoFirst);
return 0;
}
//=============================================================================
//函数:SendRequestHeader
//描述:通过SOCKET发送HTTP请求头
//-----------------------------------------------------------------------------
DownLDInternal
int DownLDCall SendRequestHeader
(
SOCKET sock, //套接字
char*szHost, //主机名
char*szServ, //服务名
char*szPath, //请求路径
int Limited, //是否限制大小
FileSize cbStartByte, //开始字节
FileSize cbEndByte, //结束字节
fnErrReporter ErrReport //报告错误的函数
)
{
char szBuf; //发送缓冲区
strcpy(szBuf,"GET ");
strcat(szBuf,szPath);//路径
strcat(szBuf," HTTP/1.1\r\nHost: ");
strcat(szBuf,szHost);//主机
strcat(szBuf,"\r\nConnection: Keep-Alive\r\n"//保持连接
"Accept: */*\r\n"//接受任何MIME类型
"Accept-Language: zh-CN\r\n"//语言:简体中文
"User-Agent: Mozilla/5.0 (Windows NT 6.1; WOW64; Trident/7.0; rv:11.0)"
" like Gecko\r\n");//Win7 x64里的IE11的User-Agent值,居然没有MSIE字样
//如果限制了大小和范围,则给出Range域
if(Limited)
sprintf(szBuf,"%sRange: %u-%u\r\n\r\n",szBuf,cbStartByte,cbEndByte);
else if(cbStartByte)//否则判断是否要指定下载开始位置
sprintf(szBuf,"%sRange: %u-\r\n\r\n",szBuf,cbStartByte);
else
strcat(szBuf,"\r\n");
//发送HTTP请求包
# ifdef _DEBUG
ErrReport("%s",szBuf);
# endif
if(send(sock,szBuf,(int)strlen(szBuf),0)==SOCKET_ERROR)
{
ErrReport("Failed to send HTTP header.%s\n",h_error_String());
goto Cleanup;
}
return 1;
Cleanup:
return 0;
}
//=============================================================================
//函数:DownFile
//描述:根据指定的URL下载一个文件,返回下载的文件大小。
//-----------------------------------------------------------------------------
DownLDFunc(FileSize,DownFile)
(
char*szURL, //网络资源地址
int Limited, //是否限制大小
FileSize cbStartByte, //开始字节
FileSize cbEndByte, //结束字节
fnOnGetSize pfnGetSize, //取得下载到的数据的大小时调用的函数
fnOnGetData pfnGetData, //取得下载到的数据时调用的函数
fnErrReporter ErrReport,//报告错误的函数
void*pUserData //传递给用户回调函数的用户自定义参数
)
{
char szBuf; //缓冲区
char szHost; //域名
char szServ; //服务名或端口号
char szPath; //路径
int cbRecv; //接收到的字节数
int HeaderFinished=0; //是否已读取完HTTP头
int StatusCode=0; //状态代码(301、404、502等)
FileSize ContentLength=Size_Unknown;//内容长度
FileSize ContentRecv=0; //接收到的内容长度
FileSize CurPos=cbStartByte; //当前文件位置
int Retry=0; //是否重试
unsigned RcvTimeOut=MaxWaitTime; //recv最长等待时间
unsigned NbZeroByteRecv=0; //接收到的0字节包裹数
SOCKET sockClient=INVALID_SOCKET; //用于下载的套接字
//如果用户不提供报错回调函数,则报错的时候什么也不做
if(!ErrReport)
ErrReport=DoNothingErrReport;
//直接判断前7字节是不是http://,不使用ParseURL返回的“协议”
if(strnicmp(szURL,"http://",7))
{
//必须是http协议。
ErrReport("Protocol must be HTTP.\n");
goto Cleanup;
}
//解析URL,取得主机名、服务名、路径等信息
ParseURL(szURL,NULL,szHost,szServ,szPath);
do//while(Retry--);
{
//=====================================================================
//这一层循环用于提供重试的机会。每次“尝试”相当于以下操作:
//建立Socket
//连接到域名
//发送HTTP请求
//分析服务器传回的内容
//---------------------------------------------------------------------
//初始化套接字,TCP/IP协议
sockClient=socket(AF_INET,SOCK_STREAM,IPPROTO_TCP);
if(sockClient==INVALID_SOCKET)
{
//初始化套接字失败。
ErrReport("Unable to initialize socket.%s\n",h_error_String());
goto Cleanup;
}
//设置recv函数的超时时间
setsockopt(sockClient,SOL_SOCKET,SO_RCVTIMEO,
(const char*)&RcvTimeOut,sizeof(RcvTimeOut));
//连接Socket到域名
if(!ConnectToHost(sockClient,szHost,szServ,ErrReport))
goto Cleanup;
//发送HTTP请求包
if(!SendRequestHeader(sockClient,szHost,szServ,
szPath,Limited,cbStartByte,cbEndByte,ErrReport))
goto Cleanup;
//=====================================================================
//接收内容
//---------------------------------------------------------------------
do//while(ContentRecv<ContentLength);
{
int iErrno;
//HTTP头的长度可能超过我们一个缓冲区的大小
//因此我们进行分块接收
memset(szBuf,0,sizeof(szBuf));
cbRecv=recv(sockClient,szBuf,MaxBuf,0);
if(cbRecv==SOCKET_ERROR)
{
ErrReport("Failed to receive data.%s\n",h_error_String());
goto Cleanup;
}
//检查错误代码
iErrno=h_errno;
if( iErrno==WSAENOTCONN||
iErrno==WSAESHUTDOWN ||
iErrno==WSAENETRESET ||
iErrno==WSAECONNRESET||
iErrno==WSAECONNABORTED)
{
ErrReport("The network has been disconnected.%s\n",
h_error_String());
break;
}
//超时
if(iErrno==WSAETIMEDOUT)
{
ErrReport("Respond timeout.%s\n",h_error_String());
Retry++;
break;
}
if(!cbRecv)//零字节包裹
{
NbZeroByteRecv++;//记录零字节包裹数
if(NbZeroByteRecv>=MaxZeroByteRecv)
{//如果接收到的0字节包裹数超过容忍度
Retry++;//重连
NbZeroByteRecv=0;//重新统计零字节包裹数
break;
}
continue;//否则继续
}
//=================================================================
//已读取HTTP头,接收文件内容
//-----------------------------------------------------------------
if(HeaderFinished)
{
size_t cbDataLen=cbRecv;
ContentRecv+=cbDataLen;//统计已经下载到的字节数。
if(pfnGetData && cbDataLen)
{//将数据传递给用户
if(!pfnGetData(CurPos,szBuf,cbDataLen,pUserData))
{
ErrReport("Download aborted.\n");
goto Cleanup;
}
}
CurPos+=cbDataLen;
}
//=================================================================
//未读取完HTTP头,解析HTTP头
//-----------------------------------------------------------------
else
{
char*pLinePtr;//准备一行一行分析http头
char*pChr;//字符指针
pLinePtr=szBuf;//从缓冲区开头开始
while(pLinePtr && (size_t)pLinePtr<(size_t)szBuf+cbRecv)
{//当行指针并没有超出缓冲区边界
//如果读取到两个换行符,意味着HTTP头结束
if(!strncmp(pLinePtr,"\r\n\r\n",4))
{
size_t cbDataLen;
pLinePtr+=4;//跳过两个回车,转到内容
//计算剩余的数据长度
cbDataLen=cbRecv-((size_t)pLinePtr-(size_t)szBuf);
HeaderFinished=1;//已读取Http头
ContentRecv+=cbDataLen;//统计已经下载到的字节数。
if(pfnGetData && cbDataLen)
{//将数据传递给用户
if(!pfnGetData(CurPos,pLinePtr,
cbDataLen,pUserData))
{
ErrReport("Download aborted.\n");
goto Cleanup;
}
}
CurPos+=cbDataLen;
break;
}
//跳过行开头的空格、Tab等
while( *pLinePtr==' '||
*pLinePtr=='\t' ||
*pLinePtr=='\r' ||
*pLinePtr=='\n')
pLinePtr++;
//=========================================================
//按照条件分析HTTP头的每行的数据
//---------------------------------------------------------
# ifdef _DEBUG
//调试模式下打印出HTTP头的内容
LineOut(pLinePtr,ErrReport);
# endif // _DEBUG
//=========================================================
//判断HTTP状态代码
if(!strnicmp(pLinePtr,"HTTP/1.1",8))
{
//如果是3XX表示已经移动,4XX表示无法访问,5XX则直接放弃
//"HTTP/1.1"后面可能有空格
pChr=pLinePtr+8;
while( *pChr==' '||
*pChr=='\t')
pChr++;
LineOut(pChr,ErrReport);
//读取状态代码
StatusCode=atoi(pChr);
if(StatusCode>=400)//超过400则放弃下载。
goto Cleanup;
}else
//=========================================================
//如果是Accept-Ranges域
if(!strnicmp(pLinePtr,"accept-ranges:",14))
{
//判断是不是"Accept-Ranges:none",它意味着只能从头下载。
//"Accept-Ranges:"后面可能有空格
pChr=pLinePtr+14;
while( *pChr==' '||
*pChr=='\t')
pChr++;
if(!strnicmp(pChr,"none",4))
CurPos=0;
}else
//=========================================================
//如果是Content-Length域
if(!strnicmp(pLinePtr,"content-length:",15))
{
//"Content-Length"域指定了内容的长度
//"Content-Length:"后面可能有空格
pChr=pLinePtr+15;
while( *pChr==' '||
*pChr=='\t')
pChr++;
ContentLength=(FileSize)_atoi64(pChr);
if(pfnGetSize)//这个回调函数可以是NULL
{
if(!pfnGetSize(ContentLength,pUserData))
{
ErrReport("Download aborted.\n");
goto Cleanup;
}
}
}else
//=========================================================
//如果是Location:域,并且之前指定了3XX重定向
if(StatusCode>=300 && !strnicmp(pLinePtr,"Location:",9))
{
//"Location"域指定了跳转的新位置
//"Location:"后面可能有空格
pChr=pLinePtr+9;
while( *pChr==' '||
*pChr=='\t')
pChr++;
//判断前7字节是不是http://
if(strnicmp(pChr,"http://",7))
{
//必须是http协议。
ErrReport("Protocol must be HTTP.\n");
goto Cleanup;
}
//解析URL,取得主机名、服务名、路径等信息
ParseURL(pChr,NULL,szHost,szServ,szPath);
Retry++;
# ifdef _DEBUG
ErrReport("\nRedirecting to %s\n",szHost);
# endif
break;
}
pLinePtr=strstr(pLinePtr,"\r\n");//转到下一行
}//while(pLinePtr && (size_t)pLinePtr<(size_t)szBuf+cbRecv)
}//if(!HeaderFinished)
//如果已经决定重试,则不再等待接收数据。
if(Retry)
break;
//接收总内容达到“说好的”大小,认定接收完成
}while(ContentRecv<ContentLength);
closesocket(sockClient);
}while(Retry--);
return ContentRecv;
Cleanup:
if(sockClient!=INVALID_SOCKET)
closesocket(sockClient);
return ContentRecv;
}entry.c//=============================================================================
//作者:0xAA55
//网站:http://0xaa55.com
//版权所有(C) 技术宅的结界
//请保留原作者信息,否则视为侵权
//-----------------------------------------------------------------------------
#include"download.h"
#include<io.h>
#include<stdio.h>
#include<conio.h>
#include<WinSock2.h>
//=============================================================================
//函数:Usage
//描述:介绍程序的用法
//-----------------------------------------------------------------------------
void Usage(char*argv0)
{
fprintf(stderr,"Usage:\n"
"%s URL TargetFilePath \n"
"URL should be started with \"http://\", used to specify which file you want\n"
"to download.\n"
"TargetFilePath should be the location you want to download to.\n"
" is optional, syntax is ###-[###].\n",argv0);
}
//=============================================================================
//函数:OnGetSize
//描述:取得文件大小时的回调函数
//-----------------------------------------------------------------------------
int DownLDCBCall OnGetSize(FileSize Size,void*pUserData)
{
if(Size==Size_Unknown)//如果文件大小未知
fputs("Unknown content length.\n",stderr);
//else
// fprintf(stderr,"Content length:%.I64u\n",Size);
return 1;
}
//=============================================================================
//函数类型:OnGetData
//描述:取得下载到的数据时调用的函数
//-----------------------------------------------------------------------------
int DownLDCBCall OnGetData
(
FileSize Position, //位置
void *pData, //数据指针
size_t cbData, //数据大小
void *pUserData //用户自定义参数
)
{
FILE*fp;
fprintf(stderr,"Received %u bytes.\n",cbData);
fp=fopen((const char*)pUserData,"ab");
if(fp)
{
fwrite(pData,1,cbData,fp);
fclose(fp);
}
return 1;
}
char*h_error_String();
//=============================================================================
//函数:main
//描述:程序入口点
//-----------------------------------------------------------------------------
int main(int argc,char**argv)
{
WSADATA wsaData;
int Limited=0;
size_t StartByte=0;
size_t EndByte=0;
//至少两个参数:URL和存放的文件位置
if(argc<3)
{
Usage(argc?argv:"HTTPDOWN");
return 1;
}
//第三个参数:可选的下载文件的开始位置和结束位置
if(argc>3)
{
Limited=1;
if(sscanf(argv,"%u-%u",&StartByte,&EndByte)!=2)
{
fputs("Invalid parameters.\n",stderr);
return 1;
}
}
//先删除已经存在的文件。
if(!_access(argv,0))
unlink(argv);
//初始化Winsock
if(WSAStartup(WINSOCK_VERSION,&wsaData))
goto Cleanup;
//下载并打印字节数
fprintf(stderr,"%u bytes downloaded.\n",
DownFile(argv,Limited,StartByte,EndByte,OnGetSize,OnGetData,DefErrReport,argv));
WSACleanup();
return 0;
Cleanup:
fprintf(stderr,"%s\n",h_error_String());
WSACleanup();
return 2;
}
//=============================================================================
//函数:h_error_String
//描述:打印h_errno的内容
//-----------------------------------------------------------------------------
char*h_error_String()
{
switch(h_errno)
{
case WSAEACCES:
return"WSAEACCES";
case WSAEADDRINUSE:
return"WSAEADDRINUSE";
case WSAEADDRNOTAVAIL:
return"WSAEADDRNOTAVAIL";
case WSAEAFNOSUPPORT:
return"WSAEAFNOSUPPORT";
case WSAEALREADY:
return"WSAEALREADY";
case WSAECONNABORTED:
return"WSAECONNABORTED";
case WSAECONNREFUSED:
return"WSAECONNREFUSED";
case WSAECONNRESET:
return"WSAECONNRESET";
case WSAEDESTADDRREQ:
return"WSAEDESTADDRREQ";
case WSAEFAULT:
return"WSAEFAULT";
case WSAEHOSTDOWN:
return"WSAEHOSTDOWN";
case WSAEHOSTUNREACH:
return"WSAEHOSTUNREACH";
case WSAEINPROGRESS:
return"WSAEINPROGRESS";
case WSAEINTR:
return"WSAEINTR";
case WSAEINVAL:
return"WSAEINVAL";
case WSAEISCONN:
return"WSAEISCONN";
case WSAEMFILE:
return"WSAEMFILE";
case WSAEMSGSIZE:
return"WSAEMSGSIZE";
case WSAENETDOWN:
return"WSAENETDOWN";
case WSAENETRESET:
return"WSAENETRESET";
case WSAENETUNREACH:
return"WSAENETUNREACH";
case WSAENOBUFS:
return"WSAENOBUFS";
case WSAENOPROTOOPT:
return"WSAENOPROTOOPT";
case WSAENOTCONN:
return"WSAENOTCONN";
case WSAENOTSOCK:
return"WSAENOTSOCK";
case WSAEOPNOTSUPP:
return"WSAEOPNOTSUPP";
case WSAEPFNOSUPPORT:
return"WSAEPFNOSUPPORT";
case WSAEPROCLIM:
return"WSAEPROCLIM";
case WSAEPROTONOSUPPORT:
return"WSAEPROTONOSUPPORT";
case WSAEPROTOTYPE:
return"WSAEPROTOTYPE";
case WSAESHUTDOWN:
return"WSAESHUTDOWN";
case WSAESOCKTNOSUPPORT:
return"WSAESOCKTNOSUPPORT";
case WSAETIMEDOUT:
return"WSAETIMEDOUT";
case WSATYPE_NOT_FOUND:
return"WSATYPE_NOT_FOUND";
case WSAEWOULDBLOCK:
return"WSAEWOULDBLOCK";
case WSAHOST_NOT_FOUND:
return"WSAHOST_NOT_FOUND";
case WSAEINVALIDPROCTABLE:
return"WSAEINVALIDPROCTABLE";
case WSAEINVALIDPROVIDER:
return"WSAEINVALIDPROVIDER";
case WSANOTINITIALISED:
return"WSANOTINITIALISED";
case WSANO_DATA:
return"WSANO_DATA";
case WSANO_RECOVERY:
return"WSANO_RECOVERY";
case WSAEPROVIDERFAILEDINIT:
return"WSAEPROVIDERFAILEDINIT";
case WSASYSCALLFAILURE:
return"WSASYSCALLFAILURE";
case WSASYSNOTREADY:
return"WSASYSNOTREADY";
case WSATRY_AGAIN:
return"WSATRY_AGAIN";
case WSAVERNOTSUPPORTED:
return"WSAVERNOTSUPPORTED";
case WSAEDISCON:
return"WSAEDISCON";
default:
return"";
}
}BIN下载:**** Hidden Message ***** 谢谢分享 有需要时再来瞧瞧 代码很不错 学习下 试试看看!! 大佬666
页:
[1]