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

OpenSSL偶尔输出随机乱码问题排查(C++ HTTPS请求场景)

用OpenSSL发起HTTPS请求时出现乱码的问题排查与修复

问题描述

我刚开始用OpenSSL在C++里发起HTTPS请求,有时候能收到正常响应,比如:

HTTP/1.1 200 OK Server: nginx/1.16.1 Date: Sun, 09 Aug 2020 12:14:03 GMT Content-Type: application/json; charset=UTF-8 Content-Length: 159 Connection: keep-alive Access-Control-Allow-Credentials: true Vary: Origin {"status":"success","symbol":"AAPL","last":{"price":446.71,"size":200,"exchange":2,"cond1":14,"cond2":12,"cond3":41,"cond4":0,"timestamp":1596835355456000000}}

但有时候会收到带随机乱码的异常响应:

HTTP/1.1 200 OK Server: nginx/1.16.1 Date: Sun, 09 Aug 2020 12:14:06 GMT Content-Type: application/json; charset=UTF-8 Content-Length: 159 Connection: keep-alive Access-Control-Allow-Credentials: true Vary: Origin {"status":"success","symbol":"AAPL","last":{"price":446.71,"size":200,"exchange":2,"cond1":14,"cond2":12,"cond3":41,"cond4":0,"timestamp":15968353554560000?????9??????p???????p?#??????????????u???????@???????U?????? ??????5??????

我怀疑是SSL_read的写入逻辑有问题,而且这个问题不是特定网站导致的,多个站点都出现了。下面是我的请求代码:

请求方法代码

Response* Requests::get(const char* ip_addr, std::string& route, datastructures::hash::HashTable& headers, datastructures::hash::HashTable& parameters){
    int sock = socket(AF_INET, SOCK_STREAM, 0);
    struct sockaddr_in server;
    server.sin_port = htons(443);
    server.sin_family = AF_INET;
    server.sin_addr.s_addr = inet_addr(ip_addr);
    connect(sock, (struct sockaddr*)&server, sizeof(server));
    cout<<SSL_load_error_strings()<<endl;
    cout<<SSL_library_init()<<endl;
    SSL_CTX* ctx = SSL_CTX_new(SSLv23_client_method());
    SSL* conn = SSL_new(ctx);
    cout<<SSL_set_fd(conn, sock)<<endl;
    cout<<SSL_connect(conn)<<endl;
    std::vector<datastructures::hash::Key> params = parameters.getTable();
    std::vector<datastructures::hash::Key> heads = headers.getTable();
    std::ostringstream os;
    os << "GET " << route <<"?";
    std::string request = os.str();
    os.str("");
    {
        int i;
        for(i=0;i<params.size();i++){
            os << params[i].key << "="<< *(std::string*)(params[i].value) << "&";
            request+=os.str();
            os.str("");
        }
        request += " HTTP/1.1 \r\n";
        request+=os.str();
        for(i=0;i<heads.size();i++){
            os << heads[i].key << ": " << *(std::string*)(heads[i].value) << "\r\n";
            request+=os.str();
            os.str("");
        }
        request += "\r\n";
    }
    SSL_write(conn, (const char*)request.c_str(), request.size());
    std::string ret;
    const char* retc = (const char*)malloc(10240*sizeof(char));
    int recv = SSL_read(conn, (void *)retc, 10240);
    while(true){
        ret+=retc;
        free((char*)retc);
        if(recv < 10240){
            break;
        } else{
            recv = SSL_read(conn, (void *)retc, 10240);
            retc = (const char*)malloc(10240*sizeof(char));
        }
    }
    SSL_free(conn);
    close(sock);
    return new Response(ret);
}

主函数调用代码

std::cout << requests::Requests::get("35.186.171.205", route, headers, parameters)->getRawResponse() << std::endl;

(getRawResponse是Response类中返回原始HTTP响应的方法)


问题根源分析

你的代码里内存管理逻辑完全错误,这就是导致随机乱码的核心原因,具体问题点:

  • 第一次调用SSL_read前你malloc了retc,但进入循环后立刻free了这块内存,之后调用SSL_read时,retc已经是野指针,写入的内存区域已经被释放,数据会被覆盖或者写入到无效地址,产生乱码。
  • 当需要继续读取时,你先调用SSL_read再malloc新的内存,这时候SSL_read的目标还是已经被free的旧指针,完全没有意义。
  • 没有处理SSL_read的错误返回值:当SSL_read返回<=0时,说明读取结束或者出错,应该直接终止循环,而不是继续执行。
  • 存在内存泄漏风险:如果循环多次,中间malloc的内存可能没有被正确释放。
  • 缓冲区追加逻辑错误:不管实际读取了多少字节,都把整个10240字节的缓冲区追加到结果里,当缓冲区没填满时,会把末尾的垃圾数据也加进去。

修复后的代码

调整内存分配和读取的顺序,确保每次SSL_read都指向有效的内存缓冲区,同时正确处理返回值和内存释放:

Response* Requests::get(const char* ip_addr, std::string& route, datastructures::hash::HashTable& headers, datastructures::hash::HashTable& parameters){
    int sock = socket(AF_INET, SOCK_STREAM, 0);
    if (sock < 0) {
        // 处理socket创建错误
        return nullptr;
    }

    struct sockaddr_in server;
    server.sin_port = htons(443);
    server.sin_family = AF_INET;
    server.sin_addr.s_addr = inet_addr(ip_addr);
    if (connect(sock, (struct sockaddr*)&server, sizeof(server)) < 0) {
        close(sock);
        // 处理连接错误
        return nullptr;
    }

    // 注意:SSL初始化逻辑应该只在程序启动时执行一次,不要每次请求都调用
    SSL_load_error_strings();
    SSL_library_init();
    SSL_CTX* ctx = SSL_CTX_new(SSLv23_client_method());
    if (!ctx) {
        close(sock);
        // 处理CTX创建错误
        return nullptr;
    }

    SSL* conn = SSL_new(ctx);
    if (!conn) {
        SSL_CTX_free(ctx);
        close(sock);
        return nullptr;
    }

    SSL_set_fd(conn, sock);
    if (SSL_connect(conn) <= 0) {
        SSL_free(conn);
        SSL_CTX_free(ctx);
        close(sock);
        // 处理SSL连接错误
        return nullptr;
    }

    // 构建请求报文
    std::ostringstream os;
    os << "GET " << route << "?";
    std::string request = os.str();
    os.str("");

    auto params = parameters.getTable();
    for (size_t i = 0; i < params.size(); ++i) {
        os << params[i].key << "=" << *(std::string*)(params[i].value);
        if (i != params.size() - 1) {
            os << "&";
        }
        request += os.str();
        os.str("");
    }
    request += " HTTP/1.1\r\n";

    for (const auto& head : headers.getTable()) {
        os << head.key << ": " << *(std::string*)(head.value) << "\r\n";
        request += os.str();
        os.str("");
    }
    request += "\r\n";

    // 发送请求
    int sent = SSL_write(conn, request.c_str(), request.size());
    if (sent <= 0) {
        // 处理发送错误
        SSL_free(conn);
        SSL_CTX_free(ctx);
        close(sock);
        return nullptr;
    }

    std::string ret;
    const size_t buffer_size = 10240;
    char* buffer = (char*)malloc(buffer_size); // 用char*避免const转换问题
    if (!buffer) {
        SSL_free(conn);
        SSL_CTX_free(ctx);
        close(sock);
        return nullptr;
    }

    int recv_len;
    while ((recv_len = SSL_read(conn, buffer, buffer_size)) > 0) {
        // 只追加实际读取到的字节数,避免垃圾数据
        ret.append(buffer, recv_len);
    }

    // 释放缓冲区
    free(buffer);

    // 清理SSL资源
    SSL_free(conn);
    SSL_CTX_free(ctx);
    close(sock);

    // 处理SSL_read的错误情况
    if (recv_len < 0) {
        // 读取出错,可根据需求返回错误响应
        return nullptr;
    }

    return new Response(ret);
}

额外优化点

  1. SSL初始化逻辑:SSL_load_error_strings()和SSL_library_init()应该在程序启动时只执行一次,不要每次发起请求都调用,否则会重复初始化导致资源浪费。
  2. 请求报文规范:修复了参数末尾多余&的问题,让请求符合HTTP规范。
  3. 错误处理:增加了socket创建、连接、SSL初始化等环节的错误处理,提升代码健壮性。
  4. 缓冲区使用:只追加实际读取到的字节数,避免把缓冲区未使用部分的垃圾数据加入结果。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.08 18:47:36