1. 程式人生 > >使用libcurl來下載檔案

使用libcurl來下載檔案

Libcurl為一個免費開源的,客戶端url傳輸庫,支援FTPFTPSTFTPHTTPHTTPSGOPHERTELNETDICTFILELDAP,跨平臺,支援WindowsUnixLinux等,執行緒安全,支援Ipv6.

首先一個基本原則就是:絕對不應該線上程之間共享同一個libcurl handle(CURL *物件),不管是easy handle還是multi handle(本文只介紹easy_handle)。一個執行緒每次只能使用一個handle。
    libcurl是執行緒安全的,但有兩點例外:訊號(signals)和SSL/TLS handler。 訊號用於超時失效名字解析(timing out name resolves)。libcurl依賴其他的庫來支援SSL/STL,所以用多執行緒的方式訪問HTTPS或FTPS的URL時,應該滿足這些庫對多執行緒 操作的一些要求。


libcurl總體框架分為五部分:

1. 使用curl_global_init來初始化libcurl

2. 呼叫curl_easy_init()得到easy interface指標

3. 呼叫curl_easy_setopt 設定傳輸選項  (斷點續傳,超時設定,回撥函式都在這裡面設定)

4. 呼叫curl_easy_perform()完成傳輸任務

5. 呼叫curl_easy_cleanup()釋放記憶體

6. 呼叫curl_global_cleanup();

這裡打個比方:curl_easy_setopt類似於演員化妝,進行各種造型,而curl_easy_perform()類似上臺表演,給演員們一個展示自己的舞臺。

Downloader.h :

#ifndef __Downloader_LibCurl_H__
#define __Downloader_LibCurl_H__

#pragma once
#include "curl\curl.h"
#include <atlstr.h>

#define MAXWORK 200
typedef struct DownloadInfo
{
	char url[512];
	char filePath[256];
}DLIO;

typedef struct CurDownloadInfor
{
	char url[512];     //url
	char fileName[256];   //檔名稱
	long preLocalLen;     //本地已下載的長度(大小)
	double totalFileLen;   //檔案總長度(大小)
	double CurDownloadLen;   //每次下載的檔案長度(大小)
}CURDI;

class CDownloader
{
public:
	CDownloader(void);
	~CDownloader(void);
	int StartDownloadThread();
	double GetTotalFileLenth(const char* url);             //獲取將要下載的檔案長度
	long GetLocalFileLenth(const char* fileName);         //獲取本地問價長度
	void GetFileNameFormUrl(char* fileName, const char* url);      //從URL中獲取檔名
	void AddDownloadWork(DLIO downloadWork);
	int SetConnectTimeOut(DWORD nConnectTimeOut);    //設定連線的超時時間
	int GetCurrentDownloadInfo(CURDI* lpCurDownloadInfor);
	BOOL CreateMultiDir(const char* pathName);       //是否在本地建立目錄,沒有就建立
	BOOL IsDownloadBegin();
	BOOL IsDownloadEnd();
protected:
	static DWORD WINAPI SingleDownloadProc(LPVOID lpParameter);       //執行緒函式
	static size_t WriteFunc(char *str, size_t size, size_t nmemb, void *stream);     //寫入資料(回撥函式)
	static size_t ProgressFunc(double* fileLen, double t, double d, double ultotal, double ulnow);   //下載進度
private:
	char m_filePath[512];
	char m_downloadUrl[256];
	int m_downloadCourse;   //-1 還未下載 0正在下載 1下載完成
	long m_curLocalFileLenth; //因為下載的時候已經計算了本地檔案的大小用來設定斷點,所以對於每個檔案,該數字只會被設定一次;就是下載前的本地大小;
	long m_nConnectTimeOut;      //連線的超時時間
	DLIO m_dowloadWork[MAXWORK];
	CURDI m_curDownloadInfo;
	int m_curIndex;
	CURL* m_pCurl;
};


#endif
Downloader.cpp 
#include "StdAfx.h"
#include "Downloader.h"
#include <io.h>

CDownloader::CDownloader(void)
{
	m_downloadCourse = -1;
	m_nConnectTimeOut = 0;
	curl_global_init (CURL_GLOBAL_ALL);
	for(int i=0; i<MAXWORK; i++)
	{
		memset(m_dowloadWork->url, 0, 512);
		memset(m_dowloadWork->filePath, 0, 256);
	}
	m_curIndex = 0;
}


CDownloader::~CDownloader(void)
{
	curl_global_cleanup();
}

BOOL CDownloader::IsDownloadBegin()
{
	if(m_downloadCourse == 0)
		return TRUE;
	return FALSE;
}

BOOL CDownloader::IsDownloadEnd()
{
	if(m_downloadCourse == 1)
		return TRUE;
	return FALSE;
}

BOOL CDownloader::CreateMultiDir(const char* pathName)
{
	if(pathName == NULL) return FALSE;
	char filePath[256] = {0};
	strcpy(filePath, pathName);
	int i = 0, pathLen = strlen(pathName);
	CString curPath;
	char curFilePath[256] = {0};
	WIN32_FIND_DATA swf;
	if(filePath[pathLen - 1] != '\\')	//最後一個非0字元不是‘\\’則加上
	{
		filePath[pathLen] = '\\';
	}
	while(filePath[i] != '\0')
	{
		if(filePath[i] == ':')
		{
			i+=2;
			continue;
		}
		if(filePath[i] == '\\')
		{
			memcpy(curFilePath, filePath, i);
			curFilePath[i] = '\0';
			curPath = curFilePath;
			if(FindFirstFile(curPath, &swf) == INVALID_HANDLE_VALUE) //目錄不存在就建立
			{
				if(!CreateDirectory(curPath, NULL))
				{
					return FALSE;
				}
			}
		}
		i++;
	}
	return TRUE;
}

void CDownloader::AddDownloadWork(DLIO downloadWork)
{
	char filePath[256] = {0};
	char mUrl[512] = {0};
	strcpy(mUrl, downloadWork.url);
	strcpy(filePath, downloadWork.filePath);
	int i = strlen(filePath) -1;
	BOOL isPath = TRUE;
	while(filePath[i] != '\\')
	{
		if(filePath[i] == '.' && filePath[i+1] != '\0')
		{
			isPath = FALSE;
		}
		i--;
	}
	if(isPath)
	{
		if(!CreateMultiDir(filePath))
			return;
		char fileName[256] = {0};
		GetFileNameFormUrl(fileName,mUrl);
		if(filePath[strlen(filePath)-1] != '\\')
		{
			strcat(filePath, "\\");
		}
		strcat(filePath, fileName);
	}
	else
	{
		char realPath[256] = {0};
		for(int k=0; k<i; k++)
		{
			realPath[k] = filePath[k];
		}
		realPath[i] = '\\';
		if(!CreateMultiDir(realPath))
			return;
	}
	strcpy(m_dowloadWork[m_curIndex].url, mUrl);
	strcpy(m_dowloadWork[m_curIndex].filePath, filePath);
	m_curIndex++;
}

void CDownloader::GetFileNameFormUrl(char* fileName, const char* url)
{
	int urlLen = strlen(url);
	char mUrl[512] = {0};
	char fName[256] = {0};
	strcpy(mUrl, url);
	int cutIndex = 0;
	int i = urlLen - 1, j = 0;
	while(mUrl[--i] != '/');
	i++;
	while(mUrl[i] != '\0' && mUrl[i] != '?' &&mUrl[i] != '&')
	{
		fName[j++] = mUrl[i++];
	}
	fName[j] = '\0';
	strcpy(fileName, fName);
	return ;
}

long CDownloader::GetLocalFileLenth(const char* fileName)
{
	if(m_downloadCourse == 0)		//檔案已經開始下載的時候,取到的是下載前本地檔案的大小;
		return m_curLocalFileLenth;
	char strTemp[256] = {0};
	strcpy(strTemp,fileName);
	FILE* fp = fopen(strTemp, "rb");
	if(fp != NULL)
	{
		m_curLocalFileLenth = filelength(fileno(fp));
		fclose(fp);
		return m_curLocalFileLenth;
	}
	return 0;
}

double CDownloader::GetTotalFileLenth(const char* url)
{
	char mUrl[512] = {0};
	strcpy(mUrl, url);
	double downloadFileLenth = 0;
	CURL* pCurl = curl_easy_init();
	curl_easy_setopt(pCurl, CURLOPT_URL, mUrl);
	curl_easy_setopt(pCurl, CURLOPT_HEADER, 1L);
	curl_easy_setopt(pCurl, CURLOPT_NOBODY, 1L);
	if(curl_easy_perform(pCurl) == CURLE_OK)
	{
		curl_easy_getinfo(pCurl, CURLINFO_CONTENT_LENGTH_DOWNLOAD, &downloadFileLenth);
	}
	else
	{
		downloadFileLenth = -1;
	}
	curl_easy_cleanup(pCurl);
	return downloadFileLenth;
}


size_t CDownloader::WriteFunc(char *str, size_t size, size_t nmemb, void *stream)
{
	return fwrite(str, size, nmemb, (FILE*)stream);  
}

size_t CDownloader::ProgressFunc(
	double* pFileLen,
	double t,// 下載時總大小  
	double d, // 已經下載大小  
	double ultotal, // 上傳是總大小  
	double ulnow)   // 已經上傳大小  
{
	if(t == 0) return 0;
	*pFileLen = d;
	return 0;
}

int CDownloader::StartDownloadThread()
{
	if(m_downloadCourse == -1||m_downloadCourse == 1)
	{
		HANDLE downloadThread = CreateThread(NULL, 0, SingleDownloadProc, this, 0, NULL);  
		CloseHandle(downloadThread);
		return 0;
	}
	return -1;
}

DWORD WINAPI CDownloader::SingleDownloadProc(LPVOID lpParameter)
{
	CDownloader* pDownload = (CDownloader*)lpParameter;
	int curDLIndex = 0;
	CURL* pCurl = curl_easy_init();
	while(curDLIndex <= pDownload->m_curIndex)
	{
		char fileName[256] = {0};
		char url[512] = {0};
		strcpy(fileName, pDownload->m_dowloadWork[curDLIndex].filePath);
		strcpy(url, pDownload->m_dowloadWork[curDLIndex].url);
		strcpy(pDownload->m_curDownloadInfo.url, url);
		strcpy(pDownload->m_curDownloadInfo.fileName, fileName);
		long localFileLen = pDownload->GetLocalFileLenth(fileName);
		pDownload->m_curLocalFileLenth = localFileLen;
		pDownload->m_curDownloadInfo.preLocalLen = pDownload->m_curLocalFileLenth;
		double totalFileLen = pDownload->m_curDownloadInfo.totalFileLen = pDownload->GetTotalFileLenth(url);
		if(localFileLen >= (long)totalFileLen)		//如果需要下載檔案的大小大於等於本地檔案的大小,直接下載下一個檔案
		{
			curDLIndex++;
			pDownload->m_downloadCourse = -1;
			continue;
		}
		FILE* fp = fopen(fileName,"ab+");
		if(fp == NULL) //檔案開啟錯誤,進行下一個檔案的下載
		{
			pDownload->m_downloadCourse = -1;
			continue;
		}
		curl_easy_setopt(pCurl, CURLOPT_URL, url);
		curl_easy_setopt(pCurl, CURLOPT_TIMEOUT, pDownload->m_nConnectTimeOut);
		curl_easy_setopt(pCurl, CURLOPT_HEADER, 0L);
		curl_easy_setopt(pCurl, CURLOPT_NOBODY, 0L);
		curl_easy_setopt(pCurl, CURLOPT_FOLLOWLOCATION, 1L);
		curl_easy_setopt(pCurl, CURLOPT_RESUME_FROM, localFileLen);

		curl_easy_setopt(pCurl, CURLOPT_WRITEFUNCTION, WriteFunc);
		curl_easy_setopt(pCurl, CURLOPT_WRITEDATA, fp);

		curl_easy_setopt(pCurl, CURLOPT_NOPROGRESS, 0L);
		curl_easy_setopt(pCurl, CURLOPT_PROGRESSFUNCTION, ProgressFunc);
		curl_easy_setopt(pCurl, CURLOPT_PROGRESSDATA, &(pDownload->m_curDownloadInfo.CurDownloadLen));

		pDownload->m_downloadCourse = 0;
		if(!curl_easy_perform(pCurl))
		{
			curDLIndex++;
			pDownload->m_downloadCourse = -1;
		}
		fclose(fp);
	}
	curl_easy_cleanup(pCurl);
	pDownload->m_downloadCourse = 1;
	return 0;
}

int CDownloader::GetCurrentDownloadInfo(CURDI* lpCurDownloadInfor)
{
	*lpCurDownloadInfor = m_curDownloadInfo;
	return 0;
}

int CDownloader::SetConnectTimeOut(DWORD nConnectTimeOut)
{
	if(m_downloadCourse == 0) return -1;
	else
		m_nConnectTimeOut = nConnectTimeOut;
	return 0;
}

最後CurlTest.cpp :
// test.cpp : 定義控制檯應用程式的入口點。
#include "stdafx.h"
#include <stdio.h>  
#include <stdlib.h>
#include <string.h> 
#include <curl/curl.h> 
#include "Downloader.h"
 
#pragma comment(lib,"libcurl.lib") 

int _tmain(int argc, _TCHAR* argv[])  
{    
	DWORD tick = GetTickCount();
	CDownloader murl;
 	DLIO mDlWork;

	strcpy(mDlWork.url, "http://sw.bos.baidu.com/sw-search-sp/software/f69ab46476e8e/TGPSetup_2.3.2.4083.exe");
	strcpy(mDlWork.filePath, ".\\DownloadSoft\\");
	murl.AddDownloadWork(mDlWork);      //新增到下載任務中

	strcpy(mDlWork.url, "http://sw.bos.baidu.com/sw-search-sp/software/16f6d358815f2/iTunes_12.5.1.21.exe");
	strcpy(mDlWork.filePath, ".\\DownloadSoft\\");
	murl.AddDownloadWork(mDlWork);      //新增到下載任務中

	murl.StartDownloadThread();         //開啟下載執行緒
	CURDI curInfo;
	double curDownloadLen,preLen = 0.0;
	while(1)
	{
		if(murl.IsDownloadBegin())
		{
			murl.GetCurrentDownloadInfo(&curInfo);       //獲取每次下載的資訊(一次相當於毫秒級,這裡速度也用毫秒計算)
			curDownloadLen = curInfo.CurDownloadLen;
			printf("正在下載:%s,下載進度:%6.2lf%%,下載速度:%9.2lfKB/s\r",curInfo.fileName,
				((double)curInfo.preLocalLen+curInfo.CurDownloadLen)/curInfo.totalFileLen*100,(curDownloadLen-preLen)/(double)(GetTickCount()-tick));
			tick = GetTickCount();
			Sleep(500);
		}
		if(murl.IsDownloadEnd()) break;
		preLen = curDownloadLen;
	}
	return 0;
}  

1. CURLcode curl_global_init(long flags); 在多執行緒應用中,需要在主執行緒中呼叫這個函式。這個函式設定libcurl所需的環境。通常情況,如果不顯式的呼叫它,第一次呼叫 curl_easy_init()時,curl_easy_init 會呼叫 curl_global_init,在單執行緒環境下,這不是問題。但是多執行緒下就不行了,因為curl_global_init不是執行緒安全的。在多個線 程中呼叫curl_easy_int,然後如果兩個執行緒同時發現curl_global_init還沒有被呼叫,同時呼叫 curl_global_init,悲劇就發生了。這種情況發生的概率很小,但可能性是存在的。

2. libcurl 有個很好的特性,它甚至可以控制域名解析的超時。但是在預設情況下,它是使用alarm + siglongjmp 實現的。用alarm在多執行緒下做超時,本身就幾乎不可能。如果只是使用alarm,並不會導致程式崩潰,但是,再加上siglongjmp,就要命了 (程式崩潰的很可怕,core中幾乎看不出有用資訊),因為其需要一個sigjmp_buf型的全域性變數,多執行緒修改它。(通常情況下,可以每個執行緒一個 sigjmp_buf 型的變數,這種情況下,多執行緒中使用 siglongjmp 是沒有問題的,但是libcurl只有一個全域性變數,所有的執行緒都會用)。

  具體是類似 curl_easy_setopt(curl, CURLOPT_TIMEOUT, 30L) 的超時設定,導致alarm的使用(估計發生在域名解析階段),如前所述,這在多執行緒中是不行的。解決方式是禁用掉alarm這種超時, curl_easy_setopt(curl, CURLOPT_NOSIGNAL, 1L)。

  這樣,多執行緒中使用超時就安全了。但是域名解析就沒了超時機制,碰到很慢的域名解析,也很麻煩。文件的建議是 Consider building libcurl with c-ares support to enable asynchronous DNS lookups, which enables nice timeouts for name resolves without signals.  c-ares 是非同步的 DNS 解決方案。


相關推薦

使用libcurl下載檔案

Libcurl為一個免費開源的,客戶端url傳輸庫,支援FTP,FTPS,TFTP,HTTP,HTTPS,GOPHER,TELNET,DICT,FILE和LDAP,跨平臺,支援Windows,Unix,Linux等,執行緒安全,支援Ipv6. 首先一個基本原則就是:絕對不應

php禁止用下載工具下載檔案

判斷寫的很粗糙,僅僅是一個判斷瀏覽器型別的頁面 諸位見笑了。 之前看到一個php判斷瀏覽器型別的程式碼來了點靈感:<?php if(strpos($_SERVER["HTTP_USER_AGENT"],"MSIE 8.0")) echo "In

如何配置 Aria2 進行檔案下載

什麼是 Aria2? aria2 是一個輕量級的多協議和多源命令列下載實用程式。 它支援 HTTP / HTTPS,FTP,SFTP,BitTorrent 和 Metalink。 aria2 可以通過內建的 JSON-RPC 和 XML-RPC 介面進行操作。 上面是官網對 A

JAVA中建立HTTP通訊,從伺服器上獲取HTML程式碼,通過HTTP請求下載圖片或其他二進位制檔案的程式,下載結果要按下載到的檔案型別進行存檔中。

通過HTTP請求來下載圖片或其他二進位制檔案的程式,下載結果要按下載到的檔案型別進行存檔 將程式碼從伺服器的中獲取下來的程式碼,在我之前已經講過了這裡寫連結內容 這裡我們就直接將原始碼稍加改動,加入一個檔案並請將builder 寫入即可。 import

在spring boot下如何通過rest 介面 上傳檔案下載檔案 到 hadoop hdfs

本文將用程式碼來演示在spring boot裡面,用hadoop client,通過restful API來上傳檔案 和下載檔案 到 hadoop hdfs。 裡面有一些程式碼依賴坑,注意繞行。 前提: 如果你的程式碼在windows上執行,去連線linux上的hado

下載檔案對前端需要做的事情

因為下載檔案後臺反饋給前端為位元組流或字元流。 前端傳參只需要將引數放入後臺給前臺的介面  url後: 例: http://www.laqu.com/query-item-pic?activity=1

使用SecureCRT上傳和下載檔案

  用SSH管理linux伺服器時經常需要遠端與本地之間互動檔案。而直接用SecureCRT自帶的上傳下載功能無疑是最方便的,SecureCRT下的檔案傳輸協議有ASCII.Xmodem.Zmodem。 檔案傳輸協議: 檔案傳輸是資料交換的主要形式。在進行檔案傳輸時,為使檔案能被正確識別和傳送,我們需要在兩臺

libcurl post/get上傳下載檔案 以及斷點下載(操作libcurl 實現斷點下載(續點續傳))

各位親 有時間可以去看看我的  “金駿家居淘寶店” http://jinjun1688.taobao.com/shop/view_shop.htm?tracelog=twddp 買時說明在我的部落格看到有優惠哦 還有意外禮品贈送  真正的程式設計師淘寶店 標頭檔案

Android開發中使用FileDownloader實現檔案下載功能(總結一)

今天研究了一下Android開發中檔案下載功能,記錄一下。這篇部落格主要介紹第三方下載外掛:FileDownloader的單任務的使用方法,至於多工的後面會做補充記錄,再寫一篇博文。效果圖如下:(虛擬機器連不上網)1、首先是引用方法:implementation 'com.l

分享個C++封裝Libcurl程式碼(支援下載檔案、GET\POST、重定向斷點續傳等功能)

前言 前面分享過一個Windows上封裝Winhttp和WinInet API的程式碼,結果下載頁好評特別多(呵呵),謝謝大家賞臉。文章地址:開源一個C++實現的簡單HTTP協議處理庫,裡面有程式碼資源下載地址。但是,在實際開發過程中我發現WinHttp API嚴重依賴微

使用libcurl下載檔案小例

libcurl是一個很強大的開源網路處理庫,支援包括HTTP、HTTPS、FTP……一系列網路協議。用它來進行HTTP的get\post 或者下載檔案更是小菜一碟,chrome核心都用到了它,本文主要講解一個使用curl下載檔案的小例。 首先是去下載curl的最新原始碼,然

C++ 基於libcurl的html 檔案下載

lincurl的環境配置,在這裡不做詳細描述,程式碼實現程式如下: #include <stdio.h> #include <curl/curl.h> /***********************************************

硬件——nrf51822第二篇,如何設置keil用下載程序

硬件 電子 keil .com logs alt .cn cnblogs image 轉自電子發燒友論壇 硬件——nrf51822第二篇,如何設置keil用來下載程序

爬蟲抓取網頁下載小說

程序 rip compile pla ons pos 獲取 except res 利用Python3 編寫爬蟲,從筆趣閣抓個小說下載。 import re import urllib.request import time import easygui as g # 輸

IE無法解析返回的JSON格式並提示下載檔案

問題解決方法: ①後臺定義返回型別為text/html,如 response.setContentType("text/html;charset=UTF-8"); ②前臺配置ajax引數dataType: 'text/html', ③將文字轉化為JSON格式資料 success: func

從youtube快速下載檔案到本地的方案

需要的軟體(工具): 一 vps(我使用的是搬瓦工 https://bwh1.net/) 二 百度網盤客戶端(需要vip會員加速) 步驟: 一 在搬瓦工vps中安裝youtube-dl (https://github.com/rg3/youtube-dl),安裝完後在vps上下

成功解決Git Bash執行指令碼命令下載檔案到預設C盤路徑的問題

解決問題 解決Git Bash執行指令碼命令下載檔案到預設C盤路徑的問題 1、預設下載到資料夾為   解決思路 %homedrive%    指作業系統所在盤%homepath%      指\Docum

實現從oss(阿里雲)伺服器以附件形式下載檔案(含批量下載

轉載自:https://blog.csdn.net/sinat_28771747/article/details/53520253 筆者在專案中寫一個從阿里雲伺服器上面以附件形式下載檔案的介面時,遇到了問題,網上搜索無任何相關的解決方案,最後通過通過自己查閱API文件,再結合自己的經驗,實現了下

iframe標籤實現form表單提交下載檔案

一、表單提交的程式碼常規寫法 <iframe name="testIframeName" style="display:none;"></iframe> <form target="testIframeName" method="post" acti

轉發:傳送post請求下載檔案

原文地址:https://blog.csdn.net/yunlala_/article/details/78385962 處理檔案流方案一 以下是我親試可以實現的一種方案: exportData () { const form = this.getSearc