上文主要介绍了ip_api.com、cjson的解析函数以及使用system发生curl请求;本文主要介绍使用libcurl进行curl请求。
1. libcurl简述
libcurl
是一个广泛使用的开源库,它提供了一个客户端接口,用于在不同的网络协议(如 HTTP、HTTPS、FTP 等)中进行数据传输。它常用于实现程序中的网络请求操作,尤其是用于进行 GET、POST 请求和其他 HTTP 操作。
主要功能:
-
支持多种协议:支持 HTTP、HTTPS、FTP、FTPS、SFTP、IMAP、SMTP 等多种网络协议。
-
跨平台支持:
libcurl
可以在 Linux、Windows、macOS 等多个操作系统上使用。 -
多种身份验证方式:支持多种认证方式,包括基本认证、摘要认证、客户端证书、OAuth 等。
-
文件上传与下载:支持通过 FTP 或 HTTP 上传和下载文件。
-
支持多种代理协议:支持 SOCKS 代理、HTTP 代理等。
安装:
可以通过包管理器(如 apt
、brew
或 vcpkg
)来安装 libcurl
,或者从源代码编译它。
在 Ubuntu 上安装:
sudo apt-get install libcurl4-openssl-dev
在 macOS 上安装:
brew install curl
或者直接下载源码,网址为libcurl 网址,并详细介绍了libcurl的常用接口以及使用示例,详细libcurl不过多介绍
本文使用的为libcurl-easy
阻塞式,libcurl_multi
实现方式下文详细介绍。
这里是easy和multi两种方式的区别说明:
2. libcurl 请求缺点
curl_easy_perform
是 libcurl
中用于执行单个 HTTP 请求的简单函数,但它有一些局限性。主要缺点包括:
- 阻塞式调用:该函数会阻塞程序,直到请求完成,无法进行其他操作。对于并发请求,效率较低。
- 错误处理有限:错误信息较少,调试时不易获取详细原因。
- 缺乏事件驱动机制:不支持异步请求或事件驱动模型,适合同步请求,但不适合高性能异步处理。
- 连接池管理不足:每次请求都会独立建立连接,可能导致性能下降,尤其在高并发场景下。
- 内存管理较复杂:对于大文件或流数据,内存和缓冲区管理较为繁琐,容易导致内存泄漏。
- 复杂会话管理困难:不适合复杂的 HTTP 会话管理,需要手动配置和管理更多选项。
- 超时控制有限:虽然支持设置超时,但没有内建的超时处理机制,可能会导致长时间等待。
- 并发支持差:无法高效处理多个并发请求,适合单任务场景。
综上,curl_easy_perform
适合简单同步请求,但在处理高并发、异步任务和复杂请求时,推荐使用 curl_multi_*
来提高效率和灵活性。
3. 封装libcurl请求接口
通过libcurl api封装为接口,方便后续进行其他api获取请求,代码如下:
#include <stdio.h>
#include <string.h>
#include <curl/curl.h>
typedef struct {
char *memory;
size_t size;
} MemoryStruct;
// cURL回调函数
static size_t WriteMemoryCallback(void *contents, size_t size, size_t nmemb, void *userp) {
size_t realsize = size * nmemb;
MemoryStruct *mem = (MemoryStruct *)userp;
char *ptr = realloc(mem->memory, mem->size + realsize + 1);
if(!ptr) {
fprintf(stderr, "内存不足 (realloc 返回 NULL)\n");
return 0;
}
mem->memory = ptr;
memcpy(&(mem->memory[mem->size]), contents, realsize);
mem->size += realsize;
mem->memory[mem->size] = 0;
return realsize;
}
int libcurl_get_ip_info(char *curl_url) {
CURL *curl;
CURLcode res;
MemoryStruct chunk = {0};
// 初始化内存结构
chunk.memory = malloc(1);
chunk.size = 0;
// 初始化cURL
curl_global_init(CURL_GLOBAL_DEFAULT);
curl = curl_easy_init();
if(curl) {
// 设置cURL选项
curl_easy_setopt(curl, CURLOPT_URL, curl_url);
curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, WriteMemoryCallback);
curl_easy_setopt(curl, CURLOPT_WRITEDATA, (void *)&chunk);
curl_easy_setopt(curl, CURLOPT_USERAGENT, "libcurl-agent/1.0");
curl_easy_setopt(curl, CURLOPT_TIMEOUT, 10L);
// 执行HTTP请求
res = curl_easy_perform(curl);
if(res != CURLE_OK) {
fprintf(stderr, "curl_easy_perform() 失败: %s\n", curl_easy_strerror(res));
curl_easy_cleanup(curl);
free(chunk.memory);
curl_global_cleanup();
return EXIT_FAILURE;
}
// 解析JSON数据
cJSON *root = cJSON_Parse(chunk.memory);
if (!root) {
const char *error_ptr = cJSON_GetErrorPtr();
if (error_ptr) fprintf(stderr, "JSON解析错误: %s\n", error_ptr);
curl_easy_cleanup(curl);
free(chunk.memory);
curl_global_cleanup();
return EXIT_FAILURE;
}
//添加解析json函数
/**********
************
************/
// 清理资源
cJSON_Delete(root);
curl_easy_cleanup(curl);
}
// 清理全局cURL
curl_global_cleanup();
// 释放内存
free(chunk.memory);
return EXIT_SUCCESS;
}
4. 完整代码
结合上文的json解析函数,完整根据libcurl获取ip地理位置的代码如下:
#include <stdio.h>
#include <string.h>
#include "cJSON.h"
#include <curl/curl.h>
#define CURL_IP_API "http://ip-api.com/json/?fields=status,message,countryCode,regionName,city,lat,lon"
typedef struct ip_info {
char countryCode[8];
char regionName[32];
char city[32];
double lat;
double lon;
} ip_info;
typedef struct {
char *memory;
size_t size;
} MemoryStruct;
ip_info current_ip_info = {0};
int parse_ip_json_str(cJSON *current_json_str){
int ret = -1;
cJSON *status = cJSON_GetObjectItemCaseSensitive(current_json_str, "status");
if (!cJSON_IsString(status) || strcmp(status->valuestring, "success") != 0) {
ret = -1;
goto end;
}
cJSON *item;
if ((item = cJSON_GetObjectItem(current_json_str, "countryCode")) && cJSON_IsString(item))
strncpy(current_ip_info.countryCode, item->valuestring, sizeof(current_ip_info.countryCode)-1);
if ((item = cJSON_GetObjectItem(current_json_str, "regionName")) && cJSON_IsString(item))
strncpy(current_ip_info.regionName, item->valuestring, sizeof(current_ip_info.regionName)-1);
if ((item = cJSON_GetObjectItem(current_json_str, "city")) && cJSON_IsString(item))
strncpy(current_ip_info.city, item->valuestring, sizeof(current_ip_info.city)-1);
if ((item = cJSON_GetObjectItem(current_json_str, "lat")) && cJSON_IsNumber(item))
current_ip_info.lat = item->valuedouble;
if ((item = cJSON_GetObjectItem(current_json_str, "lon")) && cJSON_IsNumber(item))
current_ip_info.lon = item->valuedouble;
if(strlen(current_ip_info.countryCode) != 0 && strlen(current_ip_info.regionName) != 0 && strlen(current_ip_info.city) != 0 && current_ip_info.lat != 0 && current_ip_info.lon != 0)
ret = 0;
end:
return ret;
}
void print_ip_json_parse(void) {
printf("current ip info:\n");
printf("countryCode ID: %s\n", current_ip_info.countryCode);
printf("regionName: %s\n", current_ip_info.regionName);
printf("city: %s\n", current_ip_info.city);
printf("lat: %.4f\n", current_ip_info.lat);
printf("lon: %.4f\n", current_ip_info.lon);
printf("\n");
}
// cURL回调函数
static size_t WriteMemoryCallback(void *contents, size_t size, size_t nmemb, void *userp) {
size_t realsize = size * nmemb;
MemoryStruct *mem = (MemoryStruct *)userp;
char *ptr = realloc(mem->memory, mem->size + realsize + 1);
if(!ptr) {
fprintf(stderr, "内存不足 (realloc 返回 NULL)\n");
return 0;
}
mem->memory = ptr;
memcpy(&(mem->memory[mem->size]), contents, realsize);
mem->size += realsize;
mem->memory[mem->size] = 0;
return realsize;
}
int libcurl_get_ip_info(char *curl_url) {
CURL *curl;
CURLcode res;
MemoryStruct chunk = {0};
// 初始化内存结构
chunk.memory = malloc(1);
chunk.size = 0;
// 初始化cURL
curl_global_init(CURL_GLOBAL_DEFAULT);
curl = curl_easy_init();
if(curl) {
// 设置cURL选项
curl_easy_setopt(curl, CURLOPT_URL, curl_url);
curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, WriteMemoryCallback);
curl_easy_setopt(curl, CURLOPT_WRITEDATA, (void *)&chunk);
curl_easy_setopt(curl, CURLOPT_USERAGENT, "libcurl-agent/1.0");
curl_easy_setopt(curl, CURLOPT_TIMEOUT, 10L);
// 执行HTTP请求
res = curl_easy_perform(curl);
if(res != CURLE_OK) {
fprintf(stderr, "curl_easy_perform() 失败: %s\n", curl_easy_strerror(res));
curl_easy_cleanup(curl);
free(chunk.memory);
curl_global_cleanup();
return EXIT_FAILURE;
}
// 解析JSON数据
cJSON *root = cJSON_Parse(chunk.memory);
if (!root) {
const char *error_ptr = cJSON_GetErrorPtr();
if (error_ptr) fprintf(stderr, "JSON解析错误: %s\n", error_ptr);
curl_easy_cleanup(curl);
free(chunk.memory);
curl_global_cleanup();
return EXIT_FAILURE;
}
//添加解析json函数
if(parse_ip_json_str(root) == 0) {
print_ip_json_parse();
}
// 清理资源
cJSON_Delete(root);
curl_easy_cleanup(curl);
}
// 清理全局cURL
curl_global_cleanup();
// 释放内存
free(chunk.memory);
return EXIT_SUCCESS;
}
int main() {
int ret = libcurl_get_ip_info(CURL_IP_API);
printf("libcurl_get_ip_info ret = %d\r\n", ret);
return 0;
}
因为curl_easy_perform
会阻塞主线程,则下文则介绍使用 libcurl-multi
来提高效率和灵活性,若有错误,麻烦指正~