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

Linux下C语言通过Socket读取HTTP GET响应并分离头体存储问题

HTTP Socket响应读取、头体分离及存储正确实现方案

原代码核心错误点

  • 字符串函数误用:strstr()、strlen()、printf("%s")均要求输入是以\0结尾的C字符串,socket返回的原始二进制数据没有结束符,会触发越界访问,这也是Valgrind报未初始化内存的直接原因。同时二进制数据(如图片)中自带\0会导致strlen()提前截断,文件写入不完整。
  • 读写长度不匹配:read()返回值n是本次实际读到的字节数,后续write()直接写固定2000字节,会写入大量未初始化的垃圾数据到文件,导致图片等二进制文件损坏。
  • 状态码解析逻辑不可靠:直接全局搜索1.匹配HTTP版本(如HTTP/1.1),如果响应头其他位置出现1.会直接解析错误。
  • 空指针风险:如果服务端断开连接仍未找到头体分隔符\r\n\r\n,end为NULL时执行end +=4会触发段错误。
  • 头查找逻辑丢数据:移动缓冲区时逻辑错误,会丢失已读的部分响应头数据,可能永远找不到分隔符。

修复后完整实现

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/socket.h>

// 二进制内存子串查找函数,无\0依赖,支持任意格式数据
void* mem_search(const void* haystack, size_t haystack_len, const void* needle, size_t needle_len) {
    if (needle_len == 0 || haystack_len < needle_len) return NULL;
    for (size_t i = 0; i <= haystack_len - needle_len; i++) {
        if (memcmp((unsigned char*)haystack + i, needle, needle_len) == 0) {
            return (unsigned char*)haystack + i;
        }
    }
    return NULL;
}

void process_http_response(int sd, const char* full_path) {
    unsigned char buf[2048];
    ssize_t n;
    int header_parsed = 0;
    int status = 0;
    const char header_sep[] = "\r\n\r\n";
    const size_t sep_len = sizeof(header_sep) - 1;

    // 暂存未解析完成的响应头,避免数据丢失
    unsigned char header_buf[4096];
    size_t header_buf_len = 0;
    int file_fd = -1;

    while ((n = read(sd, buf, sizeof(buf))) > 0) {
        // 打印完整响应内容,按实际字节数输出,兼容文本和二进制数据
        fwrite(buf, 1, n, stdout);
        fflush(stdout);

        if (!header_parsed) {
            // 新数据拼入响应头缓冲区
            if (header_buf_len + n > sizeof(header_buf)) {
                perror("Response header too large");
                exit(EXIT_FAILURE);
            }
            memcpy(header_buf + header_buf_len, buf, n);
            header_buf_len += n;

            // 查找头体分隔符
            void* sep_pos = mem_search(header_buf, header_buf_len, header_sep, sep_len);
            if (sep_pos != NULL) {
                header_parsed = 1;
                // 从响应第一行解析状态码,逻辑更可靠
                char* line_end = memchr(header_buf, '\n', header_buf_len);
                if (line_end) {
                    char* status_pos = memchr(header_buf, ' ', line_end - (char*)header_buf);
                    if (status_pos) {
                        status = strtol(status_pos + 1, NULL, 10);
                    }
                }

                // 状态码正常时创建文件存储响应体
                if (status >= 200 && status < 300) {
                    creat_directories(full_path);
                    file_fd = open(full_path, O_CREAT | O_WRONLY | O_TRUNC, 0644);
                    if (file_fd < 0) {
                        perror("ERROR: cannot open file");
                        exit(EXIT_FAILURE);
                    }
                    // 写入当前缓冲区中已读取到的响应体部分
                    size_t body_part_len = header_buf_len - ((unsigned char*)sep_pos - header_buf) - sep_len;
                    if (body_part_len > 0) {
                        write(file_fd, (unsigned char*)sep_pos + sep_len, body_part_len);
                    }
                }
            }
        } else {
            // 响应头解析完成后,后续所有数据直接写入文件
            if (file_fd >= 0) {
                write(file_fd, buf, n);
            }
        }
    }

    if (n < 0) {
        perror("Read socket error");
    }
    if (file_fd >= 0) {
        close(file_fd);
    }
}

实现说明

  • 所有读写、打印操作完全基于实际返回的字节数执行,不会越界访问内存,Valgrind不会再报未初始化内存错误
  • 二进制响应(图片、压缩包等)不会被截断、不会混入垃圾数据,可正常打开使用
  • 响应头独立缓冲区设计,不会丢失分段返回的响应头数据,可准确识别头体分隔符
  • 状态码从响应首行解析,不会被响应头其他字段干扰

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.09.25 08:15:04