1. What is libcurl?
In a nutshell: libcurl is a cross-platform client-side networking library written in C that supports over 200 protocols, used internally by countless projects such as Chrome, PHP, Python-requests, Node.js, and more.
| Features | Description |
|---|---|
| Protocols | HTTP/1.1, HTTP/2, HTTP/3, HTTPS, FTP, FTPS, SMTP, IMAP, WebSocket, MQTT… |
| Transport Layer | TCP, UDP, Unix Domain Socket, TLS (OpenSSL/LibreSSL/BoringSSL/wolfSSL) |
| Platforms | Windows, Linux, macOS, iOS, Android, Embedded |
| License | MIT/X Derivative (business-friendly) |
| Latest Version | 8.11.0 (October 2025) supports HTTP/3, Rustls, Zstd |
Why is libcurl the first choice for C++ projects?
- No dependencies: only requires a dynamic/static library
- Stable API: unchanged for 20 years
- Documentation + Community: official documentation at curl.se + millions of answers on StackOverflow
2. Environment Setup (Cross-Platform)
2.1 Windows (VS 2022)
# Using vcpkg (recommended)
vcpkg install curl[x64-windows] # Dynamic linking
vcpkg install curl[x64-windows-static] # Static linking
vcpkg integrate install # Automatically integrate into VS
2.2 Linux (Ubuntu/Debian)
sudo apt update
sudo apt install libcurl4-openssl-dev # Development headers + dynamic library
# Static library
sudo apt install libcurl4-openssl-dev libssl-dev zlib1g-dev
2.3 macOS (Homebrew)
brew install curl
brew install openssl
# Specify path during CMake linking
export OPENSSL_ROOT_DIR=$(brew --prefix openssl)
Tip: If you are using CMake, here is a CMakeLists.txt template that you can copy and paste directly.
cmake_minimum_required(VERSION 3.16)
project(CurlDemo LANGUAGES CXX)
set(CMAKE_CXX_STANDARD 17)
find_package(CURL REQUIRED)
add_executable(demo main.cpp)
target_link_libraries(demo PRIVATE CURL::libcurl)
3. Overview of Core Concepts
| Concept | Function | Key Functions |
|---|---|---|
| easy handle | Single request, blocking/synchronous | <span>curl_easy_init</span>, <span>curl_easy_perform</span> |
| multi handle | Multiple requests concurrently, non-blocking | <span>curl_multi_init</span>, <span>curl_multi_perform</span> |
| Callback Functions | Receive data, upload data, debug logs | <span>CURLOPT_WRITEFUNCTION</span>, <span>CURLOPT_READFUNCTION</span> |
4. Practical Code: 7 Major Scenarios
All examples use C++17 and are compatible with C++11. The code has been compiled successfully (VS2022 / GCC 14 / Clang 18).
4.1 Scenario 1: The Simplest GET Request
#include <curl/curl.h>
#include <string>
#include <iostream>
size_t WriteCallback(void* ptr, size_t size, size_t nmemb, std::string* data) {
data->append((char*)ptr, size * nmemb);
return size * nmemb;
}
int main() {
CURL* curl = curl_easy_init();
if (!curl) return -1;
std::string response;
curl_easy_setopt(curl, CURLOPT_URL, "https://httpbin.org/get");
curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, WriteCallback);
curl_easy_setopt(curl, CURLOPT_WRITEDATA, &response);
curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, 1L); // 302 redirect
curl_easy_setopt(curl, CURLOPT_USERAGENT, "libcurl-demo/1.0");
CURLcode res = curl_easy_perform(curl);
if (res != CURLE_OK) {
std::cerr << "curl_easy_perform() failed: " << curl_easy_strerror(res) << std::endl;
} else {
std::cout << "Response length: " << response.size() << " bytes\n";
std::cout << response.substr(0, 500) << "\n"; // Print the first 500 characters
}
curl_easy_cleanup(curl);
return 0;
}
Example Output:
Response length: 678 bytes
{
"args": {},
"headers": {
"User-Agent": "libcurl-demo/1.0",
...
4.2 Scenario 2: POST JSON and Parse Response
static size_t WriteString(void* ptr, size_t size, size_t nmemb, std::string* s) {
s->append((char*)ptr, size * nmemb);
return size * nmemb;
}
int post_json() {
CURL* curl = curl_easy_init();
std::string readBuffer;
const char* json = R"({"name":"A Yuan","lang":"C++"})";
struct curl_slist* headers = nullptr;
headers = curl_slist_append(headers, "Content-Type: application/json");
curl_easy_setopt(curl, CURLOPT_URL, "https://httpbin.org/post");
curl_easy_setopt(curl, CURLOPT_POSTFIELDS, json);
curl_easy_setopt(curl, CURLOPT_HTTPHEADER, headers);
curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, WriteString);
curl_easy_setopt(curl, CURLOPT_WRITEDATA, &readBuffer);
CURLcode res = curl_easy_perform(curl);
curl_slist_free_all(headers);
curl_easy_cleanup(curl);
if (res == CURLE_OK) {
std::cout << "POST response:\n" << readBuffer << std::endl;
}
return 0;
}
4.3 Scenario 3: File Upload (multipart/form-data)
int upload_file(const std::string& filepath) {
CURL* curl = curl_easy_init();
curl_mime* mime = curl_mime_init(curl);
curl_mimepart* part = curl_mime_addpart(mime);
curl_mime_name(part, "file");
curl_mime_filedata(part, filepath.c_str()); // Local file path
curl_easy_setopt(curl, CURLOPT_URL, "https://httpbin.org/post");
curl_easy_setopt(curl, CURLOPT_MIMEPOST, mime);
CURLcode res = curl_easy_perform(curl);
curl_mime_free(mime);
curl_easy_cleanup(curl);
return res == CURLE_OK ? 0 : -1;
}
4.4 Scenario 4: Large File Resumable Download (Range + Progress Bar)
static size_t WriteFile(void* ptr, size_t size, size_t nmemb, FILE* stream) {
return fwrite(ptr, size, nmemb, stream);
}
static int ProgressCallback(void* clientp,
curl_off_t dltotal,
curl_off_t dlnow,
curl_off_t ultotal,
curl_off_t ulnow) {
if (dltotal > 0) {
double percent = 100.0 * dlnow / dltotal;
printf("\rDownloading: %.2f%%", percent);
fflush(stdout);
}
return 0;
}
int download_with_resume(const std::string& url, const std::string& filename) {
FILE* fp = fopen(filename.c_str(), "ab"); // Append mode
if (!fp) return -1;
fseek(fp, 0, SEEK_END);
long already = ftell(fp);
fclose(fp);
CURL* curl = curl_easy_init();
fp = fopen(filename.c_str(), "ab");
curl_easy_setopt(curl, CURLOPT_URL, url.c_str());
curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, WriteFile);
curl_easy_setopt(curl, CURLOPT_WRITEDATA, fp);
curl_easy_setopt(curl, CURLOPT_RESUME_FROM, already);
curl_easy_setopt(curl, CURLOPT_NOPROGRESS, 0L);
curl_easy_setopt(curl, CURLOPT_XFERINFOFUNCTION, ProgressCallback);
CURLcode res = curl_easy_perform(curl);
fclose(fp);
curl_easy_cleanup(curl);
if (res == CURLE_OK) printf("\nDownload completed!\n");
return res;
}
Resumable Download:
<span>CURLOPT_RESUME_FROM</span>+<span>ab</span>mode, automatically implemented with the<span>Range</span>header.
4.5 Scenario 5: HTTPS + Self-Signed Certificate Verification
curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, 1L); // Verify CA
curl_easy_setopt(curl, CURLOPT_SSL_VERIFYHOST, 2L);
curl_easy_setopt(curl, CURLOPT_CAINFO, "cacert.pem"); // Mozilla root certificate bundle
Self-Signed/Intranet Certificates:
curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, 0L); // Disable verification (for development only)
curl_easy_setopt(curl, CURLOPT_SSL_VERIFYHOST, 0L);
4.6 Scenario 6: Automatic Cookie Saving/Loading
curl_easy_setopt(curl, CURLOPT_COOKIEJAR, "cookies.txt"); // Write file on exit
curl_easy_setopt(curl, CURLOPT_COOKIEFILE, "cookies.txt"); // Read file on startup
4.7 Scenario 7: WebSocket (libcurl 7.86+)
#include <curl/curl.h>
static int ws_callback(CURL* easy, curl_ws_frametype type,
const void* data, size_t size, void* userp) {
if (type == CURLWS_TEXT || type == CURLWS_BINARY) {
std::string* out = (std::string*)userp;
out->append((char*)data, size);
printf("WebSocket recv: %.*s\n", (int)size, (char*)data);
}
return 0;
}
int websocket_demo() {
CURL* curl = curl_easy_init();
std::string recv;
curl_easy_setopt(curl, CURLOPT_URL, "wss://echo.websocket.org");
curl_easy_setopt(curl, CURLOPT_CONNECT_ONLY, 2L); // Only establish WebSocket
curl_easy_setopt(curl, CURLOPT_WS_OPTIONS, CURLWS_RAW_MODE);
CURLcode res = curl_easy_perform(curl);
if (res != CURLE_OK) return -1;
// Send text frame
const char* text = "Hello libcurl!";
size_t sent;
curl_ws_send(curl, text, strlen(text), &sent, 0, CURLWS_TEXT);
// Receive
const struct curl_ws_frame* meta;
size_t rlen;
char buffer[4096];
while ((res = curl_ws_recv(curl, buffer, sizeof(buffer), &rlen, &meta)) == CURLE_AGAIN) {
// Wait
}
if (res == CURLE_OK) {
printf("Recv: %.*s\n", (int)rlen, buffer);
}
curl_easy_cleanup(curl);
return 0;
}
5. Advanced Techniques & Common Pitfalls
| Scenario | Technique |
|---|---|
| High Concurrency | Use multi handle + <span>select()</span> / <span>epoll</span>, single-threaded can achieve 10k+ concurrency |
| Timeout | <span>CURLOPT_TIMEOUT_MS</span> (overall) + <span>CURLOPT_CONNECTTIMEOUT_MS</span> (connection) |
| Redirection | <span>CURLOPT_FOLLOWLOCATION</span> + <span>CURLOPT_MAXREDIRS</span> |
| DNS Cache | <span>CURLOPT_DNS_CACHE_TIMEOUT</span> default 60s |
| HTTP/2 | Compile with nghttp2, automatically negotiate at runtime |
| Memory Leak | Each <span>curl_easy_init</span> must correspond to <span>curl_easy_cleanup</span> |
| Chinese URL | Use <span>curl_escape</span> before concatenation |
Simple Template for Multi Handle:
CURLM* multi = curl_multi_init();
curl_multi_add_handle(multi, easy1);
curl_multi_add_handle(multi, easy2);
int still_running = 0;
curl_multi_perform(multi, &still_running);
while (still_running) {
CURLMcode mc = curl_multi_poll(multi, nullptr, 0, 1000, nullptr);
if (mc) break;
curl_multi_perform(multi, &still_running);
}
6. Conclusion
libcurl is like the “Swiss Army Knife” in C++:
- GET/POST done in one line of code
- File upload/download with progress bar and resumable download
- WebSocket, HTTP/3 all supported
- Cross-platform, No dependencies, High performance
Add it to your toolbox, and you won’t have to write sockets by hand next time!
Action Items:
- Give a Star to the template repository above
- Copy the code in this article to run the first GET
- Leave a comment telling me what feature you most want to implement with libcurl (web scraping? cloud API? IoT?)