Skip to content

libcurl — HTTP 客户端

libcurl 是最广泛使用的 C/C++ HTTP 客户端库,支持 HTTP/HTTPS/FTP/SMTP 等 30+ 协议,稳定可靠,是生产级 HTTP 请求的首选。

安装

bash
# Ubuntu
sudo apt install libcurl4-openssl-dev

# vcpkg
vcpkg install curl

# CMake
find_package(CURL REQUIRED)
target_link_libraries(myapp PRIVATE CURL::libcurl)

C++ RAII 封装

cpp
#include <curl/curl.h>
#include <string>
#include <stdexcept>

// 写回调:将响应数据写入 string
static size_t write_callback(char* ptr, size_t size, size_t nmemb, void* userdata) {
    auto* buf = static_cast<std::string*>(userdata);
    buf->append(ptr, size * nmemb);
    return size * nmemb;
}

class CurlHandle {
    CURL* curl_ = nullptr;
public:
    CurlHandle() {
        curl_ = curl_easy_init();
        if (!curl_) throw std::runtime_error("curl_easy_init 失败");
    }
    ~CurlHandle() { if (curl_) curl_easy_cleanup(curl_); }
    CURL* get() { return curl_; }

    CurlHandle(const CurlHandle&) = delete;
    CurlHandle& operator=(const CurlHandle&) = delete;
};

struct Response {
    int status_code;
    std::string body;
    std::string headers;
};

Response http_get(const std::string& url,
                  const std::vector<std::string>& headers = {}) {
    CurlHandle curl;
    Response resp;

    curl_easy_setopt(curl.get(), CURLOPT_URL, url.c_str());
    curl_easy_setopt(curl.get(), CURLOPT_WRITEFUNCTION, write_callback);
    curl_easy_setopt(curl.get(), CURLOPT_WRITEDATA, &resp.body);
    curl_easy_setopt(curl.get(), CURLOPT_HEADERDATA, &resp.headers);
    curl_easy_setopt(curl.get(), CURLOPT_TIMEOUT, 30L);
    curl_easy_setopt(curl.get(), CURLOPT_FOLLOWLOCATION, 1L);
    curl_easy_setopt(curl.get(), CURLOPT_SSL_VERIFYPEER, 1L);

    // 自定义请求头
    struct curl_slist* header_list = nullptr;
    for (const auto& h : headers) {
        header_list = curl_slist_append(header_list, h.c_str());
    }
    if (header_list) {
        curl_easy_setopt(curl.get(), CURLOPT_HTTPHEADER, header_list);
    }

    CURLcode rc = curl_easy_perform(curl.get());
    if (header_list) curl_slist_free_all(header_list);

    if (rc != CURLE_OK) {
        throw std::runtime_error(curl_easy_strerror(rc));
    }

    curl_easy_getinfo(curl.get(), CURLINFO_RESPONSE_CODE, &resp.status_code);
    return resp;
}

GET / POST / PUT / DELETE

cpp
// GET
auto resp = http_get("https://api.example.com/users",
                     {"Authorization: Bearer token123",
                      "Accept: application/json"});
std::cout << resp.status_code << "\n";
std::cout << resp.body << "\n";

// POST JSON
Response http_post(const std::string& url, const std::string& json_body) {
    CurlHandle curl;
    Response resp;

    curl_easy_setopt(curl.get(), CURLOPT_URL, url.c_str());
    curl_easy_setopt(curl.get(), CURLOPT_POST, 1L);
    curl_easy_setopt(curl.get(), CURLOPT_POSTFIELDS, json_body.c_str());
    curl_easy_setopt(curl.get(), CURLOPT_POSTFIELDSIZE, json_body.size());
    curl_easy_setopt(curl.get(), CURLOPT_WRITEFUNCTION, write_callback);
    curl_easy_setopt(curl.get(), CURLOPT_WRITEDATA, &resp.body);

    struct curl_slist* headers = nullptr;
    headers = curl_slist_append(headers, "Content-Type: application/json");
    curl_easy_setopt(curl.get(), CURLOPT_HTTPHEADER, headers);

    CURLcode rc = curl_easy_perform(curl.get());
    curl_slist_free_all(headers);

    if (rc != CURLE_OK) throw std::runtime_error(curl_easy_strerror(rc));
    curl_easy_getinfo(curl.get(), CURLINFO_RESPONSE_CODE, &resp.status_code);
    return resp;
}

// 使用
nlohmann::json body = {{"name", "Alice"}, {"age", 30}};
auto r = http_post("https://api.example.com/users", body.dump());

文件上传与下载

cpp
// 文件下载(带进度)
static int progress_callback(void* clientp, curl_off_t dltotal,
                              curl_off_t dlnow, curl_off_t, curl_off_t) {
    if (dltotal > 0) {
        printf("\r下载进度: %.1f%%", 100.0 * dlnow / dltotal);
        fflush(stdout);
    }
    return 0;
}

void download_file(const std::string& url, const std::string& path) {
    CurlHandle curl;
    FILE* fp = fopen(path.c_str(), "wb");

    curl_easy_setopt(curl.get(), CURLOPT_URL, url.c_str());
    curl_easy_setopt(curl.get(), CURLOPT_WRITEFUNCTION, nullptr);  // 默认写到 FILE*
    curl_easy_setopt(curl.get(), CURLOPT_WRITEDATA, fp);
    curl_easy_setopt(curl.get(), CURLOPT_XFERINFOFUNCTION, progress_callback);
    curl_easy_setopt(curl.get(), CURLOPT_NOPROGRESS, 0L);
    curl_easy_setopt(curl.get(), CURLOPT_FOLLOWLOCATION, 1L);

    curl_easy_perform(curl.get());
    fclose(fp);
    printf("\n下载完成\n");
}

// multipart 文件上传
void upload_file(const std::string& url, const std::string& file_path) {
    CurlHandle curl;
    Response resp;

    curl_mime* form = curl_mime_init(curl.get());
    curl_mimepart* field = curl_mime_addpart(form);
    curl_mime_name(field, "file");
    curl_mime_filedata(field, file_path.c_str());

    curl_mimepart* field2 = curl_mime_addpart(form);
    curl_mime_name(field2, "description");
    curl_mime_data(field2, "My file", CURL_ZERO_TERMINATED);

    curl_easy_setopt(curl.get(), CURLOPT_URL, url.c_str());
    curl_easy_setopt(curl.get(), CURLOPT_MIMEPOST, form);
    curl_easy_setopt(curl.get(), CURLOPT_WRITEFUNCTION, write_callback);
    curl_easy_setopt(curl.get(), CURLOPT_WRITEDATA, &resp.body);

    curl_easy_perform(curl.get());
    curl_mime_free(form);
}

连接池(multi handle)

cpp
// curl_multi:并发多个请求
CURLM* multi = curl_multi_init();

// 添加多个请求
std::vector<std::pair<CURL*, std::string>> handles;
for (const auto& url : urls) {
    CURL* easy = curl_easy_init();
    auto* buf = new std::string();
    curl_easy_setopt(easy, CURLOPT_URL, url.c_str());
    curl_easy_setopt(easy, CURLOPT_WRITEFUNCTION, write_callback);
    curl_easy_setopt(easy, CURLOPT_WRITEDATA, buf);
    curl_multi_add_handle(multi, easy);
    handles.push_back({easy, ""});
}

// 并发执行
int running = 0;
do {
    curl_multi_perform(multi, &running);
    curl_multi_wait(multi, nullptr, 0, 1000, nullptr);
} while (running > 0);

// 清理
for (auto& [easy, _] : handles) {
    curl_multi_remove_handle(multi, easy);
    curl_easy_cleanup(easy);
}
curl_multi_cleanup(multi);

关键认知

libcurl 是最稳定可靠的 HTTP 客户端,支持所有主流协议和认证方式。用 RAII 包装 CURL* 句柄避免资源泄漏。并发请求用 curl_multi,比多线程更高效。生产代码必须设置超时(CURLOPT_TIMEOUT)和 SSL 验证(CURLOPT_SSL_VERIFYPEER)。

系统学习 C++ 生态,深入底层架构