C++ LibCurl 庫的使用方法

2023-08-23 12:01:30

LibCurl是一個開源的免費的多協定資料傳輸開源庫,該框架具備跨平臺性,開源免費,並提供了包括HTTPFTPSMTPPOP3等協定的功能,使用libcurl可以方便地進行網路資料傳輸操作,如傳送HTTP請求、下載檔案、傳送電子郵件等。它被廣泛應用於各種網路應用開發中,特別是涉及到資料傳輸的場景。

首先讀者需要自行下載該庫,如下筆者選擇下載curl-8.0.1.zip這個原始碼版本,讀者可找到如下頁面,並點選對應版本完成下載,當下載好以後讀者可自行將其解壓縮到任意目錄下。

當讀者解壓縮後,可開啟VS2013 開發人員命令提示並切換帶該目錄中的curl-8.0.1\winbuild目錄,通過執行如下兩條命令即可分別實現編譯靜態庫或動態庫,我們以靜態庫編譯為主,執行如下命令讀者可自行等待一段時間。

  • 動態庫: nmake /f Makefile.vc mode=dll VC=13 MACHINE=x86 DEBUG=no
  • 靜態庫: nmake / f Makefile.vc mode = static VC = 13 ENABLE_IDN = no MACHINE = x86 DEBUG = no

這個庫在編譯通過後會自動生成檔案到builds\libcurl-vc13-x86-release-static-ipv6-sspi-schannel目錄內,讀者可自行開啟該目錄,即可看到該目錄內的標頭檔案以及庫目錄檔案,如下圖所示;

讀者可自行設定這個靜態庫,通常只需要設定includelib檔案即可,該庫的使用很簡單,首先我們需要呼叫curl_easy_init()函數對CURL物件進行初始化,接著通過呼叫curl_easy_setopt()並傳入一個存取URL連結,當存取成功後則可呼叫curl_easy_perform()函數得到存取結果,這就是該庫基本使用方法,如下程式碼。

#define CURL_STATICLIB
#define BUILDING_LIBCURL
#include <iostream>
#include "curl/curl.h"

#pragma comment (lib,"libcurl_a.lib")
#pragma comment (lib,"wldap32.lib")
#pragma comment (lib,"ws2_32.lib")
#pragma comment (lib,"Crypt32.lib")

using namespace std;

int main(int argc, char *argv[])
{
	CURL *curl;
	CURLcode res;
	curl = curl_easy_init();
	if (curl)
	{
		curl_easy_setopt(curl, CURLOPT_URL, "https://www.lyshark.com");
		res = curl_easy_perform(curl);
		curl_easy_cleanup(curl);
	}

	std::cout << "返回狀態: " << res << std::endl;

	system("pause");
	return 0;
}

執行上述程式碼,讀者可看到網站www.lyshark.com的原始碼,如下圖所示;

上述程式碼中的curl_easy_setopt()函數第二個引數可以使用多種型別的變數定義,我們可以通過傳入不同的常數來定義請求頭中的引數,例如當我們需要修改協定頭時,可以使用CURLOPT_HTTPHEADER常數,並在其後第三個引數中傳入該常數所對應的結構即可,這個結構體定義有許多型別,具體如下下表所示;

常數名稱 描述
CURLINFO_EFFECTIVE_URL 最後一個有效的URL地址
CURLINFO_HTTP_CODE 最後一個收到的HTTP程式碼
CURLINFO_FILETIME 遠端獲取檔案的時間,如果無法獲取,則返回值為-1
CURLINFO_TOTAL_TIME 最後一次傳輸所消耗的時間
CURLINFO_NAMELOOKUP_TIME 名稱解析所消耗的時間
CURLINFO_CONNECT_TIME 建立連線所消耗的時間
CURLINFO_PRETRANSFER_TIME 從建立連線到準備傳輸所使用的時間
CURLINFO_STARTTRANSFER_TIME 從建立連線到傳輸開始所使用的時間
CURLINFO_REDIRECT_TIME 在事務傳輸開始前重定向所使用的時間
CURLINFO_SIZE_UPLOAD 以位元組為單位返回上傳資料量的總值
CURLINFO_SIZE_DOWNLOAD 以位元組為單位返回下載資料量的總值
CURLINFO_SPEED_DOWNLOAD 平均下載速度
CURLINFO_SPEED_UPLOAD 平均上傳速度
CURLINFO_HEADER_SIZE header部分的大小
CURLINFO_HEADER_OUT 傳送請求的字串
CURLINFO_REQUEST_SIZE 在HTTP請求中有問題的請求的大小
CURLINFO_SSL_VERIFYRESULT 通過設定CURLOPT_SSL_VERIFYPEER返回的SSL證書驗證請求的結果
CURLINFO_CONTENT_LENGTH_DOWNLOAD 從Content-Length: field中讀取的下載內容長度
CURLINFO_CONTENT_LENGTH_UPLOAD 上傳內容大小的說明
CURLINFO_CONTENT_TYPE 下載內容的Content-Type:值,NULL表示伺服器沒有傳送有效的Content-Type:header

如下案例是一個簡單的GET請求封裝,通過呼叫GetStatus()函數實現對特定頁面發起請求的功能,其中curl_slist_append()用於增加新的請求頭資料,在呼叫curl_easy_setopt()函數時,分別傳入了CURLOPT_HTTPHEADER設定請求頭,CURLOPT_WRITEFUNCTION設定回撥,CURLINFO_PRIMARY_IP獲取目標IP地址,CURLINFO_RESPONSE_CODE獲取目標返回程式碼,此處的write_data()函數直接返回0則表示遮蔽所有的頁面輸出內容。

#define CURL_STATICLIB
#define BUILDING_LIBCURL
#include <iostream>
#include "curl/curl.h"

#pragma comment (lib,"libcurl_a.lib")
#pragma comment (lib,"wldap32.lib")
#pragma comment (lib,"ws2_32.lib")
#pragma comment (lib,"Crypt32.lib")

using namespace std;

// 設定CURLOPT_WRITEFUNCTION回撥函數,返回為空遮蔽輸出
static size_t write_data(char *d, size_t n, size_t l, void *p)
{
	return 0;
}

// 獲取網站返回值
void GetStatus(char *UrlPage)
{
	CURLcode return_code;

	// 初始化模組
	return_code = curl_global_init(CURL_GLOBAL_WIN32);
	if (CURLE_OK != return_code)
	{
		return;
	}

	// 初始化填充請求頭
	struct curl_slist *headers = NULL;
	headers = curl_slist_append(headers, "User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:76.0)");
	headers = curl_slist_append(headers, "Referer: https://www.lyshark.com");

	// 初始化請求庫
	CURL *easy_handle = curl_easy_init();
	if (NULL != easy_handle)
	{
		// CURLOPT_HTTPHEADER 自定義設定請求頭
		curl_easy_setopt(easy_handle, CURLOPT_HTTPHEADER, headers);

		// CURLOPT_URL 自定義請求的網站
		curl_easy_setopt(easy_handle, CURLOPT_URL, UrlPage);

		// CURLOPT_WRITEFUNCTION 設定回撥函數,遮蔽輸出
		curl_easy_setopt(easy_handle, CURLOPT_WRITEFUNCTION, write_data);

		// 執行CURL存取網站
		return_code = curl_easy_perform(easy_handle);

		char *ipAddress = { 0 };

		// CURLINFO_PRIMARY_IP 獲取目標IP資訊
		return_code = curl_easy_getinfo(easy_handle, CURLINFO_PRIMARY_IP, &ipAddress);
		if ((CURLE_OK == return_code) && ipAddress)
		{
			std::cout << "目標IP: " << ipAddress << std::endl;
		}

		long retcode = 0;

		// CURLINFO_RESPONSE_CODE 獲取目標返回狀態
		return_code = curl_easy_getinfo(easy_handle, CURLINFO_RESPONSE_CODE, &retcode);
		if ((CURLE_OK == return_code) && retcode)
		{
			std::cout << "返回狀態碼: " << retcode << std::endl;
		}
	}
	curl_easy_cleanup(easy_handle);
	curl_global_cleanup();
}

int main(int argc, char *argv[])
{
	GetStatus("https://www.lyshark.com");
	system("pause");
	return 0;
}

執行上述程式碼,則可以獲取到www.lyshark.com目標主機的IP地址以及頁面返回狀態,如下圖所示;

當然該庫同樣支援POST請求方式,在使用POST請求時我們可以通過CURLOPT_COOKIEFILE引數指定Cookie引數,通過CURLOPT_POSTFIELDS指定POST的資料集,而如果需要使用代理模式則可以通過CURLOPT_PROXY方式來指定代理地址,

#define CURL_STATICLIB
#define BUILDING_LIBCURL
#include <iostream>
#include "curl/curl.h"

#pragma comment (lib,"libcurl_a.lib")
#pragma comment (lib,"wldap32.lib")
#pragma comment (lib,"ws2_32.lib")
#pragma comment (lib,"Crypt32.lib")

using namespace std;

bool SendPost(char *Url, char *Cookie, char *PostVal)
{
	CURL *curl;
	CURLcode res;

	// 初始化庫
	curl = curl_easy_init();
	if (curl)
	{
		// 設定請求頭
		struct curl_slist *headers = NULL;
		headers = curl_slist_append(headers, "User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:76.0)");
		headers = curl_slist_append(headers, "Referer: https://www.lyshark.com");

		// 設定請求頭
		curl_easy_setopt(curl, CURLOPT_HTTPHEADER, headers);

		// 指定URL
		curl_easy_setopt(curl, CURLOPT_URL, Url);

		// 指定cookie引數
		curl_easy_setopt(curl, CURLOPT_COOKIEFILE, Cookie);

		// 指定post內容
		curl_easy_setopt(curl, CURLOPT_POSTFIELDS, PostVal);

		// 是否代理
		// curl_easy_setopt(curl, CURLOPT_PROXY, "10.99.60.201:8080");
		res = curl_easy_perform(curl);
		curl_easy_cleanup(curl);
	}
	return true;
}

int main(int argc, char *argv[])
{
	// 傳入網址 cookie 以及post引數
	SendPost("https://www.lyshark.com/post.php", "1e12sde342r2", "&logintype=uid&u=xieyan&psw=xxx86");

	system("pause");
	return 0;
}

該函數的呼叫需要有一個POST結構才可測試,此處由於我並沒有指定介面所有返回了頁面錯誤資訊,如下圖所示;

接著繼續實現下載頁面到原生的功能,該功能實現的原理是利用write_data回撥函數,當頁面資料被讀入到記憶體時回撥函數會被觸發,在該回撥函數的內部通過呼叫fwrite函數將ptr指標中的資料儲存本地,實現這段程式碼如下所示;

#define CURL_STATICLIB
#define BUILDING_LIBCURL
#include <iostream>
#include "curl/curl.h"

#pragma comment (lib,"libcurl_a.lib")
#pragma comment (lib,"wldap32.lib")
#pragma comment (lib,"ws2_32.lib")
#pragma comment (lib,"Crypt32.lib")

using namespace std;

FILE *fp;

size_t write_data(void *ptr, size_t size, size_t nmemb, void *stream)
{
	int written = fwrite(ptr, size, nmemb, (FILE *)fp);
	return written;
}

BOOL GetUrl(char *URL, char *FileName)
{
	CURL *curl;

	curl_global_init(CURL_GLOBAL_ALL);
	curl = curl_easy_init();

	curl_easy_setopt(curl, CURLOPT_URL, URL);

	// 在螢幕列印請求連線過程和返回http資料
	curl_easy_setopt(curl, CURLOPT_VERBOSE, 1L);

	// 查詢次數,防止查詢太深
	curl_easy_setopt(curl, CURLOPT_MAXREDIRS, 1);

	// 設定連線超時
	curl_easy_setopt(curl, CURLOPT_CONNECTTIMEOUT, 3);

	// 接收資料時超時設定
	curl_easy_setopt(curl, CURLOPT_TIMEOUT, 3);

	if ((fp = fopen(FileName, "w")) == NULL)
	{
		curl_easy_cleanup(curl);
		return FALSE;
	}
	// CURLOPT_WRITEFUNCTION 將後繼的動作交給write_data函數處理
	curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, write_data);

	curl_easy_perform(curl);
	curl_easy_cleanup(curl);
	return TRUE;
}

int main(int argc, char *argv[])
{
	// 下載網頁到本地
	GetUrl("https://www.lyshark.com", "./lyshark.html");

	system("pause");
	return 0;
}

當讀者執行上述程式後,即可將www.lyshark.com網站頁面原始碼,下載到本地當前目錄下lyshark.html,輸出效果如下圖所示;

為了能解析引數,我們還是需要將頁面原始碼讀入到記憶體中,要實現這個需求並不難,首先我們定義一個std::string容器,然後當有新資料產生時觸發WriteCallback在該函數內,我們直接將資料拷貝到一個記憶體指標中,也就是儲存到read_buffer內,並將該緩衝區返回給呼叫者即可,如下則是完整原始碼。

#define CURL_STATICLIB
#define BUILDING_LIBCURL
#include <iostream>
#include <string>
#include "curl/curl.h"

#pragma comment (lib,"libcurl_a.lib")
#pragma comment (lib,"wldap32.lib")
#pragma comment (lib,"ws2_32.lib")
#pragma comment (lib,"Crypt32.lib")

using namespace std;

// 儲存回撥函數
size_t WriteCallback(char *contents, size_t size, size_t nmemb, void *userp)
{
	((std::string*)userp)->append((char*)contents, size * nmemb);
	return size * nmemb;
}

// 獲取資料並放入string中.
std::string GetUrlPageOfString(std::string url)
{
	std::string read_buffer;
	CURL *curl;

	curl_global_init(CURL_GLOBAL_ALL);
	curl = curl_easy_init();
	if (curl)
	{
		// 忽略證書檢查
		curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, 0L);

		// 重定向
		curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, 1);

		// URL路徑
		curl_easy_setopt(curl, CURLOPT_URL, url);

		// 查詢次數,防止查詢太深
		curl_easy_setopt(curl, CURLOPT_MAXREDIRS, 1);

		// 連線超時
		curl_easy_setopt(curl, CURLOPT_CONNECTTIMEOUT, 3);

		// 接收資料時超時設定
		curl_easy_setopt(curl, CURLOPT_TIMEOUT, 3);

		// 寫入回撥函數
		curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, WriteCallback);
		curl_easy_setopt(curl, CURLOPT_WRITEDATA, &read_buffer);

		curl_easy_perform(curl);
		curl_easy_cleanup(curl);

		return read_buffer;
	}
	return "None";
}

int main(int argc, char *argv[])
{
	std::string urls = GetUrlPageOfString("https://www.lyshark.com");
	std::cout << "接收長度: " << urls.length() << " bytes" << std::endl;

	system("pause");
	return 0;
}

如下圖所示,則是執行後輸出記憶體資料長度,當然我們也可以直接輸出urls中的資料,也就是網頁的原始碼;

本文作者: 王瑞
本文連結: https://www.lyshark.com/post/6aa9753b.html
版權宣告: 本部落格所有文章除特別宣告外,均採用 BY-NC-SA 許可協定。轉載請註明出處!