VC++支持断点续下或续传的功能

news2025/1/17 0:25:56

VC++使用多线程和Socket实现断点续下

一、断点续下的基本原理:

1.断点续传的理解可以分为两部分:一部分是断点,一部分是续传。断点的由来是在下载过程中,将一个下载文件分成了多个部分,同时进行多个部分一起的下载,当某个时间点,任务被暂停了,此时下载暂停的位置就是断点了。续传就是当一个未完成的下载任务再次开始时,会从上次的断点继续传送。

2.使用多线程断点续传下载的时候,将下载或上传任务(一个文件或一个压缩包)人为的划分为几个部分,每一个部分采用一个线程进行上传或下载,多个线程并发可以占用服务器端更多资源,从而加快下载速度。在下载(或上传)过程中,如果网络故障、电量不足等原因导致下载中断,这就需要使用到断点续传功能。下次启动时,可以从记录位置(已经下载的部分)开始,继续下载以后未下载的部分,避免重复部分的下载。断点续传实质就是能记录上一次已下载完成的位置。

注意:要实现HTTP断点续传,Web服务器必须支持HTTP/1.1

3.HTTP请求是有一个Header的,里面有个Range属性是定义下载区域的,它接收的值是一个区间范围,比如:Range:bytes=0-10000。这样我们就可以按照一定的规则,将一个大文件拆分为若干很小的部分,然后分批次的下载,每个小块下载完成之后,再合并到文件中;这样即使下载中断了,重新下载时,也可以通过文件的字节长度来判断下载的起始点,然后重启断点续传的过程,直到最后完成下载过程。

二、FTP实现断点续传

FTP协议也可以支持断点续传下载数据,基本原理是用get命令拿数据的时候在文件名后面加上要获取的起始位置。FTP实现断点续传有三个条件:

①断点续传需要服务器的支持,FTP服务器必须能提供断点续传的功能。传统的FTP Server是不支持断点续传的,因为它不支持REST指令;目前包括IIS和大部分的FTP架设软件都有了这个功能。用Serv-U架设FTP服务器就能支持断点续传。

②支持断点续传的下载工具软件

QQ旋风、迅雷、影音传送带等大多下载软件都支持断点续传;IE浏览器5.0以前的版本默认的自带下载方式不支持断点续传。在手机上,UC浏览器支持断点续传,能够自动存储已下载的部分,重新打开之后可以继续在已下载部分的基础上继续下载。

③FTP服务器上的文件要与下载到硬盘中的文件名相同。

在使用IE下载文件时,遇到网络中断,不需要重新启动机器,也可实现断点续传。前提是,在恢复下载、开始断点续传并提示再次保存文件时,要使用和第一次下载时相同的路径和文件名。

三、断点续传的基本原理包括以下几个步骤:

  1. 文件分割:下载的文件被分割成多个小块(或称为“分片”、“段”等)。

  2. 下载记录:客户端在下载每个文件块时,会记录下已经成功下载的块的信息,这通常包括块的序号、大小、校验码等。

  3. 中断检测:如果下载过程中发生中断,客户端会检测到这一情况。

  4. 恢复请求:当用户决定重新开始下载时,客户端会根据记录的下载信息,向服务器发送恢复请求,请求从最后一个成功下载的块开始继续下载。

  5. 服务器响应:服务器接收到恢复请求后,会根据请求中的信息,从指定的块开始发送数据。

  6. 数据校验:客户端在接收到数据后,会进行数据校验,确保接收的数据块是正确的。

  7. 合并文件:随着下载的进行,客户端会将下载的块按顺序合并,最终形成完整的文件。

  8. 完成下载:当所有块都下载并合并完成后,下载任务结束。

下面我们就新建工程来实现断点续下,如下笔者是直接用从网上下载的工程,叫做DownLoadTest,后面这个工程也会上传Gitee。
在这里插入图片描述

具体的代码是:

1.首先定义一个线程基类,用来启动下载。头文件为Thread.h

#ifndef _THREAD_SPECIFICAL_H__
#define _THREAD_SPECIFICAL_H__

#define    WIN32_LEAN_AND_MEAN   //防止windows.h引入winsock.h与winsock2.h冲突
#include   <windows.h>   

static unsigned int __stdcall threadFunction(void *);

class Thread {
   friend unsigned int __stdcall threadFunction(void *);
public:
   Thread();
   virtual ~Thread();
   int start(void * = NULL);//线程启动函数,其输入参数是无类型指针。
   void stop();
   void* join();//等待当前线程结束
   void detach();//不等待当前线程
   static void sleep(unsigned int);//让当前线程休眠给定时间,单位为毫秒

protected:
   virtual void * run(void *) = 0;//用于实现线程类的线程函数调用

private:
   HANDLE threadHandle;
   bool started;
   bool detached;
   void * param;
   unsigned int threadID;
};

#endif

Thread.cpp代码如下:

unsigned int __stdcall threadFunction(void * object)
{
   Thread * thread = (Thread *) object;
   return (unsigned int ) thread->run(thread->param);
}

Thread::Thread()
{
   started = false;
   detached = false;
}

Thread::~Thread()
{
   stop();
}

int Thread::start(void* pra)
{
   if (!started)
   {
   param = pra;
   if (threadHandle = (HANDLE)_beginthreadex(NULL, 0, threadFunction, this, 0, &threadID))
   {
   if (detached)
   {
   CloseHandle(threadHandle);
   }
   started = true;
   }
   }
   return started;
}

//wait for current thread to end.
void * Thread::join()
{
   DWORD status = (DWORD) NULL;
   if (started && !detached)
   {
   WaitForSingleObject(threadHandle, INFINITE);
   GetExitCodeThread(threadHandle, &status); 
   CloseHandle(threadHandle);
   detached = true;
   }

   return (void *)status;
}

void Thread::detach()
{
   if (started && !detached)
   {
   CloseHandle(threadHandle);
   }
   detached = true;
}

void Thread::stop()
{
   if (started && !detached)
   {
   TerminateThread(threadHandle, 0);

   //Closing a thread handle does not terminate 
   //the associated thread. 
   //To remove a thread object, you must terminate the thread, 
   //then close all handles to the thread.
   //The thread object remains in the system until 
   //the thread has terminated and all handles to it have been 
   //closed through a call to CloseHandle
   CloseHandle(threadHandle);
   detached = true;
   }
}

void Thread::sleep(unsigned int delay)
{
   ::Sleep(delay);
}

2.接下来从该类继承一个类DownLoadHelper用来真正的实现下载操作。

#include <vector>
#include <string>
#include <iostream>
using namespace std;
#include "Thread.h"
#include "ChineseCode.h"

//每个任务线程数
#define THREAD_COUNT 3
//重连时间
#define RECONNECT_INTERVAL 10000    

class DownloadHelper: public Thread
{
public:
    void * run(void *);
    bool startDownload();
    //url:"http://www.abc.com/123.jpg"
    //location: "f:\\download\\123.jpg"
    bool addDownloadTask(const char* remoteUrl, const char* localFolder);
    DownloadHelper();
    virtual ~DownloadHelper();
    //传入函数指针,下载完成后调用
    void setOnFinish(void (*func)());
private:
    //判断下载列表的文件是否已经存在
//传入index是downloadListRemoteURLs的下标
    bool exist(int index);
    //文件网络url路径
    vector<string> downloadListRemoteURLs;
    //文件在本地保存的目录
    vector<string> downloadListLocalFolders;
    //完成后调用的函数
    void (*onFinish)();
    ChineseCode chineseCode;

};

bool existInVector(vector<string>& array, string& str);

DownLoadHelper.cpp文件实现如下:

#include "stdafx.h"
#include "DownloadHelper.h"
#include "Mydownload.h"
#include <io.h>

//
// Construction/Destruction
//

DownloadHelper::DownloadHelper()
{
    onFinish = NULL;
}

DownloadHelper::~DownloadHelper()
{

}

//添加下载任务,以传入的url作为唯一标识符
bool DownloadHelper::addDownloadTask(const char* remoteUrl, const char* localFolder)
{
    string remoteUrlString(remoteUrl);
    string localFolderString(localFolder);

    if(!existInVector(downloadListRemoteURLs,remoteUrlString)){
        downloadListRemoteURLs.push_back(remoteUrlString);
        downloadListLocalFolders.push_back(localFolderString);
        return true;
    }else{
        return false;
    }
}

    //每次删除第一个
/*    vector<string>::iterator startIterator = downloadListRemoteURLs.begin();  
    downloadListRemoteURLs.erase( startIterator );
*/

//判断字符串是否已经在vector<string>中出现
bool existInVector(vector<string>& array, string& str){
    for(int k = 0;k<array.size();k++){
        if(array[k].compare(str)==0)
            return true;
    }
    return false;
}

//开始下载
bool DownloadHelper::startDownload()
{
    this->start();
    return true;
}

//多线程重构函数
void * DownloadHelper::run(void *)
{    
    //分配空间,用于跟踪。
    unsigned long temp = 0;
    unsigned long *downloaded = &temp;
    unsigned long totalSize = 1024;
    while(downloadListRemoteURLs.size()>0)
    {
        cout<<downloadListRemoteURLs[0]<<endl;
        //默认三线程下载,可以修改,但必须保持不变,因为断点续传需要前后两次线程数一致
        while(true){
            //阻塞式,直到下载成功或者网络出错才跳出
            fnMyDownload(downloadListRemoteURLs[0].data(),
                downloadListLocalFolders[0].data(),downloaded,totalSize,"",0,THREAD_COUNT);    
            if(!exist(0)){
                //文件不存在,表示下载中断
                cout<<"网络中断,等待重连..."<<endl;
                Sleep(RECONNECT_INTERVAL);    //10秒后重连
            }else{
                //下载成功,删除第一个任务
                vector<string>::iterator startIterator = downloadListRemoteURLs.begin();  
                downloadListRemoteURLs.erase( startIterator );
                startIterator = downloadListLocalFolders.begin();  
                downloadListLocalFolders.erase( startIterator );
                break;    
            }
        }
    }
    if(onFinish!=NULL){
        onFinish();
    }
    return NULL;
}

//判断下载列表的文件是否已经存在
//传入index是downloadListRemoteURLs的下标
bool DownloadHelper::exist(int index)
{
    string fileName = downloadListRemoteURLs[index].substr(downloadListRemoteURLs[index].find_last_of("/")+1);
    string file(downloadListLocalFolders[index].data());    //copy
    file.append(fileName);
    return (_access(file.data(), 0) == 0);;
}


    //传入函数指针,下载完成后调用
void DownloadHelper::setOnFinish(void (*func)()){
    onFinish = func;
}

因为这个项目是使用Http进行下载,所以定义一个Http类,用来处理Http下载请求,对于Http协议,笔者也不是很懂,后面还需要学习一下。这里就直接用原来的代码了。

定义一个HttpGet类来处理Http请求:头文件是MyDownLoad.h
#ifndef Mydownload___
#define Mydownload___

#include "stdafx.h"
#define MAX_RECV_LEN           100   // 每次接收最大字符串长度.
#define MAX_PENDING_CONNECTS   4     // 等待队列的长度.

class  CHttpSect
{
public:
    CString  szProxyAddr;     // 理服务器地址.
    CString  szHostAddr;      // Host地址.
    int      nProxyPort;      // 代理服务端口号.
    int      nHostPort;       // Host端口号.
    CString  szHttpAddr;      // Http文件地址.
    CString  szHttpFilename;  // Http文件名.
    CString  szDesFilename;   // 下载后的文件名.
    DWORD    nStart;          // 分割的起始位置.
    DWORD    nEnd;            // 分割的起始位置.
    DWORD    bProxyMode;      // 下载模态. 
};

class  CHttpGet  
{
public:
    CHttpGet();
    virtual ~CHttpGet();
    //static unsigned long m_downloaded;

private:
    CHttpSect *sectinfo;
    static int m_nCount;
    static UINT ThreadDownLoad(void* pParam);

public:
    static DWORD m_nFileLength;

private:
    static SOCKET ConnectHttpProxy(CString strProxyAddr,int nPort);
    static SOCKET ConnectHttpNonProxy(CString strHostAddr,int nPort);
    static BOOL SendHttpHeader(SOCKET hSocket,CString strHostAddr,
                CString strHttpAddr,CString strHttpFilename,DWORD nPos);
    static DWORD GetHttpHeader(SOCKET sckDest,char *str);
    static DWORD GetFileLength(char *httpHeader);
    static BOOL SocketSend(SOCKET sckDest,CString szHttp);

    BOOL FileCombine(CHttpSect *pInfo, FILE *fpwrite);

public:
    BOOL HttpDownLoadProxy(
            CString strProxyAddr,
            int nProxyPort,
            CString strHostAddr,
            CString strHttpAddr,
            CString strHttpFileName,
            CString strWriteFileName,
            int nSectNum,
            DWORD &totalSize);

    BOOL HttpDownLoadNonProxy(
            CString strHostAddr,
            CString strHttpAddr,
            CString strHttpFileName,
            CString strWriteFileName,
            int nSectNum,
            DWORD &totalSize);

    BOOL HttpDownLoad(
            CString strProxyAddr,
            int nProxyPort,
            CString strHostAddr,
            int nHostPort,
            CString strHttpAddr,
            CString strHttpFileName,
            CString strWriteFileName,
            int nSectNum,
            BOOL bProxy);
};

另外我们还定义一个Socket类,使用Socket来连接服务器进行下载操作。

class CDealSocket  
{
public:
    CDealSocket();
    virtual ~CDealSocket();

public:
    SOCKET GetConnect(CString host ,int port);
    SOCKET Listening(int port);
    CString GetResponse(SOCKET hSock);
};

一个文件类CMyFile,用来辅助需要下载的文件。

class CMyFile  
{
public:
    CMyFile();
    virtual ~CMyFile();

public:
    BOOL FileExists(LPCTSTR lpszFileName);
    FILE* GetFilePointer(LPCTSTR lpszFileName);
    DWORD GetFileSizeByName(LPCTSTR lpszFileName);
    CString GetShortFileName(LPCSTR lpszFullPathName);
};

MyDownLoad.cpp文件太长了,这里就不贴出来了。直接去项目看下。

在主函数中的测试程序如下:

DownloadHelper downloadHelper;
    downloadHelper.addDownloadTask("http://192.168.1.112/com.zip","C:\\Users\\Administrator\\Desktop\\");
    downloadHelper.startDownload();
    downloadHelper.join();

上面用用到的网址,是自己在本地搭建的Http服务器。另外需要讲一下在Windows上搭建Http服务器的过程。使用IIS搭建Http服务器进行测试时,遇到了如下问题:http error 503.the service is unavailable错误。最后的解决办法遇到相同问题的可以参考这篇文章。# 成功解决http error 503.the service is unavailable错误

小结:这个项目中用到的知识点有:

1.多线程编程。

2.Http协议。

3.Socket编程。

4.如何在本地搭建Http服务器。

参考文章:

1.解读断点续传的基本原理 - duanxz - 博客园 (cnblogs.com)

2.windows环境(本地端以及华为云服务器)搭建HTTP服务器_本地服务器-CSDN博客

  1. https://developer.aliyun.com/article/1217820问题解决办法。

最后,就为大家介绍到这里了。项目的源码:DownLoadTest

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1853485.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

智能优化算法改进策略之局部搜索算子(四)--梯度搜索法

2、仿真实验 以海洋捕食者算法&#xff08;MPA&#xff09;为基本算法。考察基于梯度搜索的改进海洋捕食者算法&#xff08;命名为GBSMPA&#xff09; vs. 海洋捕食者算法&#xff08;MPA&#xff09; 在Sphere函数上的比较 在Penalized1函数上的比较 在CEC2017-1上的比较 在C…

cuda与cudnn下载(tensorflow-gpu)

目录 前言 正文 前言 &#xff01;&#xff01;&#xff01;tensorflow-gpu的版本要与cuda与cudnn想对应。这点十分重要&#xff01;推荐下载较新的。即tensorflow-gpu2.60及以上&#xff0c;cuda11.x及以上&#xff0c;cudnn8.x及以上。 所以&#xff0c;下载之前先检查好…

朱啸虎:五年后大模型公司很难独立存在,核心不在技术,而是寻找尖刀式场景

人工智能时代&#xff0c;AI赛道如何投&#xff1f; 6月21日&#xff0c;金沙江创投合伙人朱啸虎在“创投十年”高峰论坛上&#xff0c;分享了以“扬鞭奋蹄正当时”为主题的演讲&#xff0c;聚焦创投行业当前的新形势和新变化作出分享。 要点如下&#xff1a; 1、今天一级市…

计算机网络之奇偶校验码和CRC冗余校验码

今天我们来看看有关于计算机网络的知识——奇偶校验码和CRC冗余校验码&#xff0c;这两种检测编码的方式相信大家在计算机组成原理当中也有所耳闻&#xff0c;所以今天我就来跟大家分享有关他们的知识。 奇偶校验码 奇偶校验码是通过增加冗余位使得码字中1的个数恒为奇数或偶数…

示例:推荐一个应用Adorner做的通知和提示消息对话框

一、目的&#xff1a;在开发过程中&#xff0c;增加一些提示消息可以很好的提高用户体验&#xff0c;下面介绍一个用于增加提示消息的库 二、效果如下 可以看到右侧顶端弹出提示消息&#xff0c;消息间隔3s自动退出 三、环境 VS2022 Net7 四、使用方式 安装nuget包&#xff…

什么是APP分发-了解APP分发的核心概念

APP分发的定义和意义 大家有没有过这样的经历&#xff1a;辛辛苦苦开发了一款APP&#xff0c;却不知道该怎么让更多人知道和使用&#xff1f;APP分发的重要性就凸显出来了。APP分发就是将你的应用推送到不同的应用市场和平台&#xff0c;让更多用户能够下载和使用。 小猪app封…

智能优化算法改进策略之局部搜索算子(七)--自适应模式搜索法

1、原理介绍 模式搜索法[1]是Hooke与Jeeves提出的一种直接搜索算法&#xff0c;其目的是通过比较目标函数在有限点集中的函数值来优化目标函数。更重要的是&#xff0c;它不仅不使用任何导数知识&#xff0c;而且不需要隐式地建立任何一种导数近似。 在这种直接搜索技术中&…

C++学习合集

#整理到一块&#xff0c;方便查东西&#xff0c;顺便补充一些之前没有学习到的东西# 变量 char--1字节 short--2字节 int-4字节 long--4字节 long long(int)--8字节&#xff1b;准确来说变量的大小取决于编译器&#xff0c;1字节8个二进制位&#xff0c;其中最高位为符号位…

C语言---自定义类型:结构体

结构体回顾 结构体 自定义的类型&#xff1a;结构体、联合体、枚举 结构是一些值的集合&#xff0c;这些值成为成员变量&#xff0c;结构的每个成员可以是不同类型的变量 //描述一本书&#xff1a;书名、作者、定价、书号//结构体类型---类似于整型、浮点型 struct Book {c…

操作系统真象还原:用户进程

第11章-用户进程 这是一个网站有所有小节的代码实现&#xff0c;同时也包含了Bochs等文件 11.1 为什么要有任务状态TSS Linux 任务切换未采用 Intel 的做法&#xff0c;而是用了一套自己的方法&#xff0c;只是用了 TSS 的一小部分功能。 操作系统最直接控制的就是 CPU&…

茴香豆的使用

RAG RAG 模型的核心在于两大部分&#xff1a;检索器&#xff08;Retriever&#xff09;和生成器&#xff08;Generator&#xff09;。检索器的作用是从一个庞大的数据集中&#xff0c;根据输入的问题或者提示&#xff0c;快速有效地检索出最相关的信息或文档。这一步骤通常利用…

[面试题]Jenkins

[面试题]Java【基础】[面试题]Java【虚拟机】[面试题]Java【并发】[面试题]Java【集合】[面试题]MySQL[面试题]Maven[面试题]Spring Boot[面试题]Spring Cloud[面试题]Spring MVC[面试题]Spring[面试题]MyBatis[面试题]Nginx[面试题]缓存[面试题]Redis[面试题]消息队列[面试题]…

Java基础(二)——数组,方法,方法重载

个人简介 &#x1f440;个人主页&#xff1a; 前端杂货铺 ⚡开源项目&#xff1a; rich-vue3 &#xff08;基于 Vue3 TS Pinia Element Plus Spring全家桶 MySQL&#xff09; &#x1f64b;‍♂️学习方向&#xff1a; 主攻前端方向&#xff0c;正逐渐往全干发展 &#x1…

颠覆多跳事实验证!Causal Walk 前门调整技术引领去偏新纪元

Causal Walk: Debiasing Multi-Hop Fact Verifcation with Front-Door Adjustment 论文地址: Causal Walk: Debiasing Multi-Hop Fact Verification with Front-Door Adjustment| Proceedings of the AAAI Conference on Artificial Intelligencehttps://ojs.aaai.org/index.p…

Linux Ubuntu 多块屏幕(副屏)装反矫正

一、案例 Ubuntu系统&#xff0c;主屏幕正常显示&#xff0c;第二块屏幕(副屏)装反了【各种原因&#xff0c;可能是线不够长&#xff0c;只能上下颠倒装起来】。 现需求&#xff0c;需要将第二块屏幕给矫正过来&#xff0c;正常显示&#xff01;&#xff01;&#xff01; 二、…

累了就坐下来喝杯茶 然后继续前行

前言&#xff1a; 今天是情人节就不发技术文章了 先祝愿各位有情人总成眷属。也包括我&#xff0c;哈哈哈 今天要分享的是一个diy制作的教程 因为我女朋友不在这边&#xff0c;心中那份思念难耐 所以有感而发 写了这篇diy 教程的文章 效果图&#xff1a; 需要的材料 502胶水…

websocket 安全通信

WebSocket 协议 WebSocket:在 2008 年诞生,2011 年成为国际标准。它允许服务器主动向客户端推送信息,客户端也可以主动向服务器发送信息,实现了真正的双向平等对话。它是一种在单个 TCP 连接上进行全双工通讯的协议,能够更高效地进行实时通信。 传统的轮询:浏览器需要不…

网络技术原理需要解决的5个问题

解决世界上任意两台设备时如何通讯的&#xff1f;&#xff1f; 第一个问题&#xff0c;pc1和pc3是怎么通讯的&#xff1f; 这俩属于同一个网段&#xff0c;那么同网段的是怎么通讯的&#xff1f; pc1和pc2属于不同的网段&#xff0c;第二个问题&#xff0c;不同网段的设备是…

北京BJ90升级新款迈巴赫大连屏四座头等舱行政四座马鞍

北京BJ90升级奔驰迈巴赫头等舱行政四座大联屏的内饰效果会非常出色&#xff0c;将为车辆带来更豪华、高端的内饰氛围。以下是升级后可能的效果&#xff1a; • 科技感提升&#xff1a;奔驰的中控系统一直以来都以其先进的科技和用户友好的界面而闻名。升级后&#xff0c;北京B…

程序员如何高效读代码?

程序员高效读代码的技巧包括以下几点&#xff1a; 明确阅读目的&#xff1a;在开始阅读代码之前&#xff0c;先明确你的阅读目的。是为了理解整个系统的架构&#xff1f;还是为了修复一个具体的bug&#xff1f;或者是为了了解某个功能是如何实现的&#xff1f;明确目的可以帮助…