#include <iostream>
#include <iosfwd>
#include <fstream>
#include <regex>
#include <string>
#include <winsock2.h>
#include <pthread.h>
#include <map>
#include <queue>
/**
*要实现多线程http协议下载文件的功能,支持断点续传和多线程同时下载
*http请求使用Range: bytes完成
*同时写n个临时文件,最后执行完成拼接在一起
*目前支持多线程下载,自动拼接文件,
*后续增加一个守护进程,判断是否都下载完成,记录下载进度,若是未下载完成,继续下载,若是下载完成,就完成文件拼接
**/
using namespace std;
SOCKET sock[100];
//测试主机和端口
const char *testHostName="www.qxmuye.com";
const short testPort=80;
const string testPortChar="80";
//文件地址
const char *url="/article/UploadPic/2015-9/2015982144994925.gif";
const string dataFile="D://testmap//test.gif";
const string tmpFile="D://testmap//tmpfile";
//分块下载的块大小
const int getSize=100000;
int fileNameInt;
//发送http请求包
bool sendHttpQuery(string sendQueryStr,int sockNum){
int n=0;
//初始化socket
sock[sockNum] = socket(AF_INET, SOCK_STREAM, 0);
if (sock[sockNum] == INVALID_SOCKET)
{
cout << "建立socket失败! 错误码: " << WSAGetLastError() << endl;
return false;
}
sockaddr_in sa = { AF_INET };
u_short port=0;
sa.sin_port = htons(port);
sa.sin_addr.s_addr = htonl(INADDR_ANY);
n = bind(sock[sockNum], (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);
//sa.sin_addr.S_un.S_addr = inet_addr(*(p->h_addr_list));
//cout << *(p->h_addr_list) << endl;
//连接
n = connect(sock[sockNum], (sockaddr*)&sa, sizeof(sa));
if (n == SOCKET_ERROR)
{
cout << "connect函数失败! 错误码: " << WSAGetLastError() << endl;
return false;
}
//按照http送GET请求
cout<<"发送http请求:"+sendQueryStr<<endl;
if (SOCKET_ERROR == send(sock[sockNum], sendQueryStr.c_str(), sendQueryStr.size(), 0))
{
cout << "send error! 错误码: " << WSAGetLastError() << endl;
closesocket(sock[sockNum]);
return false;
}
return true;
}
//文件拼接,将第二个参数文件中的内容复制给第一个参数中的文件,两个文件要求已打开
void getGolobalFile(ofstream &fGolobal,ifstream &fPart){
//fGolobal.open(dataFile,ios::app|ios::binary);
char tmpFileChar[1001];
if(!fPart.is_open()){
cout<<"临时文件未打开!"<<endl;
}
if(!fGolobal.is_open()){
cout<<"总文件未打开!"<<endl;
}
while(!fPart.eof()){
fPart.read(tmpFileChar,1000);
fGolobal.write(tmpFileChar,fPart.gcount());
//char readNumChar[10];
//sprintf(readNumChar,"%d",fPart.gcount());
//cout<<"复制字符长度:"+(string)readNumChar<<endl;
//memset(tmpFileChar,0,1024);
}
cout<<"文件复制完成!"<<endl;
}
//从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;
}
//下载线程,将特定的文件块下载到特定的临时文件中,根据参数设置特定的临时文件
void *downloadTmpFile(void *args){
int n;
//修改子线程为分离状态,不阻塞主线程
pthread_detach(pthread_self());
int tmpNum=*(int *)args;//先将指针转为int型指针,再取值
char tmpBuf[1024];
memset(tmpBuf, 0, sizeof(tmpBuf));
char tmpNumChar[10];
sprintf(tmpNumChar,"%d",tmpNum);
ofstream fileTmp;
//临时文件新建
fileTmp.open(tmpFile+(string)tmpNumChar ,ios::out|ios::binary);
cout << " 文档打开 "+tmpFile+(string)tmpNumChar << endl;
if(!fileTmp.is_open()){
cout << " 文档打开失败! "+tmpFile+(string)tmpNumChar << endl;
}
char *startMark=new char(10);
char *endMark=new char(10);//设置开始地址,结束地址,注意
memset(startMark,0,10);
memset(endMark,0,10);
sprintf(startMark,"%d",(tmpNum*getSize+0));
sprintf(endMark,"%d",(tmpNum*getSize+(getSize-1)));
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,tmpNum)){
cout<<" http 请求发送失败! "<<endl;
}
n = recv(sock[tmpNum], tmpBuf, sizeof(tmpBuf)-1, 0);
char *cpos = strstr(tmpBuf, "\r\n\r\n");
fileTmp.write(cpos + strlen("\r\n\r\n"), n - (cpos - tmpBuf) - strlen("\r\n\r\n"));
while ((n = recv(sock[tmpNum], tmpBuf, sizeof(tmpBuf)-1, 0)) > 0)
{
//写入临时文件
fileTmp.write(tmpBuf, n);
}
fileTmp.close();
}
int main()
{
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;
}
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,0)){
cout<<" http 请求发送失败! "<<endl;
return 0;
}
//思路:将Content-Range: bytes 0-1/后面的值作为长度解析出来
//再根据文件长度决定循环几次请求(后续实现分配几个线程下载)
recv(sock[0], 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;
int ret[getTimeInt+1];
pthread_t testthreadDownload[getTimeInt+1];
//ofstream fBig;
//fBig.open()
while(i<=getTimeInt){
//注意,使用sleep让线程得以创建,不然可能还没创建线程就已经销毁,考虑使用信号量
ret[i]= pthread_create(&testthreadDownload[i],NULL,&downloadTmpFile,&i);
Sleep(1000);
if(ret[i]){
cout<<"创建临时文件线程失败!"<<endl;
return 1;
}
i++;
}
i=0;
while(i<=getTimeInt){
pthread_join(testthreadDownload[i],NULL);
i++;
}
//
Sleep(40000);
i=0;
//pthread_t testthreadFileAdd[getTimeInt+1];
ofstream fileGolobal;
ifstream fileTmp[getTimeInt+1];
while(i<=getTimeInt){
fileGolobal.open(dataFile,ios::app|ios::out|ios::binary);
char tmpNumChar[5];
sprintf(tmpNumChar,"%d",i);
cout<<"打开文件"+tmpFile+(string)tmpNumChar<<endl;
//if(fileTmp[i].is_open()){
// fileTmp[i].close();
//}
fileTmp[i].open(tmpFile+(string)tmpNumChar,ios::in|ios::binary);
cout<<"文件打开成功!"+tmpFile+(string)tmpNumChar<<endl;
getGolobalFile(fileGolobal,fileTmp[i]);
fileGolobal.close();
fileTmp[i].close();
i++;
}
//file.close();
return 0;
}
http协议多线程下载
最新推荐文章于 2025-10-14 00:05:36 发布
本文介绍了一个基于C++的多线程HTTP文件下载程序,支持断点续传功能。通过发送带有Range头的HTTP请求来实现文件的分块下载,并在下载完成后自动合并文件。

282

被折叠的 条评论
为什么被折叠?



