准备写一个http下载,支持断点下载,多线程下载

9 篇文章 0 订阅
7 篇文章 0 订阅

实现这个功能,需要使用http中分块下载,先写个小程序试试http中使用Range:bytes功能

#include <iostream>
#include <iosfwd>
#include <fstream>
#include <regex>
#include <string>
#include <winsock2.h>
#include <pthread.h>
#include <map>
#include <queue>

/**
*实现多线程http协议下载文件的功能,支持断点续传和多线程同时下载
*http请求使用Range: bytes完成
*为了保证文件的完整性,有两种方式
*1,使用生产者/消费者模式,将读取的信息放入队列中,由队列写文件
*2,同时写n个临时文件,最后执行完成拼接在一起
*
**/

using namespace std;

SOCKET sock;
//测试主机和端口
const char *testHostName="ww3.sinaimg.cn";
const short testPort=80;
const string testPortChar="80";
//文件地址
const char *url="/mw690/60d02b59tw1ehspkw5ewfg20dm05wb1p.gif";
//分块下载的块大小
const int getSize=10000;

int fileNameInt;


//发送http请求包
bool sendHttpQuery(string sendQueryStr){
    int n=0;
   //初始化socket
    sock = socket(AF_INET, SOCK_STREAM, 0);

    if (sock == INVALID_SOCKET)
    {
        cout << "建立socket失败! 错误码: " << WSAGetLastError() << endl;
        return false;
    }
    sockaddr_in sa = { AF_INET };
    n = bind(sock, (sockaddr*)&sa, sizeof(sa));
    if (n == SOCKET_ERROR)
    {
        cout << "bind函数失败! 错误码: " << WSAGetLastError() << endl;
        return false;
    }
    struct hostent *p = gethostbyname(testHostName);
    if (p == NULL)
    {
        cout << "主机无法解析出ip! 错误吗: " << WSAGetLastError() << endl;
        return false;
    }
    sa.sin_port = htons(testPort);
    memcpy(&sa.sin_addr, p->h_addr, 4);//   with some problems ???
    //sa.sin_addr.S_un.S_addr = inet_addr(*(p->h_addr_list));
    //cout << *(p->h_addr_list) << endl;
    //连接
    n = connect(sock, (sockaddr*)&sa, sizeof(sa));
    if (n == SOCKET_ERROR)
    {
        cout << "connect函数失败! 错误码: " << WSAGetLastError() << endl;
        return false;
    }
    //string  reqInfo = "GET / HTTP/1.1\r\n Host:www.baidu.com \r\n Connection:Keep-Alive\r\n\r\n ";
    //string reqInfo ="GET http://c.csdnimg.cn/rabbit/notev2/js/notify.js HTTP/1.1 \r\n Host: c.csdnimg.cn \r\n Connection: keep-alive \r\n Accept: */* \r\n User-Agent: Mozilla/5.0 (Windows NT 10.0) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/45.0.2454.101 Safari/537.36 \r\n Referer: http://www.csdn.net/ \r\n Accept-Encoding: gzip, deflate, sdch \r\n Accept-Language: zh-CN,zh;q=0.8\r\n\r\n";
    //string reqInfo = "GET /huajiao/huajiao.html HTTP/1.1 \r\n Host: se.360.cn \r\n Connection: close \r\n  Accept-Encoding: gzip, deflate, sdch \r\n Accept-Language: zh-CN,zh;q=0.8 \r\n Cookie: __huid=106HCYajdj5TzIlJ9HVN0evB27Lr1Z8JneHl8nAKepKUc%3D; __guid=132730903.2645167981534180400.1426119249932.2551; _ga=GA1.2.1411504458.1447744075; guid=54771369.514003739225082050.1466048704517.24 \r\n\r\n";
    //string reqInfo = "GET /thread-551469-1.html HTTP/1.1\r\nHost:bbs.51cto.com:80\r\nConnection:Close\r\n\r\n";
    //按照http送GET请求
    cout<<"发送http请求:"+sendQueryStr<<endl;
    if (SOCKET_ERROR == send(sock, sendQueryStr.c_str(), sendQueryStr.size(), 0))
    {
        cout << "send error! 错误码: " << WSAGetLastError() << endl;
        closesocket(sock);
        return false;
    }
    return true;
}

//从http的返回头信息中解析出文件长度
char * getResponseLeng(const char receiveBuf[1024]){

    //找到Content-Range: bytes 0-1/之后的字符串lengStartChar
    char *lengStartChar = strstr(receiveBuf, "Content-Range: bytes 0-1/");
    int tmpSize=sizeof("Content-Range: bytes 0-1/");//注意sizeof长度将结束符计算进去,后面需要减掉1
    //char *tmpSizeChar;
    //sprintf(tmpSizeChar,"%d",tmpSize);
    //cout<<(string)tmpSizeChar<<endl;
    //char *lengEndchar=strstr(buf, "\r\n");
    int i=0;
    bool isEnd=false;
    //在lengStartChar找到\r\n之前的位置
    while(!isEnd){
        if(lengStartChar[i+1]!='\r')i++;
        else{
            if(lengStartChar[i+2]=='\n')isEnd=true;
            else i++;
        }
    }

    //将lengStartChar中关于长度的一段赋值给长度字符串,注意减去结束符
    char *leng=new char(10);
    memset(leng,0x00,10);
    memcpy(leng,lengStartChar+tmpSize-1,i-tmpSize+2);
    //cout<<"111  "+(string)leng+"  111"<<endl;

    return leng;
}

int main()
{
    int n;

    char buf[1024];
    memset(buf, 0, sizeof(buf));
    fstream file;

    WORD version(0);
    WSADATA wsadata;
    int socket_return(0);
    version = MAKEWORD(2,0);
    socket_return = WSAStartup(version,&wsadata);
    if (socket_return != 0)
    {
        return 0;
    }
    file.open("D:\\testmap\\test.gif" ,ios::out|ios::binary);


    cout << " 文档打开 D:\\testmap\\test.gif"  << endl;
    if(!file.is_open()){
       cout << " 文档打开失败! D:\\testmap\\test.gif" << endl;
    }

    //for()
    string strFirst="GET "+(string)url+" HTTP/1.1\r\nHost:"+(string)testHostName+":"+testPortChar+"\r\nRange:bytes=0-1\r\nConnection:Close\r\n\r\n";
    //发送http请求
    if(!sendHttpQuery(strFirst)){
        cout<<" http 请求发送失败! "<<endl;
        return 0;
    }

    //思路:将Content-Range: bytes 0-1/后面的值作为长度解析出来
    //再根据文件长度决定循环几次请求(后续实现分配几个线程下载)
    n = recv(sock, buf, sizeof(buf)-1, 0);

    cout<<buf<<endl;
    char *lengChar=getResponseLeng(buf);
    cout<<"长度为 :"+(string)lengChar<<endl;
    int lengInt=atoi(lengChar);
    //得到每次取1000长度总共需要循环的次数
    int getTimeInt=lengInt/getSize;

    int i=0;

    while(i<=getTimeInt){

        char tmpBuf[1024];
        memset(tmpBuf, 0, sizeof(tmpBuf));

        char *startMark=new char(10);
        char *endMark=new char(10);//设置开始地址,结束地址,注意
        memset(startMark,0,10);
        memset(endMark,0,10);
        //string startMark;
        //string endMark;
       // startMark=itoa();
        sprintf(startMark,"%d",(i*getSize+0));
        sprintf(endMark,"%d",(i*getSize+(getSize-1)));
        //startMark[5]='\0';
        //endMark[5]='\0';
        string strFirst="GET "+(string)url+" HTTP/1.1\r\nHost:"+(string)testHostName+":"+testPortChar+"\r\nRange:bytes="+(string)startMark+"-"+(string)endMark+"\r\nConnection:Close\r\n\r\n";
        //发送http请求
        if(!sendHttpQuery(strFirst)){
            cout<<" http 请求发送失败! "<<endl;
            return 0;
        }

        n = recv(sock, tmpBuf, sizeof(tmpBuf)-1, 0);

        char *cpos = strstr(tmpBuf, "\r\n\r\n");

        //显示响应头
        //char *requestHeader=new char(200);
        //memset(requestHeader,0,200);
        //memcpy(requestHeader,tmpBuf,cpos - tmpBuf+1);


        file.write(cpos + strlen("\r\n\r\n"), n - (cpos - tmpBuf) - strlen("\r\n\r\n"));
       // cout<<cpos + strlen("\r\n\r\n") <<endl;
      //cout<<requestHeader<<endl;
        while ((n = recv(sock, tmpBuf, sizeof(tmpBuf)-1, 0)) > 0)
        {
            file.write(tmpBuf, n);
        }
        i++;
    }
    file.close();
    return 0;
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
### 回答1: 好的,我可以向你提供一个基本的多线程断点续传的下载方法。 首先,你需要创建一个线程类,用于下载文件的指定部分。这个线程类需要记录文件的开始和结束位置,并且在运行时使用这些位置来设置 HTTP 请求的 `Range` 头部。 然后,你可以创建一个主线程来管理多个下载线程。这个主线程需要获取文件的大小,并将文件分成若干个部分来下载。每个部分对应一个下载线程。 最后,你需要创建一个控制器类来管理主线程和下载线程。这个控制器类需要记录文件下载进度,并提供方法来开始、暂停和继续下载。 下面是一个示例代码,它展示了如何使用 Java 实现多线程断点续传的下载方法。这个示例使用了 Java 内置的 `ExecutorService` 来管理下载线程,并使用了 `CountDownLatch` 来等待所有线程完成。 ```java import java.io.IOException; import java.io.RandomAccessFile; import java.net.HttpURLConnection; import java.net.URL; import java.util.concurrent.CountDownLatch; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; public class MultiThreadDownloader { ### 回答2: 要编一个多线程断点续传的下载方法,可以使用Java中的多线程机制和输入输出流进行下载和保存文件的操作。 首先,创建一个下载方法,该方法需要接收下载的URL和本地保存文件的路径作为参数。在方法内部可以使用try-catch块来捕获可能的异常。 在下载方法内部,首先通过URL对象打开连接,并设置连接超时时间和请求参数。然后通过HttpURLConnection对象获取下载文件的总大小,并检查本地是否存在已下载的部分文件。 如果已有部分文件存在,就设置连接的Range头参数指定从现有文件的末尾开始下载。如果不存在部分文件,则创建一个新的文件来保存下载内容。这里可以使用RandomAccessFile类来支持随机访问文件。 然后,创建多个线程来进行文件下载。可以根据文件大小和预设的线程数量来确定每个线程负责下载文件范围。每个线程都应该打开一个输入流来读取远程文件的内容,并打开一个输出流将内容入本地文件。 在下载过程中,可以设置一个缓冲区来提高效率。可以使用byte数组作为缓冲区,在循环中不断读取输入流的内容并入输出流。 每个线程完成下载后,应该将已下载的字节数保存到一个全局变量中。所有线程下载完成后,可以关闭输入流和输出流,并检查下载是否成功。 如果下载成功,可以将下载进度保存到本地文件或数据库中,以便在需要时进行断点续传;如果下载未完成或失败,应将下载的临时文件删除,以防止出现错误的文件。 最后,在应用程序中调用这个下载方法,传入下载的URL和保存文件的路径即可开始下载,并可以设置断点续传的功能。 这就是一个使用Java多线程实现断点续传的下载方法的基本思路。根据实际需求,还可以添加进度条显示、下载速度计算等功能来提升用户体验。 ### 回答3: 编Java多线程断点续传的下载方法,主要需要实现以下几个步骤: 1. 创建一个类来表示下载任务,其中包含下载链接、保存路径、文件名等相关信息,以及记录已经下载的字节数等状态信息。 2. 在下载方法中,首先判断之前是否有未下载完的文件,如果有,则将已下载的字节数设置为已完成的字节数。 3. 设置多线程下载的线程数,根据文件大小计算每个线程需要下载的字节数。 4. 创建多个线程,每个线程的下载范围(起始和结束字节位置)根据已下载的字节数和计算得出。 5. 在每个线程中,根据指定的范围发送HTTP请求获取文件字节流,然后将字节流文件指定的位置,并累加已下载的字节数。 6. 当所有线程都下载完成后,将下载任务标志为已完成。 7. 如果下载过程中出现异常或者手动中断下载,则保存已下载的字节数,并将下载任务标志为未完成。 8. 如果下载中断后重新进行下载任务,则从保存的已下载字节数开始,继续进行下载。 9. 下载完成后,关闭所有的线程,释放资源。 通过上述步骤,我们可以实现一个多线程断点续传的下载方法,并保证在中断或异常情况下能够从上次中断的位置继续下载

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值