OpenSSL偶尔输出随机乱码问题排查(C++ 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); }
额外优化点
- SSL初始化逻辑:
SSL_load_error_strings()和SSL_library_init()应该在程序启动时只执行一次,不要每次发起请求都调用,否则会重复初始化导致资源浪费。 - 请求报文规范:修复了参数末尾多余
&的问题,让请求符合HTTP规范。 - 错误处理:增加了socket创建、连接、SSL初始化等环节的错误处理,提升代码健壮性。
- 缓冲区使用:只追加实际读取到的字节数,避免把缓冲区未使用部分的垃圾数据加入结果。
内容的提问来源于stack exchange,提问作者Serket

