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

popen调用成功但fgets/fread无法获取数据的原因排查

问题

直接在控制台执行以下命令时,能稳定返回包含<item id="4" value="t9.tar.gz" />的结果:

printf "GET /xml?method=gw.log.download&id=4&pass=voip HTTP/1.1\r\n\r\n" | nc 127.0.0.1 80

控制台输出示例:

/mnt/rwfs/var/run # printf "GET /xml?method=gw.log.download&id=4&pass=voip HTTP/1.1\r\n\r\n" | nc 127.0.0.1 80
tar: removing leading '/' from member names
var/log/usb.log
var/log/version
var/log/versions/
var/log/versions/initsys
var/log/warning.log
var/tmp/message.log
HTTP/1.0 200 OK
Date: Wed, 21 Aug 2024 06:52:18 GMT
Server:
X-Frame-Options:DENY
Cache-Control:no-cache
Pragma:no-cache
Expires:-1
Referrer-Policy: strict-origin-when-cross-origin
Strict-Transport-Securit: max-age = 16070400
X-Content-Type-Options: nosniff
X-Download-Options: noopen
X-Permitted-Cross-Domain-Policies: master-only
X-XSS-Protection: 1; mode=block
Access-Control-Allow-Headers: accept, Content-Type
Access-Control-Allow-Origin: (null)
Connection: close
Content-Type: text/xml
Content-Length: 148

<?xml version="1.0" encoding="utf-8" ?>
<rsp stat="ok">
<config  method="gw.log.download">
<item id="4" value="t9.tar.gz" />
</config>
</rsp>

但通过如下C代码调用该命令时,约50%概率出现fgets/fread直接返回NULL的情况,无法提取到目标文件名:

static void request_download_log(char *tar_file_name)
{
    char sys_cmd[128] = {0};
    FILE *fp = NULL;
    char buff[128] = {0};
    char *p = NULL;
    char *q = NULL;

    tar_file_name[0] = 0;

    sprintf(sys_cmd, "printf \"GET /xml?method=gw.log.download&id=4&pass=%s HTTP/1.1\\r\\n\\r\\n\" | nc 127.0.0.1 80  2>&1", "voip");

    fp = popen(sys_cmd, "r");

    if (NULL != fp) {
        //while (fgets(buff, sizeof(buff), fp) != NULL) {
        while(fread(buff, sizeof(char), sizeof(buff) - 1, fp) > 0) {
            if ((p = strstr(buff, "value=\"")) != NULL) {
                p += strlen("value=\"");
                q = p;
                while (*q != '"') {
                    q++;
                }
                *q = 0;

                strcpy(tar_file_name, p);
                break;
            }
        }
        pclose(fp);
    }

    debug_log(eLOG_COMP_FUNCTION, 4, __FUNCTION__, __LINE__, "%s:%s", __FUNCTION__, tar_file_name);
}

原因分析

  1. 目标字符串跨缓冲区分割:你要找的value=\"可能被拆分在两个连续的fread缓冲区中,比如前一个缓冲区末尾是value=,后一个开头是"t9.tar.gz",单次strstr无法匹配到完整字符串。代码找到匹配就立刻退出循环,若前几次读取都没命中完整目标,直到读完所有数据也找不到,就会返回空结果,这种情况的概率完全取决于输出数据在管道中的分段情况。
  2. 子进程与管道同步问题:nc在服务器关闭连接后可能提前退出,此时子进程的输出缓冲区可能还没完全把数据刷到管道里,父进程的fread就提前遇到EOF返回0,导致后续数据没被读取。
  3. 信号中断读取过程:子进程退出时会触发SIGCHLD信号,如果父进程没处理这个信号,可能会打断fread的系统调用,导致读取错误,表现为fread直接返回0或NULL。

解决建议

  1. 累积数据后再匹配:不要每次读取后立刻匹配,而是把所有读取到的数据累积到一个足够大的缓冲区里,再统一查找目标字符串,避免跨缓冲区的问题。示例修改:
    static void request_download_log(char *tar_file_name)
    {
        char sys_cmd[128] = {0};
        FILE *fp = NULL;
        char total_buff[1024] = {0}; // 扩大累积缓冲区
        char read_buf[128] = {0};
        char *p = NULL;
        char *q = NULL;
    
        tar_file_name[0] = 0;
    
        sprintf(sys_cmd, "printf \"GET /xml?method=gw.log.download&id=4&pass=%s HTTP/1.1\\r\\n\\r\\n\" | nc 127.0.0.1 80  2>&1", "voip");
    
        fp = popen(sys_cmd, "r");
    
        if (NULL != fp) {
            size_t total_len = 0;
            // 先累积所有输出数据
            while(fread(read_buf, sizeof(char), sizeof(read_buf)-1, fp) > 0) {
                if (total_len + sizeof(read_buf)-1 < sizeof(total_buff)) {
                    strncat(total_buff, read_buf, sizeof(read_buf)-1);
                    total_len += sizeof(read_buf)-1;
                }
            }
            // 统一查找目标字符串
            if ((p = strstr(total_buff, "value=\"")) != NULL) {
                p += strlen("value=\"");
                q = p;
                while (*q != '"' && q < total_buff + sizeof(total_buff)) {
                    q++;
                }
                *q = 0;
                strncpy(tar_file_name, p, strlen(p)); // 用strncpy避免缓冲区溢出
            }
            pclose(fp);
        }
    
        debug_log(eLOG_COMP_FUNCTION, 4, __FUNCTION__, __LINE__, "%s:%s", __FUNCTION__, tar_file_name);
    }
    
  2. 添加错误检查:在fread返回0后,用ferror(fp)检查是否是读取错误,如果是错误可以尝试重新执行命令或记录错误日志。
  3. 改用直接Socket通信:彻底避免shell命令和管道的不确定性,直接在C代码里创建TCP连接,发送HTTP请求并读取响应,这是最可靠的方案。核心步骤:
    • 创建TCP Socket连接到127.0.0.1:80
    • 构造完整的HTTP请求字符串并发送
    • 循环读取响应数据并累积
    • 在累积的数据中查找目标字符串
    • 关闭Socket

内容的提问来源于stack exchange,提问作者dragonkyl

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.19 12:51:00