简介
libcurl是一个功能强大、使用广泛的开源网络传输库,支持多种协议,包括HTTP、HTTPS、FTP、FTPS、SCP、SFTP、TFTP、TELNET、DICT、FILE和LDAP等。作为一个客户端URL传输库,libcurl提供了简单易用的API,使开发者能够轻松地在自己的应用程序中集成网络功能。
libcurl的主要特点:
- 多协议支持:一个库支持多种网络协议
- 可移植性:支持几乎所有操作系统平台
- 线程安全:多线程环境下可靠工作
- 高性能:异步支持和并行传输
- 成熟稳定:经过多年发展和广泛应用的验证
- SSL支持:提供安全传输功能
- 丰富的API:提供简单和高级的接口满足不同需求
libcurl的基本概念
在深入学习libcurl之前,我们需要了解一些核心概念:
1. Handle(句柄)
libcurl的API是基于句柄的,主要有以下几种:
- Easy句柄:用于单个传输操作
- Multi句柄:用于管理多个同时进行的传输
- Share句柄:用于在多个Easy句柄之间共享数据
2. 接口类型
libcurl提供了两种主要的接口类型:
- Easy接口:简单、同步、阻塞式的API,适合单个传输
- Multi接口:复杂但更灵活的API,支持同时进行多个非阻塞传输
3. 回调函数
libcurl使用回调机制处理数据传输:
- 写入回调:接收从服务器下载的数据
- 读取回调:提供上传到服务器的数据
- 进度回调:报告传输进度
- 头部回调:处理接收到的HTTP头信息
环境搭建
Windows环境
-
下载预编译的库:
访问 curl官网下载页面,下载Windows版本的开发包。
-
使用vcpkg安装:
vcpkg install curl
-
配置Visual Studio:
- 添加包含目录:
$(CURL_ROOT)\include
- 添加库目录:
$(CURL_ROOT)\lib
- 链接库:
libcurl.lib
(或libcurl_d.lib
用于调试版本)
- 添加包含目录:
Linux环境
使用包管理器安装开发包:
# Debian/Ubuntu
sudo apt-get install libcurl4-openssl-dev
# CentOS/RHEL
sudo yum install libcurl-devel
# Arch Linux
sudo pacman -S curl
macOS环境
使用Homebrew安装:
brew install curl
从源码编译
-
下载源码:
git clone https://github.com/curl/curl.git cd curl
-
配置和编译:
./buildconf ./configure --with-ssl make sudo make install
基本使用方法
Easy接口
Easy接口是libcurl最简单的使用方式,适合单个URL的传输操作。下面是一个基本的HTTP GET请求示例:
#include <stdio.h>
#include <curl/curl.h>
// 回调函数,处理接收到的数据
size_t write_callback(void *ptr, size_t size, size_t nmemb, void *userdata) {
size_t real_size = size * nmemb;
printf("%.*s", (int)real_size, (char*)ptr);
return real_size;
}
int main(void) {
CURL *curl;
CURLcode res;
// 初始化libcurl
curl_global_init(CURL_GLOBAL_DEFAULT);
// 创建一个easy句柄
curl = curl_easy_init();
if(curl) {
// 设置URL
curl_easy_setopt(curl, CURLOPT_URL, "https://example.com");
// 设置回调函数
curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, write_callback);
// 执行请求
res = curl_easy_perform(curl);
// 检查错误
if(res != CURLE_OK)
fprintf(stderr, "curl_easy_perform() failed: %s\n", curl_easy_strerror(res));
// 清理句柄
curl_easy_cleanup(curl);
}
// 全局清理
curl_global_cleanup();
return 0;
}
编译命令:
gcc -o http_get http_get.c -lcurl
发送HTTP POST请求
#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) {
// POST数据
const char *postdata = "name=小明&age=25";
curl_easy_setopt(curl, CURLOPT_URL, "https://postman-echo.com/post");
curl_easy_setopt(curl, CURLOPT_POSTFIELDS, postdata);
// 执行请求
res = curl_easy_perform(curl);
if(res != CURLE_OK)
fprintf(stderr, "curl_easy_perform() failed: %s\n", curl_easy_strerror(res));
curl_easy_cleanup(curl);
}
curl_global_cleanup();
return 0;
}
Multi接口
Multi接口允许在单线程中同时执行多个传输操作,非常适合需要并行请求的场景:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <curl/curl.h>
// 用于存储每个传输的数据
struct transfer_data {
char *memory;
size_t size;
};
// 写入回调
static size_t write_callback(void *contents, size_t size, size_t nmemb, void *userp) {
size_t realsize = size * nmemb;
struct transfer_data *mem = (struct transfer_data *)userp;
char *ptr = realloc(mem->memory, mem->size + realsize + 1);
if(ptr == NULL) return 0; // 内存不足
mem->memory = ptr;
memcpy(&(mem->memory[mem->size]), contents, realsize);
mem->size += realsize;
mem->memory[mem->size] = 0;
return realsize;
}
int main(void) {
CURL *handles[2];
CURLM *multi_handle;
CURLcode res;
CURLMcode mres;
int still_running = 1;
struct transfer_data data[2] = {{NULL, 0}, {NULL, 0}};
// 初始化
curl_global_init(CURL_GLOBAL_DEFAULT);
// 准备两个easy句柄
handles[0] = curl_easy_init();
handles[1] = curl_easy_init();
// 初始化数据缓冲区
data[0].memory = malloc(1);
data[0].size = 0;
data[1].memory = malloc(1);
data[1].size = 0;
// 设置第一个easy句柄
curl_easy_setopt(handles[0], CURLOPT_URL, "https://example.com/");
curl_easy_setopt(handles[0], CURLOPT_WRITEFUNCTION, write_callback);
curl_easy_setopt(handles[0], CURLOPT_WRITEDATA, (void *)&data[0]);
// 设置第二个easy句柄
curl_easy_setopt(handles[1], CURLOPT_URL, "https://example.org/");
curl_easy_setopt(handles[1], CURLOPT_WRITEFUNCTION, write_callback);
curl_easy_setopt(handles[1], CURLOPT_WRITEDATA, (void *)&data[1]);
// 创建multi句柄并添加easy句柄
multi_handle = curl_multi_init();
curl_multi_add_handle(multi_handle, handles[0]);
curl_multi_add_handle(multi_handle, handles[1]);
// 执行传输
do {
int numfds;
mres = curl_multi_perform(multi_handle, &still_running);
if(still_running) {
/* 等待活动,超时或"错误" */
mres = curl_multi_wait(multi_handle, NULL, 0, 1000, &numfds);
}
if(mres != CURLM_OK) {
fprintf(stderr, "curl_multi_wait() failed, code %d.\n", mres);
break;
}
} while(still_running);
// 输出结果
printf("First URL content size: %zu bytes\n", data[0].size);
printf("Second URL content size: %zu bytes\n", data[1].size);
// 清理
curl_multi_remove_handle(multi_handle, handles[0]);
curl_multi_remove_handle(multi_handle, handles[1]);
curl_multi_cleanup(multi_handle);
curl_easy_cleanup(handles[0]);
curl_easy_cleanup(handles[1]);
free(data[0].memory);
free(data[1].memory);
curl_global_cleanup();
return 0;
}
常见应用场景
1. REST API调用
使用libcurl调用REST API是非常常见的应用场景。以下是一个调用JSON API的例子:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <curl/curl.h>
struct memory {
char *response;
size_t size;
};
static size_t cb(void *data, size_t size, size_t nmemb, void *clientp) {
size_t realsize = size * nmemb;
struct memory *mem = (struct memory *)clientp;
char *ptr = realloc(mem->response, mem->size + realsize + 1);
if(ptr == NULL) return 0;
mem->response = ptr;
memcpy(&(mem->response[mem->size]), data, realsize);
mem->size += realsize;
mem->response[mem->size] = 0;
return realsize;
}
int main(void) {
CURL *curl;
CURLcode res;
struct memory chunk = {0};
chunk.response = malloc(1);
chunk.size = 0;
curl_global_init(CURL_GLOBAL_ALL);
curl = curl_easy_init();
if(curl) {
struct curl_slist *headers = NULL;
headers = curl_slist_append(headers, "Accept: application/json");
headers = curl_slist_append(headers, "Content-Type: application/json");
headers = curl_slist_append(headers, "charsets: utf-8");
curl_easy_setopt(curl, CURLOPT_URL, "https://api.example.com/users");
curl_easy_setopt(curl, CURLOPT_HTTPHEADER, headers);
curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, cb);
curl_easy_setopt(curl, CURLOPT_WRITEDATA, (void *)&chunk);
res = curl_easy_perform(curl);
if(res != CURLE_OK) {
fprintf(stderr, "curl_easy_perform() failed: %s\n", curl_easy_strerror(res));
} else {
printf("API Response:\n%s\n", chunk.response);
}
curl_slist_free_all(headers);
curl_easy_cleanup(curl);
}
free(chunk.response);
curl_global_cleanup();
return 0;
}
2. 文件下载
使用libcurl下载文件到本地:
#include <stdio.h>
#include <curl/curl.h>
static size_t write_data(void *ptr, size_t size, size_t nmemb, void *stream) {
size_t written = fwrite(ptr, size, nmemb, (FILE *)stream);
return written;
}
int main(void) {
CURL *curl;
FILE *fp;
CURLcode res;
const char *url = "http://example.com/file.zip";
const char *outfilename = "downloaded_file.zip";
curl = curl_easy_init();
if (curl) {
fp = fopen(outfilename, "wb");
curl_easy_setopt(curl, CURLOPT_URL, url);
curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, write_data);
curl_easy_setopt(curl, CURLOPT_WRITEDATA, fp);
curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, 1L);
curl_easy_setopt(curl, CURLOPT_VERBOSE, 1L);
// 添加进度显示
curl_easy_setopt(curl, CURLOPT_NOPROGRESS, 0L);
res = curl_easy_perform(curl);
curl_easy_cleanup(curl);
fclose(fp);
if(res != CURLE_OK)
fprintf(stderr, "下载失败: %s\n", curl_easy_strerror(res));
else
printf("文件已下载到 %s\n", outfilename);
}
return 0;
}
3. HTTPS安全连接
以下是一个使用HTTPS协议并验证证书的例子:
#include <stdio.h>
#include <curl/curl.h>
int main(void) {
CURL *curl;
CURLcode res;
curl_global_init(CURL_GLOBAL_DEFAULT);
curl = curl_easy_init();
if(curl) {
curl_easy_setopt(curl, CURLOPT_URL, "https://example.com");
/* 设置CA证书路径 */
curl_easy_setopt(curl, CURLOPT_CAINFO, "/path/to/ca-bundle.crt");
/* 验证对方证书 */
curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, 1L);
curl_easy_setopt(curl, CURLOPT_SSL_VERIFYHOST, 2L);
res = curl_easy_perform(curl);
if(res != CURLE_OK)
fprintf(stderr, "curl_easy_perform() failed: %s\n", curl_easy_strerror(res));
curl_easy_cleanup(curl);
}
curl_global_cleanup();
return 0;
}
跨平台编译指南
为ESP32交叉编译
ESP32是一种流行的IoT开发板,我们可以为其编译libcurl以实现网络功能。以下是具体步骤:
1. 安装ESP-IDF
ESP-IDF是乐鑫官方的ESP32开发框架,首先需要安装它:
git clone --recursive https://github.com/espressif/esp-idf.git
cd esp-idf
./install.sh
. ./export.sh
2. 准备libcurl源码
获取并准备libcurl源码:
git clone https://github.com/curl/curl.git
cd curl
3. 配置交叉编译环境
创建一个配置脚本esp32_configure.sh
:
#!/bin/bash
export IDF_PATH=/path/to/esp-idf
export PATH=$IDF_PATH/tools:$PATH
./configure \
--host=xtensa-esp32-elf \
--prefix=$PWD/install-esp32 \
--disable-shared \
--enable-static \
--disable-dependency-tracking \
--disable-manual \
--disable-ntlm-wb \
--disable-ldap \
--disable-ldaps \
--disable-rtsp \
--disable-dict \
--disable-telnet \
--disable-tftp \
--disable-pop3 \
--disable-imap \
--disable-smtp \
--disable-gopher \
--disable-smb \
--without-zlib \
--without-ssl \
--without-brotli \
--without-libidn2 \
--without-librtmp \
--without-libpsl \
--disable-ipv6 \
--disable-unix-sockets \
--disable-verbose \
--disable-alt-svc
给脚本添加执行权限并运行:
chmod +x esp32_configure.sh
./esp32_configure.sh
4. 编译和安装
make
make install
5. 在ESP32项目中使用
创建一个组件目录结构:
my-esp32-project/
├── components/
│ └── libcurl/
│ ├── include/
│ │ └── curl/
│ │ └── *.h (从libcurl安装目录复制)
│ ├── CMakeLists.txt
│ └── libcurl.a (从libcurl安装目录复制)
└── main/
├── CMakeLists.txt
└── main.c
components/libcurl/CMakeLists.txt
内容:
idf_component_register(
INCLUDE_DIRS "include"
PRIV_INCLUDE_DIRS ""
PRIV_REQUIRES "lwip"
REQUIRES ""
LDFRAGMENTS "libcurl.lf"
)
add_prebuilt_library(libcurl "libcurl.a" REQUIRES lwip)
创建components/libcurl/libcurl.lf
文件:
[mapping:libcurl]
archive: libcurl.a
entries:
* (noflash)
在main/main.c
中使用libcurl:
#include "esp_system.h"
#include "esp_wifi.h"
#include "esp_event.h"
#include "nvs_flash.h"
#include "curl/curl.h"
static const char *TAG = "CURL_EXAMPLE";
// ... Wi-Fi连接代码 ...
void app_main(void) {
// 初始化NVS
esp_err_t ret = nvs_flash_init();
if (ret == ESP_ERR_NVS_NO_FREE_PAGES || ret == ESP_ERR_NVS_NEW_VERSION_FOUND) {
ESP_ERROR_CHECK(nvs_flash_erase());
ret = nvs_flash_init();
}
ESP_ERROR_CHECK(ret);
// 连接Wi-Fi
wifi_init_sta();
// 使用libcurl
CURL *curl;
CURLcode res;
curl_global_init(CURL_GLOBAL_DEFAULT);
curl = curl_easy_init();
if(curl) {
curl_easy_setopt(curl, CURLOPT_URL, "http://example.com");
curl_easy_setopt(curl, CURLOPT_VERBOSE, 1L);
res = curl_easy_perform(curl);
if(res != CURLE_OK)
ESP_LOGE(TAG, "curl_easy_perform() failed: %s", curl_easy_strerror(res));
curl_easy_cleanup(curl);
}
curl_global_cleanup();
}
为其他嵌入式平台交叉编译
交叉编译libcurl的一般步骤如下:
- 安装交叉编译工具链
- 准备libcurl源码
- 配置编译选项
- 编译和安装
- 集成到目标项目
以下是为ARM平台交叉编译的示例:
export CROSS_COMPILE=arm-linux-gnueabihf-
export CC=${CROSS_COMPILE}gcc
export LD=${CROSS_COMPILE}ld
export AS=${CROSS_COMPILE}as
export AR=${CROSS_COMPILE}ar
./configure \
--host=arm-linux-gnueabihf \
--prefix=/path/to/install \
--disable-shared \
--enable-static
make
make install
高级功能与技巧
1. 使用Cookie
#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) {
// 设置Cookie
curl_easy_setopt(curl, CURLOPT_URL, "http://example.com");
curl_easy_setopt(curl, CURLOPT_COOKIE, "name=value; name2=value2");
// 或者从文件加载Cookie
curl_easy_setopt(curl, CURLOPT_COOKIEFILE, "cookies.txt");
// 保存接收到的Cookie
curl_easy_setopt(curl, CURLOPT_COOKIEJAR, "received_cookies.txt");
res = curl_easy_perform(curl);
if(res != CURLE_OK)
fprintf(stderr, "curl_easy_perform() failed: %s\n", curl_easy_strerror(res));
curl_easy_cleanup(curl);
}
curl_global_cleanup();
return 0;
}
2. 设置HTTP代理
#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) {
curl_easy_setopt(curl, CURLOPT_URL, "http://example.com");
// 设置HTTP代理
curl_easy_setopt(curl, CURLOPT_PROXY, "http://proxy.example.com:8080");
// 如果代理需要认证
curl_easy_setopt(curl, CURLOPT_PROXYUSERNAME, "user");
curl_easy_setopt(curl, CURLOPT_PROXYPASSWORD, "password");
res = curl_easy_perform(curl);
if(res != CURLE_OK)
fprintf(stderr, "curl_easy_perform() failed: %s\n", curl_easy_strerror(res));
curl_easy_cleanup(curl);
}
curl_global_cleanup();
return 0;
}
3. 上传文件
#include <stdio.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <curl/curl.h>
int main(void) {
CURL *curl;
CURLcode res;
struct stat file_info;
FILE *fd;
fd = fopen("localfile.bin", "rb");
if(!fd)
return 1;
if(fstat(fileno(fd), &file_info) != 0)
return 1;
curl_global_init(CURL_GLOBAL_ALL);
curl = curl_easy_init();
if(curl) {
curl_easy_setopt(curl, CURLOPT_URL, "ftp://example.com/upload/file.bin");
curl_easy_setopt(curl, CURLOPT_UPLOAD, 1L);
curl_easy_setopt(curl, CURLOPT_READDATA, fd);
curl_easy_setopt(curl, CURLOPT_INFILESIZE_LARGE, (curl_off_t)file_info.st_size);
res = curl_easy_perform(curl);
if(res != CURLE_OK)
fprintf(stderr, "curl_easy_perform() failed: %s\n", curl_easy_strerror(res));
curl_easy_cleanup(curl);
}
fclose(fd);
curl_global_cleanup();
return 0;
}
4. WebSocket支持
从libcurl 7.86.0版本开始,libcurl提供了WebSocket支持。以下是一个简单的WebSocket客户端示例:
#include <stdio.h>
#include <string.h>
#include <curl/curl.h>
struct ws_data {
const char *text;
size_t remains;
};
static size_t send_callback(void *ptr, size_t size, size_t nmemb, void *userp) {
struct ws_data *data = (struct ws_data *)userp;
if(!data->remains)
return 0;
size_t len = size * nmemb;
if(len > data->remains)
len = data->remains;
memcpy(ptr, data->text, len);
data->text += len;
data->remains -= len;
return len;
}
static size_t recv_callback(void *data, size_t size, size_t nmemb, void *userp) {
size_t len = size * nmemb;
printf("Received %zu bytes: %.*s\n", len, (int)len, (char *)data);
return len;
}
int main(void) {
CURL *curl;
CURLcode res;
struct curl_ws_frame frame;
struct ws_data data;
const char *send_text = "Hello WebSocket!";
data.text = send_text;
data.remains = strlen(send_text);
curl_global_init(CURL_GLOBAL_DEFAULT);
curl = curl_easy_init();
if(curl) {
curl_easy_setopt(curl, CURLOPT_URL, "wss://echo.websocket.org");
curl_easy_setopt(curl, CURLOPT_CONNECT_ONLY, 2L); /* WebSocket */
res = curl_easy_perform(curl);
if(res == CURLE_OK) {
/* 连接已建立,发送WebSocket消息 */
frame.age = CURL_WS_FRAME;
frame.flags = CURL_WS_TEXT;
frame.offset = 0;
frame.len = data.remains;
curl_easy_setopt(curl, CURLOPT_READFUNCTION, send_callback);
curl_easy_setopt(curl, CURLOPT_READDATA, &data);
res = curl_ws_send(curl, &frame, NULL);
if(res != CURLE_OK) {
fprintf(stderr, "curl_ws_send() failed: %s\n", curl_easy_strerror(res));
}
/* 接收WebSocket消息 */
size_t nread;
char buffer[1024];
frame.len = sizeof(buffer);
frame.offset = 0;
res = curl_ws_recv(curl, buffer, sizeof(buffer), &nread, &frame);
if(res != CURLE_OK) {
fprintf(stderr, "curl_ws_recv() failed: %s\n", curl_easy_strerror(res));
} else {
printf("Received %zu bytes: %.*s\n", nread, (int)nread, buffer);
}
} else {
fprintf(stderr, "curl_easy_perform() failed: %s\n", curl_easy_strerror(res));
}
curl_easy_cleanup(curl);
}
curl_global_cleanup();
return 0;
}
常见问题与解决方案
1. SSL证书验证问题
问题:在HTTPS连接时出现SSL证书验证失败。
解决方案:
-
提供CA证书路径:
curl_easy_setopt(curl, CURLOPT_CAINFO, "path/to/ca-bundle.crt");
-
禁用证书验证(不推荐用于生产环境):
curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, 0L);
2. 内存泄漏问题
问题:使用libcurl时出现内存泄漏。
解决方案:
- 确保每次
curl_easy_init()
都有对应的curl_easy_cleanup()
- 释放所有通过
curl_slist_append()
创建的列表 - 释放回调函数中分配的内存
- 使用工具如Valgrind检测内存泄漏
3. 连接超时问题
问题:在网络不稳定的环境中,连接经常超时。
解决方案:
设置适当的超时参数:
curl_easy_setopt(curl, CURLOPT_CONNECTTIMEOUT, 10L); // 连接超时时间(秒)
curl_easy_setopt(curl, CURLOPT_TIMEOUT, 30L); // 总操作超时时间(秒)
4. 重定向问题
问题:请求不跟随重定向。
解决方案:
启用自动重定向:
curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, 1L);
curl_easy_setopt(curl, CURLOPT_MAXREDIRS, 5L); // 最大重定向次数
参考资源
本文提供了libcurl库的基本介绍、安装方法、基本用法和高级应用,以及如何在各种平台上进行交叉编译。希望这些内容能够帮助您更好地使用libcurl实现各种网络功能。
关注 嵌入式软件客栈 公众号,获取更多内容