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

使用Sockets和OpenSSL下载BMP图片时文件损坏问题求助

BMP图片下载损坏问题修复方案

你的代码下载的图片损坏,核心原因是服务器采用HTTP/1.1的分块编码返回数据,而代码直接将分块标识和图片数据混存,导致提取的图片包含无效内容。另外,依赖"BM"标识分割响应头和体的方式也存在误判风险,以下是具体修复方案:

问题分析

  1. 分块编码未处理:HTTP/1.1默认持久连接,服务器常用分块传输(Transfer-Encoding: chunked)返回数据。此时响应体不是纯二进制图片,而是被拆分为多个块,每个块开头带十六进制长度标识+\r\n,末尾还有结束块标识,这些额外内容会破坏图片结构。
  2. 响应分割方式不严谨:靠"BM"找图片起始位置,若响应头中恰好出现该字符串,会导致错误截断。
  3. SSL读取错误处理缺失:SSL_read返回<=0时,未区分正常关闭和异常错误,可能导致读取不完整或错误处理。

修复代码

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netdb.h>
#include <openssl/ssl.h>
#include <openssl/bio.h>
#include <openssl/err.h>

#define SERVER "filesamples.com"
#define PORT 443
#define PATH "/samples/image/bmp/sample_640%C3%97426.bmp"

// 读取HTTP响应,分离头和体并处理分块编码
int read_http_response(SSL *ssl, FILE *body_fp) {
    char buffer[4096];
    int bytes_read;
    char *header_end = NULL;
    int in_header = 1;
    int in_chunk = 0;
    unsigned long chunk_len = 0;
    int chunk_remaining = 0;

    while (1) {
        bytes_read = SSL_read(ssl, buffer, sizeof(buffer));
        if (bytes_read <= 0) {
            int ssl_err = SSL_get_error(ssl, bytes_read);
            if (ssl_err == SSL_ERROR_ZERO_RETURN) {
                // 连接正常关闭,退出循环
                break;
            } else {
                fprintf(stderr, "SSL读取错误: %d\n", ssl_err);
                return -1;
            }
        }

        int pos = 0;
        while (pos < bytes_read) {
            if (in_header) {
                // 查找响应头与体的分隔符\r\n\r\n
                header_end = memmem(buffer + pos, bytes_read - pos, "\r\n\r\n", 4);
                if (header_end != NULL) {
                    // 跳过响应头,开始处理响应体
                    pos = header_end - buffer + 4;
                    in_header = 0;
                } else {
                    // 未找到分隔符,跳过当前缓冲区数据
                    pos = bytes_read;
                }
            } else {
                if (!in_chunk) {
                    // 读取分块长度(十六进制格式)
                    char *chunk_end = memchr(buffer + pos, '\r', bytes_read - pos);
                    if (chunk_end != NULL) {
                        *chunk_end = '\0';
                        chunk_len = strtoul(buffer + pos, NULL, 16);
                        *chunk_end = '\r'; // 恢复原数据
                        // 跳过长度标识和后面的\r\n
                        pos = chunk_end - buffer + 2;
                        in_chunk = 1;
                        chunk_remaining = chunk_len;
                        if (chunk_len == 0) {
                            // 分块结束,返回成功
                            return 0;
                        }
                    } else {
                        // 分块长度跨缓冲区,暂时跳过
                        pos = bytes_read;
                    }
                } else {
                    // 读取当前分块的实际数据
                    int copy_len = bytes_read - pos;
                    if (copy_len > chunk_remaining) {
                        copy_len = chunk_remaining;
                    }
                    fwrite(buffer + pos, 1, copy_len, body_fp);
                    pos += copy_len;
                    chunk_remaining -= copy_len;
                    if (chunk_remaining == 0) {
                        // 当前分块读取完成,跳过末尾的\r\n
                        in_chunk = 0;
                        if (pos + 2 <= bytes_read && memcmp(buffer + pos, "\r\n", 2) == 0) {
                            pos += 2;
                        }
                    }
                }
            }
        }
    }
    return 0;
}

int main() {
    int sockfd;
    struct sockaddr_in serv_addr;
    struct hostent *server;

    // 初始化OpenSSL环境
    SSL_library_init();
    OpenSSL_add_all_algorithms();
    SSL_load_error_strings();
    SSL_CTX *ctx = SSL_CTX_new(TLS_client_method());

    if (ctx == NULL) {
        ERR_print_errors_fp(stderr);
        return 1;
    }

    SSL *ssl = SSL_new(ctx);

    sockfd = socket(AF_INET, SOCK_STREAM, 0);
    if (sockfd < 0) {
        perror("创建socket失败");
        return 1;
    }

    server = gethostbyname(SERVER);
    if (server == NULL) {
        fprintf(stderr, "解析服务器域名失败\n");
        return 1;
    }

    memset(&serv_addr, 0, sizeof(serv_addr));
    serv_addr.sin_family = AF_INET;
    serv_addr.sin_port = htons(PORT);
    memcpy(&serv_addr.sin_addr.s_addr, server->h_addr, server->h_length);

    if (connect(sockfd, (struct sockaddr*)&serv_addr, sizeof(serv_addr)) < 0) {
        perror("连接服务器失败");
        return 1;
    }

    BIO *sbio = BIO_new_socket(sockfd, BIO_NOCLOSE);
    SSL_set_bio(ssl, sbio, sbio);
    if (SSL_set_tlsext_host_name(ssl, SERVER) != 1) {
        fprintf(stderr, "设置SNI失败\n");
        return 1;
    }
    if (SSL_connect(ssl) <= 0) {
        ERR_print_errors_fp(stderr);
        return 1;
    }

    // 构造HTTP请求,添加Connection: close让服务器返回数据后关闭连接
    char request[1024];
    snprintf(request, sizeof(request), "GET %s HTTP/1.1\r\nHost: %s\r\nConnection: close\r\n\r\n", PATH, SERVER);
    int bytes_written = SSL_write(ssl, request, strlen(request));
    if (bytes_written <= 0) {
        fprintf(stderr, "发送请求失败\n");
        return 1;
    }

    // 直接打开图片文件准备写入
    FILE *fp = fopen("sample.bmp", "wb");
    if (fp == NULL) {
        perror("打开图片文件失败");
        return 1;
    }

    if (read_http_response(ssl, fp) != 0) {
        fprintf(stderr, "读取响应失败\n");
        fclose(fp);
        return 1;
    }

    fclose(fp);
    printf("图片下载成功\n");

    // 清理资源
    SSL_shutdown(ssl);
    SSL_free(ssl);
    SSL_CTX_free(ctx);
    close(sockfd);

    return 0;
}

关键修改说明

  • 添加Connection: close请求头:让服务器返回数据后主动关闭连接,避免HTTP/1.1持久连接导致的SSL_read阻塞。
  • 解析分块编码:去掉分块的长度标识和换行符,只保留实际的图片二进制数据。
  • 正确分割响应头与体:用标准的\r\n\r\n分隔符拆分,避免依赖"BM"标识的误判。
  • 完善错误处理:区分SSL读取的正常关闭和异常错误,确保数据读取完整。
  • 直接写入图片文件:省去中间的response.txt步骤,减少数据处理环节。

编译运行

编译时需要链接OpenSSL库:

gcc -o bmp_download bmp_download.c -lssl -lcrypto

运行生成的可执行文件,会得到可正常打开的sample.bmp。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.20 01:07:03