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
相关产品推荐
相关产品推荐

