使用libcurl向本地HTTPS服务器请求时出现CURLE_RECV_ERROR(错误码56)
CURLE_RECV_ERROR(56):libcurl(mbedTLS)单片机无法接收本地Python HTTPS服务器响应
问题背景
- 搭载libcurl(以mbedTLS为SSL库)的单片机向本地Python HTTPS测试服务器发起请求时,返回CURLE_RECV_ERROR(错误码56)
- 服务器可正常响应其他客户端,设备也能正常访问公网HTTPS服务器,已排除代理、防火墙问题
- 已添加服务器自签名证书,且关闭了
CURLOPT_SSL_VERIFYHOST和CURLOPT_SSL_VERIFYPEER校验 - 服务器日志显示已返回200响应,但客户端libcurl日志仅记录发送GET请求后直接关闭连接,未收到响应内容
Python HTTPS服务器代码
import http.server import ssl import logging from io import BytesIO # Configure logging logging.basicConfig(level=logging.INFO) class HTTPRequestHandler(http.server.SimpleHTTPRequestHandler): def do_GET(self): original_wfile = self.wfile self.wfile = BytesIO() super().do_GET() self.send_captured_response(original_wfile) def send_captured_response(self, original_wfile): response_body = self.wfile.getvalue() logging.info(f"Response body:\n{response_body.decode('utf-8')}") self.wfile = original_wfile self.wfile.write(response_body) def create_server(server_class=http.server.HTTPServer, handler_class=HTTPRequestHandler, port=443): server_address = ('192.168.0.107', port) httpd = server_class(server_address, handler_class) # Set up SSL context context = ssl.SSLContext(ssl.PROTOCOL_TLS_SERVER) context.load_cert_chain('Path/To/certificate.pem', 'Path/To/private.key') httpd.socket = context.wrap_socket(httpd.socket, server_side=True) return httpd if __name__ == "__main__": httpd = create_server() logging.info("Serving HTTPS on port 443...") httpd.serve_forever()
服务器日志
INFO:root:Serving HTTPS on port 443... 192.168.0.201 - - [17/Apr/2024] "GET / HTTP/1.1" 200 - INFO:root:Response body: HTTP/1.0 200 OK Server: SimpleHTTP/0.6 Python/3.11.5 Date: Thu, 17 Apr 2024 Content-type: text/html Content-Length: 941 Last-Modified: Thu, 17 Apr <!DOCTYPE html> <html lang="en"> <head> The rest is simply an HTML content ...
客户端C++代码
#include <iostream> #include <curl/curl.h> int main() { CURL* curl; CURLcode res; curl_global_init(CURL_GLOBAL_DEFAULT); curl = curl_easy_init(); if (curl) { curl_easy_setopt(curl, CURLOPT_URL, "https://192.168.0.107:443"); curl_easy_setopt(curl, CURLOPT_SSL_VERIFYHOST, 0L); // Disables host name verification curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, 0L); // Disables Peer's certificate verfication res = curl_easy_perform(curl); std::cout << "Curl Response Response Code: " << res << std::endl; curl_easy_cleanup(curl); } curl_global_cleanup(); return 0; }
客户端libcurl日志
* timeout on name lookup is not supported * Trying 192.168.0.107:443... * Connected to 192.168.0.107 (192.168.0.107) port 443 * mbedTLS: Connecting to 192.168.0.107:443 * mbedTLS: Handshake complete, cipher is TLS-ECDHE-RSA-WITH-AES-128-GCM-SHA256 * SSL connected * using HTTP/1.x > GET / HTTP/1.1 Host: 192.168.0.107 Accept: */* * Closing connection Curl Code: 56
问题排查与解决
核心原因
- 服务器响应处理逻辑异常:自定义的
HTTPRequestHandler中,替换self.wfile为BytesIO后调用父类do_GET(),父类方法会自动完成响应发送流程,后续手动写回original_wfile的操作会导致响应数据写入不完整,触发TCP连接异常关闭。 - 响应头格式错误:服务器日志显示
Date和Last-Modified字段格式不完整,会导致libcurl解析响应头失败,主动关闭连接。
修复步骤
- 简化服务器响应日志逻辑:去掉拦截
wfile的操作,直接在父类响应方法中添加日志:
class HTTPRequestHandler(http.server.SimpleHTTPRequestHandler): def send_response(self, code, message=None): super().send_response(code, message) logging.info(f"Sent {code} response for path {self.path}")
确保响应头格式正确:依赖父类
SimpleHTTPRequestHandler自动生成规范的Date、Last-Modified等响应头,不要手动修改破坏流程。客户端添加响应回调:给libcurl设置
CURLOPT_WRITEFUNCTION,确保框架能正确接收响应数据:
size_t write_callback(void *contents, size_t size, size_t nmemb, void *userp) { return size * nmemb; // 仅返回接收长度,也可在此处理响应内容 } int main() { // ... 原有初始化代码 ... if (curl) { curl_easy_setopt(curl, CURLOPT_URL, "https://192.168.0.107:443"); curl_easy_setopt(curl, CURLOPT_SSL_VERIFYHOST, 0L); curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, 0L); curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, write_callback); // 添加回调 res = curl_easy_perform(curl); // ... 原有后续代码 ... } }
- 验证mbedTLS加密套件支持:确认单片机上的mbedTLS版本支持协商的
TLS-ECDHE-RSA-WITH-AES-128-GCM-SHA256加密套件,无数据包校验过度严格的配置。
内容的提问来源于stack exchange,提问作者Bornak
相关产品推荐
相关产品推荐

