- UID
- 1
- 精华
- 积分
- 76361
- 威望
- 点
- 宅币
- 个
- 贡献
- 次
- 宅之契约
- 份
- 最后登录
- 1970-1-1
- 在线时间
- 小时
|
原帖链接: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。
根据如上的经验,我写了这个简单的下载器。
经过测试,它能正确下载文件。
其中下载的文件是可以被正常打开的,有图为证:
而且对于3XX重定向的链接,它也能正确下载:
我这个程序使用了三个文件,为了使代码可复用。
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[strlen(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[strlen(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[strlen(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[cbHost]='\0';
- }
- if(szServOut)//如果需要返回服务名
- {
- size_t cbServ=(size_t)pPath-(size_t)pChr-1;//服务名字符串长度
- strncpy(szServOut,pChr+1,cbServ);
- szServOut[cbServ]='\0';
- }
- }
- else
- {
- if(szHostOut)//如果需要返回主机名
- {
- size_t cbHost=(size_t)pPath-(size_t)pHost;//主机名字符串长度
- strncpy(szHostOut,pHost,cbHost);
- szHostOut[cbHost]='\0';
- }
- if(szServOut)//如果需要返回服务名
- strcpy(szServOut,"80");
- }
- if(szPathOut)//如果需要返回路径
- {
- size_t LineLen=GetLineLen(pPath);
- strncpy(szPathOut,pPath,LineLen);
- szPathOut[LineLen]='\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[MaxSendBuf]; //发送缓冲区
- 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[MaxBuf+1]; //缓冲区
- char szHost[MaxHost]; //域名
- char szServ[MaxServ]; //服务名或端口号
- char szPath[MaxPath]; //路径
- 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 [Range]\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"
- "[Range] 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[0]:"HTTPDOWN");
- return 1;
- }
- //第三个参数:可选的下载文件的开始位置和结束位置
- if(argc>3)
- {
- Limited=1;
- if(sscanf(argv[3],"%u-%u",&StartByte,&EndByte)!=2)
- {
- fputs("Invalid parameters.\n",stderr);
- return 1;
- }
- }
- //先删除已经存在的文件。
- if(!_access(argv[2],0))
- unlink(argv[2]);
-
- //初始化Winsock
- if(WSAStartup(WINSOCK_VERSION,&wsaData))
- goto Cleanup;
-
- //下载并打印字节数
- fprintf(stderr,"%u bytes downloaded.\n",
- DownFile(argv[1],Limited,StartByte,EndByte,OnGetSize,OnGetData,DefErrReport,argv[2]));
-
- 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下载:
HttpDown.exe
(87.5 KB, 下载次数: 43)
|
http, tcpip, winsock, http, tcpip, winsock, http, tcpip, winsock, http, tcpip, winsock, http, tcpip, winsock, http, tcpip, winsock, http, tcpip, winsock, C语言, http, tcpip, winsock
|