- 环境准备: 如何安装
libcurl开发包。 - 核心概念: 了解
libcurl的基本工作流程和关键结构体。 - 完整示例: 从一个简单的 "Hello World" (获取网页内容) 开始,逐步讲解代码。
- 进阶示例: 如何处理 POST 请求、上传文件、添加 HTTP 头部等。
- 编译与运行: 如何编译链接你的 C 程序。
- 错误处理与资源释放: 强调编写健壮代码的重要性。
- 总结与最佳实践。
环境准备
在开始编码之前,你需要在你的 Linux 系统上安装 libcurl 的开发头文件和库文件。

(图片来源网络,侵删)
对于基于 Debian/Ubuntu 的系统:
sudo apt-get update sudo apt-get install libcurl4-openssl-dev
或者,如果你使用较新的版本,可能是 libcurl4-dev:
sudo apt-get install libcurl4-dev
对于基于 Red Hat/CentOS/Fedora 的系统:
sudo yum install libcurl-devel
或者对于 dnf:

(图片来源网络,侵删)
sudo dnf install libcurl-devel
核心概念
libcurl 的使用遵循一个固定的模式:
#include <curl/curl.h>: 包含必要的头文件。- *`CURL handle = curl_easy_init();
**: 初始化一个CURL` 句柄,这个句柄将贯穿整个请求的生命周期。 curl_easy_setopt(handle, CURLoption option, ...);: 设置选项,这是libcurl最核心的部分,你通过这个函数告诉libcurl你想做什么,比如请求的 URL、回调函数、超时时间等。CURLcode res = curl_easy_perform(handle);: 执行请求,这个函数会阻塞,直到请求完成或失败。curl_easy_cleanup(handle);: 清理句柄,释放所有相关的资源。- 回调函数: 对于需要获取数据(如响应体)或发送数据(如 POST 请求体)的场景,你需要提供一个回调函数。
libcurl会在适当的时候调用这个函数。
关键结构体和函数:
CURL *: 一个句柄指针,代表一个独立的传输会话。curl_easy_init(): 初始化CURL句柄。curl_easy_setopt(): 设置CURL句柄的选项。curl_easy_perform(): 执行预定义的请求。curl_easy_cleanup(): 清理CURL句柄。curl_easy_getinfo(): 在请求完成后,获取一些信息,如响应码、总耗时等。curl_global_init() / curl_global_cleanup(): (可选但推荐) 全局初始化和清理,应该在程序开始时调用init,在结束时调用cleanup。
完整示例 1: 获取网页内容 (GET 请求)
这是最经典的入门示例,我们将从 httpbin.org 获取一个简单的文本响应。
get_example.c
#include <stdio.h>
#include <curl/curl.h>
// 这是一个回调函数,libcurl会在接收到数据时调用它
// 参数说明:
// - contents: libcurl接收到的数据块
// - size: 每个数据块的大小 (通常为1)
// - nmemb: 数据块的数量
// - userp: 用户自定义指针 (我们在CURLOPT_WRITEFUNCTION中设置)
// 返回值: 本次回调函数处理的总字节数,如果返回的值和(size * nmemb)不一致,libcurl会中止传输。
static size_t
WriteCallback(void *contents, size_t size, size_t nmemb, void *userp)
{
(void)userp; // 避免未使用参数的警告
// 计算本次接收到的数据大小
size_t realsize = size * nmemb;
// 将数据直接打印到标准输出
printf("%.*s", (int)realsize, (char*)contents);
return realsize;
}
int main(void)
{
CURL *curl;
CURLcode res;
// 1. 全局初始化 (推荐)
curl_global_init(CURL_GLOBAL_ALL);
// 2. 初始化一个curl句柄
curl = curl_easy_init();
if(curl) {
// 3. 设置curl选项
// 设置要请求的URL
curl_easy_setopt(curl, CURLOPT_URL, "http://httpbin.org/get");
// 设置我们的WriteCallback函数作为数据接收的回调
curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, WriteCallback);
// 可以设置一个用户指针,在回调函数中可以通过userp参数访问
// curl_easy_setopt(curl, CURLOPT_WRITEDATA, &some_struct);
// 设置一个User-Agent,有些网站会拒绝没有UA的请求
curl_easy_setopt(curl, CURLOPT_USERAGENT, "libcurl-agent/1.0");
// 4. 执行请求
res = curl_easy_perform(curl);
// 检查执行结果
if(res != CURLE_OK) {
fprintf(stderr, "curl_easy_perform() failed: %s\n",
curl_easy_strerror(res));
} else {
// 获取并打印响应码
long response_code;
curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &response_code);
printf("\n\nHTTP Response Code: %ld\n", response_code);
}
// 5. 清理curl句柄
curl_easy_cleanup(curl);
}
// 6. 全局清理
curl_global_cleanup();
return 0;
}
编译与运行
假设你的文件名为 get_example.c,使用以下命令进行编译:

(图片来源网络,侵删)
-
基础编译:
gcc get_example.c -o get_example
这个命令可能会报错,因为链接器找不到
libcurl库。 -
正确编译 (推荐): 你需要告诉编译器
libcurl的头文件位置和库文件位置。pkg-config工具可以自动帮你完成这些。gcc $(pkg-config --cflags --libs libcurl) get_example.c -o get_example
pkg-config --cflags libcurl: 输出编译时需要的头文件路径。pkg-config --libs libcurl: 输出链接时需要的库名和路径。
运行编译后的程序:
./get_example
预期输出:
你会看到 httpbin.org 返回的 JSON 格式的数据,以及最后的 HTTP 响应码 200。
进阶示例
1 发送 POST 请求
发送 POST 请求需要设置 CURLOPT_POSTFIELDS 选项。
post_example.c
#include <stdio.h>
#include <string.h>
#include <curl/curl.h>
static size_t WriteCallback(void *contents, size_t size, size_t nmemb, void *userp) {
(void)userp;
printf("%.*s", (int)(size * nmemb), (char*)contents);
return size * nmemb;
}
int main(void)
{
CURL *curl;
CURLcode res;
curl_global_init(CURL_GLOBAL_ALL);
curl = curl_easy_init();
if(curl) {
// 要POST的数据
char *post_data = "name=david&project=libcurl";
curl_easy_setopt(curl, CURLOPT_URL, "http://httpbin.org/post");
curl_easy_setopt(curl, CURLOPT_POSTFIELDS, post_data); // 设置POST数据
curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, WriteCallback);
// 可选:设置Content-Type头
struct curl_slist *headers = NULL;
headers = curl_slist_append(headers, "Content-Type: application/x-www-form-urlencoded");
curl_easy_setopt(curl, CURLOPT_HTTPHEADER, headers);
res = curl_easy_perform(curl);
if(res != CURLE_OK) {
fprintf(stderr, "curl_easy_perform() failed: %s\n", curl_easy_strerror(res));
}
// 清理自定义的headers
curl_slist_free_all(headers);
curl_easy_cleanup(curl);
}
curl_global_cleanup();
return 0;
}
2 上传文件
上传文件(模拟一个 multipart/form-data 上传)需要使用 curl_mime API。
upload_example.c
#include <stdio.h>
#include <curl/curl.h>
int main(void)
{
CURL *curl;
CURLcode res;
curl_global_init(CURL_GLOBAL_ALL);
curl = curl_easy_init();
if(curl) {
// 创建一个MIME结构
curl_mime *mime = curl_mime_init(curl);
curl_mimepart *part = curl_mime_addpart(mime);
// 设置表单字段的名称
curl_mime_name(part, "file");
// 设置要上传的文件
curl_mime_filedata(part, "your_file_to_upload.txt"); // <--- 替换为你的文件名
// 设置文件的MIME类型 (可选)
curl_mime_type(part, "text/plain");
curl_easy_setopt(curl, CURLOPT_URL, "http://httpbin.org/post");
// 将MIME结构设置为表单数据
curl_easy_setopt(curl, CURLOPT_MIMEPOST, mime);
// 如果不需要打印响应体,可以不设置WRITEFUNCTION
// curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, WriteCallback);
res = curl_easy_perform(curl);
if(res != CURLE_OK) {
fprintf(stderr, "curl_easy_perform() failed: %s\n", curl_easy_strerror(res));
}
// 清理MIME结构
curl_mime_free(mime);
curl_easy_cleanup(curl);
}
curl_global_cleanup();
return 0;
}
错误处理与资源释放
这是极其重要的一点,一个健壮的程序必须正确处理错误并释放所有分配的资源。
- 错误处理:
curl_easy_perform()返回CURLcode类型,你需要检查它是否为CURLE_OK,如果不是,可以使用curl_easy_strerror(res)将错误码转换为可读的字符串。 - 资源释放:
curl_easy_cleanup(): 必须为每一个成功init的句柄调用它,它会释放句柄本身以及所有通过setopt与之关联的资源(除了CURLOPT_HTTPHEADER创建的列表)。curl_slist_free_all(): 如果你使用了curl_slist_append创建了 HTTP 头列表,必须使用此函数手动释放。curl_mime_free(): 如果你使用了curl_mimeAPI,必须使用此函数释放。curl_global_cleanup(): 在程序结束前调用,释放libcurl的全局资源。
最佳实践: 使用 goto 进行集中式资源清理,虽然 goto 通常不推荐,但在 C 语言的资源清理场景下,它可以非常有效地避免代码重复和遗漏。
int main(void) {
CURL *curl = NULL;
CURLcode res;
struct curl_slist *headers = NULL;
int ret = 1; // 默认返回错误码
curl_global_init(CURL_GLOBAL_ALL);
curl = curl_easy_init();
if (!curl) goto cleanup;
headers = curl_slist_append(headers, "X-Custom-Header: value");
if (!headers) goto cleanup;
curl_easy_setopt(curl, CURLOPT_URL, "http://example.com");
curl_easy_setopt(curl, CURLOPT_HTTPHEADER, headers);
// ... 其他设置 ...
res = curl_easy_perform(curl);
if (res != CURLE_OK) {
fprintf(stderr, "Error: %s\n", curl_easy_strerror(res));
goto cleanup;
}
printf("Request successful!\n");
ret = 0; // 成功
cleanup:
if (headers) curl_slist_free_all(headers);
if (curl) curl_easy_cleanup(curl);
curl_global_cleanup();
return ret;
}
总结与最佳实践
- 遵循基本流程:
init->setopt->perform->getinfo->cleanup。 - 始终检查返回值:
curl_easy_init,curl_easy_perform,curl_slist_append等函数都可能失败,必须检查它们的返回值。 - 回调函数是核心: 数据的接收和发送都通过回调函数完成,务必理解其工作原理和返回值的意义。
- 使用
pkg-config编译: 这是链接libcurl等库最标准、最可靠的方式。 - 全局初始化/清理: 对于简单的程序,
curl_global_init和curl_global_cleanup可以省略,libcurl会自动处理,但对于复杂程序(如库、多线程程序),强烈建议手动调用,以避免潜在的资源竞争问题。 - 查阅官方文档:
libcurl功能非常强大,本教程只是冰山一角,遇到问题时,官方文档 是最好的朋友,特别是它的 "Examples" 部分,提供了大量高质量的代码示例。
