libcurl庫的使用的一些心得
一、概述
最近由於要做一個下載工具,最開始想到了libcurl庫,和它提供的一些接口。網上的很多資料都是不全,或者是沒有什麼例子的,由於資質駑鈍,使用libcurl庫提供的api總是不那麼順暢。最後通過自己努力和查看官網提供的一些幫助文檔和例子,終於把這個下載器製作出來了,現在把一些心得寫出來,供後來人少走些彎路吧。(國內的程序猿總是沒有多少分享精神的,很多資料的都是這copy點,那裏copy點的,要不就在官網弄點英文資料,前面加幾句話說,這個很全什麼的,看的我想罵娘)
二、libcurl提供的一些api的使用
1、CURLcode curl_global_init(long flags);----全局初始化函數,在windows下使用CURL_GLOBAL_WIN32,初始化win sock的一些信息。
2、CURL *curl_easy_init();-----初始化一個CURL*的句柄。
3、CURLcode curl_easy_setopt(CURL *curl, CURLoption option, ...);----設置http的一些屬性,設置給libcurl
4、CURLM *curl_multi_init(void);------初始化一個CURLM * 句柄,使用這個可以實現非阻塞模式,建議使用該函數
5、CURLMcode curl_multi_add_handle(CURLM *multi_handle, CURL *curl_handle);----把CURL*句柄添加到CURLM * 中
6、CURLcode curl_easy_perform(CURL *curl);----執行下載或者上傳任務,執行的是函數curl_easy_setopt設置的一些選項
7、CURLMcode curl_multi_perform(CURLM *multi_handle, int *running_handles);-----執行下載或者上傳任務,通過curl_multi_add_handle加入到CURLM*句柄中的各個任務。主要使用的是select非阻塞模式,通過這個可以比較容易的實現多線程任務。
8、void curl_easy_cleanup(CURL *curl);----清理CURL*句柄。
9、CURLMcode curl_multi_cleanup(CURLM *multi_handle);----清理CURLM*句柄。
10、void curl_global_cleanup(void);----清理全局初始化的堆棧操作。
注:最好看官網提供的一些英文資料和它提供的一些例子,這些東西比在網上隨便找的資料有用的多。
三、設計思想。
1、對於一些大文件,創建一些線程去執行下載任務。----- 最主要的就是如何讓各個線程從指定位置開始下載和到指定位置停止下載的問題。
2、如何實現下載,可以通過curl_easy_setopt函數設置一個回調函數完成對本地文件的寫入,完成下載工作。---下載或其他任務,會在curl_easy_perform或curl_multi_perform函數調用後執行。
3、對於大文件,獲取到http(或其他服務器)上的文件大小後,根據需要創建的線程數,給每個線程分配固定的數據段進行下載。
4、等各個文件分塊下載完成後,把下載的文件塊拼裝起來,組合成需要的下載文件。----完成所有下載工作(這部分工作應該是最簡單的)。
四、一些代碼實例
1、從http頭獲取到下載文件的大小(我在網上沒有找到實例)
/*
* get file size from http head data
*/
size_t getcontentlengthfunc(void *ptr, size_t size, size_t nmemb, void *stream)
{
int r;
long len = 0;
/* _snscanf() is Win32 specific */
// r = _snscanf(ptr, size * nmemb, "Content-Length: %ld\n", &len);
r = sscanf((const char*)ptr, "Content-Length: %ld\n", &len);
if (r) /* Microsoft: we don't read the specs */
*((long *) stream) = len;
return size * nmemb;
}
size_t write_func(void* , size_t , size_t , void* )
{
return 0;
}
int32 getdownloadfilesize(const char* url)
{
#if WIN32
curl_global_init(CURL_GLOBAL_WIN32);
#else
curl_global_init(CURL_GLOBAL_ALL);
#endif
CURL* hcurl = curl_easy_init();
curl_easy_setopt(hcurl, CURLOPT_URL, url);
curl_easy_setopt(hcurl, CURLOPT_FOLLOWLOCATION, 1L); // 重定向
curl_easy_setopt(hcurl, CURLOPT_CONNECTTIMEOUT, TIME_OUT); // set connect timeout
long filesize = 0;
/* set http head call back function */
curl_easy_setopt(hcurl, CURLOPT_HEADERFUNCTION, getcontentlengthfunc);
curl_easy_setopt(hcurl, CURLOPT_HEADERDATA, &filesize);
FILE* fp = fopen("test.txt", "ab+");
if(!fp)
return 0;
curl_easy_setopt(hcurl, CURLOPT_WRITEDATA, fp);
curl_easy_setopt(hcurl, CURLOPT_WRITEFUNCTION, write_func);
curl_easy_perform(hcurl); // 函數會執行失敗哦,不過沒關係,我們已經從得到了filesize的大小了。
fclose(fp);
system("if exist test.txt del test.txt");
// 清理工作
curl_easy_cleanup(hcurl);
curl_global_cleanup();
return (int32)filesize;
}
2、設置斷點續傳。
void setcurlopt()
{
curl_off_t local_file_len = -1 ;
struct stat file_info;
bool flag = false;
// g_loopsize是分段的大小
curl_off_t nbeginsize = (nthreadcode - 1) * g_loopsize;
/* get local file size */
if(stat(m_strfile.c_str(), &file_info) == 0)
{
local_file_len = file_info.st_size;
nbeginsize += (int32)local_file_len;
flag = true;
}
/* open file use append model */
FILE* fp = fopen(m_strfile.c_str(), "ab+");
if (fp == NULL) {
perror(NULL);
return;
}
/*
*把需要文件開始下載的位置傳給libcurl,多線程分段也主要
* 是通過該函數設置下載部分,這裏的參數類型必須爲curl_off_t ,否則會得不到http的相應
*/
curl_easy_setopt(m_hcurl, CURLOPT_RESUME_FROM_LARGE, flag?local_file_len,0);
// 把文件指針傳給libcurl,用戶可以根據需求自己設計回調和需要傳遞的指針
SDownloadFile* file = xxx;
curl_easy_setopt(m_hcurl, CURLOPT_WRITEDATA, file);
// 設置回調函數,writefunc是一個特殊的回調函數,我用多線程分段下載的
curl_easy_setopt(m_hcurl, CURLOPT_WRITEFUNCTION, wirtefunc);
}
/*
* save the downloaded data to defined file
* 這裏特別說明一下,分段去調用最後可能出現一個錯誤信息:failed write body(xxx!=xxxx)
* 這個可以忽略,具體請查看libcurl源碼部分。
*/
size_t wirtefunc(void *ptr, size_t size, size_t nmemb, void *stream)
{
SDownloadFile* pinfo = (SDownloadFile*)stream;
assert(pinfo);
if(NULL == pinfo)
return 0;
uint32 ret = 0;
int32 nLave = pinfo->nBeginLen + size * nmemb - pinfo->nEndLen;
if(nLave > 0) // one part download success
{
fflush(pinfo->fp);
fwrite(ptr, size, pinfo->nEndLen - pinfo->nBeginLen, pinfo->fp);
fflush(pinfo->fp);
pinfo->nBeginLen = pinfo->nEndLen;
}
else
{
assert(pinfo->nBeginLen + size * nmemb < (uint32)pinfo->nEndLen);
ret = fwrite(ptr, size, nmemb, pinfo->fp);
pinfo->nBeginLen += ret;
}
return ret;
}
typedef struct _SDownloadFile
{
FILE* fp;
string strfilename;
int32 nBeginLen;
int32 nEndLen;
int32 ncode;
}SDownloadFile;
typedef signed char int8;
typedef unsigned char uint8;
typedef signed short int16;
typedef unsigned short uint16;
typedef signed int int32;
typedef unsigned int uint32;
typedef signed long long int64;
typedef unsigned long long uint64;
3、關於文件組合。
我個人認爲這個沒有難度: 把各個部分已二進制方式讀出來,然後寫入到一個新的文件中,並刪除那些下載的部分文件。
-