Linux C语言如何使用curl?

99ANYc3cd6
预计阅读时长 31 分钟
位置: 首页 C语言 正文
  1. 环境准备: 如何安装 libcurl 开发包。
  2. 核心概念: 了解 libcurl 的基本工作流程和关键结构体。
  3. 完整示例: 从一个简单的 "Hello World" (获取网页内容) 开始,逐步讲解代码。
  4. 进阶示例: 如何处理 POST 请求、上传文件、添加 HTTP 头部等。
  5. 编译与运行: 如何编译链接你的 C 程序。
  6. 错误处理与资源释放: 强调编写健壮代码的重要性。
  7. 总结与最佳实践

环境准备

在开始编码之前,你需要在你的 Linux 系统上安装 libcurl 的开发头文件和库文件。

linux c语言 curl
(图片来源网络,侵删)

对于基于 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:

linux c语言 curl
(图片来源网络,侵删)
sudo dnf install libcurl-devel

核心概念

libcurl 的使用遵循一个固定的模式:

  1. #include <curl/curl.h>: 包含必要的头文件。
  2. *`CURL handle = curl_easy_init();**: 初始化一个CURL` 句柄,这个句柄将贯穿整个请求的生命周期。
  3. curl_easy_setopt(handle, CURLoption option, ...);: 设置选项,这是 libcurl 最核心的部分,你通过这个函数告诉 libcurl 你想做什么,比如请求的 URL、回调函数、超时时间等。
  4. CURLcode res = curl_easy_perform(handle);: 执行请求,这个函数会阻塞,直到请求完成或失败。
  5. curl_easy_cleanup(handle);: 清理句柄,释放所有相关的资源。
  6. 回调函数: 对于需要获取数据(如响应体)或发送数据(如 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,使用以下命令进行编译:

linux c语言 curl
(图片来源网络,侵删)
  • 基础编译:

    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_mime API,必须使用此函数释放。
    • 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;
}

总结与最佳实践

  1. 遵循基本流程: init -> setopt -> perform -> getinfo -> cleanup
  2. 始终检查返回值: curl_easy_init, curl_easy_perform, curl_slist_append 等函数都可能失败,必须检查它们的返回值。
  3. 回调函数是核心: 数据的接收和发送都通过回调函数完成,务必理解其工作原理和返回值的意义。
  4. 使用 pkg-config 编译: 这是链接 libcurl 等库最标准、最可靠的方式。
  5. 全局初始化/清理: 对于简单的程序,curl_global_initcurl_global_cleanup 可以省略,libcurl 会自动处理,但对于复杂程序(如库、多线程程序),强烈建议手动调用,以避免潜在的资源竞争问题。
  6. 查阅官方文档: libcurl 功能非常强大,本教程只是冰山一角,遇到问题时,官方文档 是最好的朋友,特别是它的 "Examples" 部分,提供了大量高质量的代码示例。
-- 展开阅读全文 --
头像
fgets与gets有何区别?
« 上一篇 02-08
织梦5.7如何禁用手机自动跳转功能?
下一篇 » 02-08

相关文章

取消
微信二维码
支付宝二维码

目录[+]