You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

使用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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.06 23:50:41