(轉)libcurl庫使用方法,好長,好詳細。
一、ibcurl作為是一個多協議的便於客戶端使用的URL傳輸庫,基於C語言,提供C語言的API介面,支援DICT, FILE, FTP, FTPS, Gopher, HTTP, HTTPS, IMAP, IMAPS, LDAP, LDAPS, POP3, POP3S, RTMP, RTSP, SCP, SFTP, SMTP, SMTPS, Telnet and TFTP這些協議,同時支援使用SSL證書的安全檔案傳輸:HTTP POST, HTTP PUT, FTP 上傳, 基於HTTP形式的上傳、代理、Cookies、使用者加密碼的認證等多種應用場景。另外,libcurl是一個高移植性的庫,能在絕大多數系統上執行,包括Solaris, NetBSD, FreeBSD, OpenBSD, Darwin, HPUX, IRIX, AIX, Tru64, Linux, UnixWare, HURD, Windows, Amiga, OS/2, BeOs, Mac OS X, Ultrix, QNX, OpenVMS, RISC OS, Novell NetWare, DOS等。
二、使用步驟
1. 呼叫curl_global_init()初始化libcurl
2. 呼叫curl_easy_init()函式得到 easy interface型指標
3. 呼叫curl_easy_setopt()設定傳輸選項
4. 根據curl_easy_setopt()設定的傳輸選項,實現回撥函式以完成使用者特定任務
5. 呼叫curl_easy_perform()函式完成傳輸任務
6. 呼叫curl_easy_cleanup()釋放記憶體
7.呼叫curl_global_cleanup()析構libcurl
在整過過程中設定curl_easy_setopt()引數是最關鍵的,幾乎所有的libcurl程式都要使用它。
在基於LibCurl的程式裡,主要採用callback function (回撥函式)的形式完成傳輸任務,使用者在啟動傳輸前設定好各類引數和回撥函式,當滿足條件時libcurl將呼叫使用者的回撥函式實現特定功能。
三、函式說明
1.CURLcode curl_global_init(long flags);
描述:
這個函式只能用一次。(其實在呼叫curl_global_cleanup 函式後仍然可再用)
如果這個函式在curl_easy_init函式呼叫時還沒呼叫,它講由libcurl庫自動呼叫,所以多執行緒下最好主動呼叫該函式以防止線上程中curl_easy_init時多次呼叫。
注意:雖然libcurl是執行緒安全的,但curl_global_init是不能保證執行緒安全的,所以不要在每個執行緒中都呼叫curl_global_init,應該將該函式的呼叫放在主執行緒中。
引數:flags
CURL_GLOBAL_ALL //初始化所有的可能的呼叫。
CURL_GLOBAL_SSL //初始化支援 安全套接字層。
CURL_GLOBAL_WIN32 //初始化win32套接字型檔。
CURL_GLOBAL_NOTHING //沒有額外的初始化。
2 void curl_global_cleanup(void);
描述:在結束libcurl使用的時候,用來對curl_global_init做的工作清理。類似於close的函式。
注意:雖然libcurl是執行緒安全的,但curl_global_cleanup是不能保證執行緒安全的,所以不要在每個執行緒中都呼叫curl_global_init,應該將該函式的呼叫放在主執行緒中。
3 char *curl_version( );
描述: 列印當前libcurl庫的版本。
4 CURL *curl_easy_init( );
描述:
curl_easy_init用來初始化一個CURL的指標(有些像返回FILE型別的指標一樣). 相應的在呼叫結束時要用curl_easy_cleanup函式清理.
一般curl_easy_init意味著一個會話的開始. 它會返回一個easy_handle(CURL*物件), 一般都用在easy系列的函式中.
5 void curl_easy_cleanup(CURL *handle);
描述:
這個呼叫用來結束一個會話.與curl_easy_init配合著用.
引數:
CURL型別的指標.
6 CURLcode curl_easy_setopt(CURL *handle, CURLoption option, parameter);
描述: 這個函式最重要了.幾乎所有的curl 程式都要頻繁的使用它.它告訴curl庫.程式將有如何的行為. 比如要檢視一個網頁的html程式碼等.(這個函式有些像ioctl函式)引數:
1 CURL型別的指標
2 各種CURLoption型別的選項.(都在curl.h庫裡有定義,man 也可以檢視到)
3 parameter 這個引數 既可以是個函式的指標,也可以是某個物件的指標,也可以是個long型的變數.它用什麼這取決於第二個引數.
CURLoption 這個引數的取值很多.具體的可以檢視man手冊.
7 CURLcode curl_easy_perform(CURL *handle);
描述:這個函式在初始化CURL型別的指標 以及curl_easy_setopt完成後呼叫. 就像字面的意思所說perform就像是個舞臺.讓我們設定的
option 運作起來.引數:
CURL型別的指標.
8 void curl_global_cleanup(void);
釋放libcurl
四、curl_easy_setopt函式部分選項介紹
本節主要介紹curl_easy_setopt中跟http相關的引數。該函式是curl中非常重要的函式,curl所有設定都是在該函式中完成的,該函式的設定選項眾多,注意本節的闡述的只是部分常見選項。
CURLOPT_URL
設定訪問URL
CURLOPT_WRITEFUNCTION,CURLOPT_WRITEDATA
回撥函式原型為:size_t function( void *ptr, size_t size, size_t nmemb, void *stream); 函式將在libcurl接收到資料後被呼叫,因此函式多做資料儲存的功能,如處理下載檔案。CURLOPT_WRITEDATA 用於表明CURLOPT_WRITEFUNCTION函式中的stream指標的來源。
如果你沒有通過CURLOPT_WRITEFUNCTION屬性給easy handle設定回撥函式,libcurl會提供一個預設的回撥函式,它只是簡單的將接收到的資料列印到標準輸出。你也可以通過 CURLOPT_WRITEDATA屬性給預設回撥函式傳遞一個已經開啟的檔案指標,用於將資料輸出到檔案裡。
CURLOPT_HEADERFUNCTION,CURLOPT_HEADERDATA
回撥函式原型為 size_t function( void *ptr, size_t size,size_t nmemb, void *stream); libcurl一旦接收到http 頭部資料後將呼叫該函式。CURLOPT_WRITEDATA 傳遞指標給libcurl,該指標表明CURLOPT_HEADERFUNCTION 函式的stream指標的來源。
CURLOPT_READFUNCTION CURLOPT_READDATA
libCurl需要讀取資料傳遞給遠端主機時將呼叫CURLOPT_READFUNCTION指定的函式,函式原型是:size_t function(void *ptr, size_t size, size_t nmemb,void *stream). CURLOPT_READDATA 表明CURLOPT_READFUNCTION函式原型中的stream指標來源。
CURLOPT_NOPROGRESS,CURLOPT_PROGRESSFUNCTION,CURLOPT_PROGRESSDATA
跟資料傳輸進度相關的引數。CURLOPT_PROGRESSFUNCTION 指定的函式正常情況下每秒被libcurl呼叫一次,為了使CURLOPT_PROGRESSFUNCTION被呼叫,CURLOPT_NOPROGRESS必須被設定為false,CURLOPT_PROGRESSDATA指定的引數將作為CURLOPT_PROGRESSFUNCTION指定函式的第一個引數
CURLOPT_TIMEOUT,CURLOPT_CONNECTIONTIMEOUT:
CURLOPT_TIMEOUT 由於設定傳輸時間,CURLOPT_CONNECTIONTIMEOUT 設定連線等待時間
CURLOPT_FOLLOWLOCATION
設定重定位URL
CURLOPT_RANGE: CURLOPT_RESUME_FROM:
斷點續傳相關設定。CURLOPT_RANGE 指定char *引數傳遞給libcurl,用於指明http域的RANGE頭域,例如:
表示頭500個位元組:bytes=0-499
表示第二個500位元組:bytes=500-999
表示最後500個位元組:bytes=-500
表示500位元組以後的範圍:bytes=500-
第一個和最後一個位元組:bytes=0-0,-1
同時指定幾個範圍:bytes=500-600,601-999
CURLOPT_RESUME_FROM 傳遞一個long引數給libcurl,指定你希望開始傳遞的 偏移量。
五、libcurl使用的HTTP訊息頭
當使用libcurl傳送http請求時,它會自動新增一些http頭。我們可以通過CURLOPT_HTTPHEADER屬性手動替換、新增或刪除相應 的HTTP訊息頭。
Host
http1.1(大部分http1.0)版本都要求客戶端請求提供這個資訊頭。
Pragma
"no-cache"。表示不要緩衝資料。
Accept
"*/*"。表示允許接收任何型別的資料。
Expect
以POST的方式向HTTP伺服器提交請求時,libcurl會設定該訊息頭為"100-continue",它要求伺服器在正式處理該請求之前,返回一 個"OK"訊息。如果POST的資料很小,libcurl可能不會設定該訊息頭。
自定義選項
當前越來越多的協議都構建在HTTP協議之上(如:soap),這主要歸功於HTTP的可靠性,以及被廣泛使用的代理支援(可以穿透大部分防火牆)。 這些協議的使用方式與傳統HTTP可能有很大的不同。對此,libcurl作了很好的支援。
自定義請求方式(CustomRequest)
HTTP支援GET, HEAD或者POST提交請求。可以設定CURLOPT_CUSTOMREQUEST來設定自定義的請求方式,libcurl預設以GET方式提交請求:
curl_easy_setopt(easy_handle, CURLOPT_CUSTOMREQUEST, "MYOWNREQUEST");
struct curl_slist *headers=NULL; /* init to NULL is important */ headers = curl_slist_append(headers, "Hey-server-hey: how are you?"); headers = curl_slist_append(headers, "X-silly-content: yes"); /* pass our list of custom made headers */ curl_easy_setopt(easyhandle, CURLOPT_HTTPHEADER, headers); curl_easy_perform(easyhandle); /* transfer http */ curl_slist_free_all(headers); /* free the header list */
六、獲取http應答頭資訊
發出http請求後,伺服器會返回應答頭資訊和應答資料,如果僅僅是列印應答頭的所有內容,則直接可以通過curl_easy_setopt(curl, CURLOPT_HEADERFUNCTION, 列印函式)的方式來完成,這裡需要獲取的是應答頭中特定的資訊,比如應答碼、cookies列表等,則需要通過下面這個函式:
CURLcode curl_easy_getinfo(CURL *curl, CURLINFO info, ... );
info引數就是我們需要獲取的內容,下面是一些引數值:
1.CURLINFO_RESPONSE_CODE
獲取應答碼
2.CURLINFO_HEADER_SIZE
頭大小
3.CURLINFO_COOKIELIST
cookies列表
除了獲取應答資訊外,這個函式還能獲取curl的一些內部資訊,如請求時間、連線時間等等。
七、多執行緒問題
首先一個基本原則就是:絕對不應該線上程之間共享同一個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時,應該滿足這些庫對多執行緒 操作的一些要求。詳細可以參考:
OpenSSL: http://www.openssl.org/docs/crypto/threads.html#DESCRIPTION
GnuTLS: http://www.gnu.org/software/gnutls/manual/html_node/Multi_002dthreaded-applications.html
八、HTTP驗證
在使用HTTP協議時,客戶端有很多種方式向伺服器提供驗證資訊。預設的 HTTP驗證方法是"Basic”,它將使用者名稱與密碼以明文的方式、經Base64編碼後儲存在HTTP請求頭中,發往伺服器。當然這不太安全。
當前版本的libcurl支援的驗證方法有:basic, Digest, NTLM, Negotiate, GSS-Negotiate and SPNEGO。可以通過CURLOPT_HTTPAUTH屬性來設定具體 的驗證方式:
curl_easy_setopt(easy_handle, CURLOPT_HTTPAUTH, CURLAUTH_DIGEST);
向代理伺服器傳送驗證資訊時,可以通過CURLOPT_PROXYAUTH設定驗證方式:
curl_easy_setopt(easy_handle, CURLOPT_PROXYAUTH, CURLAUTH_NTLM);
也可以同時設定多種驗證方式(通過按位與), 使用‘CURLAUTH_ANY‘將允許libcurl可以選擇任何它所支援的驗證方式。通過CURLOPT_HTTPAUTH或 CURLOPT_PROXYAUTH屬性設定的多種驗證方式,libcurl會在執行時選擇一種它認為是最好的方式與伺服器通訊:
curl_easy_setopt(easy_handle, CURLOPT_HTTPAUTH, CURLAUTH_DIGEST|CURLAUTH_BASIC);
// curl_easy_setopt(easy_handle, CURLOPT_HTTPAUTH, CURLAUTH_ANY);
九、編譯libcurl庫
從網站https://curl.haxx.se/download找到原始碼包,官網最新版為7.56.0,但是這個壓縮包的curl-7.56.0\projects\Windows路徑下VC6-VC14各個版本的VS解決方案。
9.1 如果需要libcur支援https,需要openssl庫支援。
libcurl主要功能就是用不同的協議連線和溝通不同的伺服器,如果使用HTTPS,需要OpenSSL
libcurl https://curl.haxx.se/download.html 下載Source Archives即可
ActiveState https://www.activestate.com/activeperl/downloads 下載perl解析器,編譯openssl需要用到。
openssl https://www.openssl.org/source/ 下載openssl-1.0.2k,1.1.0以後的檔案和安裝方法都換了。
zlib http://zlib.net/ 下載1.2.7以外的版本,比如1.2.11。
支援https的libcurl庫編譯方法:
1)解壓
為了方便安裝,在D盤根目錄下新建一個名為libcurl-ssl的資料夾,將下載的三個壓縮包解壓到該資料夾。
在 curl-7.54.0 -> lib 下新建資料夾openssl用來存放openssl的標頭檔案。
2) zlib編譯:
zlib-1.2.11\contrib\vstudio\vc14\zlibvc.sln,編譯release版本。
在生成的x86\ZlibDllRelease資料夾中有zlibwapi.dll和zlibwapi.lib檔案
3) ActiveState安裝:
開啟安裝包,選擇Modify預設安裝或Repair修改安裝路徑都可以
4) openssl編譯:
這是最麻煩、最容易出錯的一環了,因為他沒有專案檔案,只能通過命令列來編譯。
在開始選單中找到vs自帶的 VS2015 x86 本機工具命令提示符
使用cd命令進入到openssl-1.0.2k資料夾中
命令列鍵入 perl Configure VC-WIN32 no-asm
命令列鍵入 ms\do_ms.bat
命令列鍵入 nmake -f ms/ntdll.mak
等待差不多五分鐘,只要不出現“stop”,安全地執行到結束,就算成功。
一旦中間出了差錯,最好是把資料夾也刪了,重新解壓、配置編譯,如果你留有編譯失敗的半成品,它可能會告訴你“無法解析XXX”。
5) 將 openssl-1.0.2k -> inc32 -> openssl 所有的.h 和 openssl-1.0.2k -> out32dll 的 libeay32.lib、libeay32.dll、ssleay32.lib、ssleay32.dll 一起復制到 curl-7.54.0 -> lib -> openssl 中
libcurl編譯:
編譯平臺選擇 DLL Debug - DLL OpenSSL
curl-7.54.0 ->projects -> Windows -> VC14 -> curl-all.sln,可能會提示升級工程,確定即可。
將 libcurl 設為啟動專案,選擇 libcurl -> Resource Files -> libcurl.rc,右鍵“移出”,它記錄著版本資訊,只會增大檔案,可以移出掉。
選擇 屬性 -> C/C++ -> 前處理器 -> 前處理器定義,將"BUILDING_LIBCURL"改成"CURL_STATICLIB"。這樣那些介面函式就不會被宣告為匯出函數了。
選擇 屬性 -> 連結器 -> 常規 -> 附加庫目錄 新增 ..\..\..\..\lib\openssl,指向curl-7.54.0 -> lib -> openssl
選擇 屬性 -> 連結器 -> 輸入 -> 附加依賴項 新增 libeay32.lib;ssleay32.lib;ws2_32.lib;wldap32.lib; 前兩個是為了OpenSSL,後兩個是CURL必須依賴的。
在編譯成功後 curl-7.54.0 -> build -> Win32 -> VC14 -> DLL Debug - DLL OpenSSL 資料夾中會生成有 libcurld.dll 和 libcurld.lib(注意名字不是libcurl)。
9.2不支援https的libcurl庫編譯方法:
使用curl-7.32.0版本中vs工程,vc自動編譯。從網站https://curl.haxx.se/download 中下載curl-7.32.0版本。解壓curl-7.32.0,找到vs工程目錄,比如:curl-7.32.0\vs\vc8\lib\vc8libcurl.vcproj
1) 開啟curl-7.32.0\vs\vc8\lib\vc8libcurl.vcproj檔案,VS2010會提示升級工程,下一步即可。
VC工程裡有些設定問題導致不能直接編譯,需要稍作修改
2) 開啟工程屬性 > C\C++ > 常規 > 附加包含目錄。這裡的包含目錄是"..\include",而這個目錄根本就不存在,它應該指向"curl-7.32.0\include"才對,所以把這裡改成"..\..\..\include"。(或者直接完整路徑也可以)
3) 開啟工程屬性 > C\C++ > 前處理器 > 前處理器定義。這裡有個預設巨集"BUILDING_LIBCURL",如果要編譯生成靜態庫,則要把它改成"CURL_STATICLIB"。這樣,那些介面函式就不會被宣告為匯出函數了。
4) 開啟工程屬性 > C\C++ > 庫管理器 > 常規 > 附加依賴項。新增ws2_32.lib和wldap32.lib,這是CURL必須依賴的。或者在程式碼中使用#pragma comment預編譯指令,手動引入這兩個lib庫。
9.3 libcurld.lib/libcurl.lib引用方法
將 curl-7.54.0 -> include 目錄下的curl資料夾,複製過去。
將libcurl編譯的 libcurld.dll 和 libcurld.lib 複製到debug。
將libcurld.dll和之前OpenSSL生成的 libeay32.dll、ssleay32.dll 各複製一份到專案資料夾下,否則會報錯。
選擇 配置屬性 -> C\C++ -> 前處理器 -> 前處理器定義,新增CURL_STATICLIB。
屬性中的 附加包含目錄、附加庫目錄和附加依賴項就在程式碼中實現。
十、例項程式碼
1 #define CURL_STATICLIB //如果是靜態庫方式,需要包含這句
2
3 #include "curl\curl.h"
4 #include <iostream>
5 #include <list>
6 #include <string>
7
8 #ifdef _DEBUG
9 #pragma comment(lib,"libcurld.lib")
10 #else
11 #pragma comment(lib,"libcurl.lib")
12 #endif
13
14 #pragma comment ( lib, "ws2_32.lib" )
15 #pragma comment ( lib, "winmm.lib" )
16 #pragma comment ( lib, "wldap32.lib" )
17 #pragma comment(lib, "Advapi32.lib")
18
19
20 std::wstring AsciiToUnicode(const std::string& str)
21 {
22 // 預算-緩衝區中寬位元組的長度
23 int unicodeLen = MultiByteToWideChar(CP_ACP, 0, str.c_str(), -1, nullptr, 0);
24 // 給指向緩衝區的指標變數分配記憶體
25 wchar_t *pUnicode = (wchar_t*)malloc(sizeof(wchar_t)*unicodeLen);
26 // 開始向緩衝區轉換位元組
27 MultiByteToWideChar(CP_ACP, 0, str.c_str(), -1, pUnicode, unicodeLen);
28 std::wstring ret_str = pUnicode;
29 free(pUnicode);
30 return ret_str;
31 }
32
33 std::string UnicodeToUtf8(const std::wstring& wstr)
34 {
35 // 預算-緩衝區中多位元組的長度
36 int ansiiLen = WideCharToMultiByte(CP_UTF8, 0, wstr.c_str(), -1, nullptr, 0, nullptr, nullptr);
37 // 給指向緩衝區的指標變數分配記憶體
38 char *pAssii = (char*)malloc(sizeof(char)*ansiiLen);
39 // 開始向緩衝區轉換位元組
40 WideCharToMultiByte(CP_UTF8, 0, wstr.c_str(), -1, pAssii, ansiiLen, nullptr, nullptr);
41 std::string ret_str = pAssii;
42 free(pAssii);
43 return ret_str;
44 }
45
46 //ANSI轉UTF8
47 std::string AsciiToUtf8(const std::string& str)
48 {
49 return UnicodeToUtf8(AsciiToUnicode(str));
50 }
51
52 //UTF8轉ANSI
53 std::string Utf8toAscii(const std::string strUTF8)
54 {
55 std::string strAnsi = "";
56 //獲取轉換為多位元組後需要的緩衝區大小,建立多位元組緩衝區
57 UINT nLen = MultiByteToWideChar(CP_UTF8, NULL, strUTF8.c_str(), -1, NULL, NULL);
58 WCHAR *wszBuffer = new WCHAR[nLen + 1];
59 nLen = MultiByteToWideChar(CP_UTF8, NULL, strUTF8.c_str(), -1, wszBuffer, nLen);
60 wszBuffer[nLen] = 0;
61 nLen = WideCharToMultiByte(936, NULL, wszBuffer, -1, NULL, NULL, NULL, NULL);
62 CHAR *szBuffer = new CHAR[nLen + 1];
63 nLen = WideCharToMultiByte(936, NULL, wszBuffer, -1, szBuffer, nLen, NULL, NULL);
64 szBuffer[nLen] = 0;
65 strAnsi = szBuffer;
66 //清理記憶體
67 delete[]szBuffer;
68 delete[]wszBuffer;
69 return strAnsi;
70 }
71
72 // reply of the requery
73 size_t req_reply(void *ptr, size_t size, size_t nmemb, void *stream)
74 {
75 if (stream == NULL || ptr == NULL || size == 0)
76 return 0;
77
78 size_t realsize = size * nmemb;
79 std::string *buffer = (std::string*)stream;
80 if (buffer != NULL)
81 {
82 buffer->append((const char *)ptr, realsize);
83 }
84 return realsize;
85 /*
86 std::string *str = (std::string*)stream;
87 (*str).append((char*)ptr, size*nmemb);
88 return size * nmemb;
89 */
90 }
91
92 /*
93 功能:get http資料
94 引數:url:請求字串。如果請求帶引數資料,直接拼湊到url後面;比如:http://127.0.0.1:8080/api/Accounts/Login?uername=admin&password=123
95 listRequestHeader:請求頭資料列表。
96 bResponseIsWithHeaderData:bool型別,表示響應體中是否包含應答頭資料。true,包含,false,不包含。如果包含的話,應答資料中包含Content-Type,Server等資訊。
97 nConnectTimeout:連線超時時間,單位為秒;
98 nTimeout:讀寫資料超時時間,單位為秒
99 返回值:CURLcode
100 */
101 CURLcode curl_get_req(const std::string &url, std::string &response, std::list<std::string> listRequestHeader, bool bResponseIsWithHeaderData = false, int nConnectTimeout = 10, int nTimeout = 10)
102 {
103 // init curl
104 CURL *curl = curl_easy_init();
105 // res code
106 CURLcode res;
107 if (curl)
108 {
109 // set params
110 curl_easy_setopt(curl, CURLOPT_URL, url.c_str()); // url
111 //curl_easy_setopt(m_curl, CURLOPT_PORT, 8089); //port
112 curl_easy_setopt(curl, CURLOPT_POST, 0); // get reqest
113 //構建HTTP報文頭
114 struct curl_slist* headers = NULL;
115 if (listRequestHeader.size() > 0)
116 {
117 std::list<std::string>::iterator iter, iterEnd;
118 iter = listRequestHeader.begin();
119 iterEnd = listRequestHeader.end();
120 for (iter; iter != iterEnd; iter++)
121 {
122 headers = curl_slist_append(headers, iter->c_str());
123 }
124 //headers = curl_slist_append(headers, "Content-Type:application/json;charset=UTF-8");
125 //headers = curl_slist_append(headers, "Content-Type:application/x-www-form-urlencoded");
126 if (headers != NULL)
127 {
128 curl_easy_setopt(curl, CURLOPT_HTTPHEADER, headers);//設定http請求頭資訊
129 }
130 }
131 curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, false); // if want to use https
132 curl_easy_setopt(curl, CURLOPT_SSL_VERIFYHOST, false); // set peer and host verify false
133 curl_easy_setopt(curl, CURLOPT_VERBOSE, 1);
134 curl_easy_setopt(curl, CURLOPT_READFUNCTION, NULL);
135 curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, req_reply);
136 curl_easy_setopt(curl, CURLOPT_WRITEDATA, (void *)&response);
137 curl_easy_setopt(curl, CURLOPT_NOSIGNAL, 1);
138 if (bResponseIsWithHeaderData)
139 {
140 curl_easy_setopt(curl, CURLOPT_HEADER, 1);//響應體中是否包含了頭資訊,比如Content-Type:application/json;charset=UTF-8
141 }
142 curl_easy_setopt(curl, CURLOPT_CONNECTTIMEOUT, nConnectTimeout); // set transport and time out time
143 curl_easy_setopt(curl, CURLOPT_TIMEOUT, nTimeout);
144 // start request
145 res = curl_easy_perform(curl);
146 if (headers != NULL)
147 {
148 curl_slist_free_all(headers); //free the list again
149 }
150 }
151 // release curl
152 curl_easy_cleanup(curl);
153 return res;
154 }
155
156 CURLcode curl_get_req_ex(CURL *curl, const std::string &url, std::string &response, std::list<std::string> listRequestHeader, bool bResponseIsWithHeaderData = false, int nConnectTimeout = 10, int nTimeout = 10)
157 {
158 // res code
159 CURLcode res;
160 if (curl)
161 {
162 // set params
163 curl_easy_reset(curl);
164 /* enable TCP keep-alive for this transfer */
165 curl_easy_setopt(curl, CURLOPT_TCP_KEEPALIVE, 1L);
166 /* keep-alive idle time to 120 seconds */
167 curl_easy_setopt(curl, CURLOPT_TCP_KEEPIDLE, 120L);
168 /* interval time between keep-alive probes: 30 seconds */
169 curl_easy_setopt(curl, CURLOPT_TCP_KEEPINTVL, 30L);
170
171 curl_easy_setopt(curl, CURLOPT_URL, url.c_str()); // url
172 //curl_easy_setopt(m_curl, CURLOPT_PORT, 8089); //port
173 curl_easy_setopt(curl, CURLOPT_POST, 0); // get reqest
174 //構建HTTP報文頭
175 struct curl_slist* headers = NULL;
176 if (listRequestHeader.size() > 0)
177 {
178 std::list<std::string>::iterator iter, iterEnd;
179 iter = listRequestHeader.begin();
180 iterEnd = listRequestHeader.end();
181 for (iter; iter != iterEnd; iter++)
182 {
183 headers = curl_slist_append(headers, iter->c_str());
184 }
185 //headers = curl_slist_append(headers, "Content-Type:application/json;charset=UTF-8");
186 //headers = curl_slist_append(headers, "Content-Type:application/x-www-form-urlencoded");
187 if (headers != NULL)
188 {
189 curl_easy_setopt(curl, CURLOPT_HTTPHEADER, headers);//設定http請求頭資訊
190 }
191 }
192 curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, false); // if want to use https
193 curl_easy_setopt(curl, CURLOPT_SSL_VERIFYHOST, false); // set peer and host verify false
194 curl_easy_setopt(curl, CURLOPT_VERBOSE, 1);
195 curl_easy_setopt(curl, CURLOPT_READFUNCTION, NULL);
196 curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, req_reply);
197 curl_easy_setopt(curl, CURLOPT_WRITEDATA, (void *)&response);
198 curl_easy_setopt(curl, CURLOPT_NOSIGNAL, 1);
199 if (bResponseIsWithHeaderData)
200 {
201 curl_easy_setopt(curl, CURLOPT_HEADER, 1);//響應體中是否包含了頭資訊,比如Content-Type:application/json;charset=UTF-8
202 }
203 curl_easy_setopt(curl, CURLOPT_CONNECTTIMEOUT, nConnectTimeout); // set transport and time out time
204 curl_easy_setopt(curl, CURLOPT_TIMEOUT, nTimeout);
205 // start request
206 res = curl_easy_perform(curl);
207 if (headers != NULL)
208 {
209 curl_slist_free_all(headers); //free the list again
210 }
211 }
212 return res;
213 }
214
215 /*
216 功能:post http資料
217 引數:url:請求字串,比如:http://127.0.0.1:8080/api/Accounts/Login
218 postParams:請求附帶的引數,比如uername=admin&password=123
219 listRequestHeader:請求頭資料列表。
220 bResponseIsWithHeaderData:bool型別,表示響應體中是否包含應答頭資料。true,包含,false,不包含。如果包含的話,應答資料中包含Content-Type,Server等資訊。
221 nConnectTimeout:連線超時時間,單位為秒;
222 nTimeout:讀寫資料超時時間,單位為秒
223 返回值:CURLcode
224 */
225 CURLcode curl_post_req(const std::string &url, const std::string &postParams, std::string &response, std::list<std::string> listRequestHeader, bool bResponseIsWithHeaderData = false, int nConnectTimeout = 10, int nTimeout = 10)
226 {
227 // init curl
228 CURL *curl = curl_easy_init();
229 // res code
230 CURLcode res;
231 if (curl)
232 {
233 // set params
234 curl_easy_setopt(curl, CURLOPT_POST, 1); // post req
235 curl_easy_setopt(curl, CURLOPT_URL, url.c_str()); // url
236 //curl_easy_setopt(m_curl, CURLOPT_PORT, 8089); //port
237 curl_easy_setopt(curl, CURLOPT_POST, 1); // post reqest
238 //構建HTTP報文頭
239 struct curl_slist* headers = NULL;
240 if (listRequestHeader.size() > 0)
241 {
242 std::list<std::string>::iterator iter, iterEnd;
243 iter = listRequestHeader.begin();
244 iterEnd = listRequestHeader.end();
245 for (iter; iter != iterEnd; iter++)
246 {
247 headers = curl_slist_append(headers, iter->c_str());
248 }
249 //headers = curl_slist_append(headers, "Content-Type:application/json;charset=UTF-8");
250 //headers = curl_slist_append(headers, "Content-Type:application/x-www-form-urlencoded");
251 if (headers != NULL)
252 {
253 curl_easy_setopt(curl, CURLOPT_HTTPHEADER, headers);//設定http請求頭資訊
254 }
255 }
256 else
257 {
258 headers = curl_slist_append(headers, "Content-Type:application/x-www-form-urlencoded");
259 if (headers != NULL)
260 {
261 curl_easy_setopt(curl, CURLOPT_HTTPHEADER, headers);//設定http請求頭資訊
262 }
263 }
264 curl_easy_setopt(curl, CURLOPT_POSTFIELDS, postParams.c_str()); // params
265 curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, false); // if want to use https
266 curl_easy_setopt(curl, CURLOPT_SSL_VERIFYHOST, false); // set peer and host verify false
267 curl_easy_setopt(curl, CURLOPT_VERBOSE, 1);
268 curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, 1); //返回的頭部中有Location(一般直接請求的url沒找到),則繼續請求Location對應的資料
269 curl_easy_setopt(curl, CURLOPT_READFUNCTION, NULL);
270 curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, req_reply);
271 curl_easy_setopt(curl, CURLOPT_WRITEDATA, (void *)&response);
272 curl_easy_setopt(curl, CURLOPT_NOSIGNAL, 1);
273 if (bResponseIsWithHeaderData)
274 {
275 curl_easy_setopt(curl, CURLOPT_HEADER, 1);//響應體中是否包含了頭資訊,比如Content-Type:application/json;charset=UTF-8
276 }
277 curl_easy_setopt(curl, CURLOPT_CONNECTTIMEOUT, nConnectTimeout);
278 curl_easy_setopt(curl, CURLOPT_TIMEOUT, nTimeout);
279 // start request
280 res = curl_easy_perform(curl);
281 if (headers != NULL)
282 {
283 curl_slist_free_all(headers); //free the list again
284 }
285 }
286 // release curl
287 curl_easy_cleanup(curl);
288 return res;
289 }
290
291 CURLcode curl_post_req_ex(CURL *curl, const std::string &url, const std::string &postParams, std::string &response, std::list<std::string> listRequestHeader, bool bResponseIsWithHeaderData = false, int nConnectTimeout = 10, int nTimeout = 10)
292 {
293 // res code
294 CURLcode res;
295 if (curl)
296 {
297 // set params
298 curl_easy_reset(curl);
299 /* enable TCP keep-alive for this transfer */
300 curl_easy_setopt(curl, CURLOPT_TCP_KEEPALIVE, 1L);
301 /* keep-alive idle time to 120 seconds */
302 curl_easy_setopt(curl, CURLOPT_TCP_KEEPIDLE, 120L);
303 /* interval time between keep-alive probes: 30 seconds */
304 curl_easy_setopt(curl, CURLOPT_TCP_KEEPINTVL, 30L);
305
306 curl_easy_setopt(curl, CURLOPT_URL, url.c_str()); // url
307 //curl_easy_setopt(m_curl, CURLOPT_PORT, 8089); //port
308 curl_easy_setopt(curl, CURLOPT_POST, 1); // post reqest
309 //構建HTTP報文頭
310 struct curl_slist* headers = NULL;
311 if (listRequestHeader.size() > 0)
312 {
313 std::list<std::string>::iterator iter, iterEnd;
314 iter = listRequestHeader.begin();
315 iterEnd = listRequestHeader.end();
316 for (iter; iter != iterEnd; iter++)
317 {
318 headers = curl_slist_append(headers, iter->c_str());
319 }
320 //headers = curl_slist_append(headers, "Content-Type:application/json;charset=UTF-8");
321 //headers = curl_slist_append(headers, "Content-Type:application/x-www-form-urlencoded;charset=UTF-8");
322 if (headers != NULL)
323 {
324 curl_easy_setopt(curl, CURLOPT_HTTPHEADER, headers);//設定http請求頭資訊
325 }
326 }
327 else
328 {
329 headers = curl_slist_append(headers, "Content-Type:application/x-www-form-urlencoded");
330 if (headers != NULL)
331 {
332 curl_easy_setopt(curl, CURLOPT_HTTPHEADER, headers);//設定http請求頭資訊
333 }
334 }
335 curl_easy_setopt(curl, CURLOPT_POSTFIELDS, postParams.c_str()); // params
336 curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, false); // if want to use https
337 curl_easy_setopt(curl, CURLOPT_SSL_VERIFYHOST, false); // set peer and host verify false
338 curl_easy_setopt(curl, CURLOPT_VERBOSE, 1);
339 curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, 1); //返回的頭部中有Location(一般直接請求的url沒找到),則繼續請求Location對應的資料
340 curl_easy_setopt(curl, CURLOPT_READFUNCTION, NULL);
341 curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, req_reply);
342 curl_easy_setopt(curl, CURLOPT_WRITEDATA, (void *)&response);
343 curl_easy_setopt(curl, CURLOPT_NOSIGNAL, 1);
344 if (bResponseIsWithHeaderData)
345 {
346 curl_easy_setopt(curl, CURLOPT_HEADER, 1);//響應體中是否包含了頭資訊,比如Content-Type:application/json;charset=UTF-8
347 }
348 curl_easy_setopt(curl, CURLOPT_CONNECTTIMEOUT, nConnectTimeout);
349 curl_easy_setopt(curl, CURLOPT_TIMEOUT, nTimeout);
350 // start request
351 res = curl_easy_perform(curl);
352 if (headers != NULL)
353 {
354 curl_slist_free_all(headers); //free the list again
355 }
356 }
357 return res;
358 }
359
360 //例項1
361 curl_global_init(CURL_GLOBAL_ALL);
362
363 //post獲取資料
364 std::string strResponse = "",strResponseAnsi = "";
365 strResponse.clear();
366 CURLcode res = curl_post_req("http://127.0.0.1:8080/api/Accounts/Login", "username=admin&password=123", strResponse);
367 if (res == CURLE_OK)
368 {
369 std::string strToken = "";
370 strResponseAnsi = Utf8toAscii(strResponse);
371 }
372
373 //get獲取資料
374 strResponse.clear();
375 res = curl_get_req("http://127.0.0.1:8080/api/Accounts/Login?username=admin&password=123", strResponse);
376 if (res == CURLE_OK)
377 {
378 int jj = 0;
379 }
380
381 curl_global_cleanup();
382 //例項2
383 //post json資料
384 CURL * curl = curl_easy_init();
385 std::string strResponse = "", strResponseAnsi = "";
386 char szRequestUrl[256] = { 0 };
387 CURLcode res = CURLE_OK;
388 sprintf_s(szRequestUrl, "%s/api/GPS/AddOne", "http://127.0.0.1:8080");
389 std::string strPostParams = "";
390 try
391 {
392 boost::property_tree::ptree ptroot;
393 ptroot.put("deviceid", "12345678");
394 ptroot.put<unsigned int>("deviceStatus", 0);
395 ptroot.put<unsigned int>("alarmFlag", 0);
396 ptroot.put("lng", fLongitude);
397 ptroot.put("lat", fLatitude);
398 ptroot.put("speed", 0);
399 ptroot.put("direction", 0);
400 ptroot.put<int>("altitude", 10);
401 ptroot.put("gpsTime", "2018-10-10 12:00:01");
402 std::stringstream sstream;
403 boost::property_tree::write_json(sstream, ptroot);
404 strPostParams = sstream.str();
405 bSuccess = true;
406 }
407 catch (boost::property_tree::ptree_error pt)
408 {
409 pt.what();
410 }
411 if (bSuccess)
412 {
413 std::string strAuthorization = "admin---";
414 std::string strRequestHeaders = strAuthorization;
415 std::list<std::string> listRequestHeader;
416 listRequestHeader.push_back(strRequestHeaders);
417 listRequestHeader.push_back("Content-Type:application/json;charset=UTF-8");
418 res = curl_post_req_ex(curl, szRequestUrl, strPostParams, strResponse, listRequestHeader);
419 if (res == CURLE_OK)
420 {
421 bSuccess = true;
422 }
423 }
424
425 curl_easy_cleanup(curl);
注意事項:
1、http模式測試,使用Postman外掛或模擬測試網站 https://www.sojson.com/httpRequest/
2、保持長連線,設定選項。
/* enable TCP keep-alive for this transfer */
curl_easy_setopt(curl, CURLOPT_TCP_KEEPALIVE, 1L);
/* keep-alive idle time to 120 seconds */
curl_easy_setopt(curl, CURLOPT_TCP_KEEPIDLE, 120L);
/* interval time between keep-alive probes: 60 seconds */
curl_easy_setopt(curl, CURLOPT_TCP_KEEPINTVL, 60L);
3、呼叫libcurl下載,然後使用netstat檢視發現有大量的TCP連線保持在CLOSE_WAIT狀態
檢視libcurl的文件說明,有這樣一個選項:
CURLOPT_FORBID_REUSE
Pass a long. Set to 1 to make the next transfer explicitly close the connection when done. Normally, libcurl keeps all connections alive when done with one transfer in case a succeeding one follows that can re-use them. This option should be used with caution and only if you understand what it does. Set to 0 to have libcurl keep the connection open for possible later re-use (default behavior).
也就是說,預設情況下libcurl完成一個任務以後,出於重用連線的考慮不會馬上關閉
如果沒有新的TCP請求來重用這個連線,那麼只能等到CLOSE_WAIT超時,這個時間預設在7200秒甚至更高,太多的CLOSE_WAIT連線會導致效能問題
解決方法:
curl_easy_setopt(curl, CURLOPT_FORBID_REUSE, 1);
最好再修改一下TCP引數調低CLOSE_WAIT和TIME_WAIT的超時時間
4、libcurl進行非同步併發
使用multi介面,multi介面的使用會比easy 介面稍微複雜點,畢竟multi介面是依賴easy介面的,首先粗略的講下其使用流程:curl_multi _init初始化一個multi curl物件,為了同時進行多個curl的併發訪問,我們需要初始化多個easy curl物件,使用curl_easy_setopt進行相關設定,然後呼叫curl_multi _add_handle把easy curl物件新增到multi curl物件中,新增完畢後執行curl_multi_perform方法進行併發的訪問,訪問結束後curl_multi_remove_handle移除相關easy curl物件,curl_easy_cleanup清除easy curl物件,最後curl_multi_cleanup清除multi curl物件。multi介面具體使用方法參考下面連結
https://blog.csdn.net/whui19890911/article/details/79320408
5、請求頭、響應頭多個引數設定。使用curl_slist_append函式一個個引數插入。
參考資料:
libcurl官網:https://curl.haxx.se/libcurl/
vc編譯libcurl:https://www.cnblogs.com/findumars/p/7496122.html
curl_errno錯誤碼說明
CURLE_UNSUPPORTED_PROTOCOL (1) – 您傳送給 libcurl 的網址使用了此 libcurl 不支援的協議。 可能是您沒有使用的編譯時選項造成了這種情況(可能是協議字串拼寫有誤,或沒有指定協議 libcurl 程式碼)。
CURLE_FAILED_INIT (2) – 非常早期的初始化程式碼失敗。 可能是內部錯誤或問題。
CURLE_URL_MALFORMAT (3) – 網址格式不正確。
CURLE_COULDNT_RESOLVE_PROXY (5) – 無法解析代理伺服器。 指定的代理伺服器主機無法解析。
CURLE_COULDNT_RESOLVE_HOST (6) – 無法解析主機。 指定的遠端主機無法解析。
CURLE_COULDNT_CONNECT (7) – 無法通過 connect() 連線至主機或代理伺服器。
CURLE_FTP_WEIRD_SERVER_REPLY (8) – 在連線到 FTP 伺服器後,libcurl 需要收到特定的回覆。 此錯誤程式碼表示收到了不正常或不正確的回覆。 指定的遠端伺服器可能不是正確的 FTP 伺服器。
CURLE_REMOTE_ACCESS_DENIED (9) – 我們無法訪問網址中指定的資源。 對於 FTP,如果嘗試更改為遠端目錄,就會發生這種情況。
CURLE_FTP_WEIRD_PASS_REPLY (11) – 在將 FTP 密碼傳送到伺服器後,libcurl 需要收到正確的回覆。 此錯誤程式碼表示返回的是意外的程式碼。
CURLE_FTP_WEIRD_PASV_REPLY (13) – libcurl 無法從伺服器端收到有用的結果,作為對 PASV 或 EPSV 命令的響應。 伺服器有問題。
CURLE_FTP_WEIRD_227_FORMAT (14) – FTP 伺服器返回 227 行作為對 PASV 命令的響應。如果 libcurl 無法解析此行,就會返回此程式碼。
CURLE_FTP_CANT_GET_HOST (15) – 在查詢用於新連線的主機時出現內部錯誤。
CURLE_FTP_COULDNT_SET_TYPE (17) – 在嘗試將傳輸模式設定為二進位制或 ascii 時發生錯誤。
CURLE_PARTIAL_FILE (18) – 檔案傳輸尺寸小於或大於預期。當伺服器先報告了一個預期的傳輸尺寸,然後所傳送的資料與先前指定尺寸不相符時,就會發生此錯誤。
CURLE_FTP_COULDNT_RETR_FILE (19) – ‘RETR’ 命令收到了不正常的回覆,或完成的傳輸尺寸為零位元組。
CURLE_QUOTE_ERROR (21) – 在向遠端伺服器傳送自定義 “QUOTE” 命令時,其中一個命令返回的錯誤程式碼為 400 或更大的數字(對於 FTP),或以其他方式表明命令無法成功完成。
CURLE_HTTP_RETURNED_ERROR (22) – 如 果 CURLOPT_FAILONERROR 設定為 TRUE,且 HTTP 伺服器返回 >= 400 的錯誤程式碼,就會返回此程式碼。 (此錯 誤程式碼以前又稱為 CURLE_HTTP_NOT_FOUND。)
CURLE_WRITE_ERROR (23) – 在向本地檔案寫入所收到的資料時發生錯誤,或由寫入回撥 (write callback) 向 libcurl 返回了一個錯誤。
CURLE_UPLOAD_FAILED (25) – 無法開始上傳。 對於 FTP,伺服器通常會拒絕執行 STOR 命令。錯誤緩衝區通常會提供伺服器對此問題的說明。 (此錯誤程式碼以前又稱為 CURLE_FTP_COULDNT_STOR_FILE。)
CURLE_READ_ERROR (26) – 讀取本地檔案時遇到問題,或由讀取回調 (read callback) 返回了一個錯誤。
CURLE_OUT_OF_MEMORY (27) – 記憶體分配請求失敗。此錯誤比較嚴重,若發生此錯誤,則表明出現了非常嚴重的問題。
CURLE_OPERATION_TIMEDOUT (28) – 操 作超時。 已達到根據相應情況指定的超時時間。 請注意: 自 Urchin 6.6.0.2 開始,超時時間可以自行更改。 要指定遠端日誌下載超時, 請開啟 urchin.conf 檔案,取消以下行的註釋標記:
#DownloadTimeout: 30
CURLE_FTP_PORT_FAILED (30) – FTP PORT 命令返回錯誤。 在沒有為 libcurl 指定適當的地址使用時,最有可能發生此問題。 請參閱 CURLOPT_FTPPORT。
CURLE_FTP_COULDNT_USE_REST (31) – FTP REST 命令返回錯誤。如果伺服器正常,則應當不會發生這種情況。
CURLE_RANGE_ERROR (33) – 伺服器不支援或不接受範圍請求。
CURLE_HTTP_POST_ERROR (34) – 此問題比較少見,主要由內部混亂引發。
CURLE_SSL_CONNECT_ERROR (35) – 同時使用 SSL/TLS 時可能會發生此錯誤。您可以訪問錯誤緩衝區檢視相應資訊,其中會對此問題進行更詳細的介紹。可能是證書(檔案格式、路徑、許可)、密碼及其他因素導致了此問題。
CURLE_FTP_BAD_DOWNLOAD_RESUME (36) – 嘗試恢復超過檔案大小限制的 FTP 連線。
CURLE_FILE_COULDNT_READ_FILE (37) – 無法開啟 FILE:// 路徑下的檔案。原因很可能是檔案路徑無法識別現有檔案。 建議您檢查檔案的訪問許可權。
CURLE_LDAP_CANNOT_BIND (38) – LDAP 無法繫結。LDAP 繫結操作失敗。
CURLE_LDAP_SEARCH_FAILED (39) – LDAP 搜尋無法進行。
CURLE_FUNCTION_NOT_FOUND (41) – 找不到函式。 找不到必要的 zlib 函式。
CURLE_AB