如何使用OpenSSL读取超过16384字节的数据?
看起来你遇到的问题是对OpenSSL的SSL_read行为和TLS记录机制理解有偏差,再加上没结合HTTP协议规则判断请求是否完成。我来一步步帮你理清:
首先得搞懂为什么你的循环会提前退出:
16384是TLS记录的默认最大长度,所以每次SSL_read最多能读取一个完整的TLS记录(16384字节)。你看到的pending=0,只是说明当前这个TLS记录里已经没有未读取的解密后数据了,但这不代表TCP连接里没有下一个TLS记录——这些后续记录可能还在TCP缓冲区里,或者正在网络传输中。
你原来的循环判断条件ssl_error == SSL_ERROR_NONE && pending == 0完全错误,因为只要SSL_read成功读取了数据(返回值>0),不管pending是0还是非0,你都应该继续尝试读取下一批数据,直到SSL_read返回0(对方关闭连接)或者返回错误且不属于需要重试的情况。
不用BIO的正确循环逻辑
你可以修改读取循环,基于SSL_read的返回值和错误码控制流程,同时处理需要等待IO的场景:
// 自定义函数:处理读取到的请求分片(你需要根据实际需求实现) void process_request_chunk(const char* data, int length) { // 示例:把分片追加到请求缓冲区中 } // 正确的读取循环 while (true) { char buffer[16'384]; const int bytes = SSL_read(ssl, buffer, sizeof(buffer)); if (bytes > 0) { // 处理刚读取到的bytes字节数据 process_request_chunk(buffer, bytes); // 继续循环,尝试读取下一批数据 continue; } const int ssl_error = SSL_get_error(ssl, bytes); if (ssl_error == SSL_ERROR_WANT_READ || ssl_error == SSL_ERROR_WANT_WRITE) { // 需要等待底层socket的IO事件:可读(读更多数据)或可写(SSL握手/加密需要写) // 用select监听socket的可读事件(示例) fd_set read_fds; FD_ZERO(&read_fds); const int sock_fd = SSL_get_fd(ssl); FD_SET(sock_fd, &read_fds); // 阻塞直到socket可读,也可以设置超时时间 select(sock_fd + 1, &read_fds, NULL, NULL, NULL); // 重试读取 continue; } else if (ssl_error == SSL_ERROR_ZERO_RETURN) { // 对方正常关闭SSL连接 break; } else { // 发生不可恢复的错误 ERR_print_errors_fp(stderr); break; } }
结合HTTP协议判断请求是否完成
上面的循环会一直读取直到对方关闭连接,但作为HTTPS服务器,你需要根据HTTP协议规则判断什么时候请求已经完全接收,而不是单纯依赖SSL状态:
- 如果是POST请求,解析
Content-Length头,当累计读取的字节数达到指定长度时,就可以停止读取并处理请求。 - 如果请求用了
Transfer-Encoding: chunked,则需要解析每个chunk的长度,直到读到最后一个空chunk。
这一步非常关键——即使SSL层面还有数据可读,只要HTTP请求已经完全接收,你就可以停止读取并返回响应了。
用BIO简化操作
如果觉得手动处理SSL_ERROR_WANT_*错误和select太麻烦,OpenSSL的BIO层可以帮你封装这些细节。BIO是OpenSSL的IO抽象,会自动处理底层的IO等待和重试逻辑,让代码更简洁:
// 假设你已经创建了SSL_CTX* ctx,且成功接收了客户端socket sock_fd BIO* ssl_bio = BIO_new_ssl(ctx, 1); // 第二个参数1表示自动重试 BIO_set_conn_socket(ssl_bio, sock_fd, BIO_NOCLOSE); // 关联socket,BIO_NOCLOSE表示我们自行管理socket生命周期 // 读取循环 char buffer[16'384]; int bytes_read; int total_request_bytes = 0; int expected_length = 0; // 从HTTP头解析出的Content-Length while (true) { bytes_read = BIO_read(ssl_bio, buffer, sizeof(buffer)); if (bytes_read > 0) { process_request_chunk(buffer, bytes_read); total_request_bytes += bytes_read; // 示例:基于Content-Length判断是否读取完整个请求 if (expected_length > 0 && total_request_bytes >= expected_length) { break; } continue; } // 处理读取错误 if (BIO_should_retry(ssl_bio)) { // BIO会自动处理IO等待,直接重试即可 continue; } else { // 不可恢复的错误 ERR_print_errors_fp(stderr); break; } } // 发送响应 const char* response = "HTTP/1.1 200 OK\r\nContent-Length: 12\r\n\r\nHello World!"; BIO_write(ssl_bio, response, strlen(response)); // 清理资源 BIO_free_all(ssl_bio);
BIO的核心优势是封装了SSL和底层socket的IO细节,你不用自己处理select/poll和SSL_ERROR_WANT_*错误,只需要调用BIO_read/BIO_write,再根据BIO_should_retry判断是否重试即可。
总结
- 不要用
SSL_pending判断是否还有数据可读——它只反映当前TLS记录内的剩余数据,不代表后续没有其他TLS记录。 - 正确的SSL读取循环要基于
SSL_read的返回值和错误码,处理重试场景。 - 作为HTTPS服务器,必须结合HTTP协议规则(
Content-Length、chunked编码)判断请求是否完全接收,而非依赖SSL状态。 - 若觉得手动处理IO等待麻烦,用BIO层能大幅简化代码。
备注:内容来源于stack exchange,提问作者Quozul

