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

2024-06-23 18:04

本文主要是介绍VC++支持断点续下或续传的功能,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

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 CloseHandleCloseHandle(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());    //copyfile.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

这篇关于VC++支持断点续下或续传的功能的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/1087877

相关文章

使用EasyPoi快速导出Word文档功能的实现步骤

《使用EasyPoi快速导出Word文档功能的实现步骤》EasyPoi是一个基于ApachePOI的开源Java工具库,旨在简化Excel和Word文档的操作,本文将详细介绍如何使用EasyPoi快速... 目录一、准备工作1、引入依赖二、准备好一个word模版文件三、编写导出方法的工具类四、在Export

JS纯前端实现浏览器语音播报、朗读功能的完整代码

《JS纯前端实现浏览器语音播报、朗读功能的完整代码》在现代互联网的发展中,语音技术正逐渐成为改变用户体验的重要一环,下面:本文主要介绍JS纯前端实现浏览器语音播报、朗读功能的相关资料,文中通过代码... 目录一、朗读单条文本:① 语音自选参数,按钮控制语音:② 效果图:二、朗读多条文本:① 语音有默认值:②

C++读写word文档(.docx)DuckX库的使用详解

《C++读写word文档(.docx)DuckX库的使用详解》DuckX是C++库,用于创建/编辑.docx文件,支持读取文档、添加段落/片段、编辑表格,解决中文乱码需更改编码方案,进阶功能含文本替换... 目录一、基本用法1. 读取文档3. 添加段落4. 添加片段3. 编辑表格二、进阶用法1. 文本替换2

C++中处理文本数据char与string的终极对比指南

《C++中处理文本数据char与string的终极对比指南》在C++编程中char和string是两种用于处理字符数据的类型,但它们在使用方式和功能上有显著的不同,:本文主要介绍C++中处理文本数... 目录1. 基本定义与本质2. 内存管理3. 操作与功能4. 性能特点5. 使用场景6. 相互转换核心区别

C#实现高性能拍照与水印添加功能完整方案

《C#实现高性能拍照与水印添加功能完整方案》在工业检测、质量追溯等应用场景中,经常需要对产品进行拍照并添加相关信息水印,本文将详细介绍如何使用C#实现一个高性能的拍照和水印添加功能,包含完整的代码实现... 目录1. 概述2. 功能架构设计3. 核心代码实现python3.1 主拍照方法3.2 安全HBIT

录音功能在哪里? 电脑手机等设备打开录音功能的技巧

《录音功能在哪里?电脑手机等设备打开录音功能的技巧》很多时候我们需要使用录音功能,电脑和手机这些常用设备怎么使用录音功能呢?下面我们就来看看详细的教程... 我们在会议讨论、采访记录、课堂学习、灵感创作、法律取证、重要对话时,都可能有录音需求,便于留存关键信息。下面分享一下如何在电脑端和手机端上找到录音功能

Android实现图片浏览功能的示例详解(附带源码)

《Android实现图片浏览功能的示例详解(附带源码)》在许多应用中,都需要展示图片并支持用户进行浏览,本文主要为大家介绍了如何通过Android实现图片浏览功能,感兴趣的小伙伴可以跟随小编一起学习一... 目录一、项目背景详细介绍二、项目需求详细介绍三、相关技术详细介绍四、实现思路详细介绍五、完整实现代码

C++右移运算符的一个小坑及解决

《C++右移运算符的一个小坑及解决》文章指出右移运算符处理负数时左侧补1导致死循环,与除法行为不同,强调需注意补码机制以正确统计二进制1的个数... 目录我遇到了这么一个www.chinasem.cn函数由此可以看到也很好理解总结我遇到了这么一个函数template<typename T>unsigned

Python使用FastAPI实现大文件分片上传与断点续传功能

《Python使用FastAPI实现大文件分片上传与断点续传功能》大文件直传常遇到超时、网络抖动失败、失败后只能重传的问题,分片上传+断点续传可以把大文件拆成若干小块逐个上传,并在中断后从已完成分片继... 目录一、接口设计二、服务端实现(FastAPI)2.1 运行环境2.2 目录结构建议2.3 serv

C++统计函数执行时间的最佳实践

《C++统计函数执行时间的最佳实践》在软件开发过程中,性能分析是优化程序的重要环节,了解函数的执行时间分布对于识别性能瓶颈至关重要,本文将分享一个C++函数执行时间统计工具,希望对大家有所帮助... 目录前言工具特性核心设计1. 数据结构设计2. 单例模式管理器3. RAII自动计时使用方法基本用法高级用法