基于curl 使用http多线程下载大文件

一、获取文件大小

int64_t CHttpClient::GetFileSize(const std::string &url)
{
    auto curl = curl_easy_init();
    if (!curl)
    {
        curl_easy_cleanup(curl);
        return -1;
    }
    double filesize = -1; //文件大小

    curl_easy_setopt(curl,CURLOPT_URL, url.c_str());
    curl_easy_setopt(curl, CURLOPT_HEADER, 1); 
    curl_easy_setopt(curl, CURLOPT_NOBODY, 1); 
    CURLcode res_code = curl_easy_perform(curl); //请求
    if (res_code != CURLE_OK)
    {
        curl_easy_cleanup(curl);
        return -1;
    }
    
    curl_easy_getinfo(curl, CURLINFO_CONTENT_LENGTH_DOWNLOAD, &filesize); 
    curl_easy_cleanup(curl);

    std::cout << "获取文件大小完成:" << (int64_t)filesize  << std::endl;
    return filesize;
}

二、划分线程

    auto client = GetClient();
    auto length = client->GetFileSize(url);
//根据线程数划分块大小
    auto blockSize = length / thNum;
    auto lastBlock = (length % thNum == 0 ? blockSize : blockSize + length % thNum );
    
    //启动多个线程进行下载
    std::vector<std::future<bool>> vecFt;
    std::vector<std::shared_ptr<INetProtocol>> vecDownload;
    for (size_t i = 0; i < thNum; i++)
    {
        auto block = (i == thNum - 1 ? lastBlock : blockSize);
        auto dlClient = GetClient();
        vecDownload.push_back(dlClient);
        vecFt.emplace_back(std::async(std::bind(&INetProtocol::Download, dlClient, url, blockSize * i,  block)));
    }

三、下载区间数据

bool CHttpClient::Download(const std::string &url, int64_t offset, int64_t blocksize)
{
    auto curl = curl_easy_init();
    if (!curl)
    {
        curl_easy_cleanup(curl);
        return false;
    }
    auto rangeEnd = offset + blocksize - 1;
    std::cout << "开始下载数据,区间:" << offset << "-" <<  rangeEnd  << ",块大小:" << blocksize << std::endl;
	m_blockData = std::make_shared<BlockData>(offset, blocksize);
	curl_easy_setopt(curl, CURLOPT_URL, url.c_str());
    curl_easy_setopt(curl, CURLOPT_FTP_RESPONSE_TIMEOUT, 10);
	curl_easy_setopt(curl, CURLOPT_WRITEDATA, this);
	curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, CHttpClient::WriteBlock);
    std::stringstream ss;
    ss << offset << "-" << rangeEnd;
	curl_easy_setopt(curl, CURLOPT_RANGE, ss.str().c_str());
	CURLcode res_code = curl_easy_perform(curl);
    if (res_code != CURLE_OK)
    {
        curl_easy_cleanup(curl);
        return false;
    }
	std::cout << "下载数据:" << offset << "-" << rangeEnd << ", 获取数据:" << m_blockData->readSize << "完成" << std::endl;
	curl_easy_cleanup(curl);
    return true;
}

四、等待所有线程完成

//同步等待所有线程完成
    bool isFaild = false;
    for (auto i = 0; i < vecFt.size(); i++)
	{
        if (!vecFt[i].get())
        {
            isFaild = true;
            std::cout << "线程:" << i << "下载失败" << std::endl;
        }       
    }

五、合并线程数据

void CDownload::MergerData(const std::string &fileName, std::vector<std::shared_ptr<INetProtocol>>& vecDownload)
{
    std::ofstream fout(fileName, std::ios::out | std::ios::binary);
	if (!fout)
	{
        throw std::runtime_error("create local file faild");
	}
    for (auto& item : vecDownload)
    {
        fout << item->GetStream().rdbuf();
    }
    
}

6、获取服务器文件MD5

std::string CHttpClient::GetContextMd5(const std::string &url)
{
    auto curl = curl_easy_init();
    if (!curl)
    {
        curl_easy_cleanup(curl);
        return std::string();
    }


    curl_easy_setopt(curl,CURLOPT_URL, url.c_str());
    curl_easy_setopt(curl, CURLOPT_HEADER, 1); 
    curl_easy_setopt(curl, CURLOPT_NOBODY, 1); 

    std::string strMd5;
    curl_easy_setopt(curl, CURLOPT_HEADERDATA, &strMd5);
    curl_easy_setopt(curl, CURLOPT_HEADERFUNCTION, CHttpClient::ReadHeader);
    CURLcode res_code = curl_easy_perform(curl); //请求
    if (res_code != CURLE_OK)
    {
        curl_easy_cleanup(curl);
        return std::string();
    }
    
    curl_easy_cleanup(curl);
    std::cout << "获取Md5完成:" << strMd5  << std::endl;
    return strMd5;
}

7、检验文件MD5是否一致

bool CDownload::CheckFile(const std::string &url, const std::string& strLocalFile)
{
    auto client = GetClient();
    auto contextMd5 = client->GetContextMd5(url);
    auto localMd5 = GetContentMd5(strLocalFile);
    std::cout << "context    md5:" << contextMd5 << std::endl;
    std::cout << "local file md5:" << localMd5  << std::endl;
    if (contextMd5 != localMd5)
    {
        std::cout << "md5 校验失败, 文件下载失败" << std::endl;
        return false;
    }
    std::cout << "md5 校验一致, 文件下载成功" << std::endl;
    return true;
}


//获取本地文件MD5,然后通过Base64编码输出
std::string CDownload::GetContentMd5(const std::string &fileName)
{
    std::ifstream fin(fileName, std::ios::in  | std::ios::binary);
    if (!fin)
    {
        throw std::runtime_error("获取文件MD5失败,文件打开失败");
    }

    EVP_MD_CTX  *mdctx;
    unsigned char *md5_digest;
    unsigned int md5_digest_len = EVP_MD_size(EVP_md5());

    mdctx = EVP_MD_CTX_new();
    EVP_DigestInit_ex(mdctx, EVP_md5(), NULL);

    const int bufSize = 4 * 1024 * 1024;
    char *pszBuf = new char[bufSize];
    while (!fin.eof())
    {
        fin.read(pszBuf, bufSize);
        EVP_DigestUpdate(mdctx, pszBuf, fin.gcount());
    }
    md5_digest = (unsigned char *)OPENSSL_malloc(md5_digest_len);
    EVP_DigestFinal_ex(mdctx, md5_digest, &md5_digest_len);
    EVP_MD_CTX_free(mdctx);
    delete[] pszBuf;
    return CBase64::encode(std::string((char*)md5_digest, md5_digest_len));
}

如需完整代码,可评论区留言

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
提供了两个 api - 一个是跳转到登录页面,请自行登录 `/login` - 一个是通用购买演出票 api,请自行传入对应参数, 支持定时功能 `/buy?event=xxx&ticketId=xxx&cron_time=xxx` - 支持自动选择观演人 `need_select` 如果不传,则不会选择观演人,如果传了,则会获取 `select_num` 的值进行观演人数量的选择, 具体逻辑参见代码 ```sh $ curl "http://127.0.0.1:9997/buy?event=169893&ticketId=xxx&need_select=True&ticketNum=1&select_num=1" ``` - 现可通过 [xiudong-go](https://github.com/ronething/xiudong-go) 进行 ticketId 的获取 - 如果多次刷新 login 且登录页面没有进行登录,可能会存在线程阻塞问题,因为 max_workers 设置了 10 个, 暂时可以通过关闭窗口解决 - 如果确认订单页面显示已售罄,需要不断刷新直到出现立即支付,这一点在捡漏的时候很有用 - 只是给大家提供点思路,其他请自行阅读代码,祝大家好运 - **注意:** 有问题请先看 , 没有则新开 issue 提问即可 ### 成功截图 昨晚抢到了。不错。 -------- 该资源内项目源码是个人的毕设,代码都测试ok,都是运行成功后才上传资源,答辩评审平均分达到96分,放心下载使用! <项目介绍> 1、该资源内项目代码都经过测试运行成功,功能ok的情况下才上传的,请放心下载使用! 2、本项目适合计算机相关专业(如计科、人工智能、通信工程、自动化、电子信息等)的在校学生、老师或者企业员工下载学习,也适合小白学习进阶,当然也可作为毕设项目、课程设计、作业、项目初期立项演示等。 3、如果基础还行,也可在此代码基础上进行修改,以实现其他功能,也可用于毕设、课设、作业等。 下载后请首先打开README.md文件(如有),仅供学习参考, 切勿用于商业用途。 --------

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值