使用CURL库发起HTTP请求时程序为何出现非确定性阻塞?
解决Raspberry Pi Zero W上libcurl周期性HTTP请求阻塞问题
问题描述
在Raspberry Pi Zero W上运行C程序,试图以1-2秒周期发起HTTP请求。程序前几分钟运行稳定,但之后会出现无响应的阻塞,一段时间后恢复,反复循环。尝试过取消每次请求后的curl_easy_cleanup(因文档提示不应频繁清理句柄),情况有所改善但仍存在阻塞;在响应回调中调用curl_easy_cleanup则引发段错误。期望实现稳定的周期性HTTP请求。
用户提供的代码:
#include <stdio.h> #include <curl/curl.h> #include <string.h> #include <pigpio.h> #define PTR_OFFSET 49 #define SIZEOF_MSG 5 #define DELAY_TIME 100 #define GPIO_PIN 25 char niz[20]; char opcija0[] = "00000"; char opcija1[] = "00001"; size_t write_callback(void *contents, size_t size, size_t nmemb, void *userp){ memcpy(niz, (char *)contents + PTR_OFFSET, SIZEOF_MSG); size_t total_size = size*nmemb; if (!memcmp(niz, opcija0, SIZEOF_MSG)) { gpioWrite(GPIO_PIN, 0); printf("GPIO OFF\n"); } if (!memcmp(niz, opcija1, SIZEOF_MSG)) { gpioWrite(GPIO_PIN, 1); printf("GPIO ON\n"); } printf("Niz je: %s\n", niz); return total_size; } int main(){ CURL *curl; CURLcode res; if (gpioInitialise() < 0) { fprintf(stderr, "pigpio initialisation failed\n"); return 1; } gpioSetMode(GPIO_PIN, PI_OUTPUT); while(1) { curl = curl_easy_init(); if (curl) { const char *url = "https://3704-109-245-204-66.ngrok-free.app/Filip/?y=1"; //Replace with wanted URL //Set the URL curl_easy_setopt(curl, CURLOPT_URL, url); //Set the write callback function to print the response curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, write_callback); //Perform the request res = curl_easy_perform(curl); //Check for errors if (res != CURLE_OK) { fprintf(stderr, "curl_easy_perform() failed: %s\n", curl_easy_strerror(res)); } else { printf("\nRequest succesful.\n"); } //Cleanup and free resources //curl_easy_cleanup(curl); } else { fprintf(stderr, "Failed to initialize libcurl.\n"); } time_sleep(2); } return 0; }
解决方案
1. 正确复用CURL句柄
当前代码每次循环创建新的CURL句柄但不清理,会导致资源泄漏,这是阻塞的核心原因之一。正确做法是在循环外初始化一次句柄,复用至程序结束:
int main(){ CURL *curl; CURLcode res; // 初始化全局libcurl环境(必须调用) curl_global_init(CURL_GLOBAL_DEFAULT); // 仅初始化一次CURL句柄 curl = curl_easy_init(); if(!curl){ fprintf(stderr, "Failed to initialize libcurl.\n"); return 1; } if (gpioInitialise() < 0) { fprintf(stderr, "pigpio initialisation failed\n"); curl_easy_cleanup(curl); curl_global_cleanup(); return 1; } gpioSetMode(GPIO_PIN, PI_OUTPUT); // 提前设置不变的选项,避免重复操作 curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, write_callback); const char *url = "https://3704-109-245-204-66.ngrok-free.app/Filip/?y=1"; curl_easy_setopt(curl, CURLOPT_URL, url); while(1) { // 直接复用句柄执行请求 res = curl_easy_perform(curl); if (res != CURLE_OK) { fprintf(stderr, "curl_easy_perform() failed: %s\n", curl_easy_strerror(res)); } else { printf("\nRequest succesful.\n"); } time_sleep(2); } // 程序结束时清理资源(虽然while(1)不会执行到,但保持代码完整性) curl_easy_cleanup(curl); curl_global_cleanup(); gpioTerminate(); return 0; }
2. 设置请求超时参数
网络波动或服务端响应慢会导致请求无限阻塞,给libcurl添加超时限制:
// 在初始化句柄后添加 // 连接超时(3秒) curl_easy_setopt(curl, CURLOPT_CONNECTTIMEOUT, 3L); // 总请求超时(5秒) curl_easy_setopt(curl, CURLOPT_TIMEOUT, 5L);
超时后libcurl会直接返回错误,避免程序长时间无响应。
3. 修复回调函数的内存问题
回调中复制字符串后未添加终止符,会导致printf输出垃圾数据,甚至触发未定义行为:
size_t write_callback(void *contents, size_t size, size_t nmemb, void *userp){ // 检查响应长度是否足够,避免越界 if(PTR_OFFSET + SIZEOF_MSG > size*nmemb){ fprintf(stderr, "Response content too short\n"); return size*nmemb; } memcpy(niz, (char *)contents + PTR_OFFSET, SIZEOF_MSG); niz[SIZEOF_MSG] = '\0'; // 添加字符串终止符 size_t total_size = size*nmemb; if (!memcmp(niz, opcija0, SIZEOF_MSG)) { gpioWrite(GPIO_PIN, 0); printf("GPIO OFF\n"); } if (!memcmp(niz, opcija1, SIZEOF_MSG)) { gpioWrite(GPIO_PIN, 1); printf("GPIO ON\n"); } printf("Niz je: %s\n", niz); return total_size; }
更健壮的做法是通过userp传递数据,避免依赖全局变量:
// 定义数据结构体 typedef struct { char buffer[20]; int gpio_pin; } ResponseData; size_t write_callback(void *contents, size_t size, size_t nmemb, void *userp){ ResponseData *data = (ResponseData*)userp; if(PTR_OFFSET + SIZEOF_MSG > size*nmemb){ fprintf(stderr, "Response content too short\n"); return size*nmemb; } memcpy(data->buffer, (char *)contents + PTR_OFFSET, SIZEOF_MSG); data->buffer[SIZEOF_MSG] = '\0'; size_t total_size = size*nmemb; if (!memcmp(data->buffer, opcija0, SIZEOF_MSG)) { gpioWrite(data->gpio_pin, 0); printf("GPIO OFF\n"); } if (!memcmp(data->buffer, opcija1, SIZEOF_MSG)) { gpioWrite(data->gpio_pin, 1); printf("GPIO ON\n"); } printf("Niz je: %s\n", data->buffer); return total_size; } // main中设置传递的数据 ResponseData resp_data = {0}; resp_data.gpio_pin = GPIO_PIN; curl_easy_setopt(curl, CURLOPT_WRITEDATA, &resp_data);
4. 监控系统资源
Raspberry Pi Zero W资源有限,长时间运行可通过以下命令监控:
top/htop:查看内存、CPU占用情况lsof -p <程序PID>:检查是否存在文件句柄泄漏
总结
核心问题是未正确复用CURL句柄导致资源泄漏,加上缺少超时设置引发无限阻塞。按照上述步骤修改后,程序可稳定实现周期性HTTP请求。
内容的提问来源于stack exchange,提问作者filipembedded
相关产品推荐
相关产品推荐

