源码: Windows下HTTP方式单线程下载

来源:互联网 发布:杀破狼js高品质百度云 编辑:程序博客网 时间:2024/05/29 14:07
     针对昨天的试验结果,书写了一个HTTP方式单线程下载的小程序,目前尚不支持断点续传。
希望各位看客使劲拍砖~~

原理:套接字发送HTTP GET方式的请求,然后根据HTTP响应,循环接收信息。

进行下载调用函数为:DownByHttpFun
传入参数为:下载的url,保存位置、错误信息等

下载过程中,获得进度函数为: GetDownloadRate
返回float型的进度,输出参数下载文件总大小和已下载文件大小
传入参数



#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <WinSock2.h>
#include <windows.h>
#include "HttpDown.h"

#pragma  comment(lib, "Ws2_32.lib")


/************************************************************************/
/* 全局变量                                                                */
/************************************************************************/
unsigned long g_FiltTotleLen  = 0;    //下载文件总大小
unsigned long g_DownedFileLen = 0;    //已下载文件大小


/************************************************************************/
/* 函数描述:判断某一指定字符串是否为ip地址                             */
/* 返回值:  是则返回0,否则返回-1                                        */
/* 作者:    liuwp                                                        */
/* 创建时间:2008-03-27                                                    */
/* 其他                                                                    */
/************************************************************************/
int IsIpAddrV4Str(char *pstr)
{
    char a[4],b[4],c[4],d[4];
    int num =0;

    if ( !pstr)
        return -1;

    num = sscanf(pstr,"%3[0-9].%3[0-9].%3[0-9].%3[0-9]",a,b,c,d);

    if ( num != 4 )
        return -1;

    if ( atoi(a)>255 ||  atoi(b)>255 || atoi(c) > 255 || atoi(d) >255  )
        return -1;

    return 0;
}


/************************************************************************/
/* 函数描述:根据下载地址获得HOST、IP等信息                             */
/* 返回值:  成功返回0,失败返回-1                                        */
/* 作者:    liuwp                                                        */
/* 创建时间:2008-03-27                                                    */
/* 其他                                                                    */
/************************************************************************/
int GetDownInfoByUlr(const char *pDownUrl, char *pIpAddr, int *Port
        , char *pGetInfo, int GetLen, char *RetErrorBuf, int ErrorLen)
{
    int RetCode = 0;
    char pUrlTempBuf[1000];
    char pHostInf[500];

    memset(pUrlTempBuf, 0, sizeof(pUrlTempBuf));
    memset(pHostInf, 0, sizeof(pHostInf));

    //参数判断
    if (!pDownUrl || !pIpAddr || !Port || !pGetInfo || !RetErrorBuf)
        return -1;

    //拷贝字符串
    strncpy(pUrlTempBuf, pDownUrl, sizeof(pUrlTempBuf)-1);

    //获得HOST起始位置
    char *pHostStart = strstr(pUrlTempBuf, "://");
    if (!pHostStart)
    {
        _snprintf(RetErrorBuf, ErrorLen-1, "给定的下载URL格式错误,应该类似 http://HOST/GET ");
        return -1;
    }

    //根据第一个 “/”获得HOST信息
    pHostStart = pHostStart + strlen("://");

    char *pHostEnd = strchr(pHostStart, '/');
    if (!pHostEnd)
    {    
        _snprintf(RetErrorBuf, ErrorLen-1, "给定的下载URL格式错误,应该类似 http://HOST/GET ");
        return -1;
    }

    *pHostEnd = '/0';
    _snprintf(pHostInf, sizeof(pHostInf)-1, "%s", pHostStart);

    //获得GET信息
    _snprintf(pGetInfo, GetLen-1, "/%s", pHostEnd+1);

    //根据HOST信息获得对应的IP和端口
    char *pPortStart = strchr(pHostInf, ':');
    if (!pPortStart)
    {
        *Port = 80;    //默认为80
    }
    else
    {
        *pPortStart = '/0';
        sscanf(pPortStart+1, "%d", Port);
    }

    //判断获得地址是否为IP,不是则转换为IP
    RetCode = IsIpAddrV4Str(pHostInf);
    if (RetCode != 0)
    {
        //初始化环境
        WSADATA WSAData;
        WSAStartup(MAKEWORD(2,2),&WSAData);

        struct hostent* lpHostEnt = gethostbyname(pHostInf);
        if(lpHostEnt == NULL)
        {
            _snprintf(RetErrorBuf, ErrorLen-1, "获得域名对应的IP地址失败, GetLastError()=%d", GetLastError());
            return -1;
        }
        else
        {    
            LPSTR lpaddr = lpHostEnt->h_addr_list[0];
            if(lpaddr)
            {
                struct in_addr inAddr;
                memcpy (&inAddr, lpaddr, 4);

                sprintf(pIpAddr, "%s", inet_ntoa (inAddr) );
            }
        }

        //清除环境
        WSACleanup();
    
    }
    else
    {
        sprintf(pIpAddr, "%s", pHostInf );
    }


    return 0;
}



/************************************************************************/
/* 函数描述:通过HTTP协议进行下载                                       */
/* 返回值:  下载成功返回0,失败返回-1                                 */
/* 作者:    liuwp                                                        */
/* 创建时间:2008-03-27                                                    */
/* 其他                                                                    */
/************************************************************************/
int DownByHttpFun(const char *pDownUrl, const char *pDownFile, unsigned long *pTotleLen
               , char *RetErrorBuf, int ErrorLen)
{
    WSADATA WSAData;
    sockaddr_in ServerAddr;
    fd_set fdset;
    struct timeval tv;
    HANDLE hFileHandle = NULL;
    bool FirstFlag = true;
    int RetCode = 0;
    int WriteLen = 0;    
    char SendBuf[1000];
    char RecvBuf[4096];
    char IpAddr[30];
    char GetInfo[600];
    int Port = 0;
    char *pConLen = NULL;
    DWORD dwFileSize  = 0;

    memset(IpAddr, 0, sizeof(IpAddr));
    memset(GetInfo, 0, sizeof(GetInfo));
    memset(RecvBuf, 0, sizeof(RecvBuf));
    memset(SendBuf, 0, sizeof(SendBuf));
    
    if (!pDownUrl || !pDownFile || !pTotleLen || !RetErrorBuf)
    {    
        _snprintf(RetErrorBuf, ErrorLen-1, "给定参数不正确");
        return -1;
    }

    //根据下载地址获得HOST, GET, IP,端口等信息
    RetCode = GetDownInfoByUlr(pDownUrl, IpAddr,  &Port
        , GetInfo, sizeof(GetInfo)-1, RetErrorBuf, ErrorLen);
    if (RetCode != 0)
        return -1;

    //初始化环境
    WSAStartup(MAKEWORD(2,2),&WSAData);
    
    //创建套接字
    SOCKET Sock = socket(AF_INET, SOCK_STREAM, 0);
    if ( SOCKET_ERROR == Sock )
    {
        _snprintf(RetErrorBuf, ErrorLen-1, "创建套接字失败, GetLastError()= %d !", GetLastError());
        goto LEXIT;
    }
    
    //给定服务器地址
    ServerAddr.sin_family = AF_INET;
    ServerAddr.sin_addr.s_addr = inet_addr(IpAddr);
    ServerAddr.sin_port = htons(Port);
    
    //连接服务器
    RetCode = connect(Sock, (struct sockaddr *)&ServerAddr, sizeof(ServerAddr));
    if (RetCode == SOCKET_ERROR)
    {
        _snprintf(RetErrorBuf, ErrorLen-1, "连接服务器失败, GetLastError()= %d !", GetLastError());
        goto LEXIT;
    }
    
    //发送信息
    _snprintf(SendBuf, sizeof(SendBuf)-1
        , "GET %s HTTP/1.1/r/nHost: %s:%d/r/n/r/n"
        ,GetInfo, IpAddr, Port);
    
    RetCode = send(Sock, SendBuf, strlen(SendBuf), 0);
    if (RetCode < 0)
    {
        _snprintf(RetErrorBuf, ErrorLen-1, "发送数据失败, GetLastError()= %d !", GetLastError());
        
        closesocket(Sock);
        WSACleanup();

        goto LEXIT;
        
    }
    
    //接收信息
    FD_ZERO(&fdset);
    FD_SET(Sock, &fdset);
    tv.tv_sec = 2*60;    //秒
    tv.tv_usec = 0;
    

    //打开文件
    hFileHandle = CreateFile(pDownFile, GENERIC_WRITE, 0
        , (LPSECURITY_ATTRIBUTES) NULL,CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL,(HANDLE) NULL);
    if (hFileHandle == INVALID_HANDLE_VALUE)
    {
        _snprintf(RetErrorBuf, ErrorLen-1, "打开文件%s失败, GetLastError()= %d !"
            , pDownFile, GetLastError());

        goto LEXIT;
    }

    //置空全局变量
    g_FiltTotleLen  = 0;    //下载文件总大小
    g_DownedFileLen = 0;    //已下载文件大小
    
    while(1)
    {
        memset(RecvBuf, 0, sizeof(RecvBuf));
        int len = select(0, &fdset, NULL, NULL, &tv);
        if (len < 1)
        {
            break;
        }
        
        //套接字接收信息
        len = recv(Sock, RecvBuf, sizeof(RecvBuf)-1 ,0);
        if (len <= 0)
        {    
            break;
        }
        
        if (!FirstFlag)    //不是第一次,追加写
        {
            dwFileSize = GetFileSize(hFileHandle, NULL);
            SetFilePointer(hFileHandle, dwFileSize, NULL, FILE_BEGIN);
            RetCode = WriteFile(hFileHandle, RecvBuf, len , (DWORD*)&WriteLen, NULL);
            if (RetCode == 0)
            {
                _snprintf(RetErrorBuf, ErrorLen-1, "写入文件失败 ! GetLastError()= %d !", GetLastError());
                goto LEXIT;
            }

            //获得此时已经下载的文件大小
            g_DownedFileLen = dwFileSize + len;
            
            //如果已经下载完毕,则跳出
            if (g_DownedFileLen == *pTotleLen)
                break;
            
            continue;
        }

        //判断返回值
        int RetCode =  0;
        sscanf(RecvBuf+strlen("HTTP/1.1 "), "%d", &RetCode);
        if (RetCode < 200 || RetCode > 300)
        {
            _snprintf(RetErrorBuf, ErrorLen-1, "下载失败, 服务器返回码为%d !", RetCode);
            goto LEXIT;
        }

        //获得总大小
        pConLen = strstr(RecvBuf, "/r/nContent-Length:");
        if (!pConLen)
        {
            _snprintf(RetErrorBuf, ErrorLen-1, "HTTP协议中未给定文件的大小!");
            goto LEXIT;
        }

        sscanf(pConLen, "/r/nContent-Length: %ld/r/n", pTotleLen);
        g_FiltTotleLen = *pTotleLen;
    
        char *pEnd = strstr(RecvBuf, "/r/n/r/n");
        if (!pEnd)
        {
            break;
        }
        
        pEnd = pEnd + strlen("/r/n/r/n");

    
        //移动到文件尾部,写文件
        dwFileSize = GetFileSize(hFileHandle, NULL);
        SetFilePointer(hFileHandle, dwFileSize, NULL, FILE_BEGIN);
        RetCode = WriteFile(hFileHandle, pEnd, len-(pEnd-RecvBuf) , (DWORD*)&WriteLen, NULL);
        if (RetCode == 0)
        {
            _snprintf(RetErrorBuf, ErrorLen-1, "写入文件失败 ! GetLastError()= %d !", GetLastError());
            goto LEXIT;
        }

        //获得此时已经下载的文件大小
        g_DownedFileLen = dwFileSize + len - (pEnd - RecvBuf);
        
        //更改第一次接收标识
        FirstFlag = false;
        
    }

    //下载完毕,判断下载大小是否相同
    dwFileSize = GetFileSize(hFileHandle, NULL);
    if (pTotleLen && *pTotleLen != 0 && *pTotleLen != dwFileSize)
    {
        _snprintf(RetErrorBuf, ErrorLen-1, "下载文件不完整, 请重新下载!");
        goto LEXIT;
    }
    
    //关闭文件
    if (hFileHandle)
    {
        CloseHandle(hFileHandle);
        hFileHandle = NULL;    
    }
    

    //关闭套接字
    if (Sock != 0)
    {
        closesocket(Sock);
        Sock = 0;
    }
    
    //清除环境
    WSACleanup();
    
    return 0;

LEXIT:

    //关闭文件
    if (hFileHandle)
    {
        CloseHandle(hFileHandle);
        hFileHandle = NULL;    
    }

    //关闭套接字
    if (Sock != 0)
    {
        closesocket(Sock);
        Sock = 0;
    }
    
    //清除环境
    WSACleanup();
    
    return -1;

}



/************************************************************************/
/* 函数描述:获得文件下载的相关信息                                     */
/* 返回值:                                                                */
/* 作者:    liuwp                                                        */
/* 创建时间:2008-03-28                                                    */
/* 其他                                                                    */
/************************************************************************/
float GetDownloadRate(unsigned long *pTotleLen , unsigned long *pDownLen)
{
    float DownRate = 0;

    if (!pTotleLen || !pTotleLen )
        return 0;

    //获得文件总大小和已下载的大小
    *pTotleLen = g_FiltTotleLen;
    *pDownLen = g_DownedFileLen;

    //计算速率
    if (g_FiltTotleLen == 0)
        return 0;

    if (g_DownedFileLen == 0)
        return 0;

    DownRate = (float)g_DownedFileLen/(float)g_FiltTotleLen;

    return DownRate;
}


原创粉丝点击