Openssl ssl_error_syscall 错误:原因与解决方法 – wiki基地


深度解析 Openssl ssl_error_syscall 错误:原因与解决方法

在开发或维护基于 OpenSSL 的网络应用程序时,SSL_ERROR_SYSCALL 是一个开发者和系统管理员可能遇到的、令人困惑且难以排查的错误。不同于那些直接指向 SSL/TLS 协议本身的错误(如证书问题、握手失败等),SSL_ERROR_SYSCALL 并非 OpenSSL 代码本身的失败,而是 OpenSSL 在执行底层系统调用(如读写网络套接字、连接、接受连接等)时,这些系统调用返回了错误。这使得故障排除变得复杂,因为问题根源可能在于网络、操作系统、资源限制或应用程序代码本身,而不仅仅是 OpenSSL 的配置或使用方式。

本文将深入探讨 SSL_ERROR_SYSCALL 错误的本质、常见的触发原因以及一套系统的排查和解决策略。

1. SSL_ERROR_SYSCALL 错误是什么?

首先,理解 OpenSSL 的内部工作原理对于理解 SSL_ERROR_SYSCALL 至关重要。OpenSSL 是一个实现了 SSL/TLS 协议的库,它负责加密、解密、证书校验、握手协商等协议层面的工作。然而,OpenSSL 本身并不知道如何直接发送或接收网络数据包。它依赖于底层操作系统提供的网络 I/O 功能,最常见的就是通过标准套接字(socket)接口进行通信。

当您在 OpenSSL 中调用 SSL_read()SSL_write() 函数来读取或写入加密数据时,OpenSSL 会在内部执行一系列操作:
1. 处理(解密或加密)应用层数据。
2. 如果需要发送,它会调用底层的系统函数,如 Unix/Linux 上的 read()write(),或 Windows 上的 recv()send(),将数据发送到套接字。
3. 如果需要接收,它会调用底层的系统函数,如 read()recv(),从套接字读取原始数据。
4. 接收到的数据会被 OpenSSL 处理(解密)。

SSL_get_error() 函数用于获取 OpenSSL 函数调用的返回码所对应的具体错误类型。当 SSL_read()SSL_write()SSL_connect()SSL_accept() 等函数返回一个表示失败的值(通常是小于等于 0 的值,具体取决于函数)时,您需要立即调用 SSL_get_error(ssl, ret) 来确定错误的性质。

SSL_ERROR_SYSCALL 就是 SSL_get_error() 可能返回的一种错误类型。它的含义是:OpenSSL 在执行某个底层的、与 I/O 相关的系统调用(如 read(), write(), connect(), accept() 等)时,该系统调用返回了一个错误。

关键点在于:

  • SSL_ERROR_SYSCALL 本身并没有提供关于底层系统错误的具体信息。
  • SSL_get_error() 返回 SSL_ERROR_SYSCALL 后,您需要立即检查系统全局的错误指示变量(在 Unix/Linux 上是 errno,在 Windows 上是 WSAGetLastError())来获取底层系统调用的具体错误码。这个系统错误码才是揭示问题根源的关键。

所以,当看到 SSL_ERROR_SYSCALL 时,它就像一个信封,告诉你“里面有份重要的系统错误信息”,而信封里的具体内容(即 errnoWSAGetLastError())才是你需要打开和阅读的。

2. ssl_error_syscall 的常见原因

由于 ssl_error_syscall 本质上是底层系统调用的失败,其原因多种多样,涵盖了网络、操作系统、资源以及应用程序代码的各个层面。以下是一些最常见的触发原因:

2.1 网络问题

这是导致 SSL_ERROR_SYSCALL 最常见的原因之一,特别是与套接字读写相关的 SYSCALL 错误。

  • 连接重置 (ECONNRESET): 这是最常见的 errno 值之一。它表示通信的另一端(对等方)突然关闭了连接,通常是通过发送一个 TCP RST (Reset) 分组。导致 ECONNRESET 的原因很多:
    • 对等方应用程序崩溃或异常退出: 导致其套接字资源被操作系统清理并发送 RST。
    • 对等方在接收到数据时,其应用程序没有在监听或处理该连接: 例如,服务器在处理请求时发生内部错误并崩溃,或者客户端在发送数据后立即关闭。
    • 防火墙或网络设备终止了空闲连接: 许多防火墙会为了节省资源而关闭长时间不活跃的连接。当一端尝试在已关闭的连接上发送数据时,防火墙或对等方会发送 RST。
    • 接收方系统过载: 可能导致内核无法处理传入的数据,发送 RST。
    • 路由或 NAT 问题: 数据包无法正确到达对等方或返回。
  • 管道破裂 (EPIPE): 通常在使用 SSL_write 时发生。这意味着您正尝试写入到一个套接字,而另一端已经关闭了连接的读取端。与 ECONNRESET 类似,但也可能发生在单向关闭的情况下。在某些系统中,对已关闭读端的套接字写入会导致 SIGPIPE 信号,如果未被捕获或忽略,会终止进程。如果忽略了 SIGPIPE,写入操作会返回 EPIPE 错误。
  • 连接超时 (ETIMEDOUT): 通常在尝试建立连接 (SSL_connect) 或在现有连接上进行读写时发生。这表示在预定的时间内没有收到对等方的响应。原因可能包括:
    • 网络拥塞或高延迟。
    • 对等方服务器无响应或已关闭。
    • 防火墙丢弃了数据包。
    • 路由问题。
  • 网络不可达 (ENETUNREACH, EHOSTUNREACH): 表示客户端尝试连接的目标网络或主机当前无法通过本地路由到达。这通常是客户端在尝试 SSL_connect 时遇到。
  • 连接被拒绝 (ECONNREFUSED): 通常在客户端尝试 SSL_connect 时发生。这表示目标主机的端口上没有服务在监听。也可能是在服务刚刚重启或崩溃时短暂出现。
  • 套接字缓冲区满 (ENOBUFS): 系统没有足够的缓冲区空间来发送数据。这通常发生在发送大量数据且接收方处理缓慢,或系统资源紧张时。

2.2 操作系统和资源限制问题

  • 文件描述符耗尽 (EMFILE, ENFILE): 每个套接字都会占用一个文件描述符。如果应用程序或系统打开了过多的文件或套接字,超出了进程或系统的限制,后续的 socket()accept() 等系统调用可能会失败,导致 SSL_ERROR_SYSCALL
  • 内存不足 (ENOMEM): 虽然不常见,但在系统内存资源极度紧张的情况下,某些系统调用(如创建新的套接字)可能会失败。
  • 进程限制 (ulimit): 在 Unix/Linux 系统中,用户的 ulimit 设置(尤其是 -n 参数控制的最大文件描述符数)可能会限制进程可以打开的套接字数量。
  • TCP/IP 栈配置问题: 操作系统的 TCP/IP 参数配置不当,如连接队列溢出、端口范围限制等,也可能间接导致与连接建立相关的系统调用失败。

2.3 应用程序代码问题

即使底层网络和系统正常,应用程序代码中的错误也可能导致 ssl_error_syscall

  • 在已关闭的套接字上进行操作 (EBADF): 如果应用程序意外地关闭了 OpenSSL 关联的底层套接字,然后又尝试在该套接字上执行 SSL_readSSL_write,底层的 read/write 调用将返回 EBADF (Bad File Descriptor),从而导致 SSL_ERROR_SYSCALL。这通常是由于资源管理不当引起的。
  • 多线程并发问题: 如果多个线程在没有适当同步的情况下同时访问同一个 SSL 对象或底层套接字,可能导致套接字状态混乱,一个线程可能在另一个线程关闭套接字后尝试使用它,从而引发 EBADF 或其他与套接字相关的错误。
  • 不恰当的非阻塞 I/O 处理: 如果应用程序使用了非阻塞套接字,但在 SSL_read/SSL_write 返回 SSL_ERROR_WANT_READ/SSL_ERROR_WANT_WRITE 后,没有正确地使用 selectpollepollIOCP 等机制等待套接字变为可读写,而是在套接字未准备好时反复调用读写,虽然这更可能导致无限循环或高 CPU 使用,但在某些极端情况下,如果底层 read/write 调用以某种方式失败(而不是返回 EAGAIN/EWOULDBLOCK),也可能引发 SYSCALL
  • 错误的套接字状态: 例如,在套接字未完全连接之前就尝试读写,或者在接受连接 (accept) 之前就尝试在其监听套接字上执行其他操作。

2.4 SSL/TLS 协议或握手问题(间接原因)

虽然 SSL_ERROR_SYSCALL 不是直接的协议错误,但某些协议层面的问题可能 间接 导致底层套接字错误。例如:

  • 握手失败后没有正确关闭连接: 如果 SSL 握手失败(例如,证书验证失败),一方决定终止连接,但没有执行优雅的 SSL 关闭(SSL_shutdown),而是直接关闭了底层套接字。另一方稍后尝试在认为仍然开放的连接上进行读写,就会遇到 ECONNRESETEPIPE,表现为 SSL_ERROR_SYSCALL
  • 接收到畸形或恶意的 SSL 记录: 如果 OpenSSL 收到一个无法解析的 SSL 记录,它可能会决定关闭连接。如果在关闭套接字之前,应用程序尝试读写,也可能触发 SYSCALL。

3. 如何排查和解决 ssl_error_syscall 错误

由于 ssl_error_syscall 的多样性,有效的故障排除需要系统化的方法。以下是一套推荐的排查步骤:

步骤 1: 捕获并记录完整的错误信息

这是最关键的第一步。仅仅知道是 SSL_ERROR_SYSCALL 是不够的。您必须捕获并记录以下信息:

  1. 哪个 OpenSSL 函数返回了错误? (SSL_read, SSL_write, SSL_connect, SSL_accept 等)
  2. 调用 SSL_get_error() 后,返回码是多少? 确认确实是 SSL_ERROR_SYSCALL
  3. 紧接着,底层的系统错误码是多少?
    • 在 Unix/Linux 上,获取 errno 的值。在 C/C++ 中,通常在调用失败的系统函数或返回 SSL_ERROR_SYSCALL 的 OpenSSL 函数 之后立即 检查全局变量 errno。最好使用 strerror(errno)perror() 来获取可读的错误消息。
    • 在 Windows 上,获取 WSAGetLastError() 的值。
  4. 错误发生时的上下文: 错误发生在连接建立阶段还是数据传输阶段?是服务器端还是客户端?涉及的具体 IP 地址和端口是什么?应用程序当时正在执行什么操作?

示例代码片段 (C/C++):

“`c++
// 假设 ret 是 SSL_read 或 SSL_write 的返回值
int ssl_err = SSL_get_error(ssl, ret);

if (ssl_err == SSL_ERROR_SYSCALL) {
// 立即获取系统错误码
int sys_errno = errno; // Unix/Linux
// 或 int sys_errno = WSAGetLastError(); // Windows

fprintf(stderr, "SSL_ERROR_SYSCALL occurred.\n");
fprintf(stderr, "  OpenSSL function returned: %d\n", ret);
fprintf(stderr, "  System Error Code (%s): %d\n",
        #ifdef _WIN32
        "WSAGetLastError"
        #else
        "errno"
        #endif
        , sys_errno);

// 获取可读的系统错误消息
#ifdef _WIN32
LPSTR messageBuffer = nullptr;
size_t size = FormatMessageA(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS,
                              nullptr, sys_errno, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), (LPSTR)&messageBuffer, 0, nullptr);
fprintf(stderr, "  System Error Message: %s\n", messageBuffer ? messageBuffer : "Unknown error");
LocalFree(messageBuffer);
#else
fprintf(stderr, "  System Error Message: %s\n", strerror(sys_errno));
#endif

// 记录其他上下文信息...

} else if (ssl_err == SSL_ERROR_SSL) {
// 处理 SSL 协议错误
fprintf(stderr, “SSL_ERROR_SSL occurred.\n”);
unsigned long err_code;
while ((err_code = ERR_get_error()) != 0) {
char err_buf[256];
ERR_error_string(err_code, err_buf);
fprintf(stderr, ” SSL error: %s\n”, err_buf);
}
}
// … 处理其他 OpenSSL 错误类型
“`

获取到具体的系统错误码后,查阅相关的文档(如 man errno 在 Unix/Linux,或 MSDN 文档在 Windows)来理解其含义。最常见的 errno 值及其含义已在原因部分列出。

步骤 2: 分析系统错误码

根据获取到的 errnoWSAGetLastError() 值,结合错误发生的上下文,初步判断问题类别。

  • ECONNRESET, EPIPE, ETIMEDOUT: 高度怀疑是网络或对等方的问题。
  • EMFILE, ENFILE, ENOMEM: 怀疑是本地资源耗尽。
  • EBADF: 几乎肯定是对底层套接字的操作失误,是应用程序代码的错误。
  • ECONNREFUSED, ENETUNREACH, EHOSTUNREACH: 怀疑是连接建立阶段的网络或目标地址问题。

步骤 3: 检查网络连接和环境

如果系统错误码指向网络问题(如 ECONNRESET, ETIMEDOUT),则需要深入检查网络环境。

  • 基本连通性测试: 使用 pingtraceroute (或 tracert 在 Windows) 检查客户端到服务器的网络可达性和路径。
  • 端口可达性测试: 使用 telnetnetcat (nc) 或 curl (curl <https://your_server:your_port>) 测试到目标地址和端口的连接。先尝试非 SSL 连接(如果可能),然后尝试 SSL 连接。这有助于区分是网络路由/防火墙问题还是 SSL 层面的问题。
    • telnet your_server your_port
    • nc -zv your_server your_port
    • curl -v <https://your_server:your_port/>
  • 防火墙检查: 确认本地和远程防火墙(包括操作系统自带防火墙、网络防火墙设备)没有阻挡相关的 IP 地址、端口或协议。检查是否有配置导致空闲连接被关闭。
  • 网络抓包分析: 使用 Wireshark (图形界面) 或 tcpdump (命令行) 在客户端和/或服务器端捕获通信流量。这是诊断 ECONNRESETETIMEDOUT 等网络错误的“黄金标准”方法。
    • 捕获过滤器: host your_peer_ip and port your_peer_port
    • 分析抓包: 查找 TCP RST (Reset) 分组。是谁发送的 RST?在发送 RST 之前发生了什么?查找 TCP FIN (Finish) 分组以确定连接是否被正常关闭。查找数据包丢失、重传、乱序等迹象。如果在服务器端捕获到客户端发送的数据,但服务器没有响应,可能指向服务器应用程序问题。如果在客户端看到发送数据后长时间没有响应,可能指向网络延迟或服务器无响应。
  • 检查对等方状态: 如果错误是 ECONNRESETEPIPE,检查对等方的应用程序日志,看是否有崩溃、异常退出或主动关闭连接的记录。确认对等方的服务正在运行且工作正常。

步骤 4: 检查系统资源限制

如果系统错误码指向资源问题(如 EMFILE, ENFILE, ENOMEM),则需要检查系统资源使用情况和限制。

  • 检查文件描述符限制:
    • 在 Unix/Linux 上,使用 ulimit -n 查看当前进程的文件描述符限制。使用 cat /proc/sys/fs/file-max 查看系统范围的限制。使用 lsof -p your_pid | wc -l 查看特定进程当前打开的文件描述符数量。如果接近或超过限制,就需要调整 ulimit 或系统配置,并查找应用程序是否存在文件描述符泄漏(即创建了套接字或其他文件描述符但未关闭)。
    • 在 Windows 上,文件句柄限制通常远高于 Unix-like 系统,但仍然存在。可以使用任务管理器或专门的工具检查进程的句柄数。
  • 检查内存使用: 使用 freetop (Unix/Linux) 或任务管理器 (Windows) 查看系统的内存使用情况。如果系统内存或交换空间耗尽,可能会导致各种系统调用失败。
  • 检查其他系统限制: 查看是否有其他相关的系统限制(如进程数、线程数等)可能被达到。

步骤 5: 审查应用程序代码

如果系统错误码是 EBADF 或其他看似与底层资源管理直接相关的错误,或者排除了网络和系统资源问题,则需要仔细检查应用程序的代码。

  • 套接字生命周期管理: 确认套接字在被 OpenSSL 使用期间没有被应用程序意外关闭。检查是否存在多处关闭套接字的代码,或者在多线程环境中没有正确同步对套接字的访问。
  • SSL 对象与套接字的关联: 确认 SSL 对象与正确的套接字相关联 (SSL_set_fd),并且在套接字关闭之前,SSL 对象已经被清理(SSL_free)。虽然 SSL_free 通常在内部关闭套接字(取决于 SSL_MODE_AUTO_RETRY 和其他设置,但更安全的是先管理套接字关闭或明确设置套接字模式),但在 SYSCALL 发生时,检查套接字状态至关重要。
  • 多线程同步: 如果在多线程环境中使用 OpenSSL,确保对共享的 SSL 对象或底层套接字有适当的锁定机制(mutexes),以防止竞态条件导致状态损坏或使用已释放/关闭的资源。OpenSSL 本身在某些版本和配置下需要应用程序提供线程锁回调。
  • 错误处理逻辑: 确认应用程序正确地检查了 OpenSSL 函数的返回值,并且在返回错误时调用了 SSL_get_error(),特别是在返回表示失败的值时。更重要的是,在 SSL_get_error() 返回 SSL_ERROR_SYSCALL 后,要立即检查并处理系统错误码 (errno/WSAGetLastError())。
  • 非阻塞 I/O 处理: 如果使用了非阻塞套接字,确保在 SSL_read/SSL_write 返回 SSL_ERROR_WANT_READSSL_ERROR_WANT_WRITE 时,应用程序没有立即重试,而是通过 select, poll, epoll, IOCP 等机制等待套接字变为可读写后再进行操作。虽然这主要与 SSL_ERROR_WANT_* 相关,但处理不当可能间接影响 SYSCALL 的表现。
  • 优雅关闭: 在连接不再需要时,尝试执行 SSL 层的优雅关闭 (SSL_shutdown),然后关闭底层套接字。直接强制关闭套接字可能导致对等方收到 RST,从而在对等方触发 ECONNRESET

6. 简化和隔离问题

如果上述步骤未能确定问题,尝试简化复现环境或代码:

  • 使用简单的测试工具: 使用 OpenSSL 自带的命令行工具 openssl s_clientopenssl s_server 来与您的应用程序进行通信。如果这些工具能够正常工作,或者它们也遇到类似的错误,可以帮助缩小问题范围。例如,如果 s_client 连接到您的服务器时也出现 SYSCALL 错误,问题可能更接近服务器端、服务器的网络环境或服务器应用程序本身;如果 s_client 工作正常,问题可能更偏向于您自己客户端应用程序的代码或环境。
  • 编写最小可复现示例: 尝试编写一个只包含 OpenSSL 初始化、连接建立、一次简单的读写以及清理的最小程序。如果在最小程序中能够重现问题,将极大地简化调试。
  • 禁用部分功能: 如果可能,临时禁用应用程序中的非核心功能,看是否与特定功能有关联。

7. 检查 OpenSSL 版本和依赖项

虽然不常见,但 OpenSSL 本身或其依赖库(如 C 标准库、网络库)中的 bug 可能导致底层的系统调用出现异常。

  • 更新 OpenSSL: 确保您使用的 OpenSSL 版本不是已知存在严重 bug 的旧版本。考虑升级到较新的稳定版本。
  • 检查依赖库: 确认系统上的 C 标准库、网络库等是正常且兼容的。

4. 避免 ssl_error_syscall 的最佳实践

预防总是优于治疗。遵循以下最佳实践可以减少遇到 ssl_error_syscall 的几率:

  • 全面的错误处理: 始终检查 OpenSSL 函数的返回值,并在返回错误时调用 SSL_get_error()。当 SSL_get_error() 返回 SSL_ERROR_SYSCALL 时,务必立即获取并记录系统错误码 (errno/WSAGetLastError()) 及其可读描述。
  • 资源管理: 确保应用程序在不再需要套接字和 SSL 对象时,能够及时、正确地释放它们。避免文件描述符泄漏。
  • 正确的非阻塞 I/O 使用: 如果使用非阻塞套接字,严格遵循事件驱动模型,使用 select, poll, epoll, IOCP 等机制等待 I/O 就绪,而不是在未就绪时重试。
  • 多线程安全: 在多线程环境中正确同步对共享 OpenSSL 资源和套接字的访问。如果 OpenSSL 版本需要,提供必要的线程锁回调。
  • 优雅关闭连接: 在可能的情况下,执行 SSL 层的优雅关闭 (SSL_shutdown),而不是直接强制关闭底层套接字。虽然不能保证对等方也执行优雅关闭,但您的应用程序遵循此实践有助于在某些情况下避免 EPIPEECONNRESET
  • 系统资源监控: 在生产环境中,监控关键的系统资源,如文件描述符使用量、内存、网络连接数等。设置预警机制,以便在达到危险阈值前采取措施。
  • 日志记录: 实现详细的日志记录,包括连接建立、数据传输过程中的关键事件以及所有的错误信息(包括 OpenSSL 错误和系统错误码)。详细的日志是故障排除的基础。
  • 环境稳定性: 确保应用程序运行的网络环境稳定,防火墙规则清晰且不会随意终止活动连接。确保对等方服务健康运行。

5. SSL_ERROR_SYSCALL 与其他 OpenSSL 错误的区别

为了更好地理解 SSL_ERROR_SYSCALL,有必要将其与一些其他常见的 OpenSSL 错误类型进行对比:

  • SSL_ERROR_NONE: 操作成功完成。
  • SSL_ERROR_SSL: 发生了 SSL/TLS 协议错误。这通常意味着在 SSL/TLS 协议层面出现了问题,例如握手失败(版本不匹配、密码套件不匹配、证书无效、证书验证失败等),或者接收到了格式不正确的 SSL 记录,或者违反了协议状态。这些错误的信息可以通过 ERR_get_error()ERR_error_string() 函数族来获取更详细的 OpenSSL 错误栈信息。区别: SSL_ERROR_SSL 是协议层面的错误,而 SSL_ERROR_SYSCALL 是底层系统 I/O 错误。
  • SSL_ERROR_WANT_READ / SSL_ERROR_WANT_WRITE: 在使用非阻塞套接字时,表示 SSL_readSSL_write 操作未能立即完成,因为底层套接字当前不可读或不可写。应用程序需要等待套接字变为可读写状态后再次调用相应的 OpenSSL 函数。区别: SSL_ERROR_WANT_* 表示操作阻塞,需要等待;SSL_ERROR_SYSCALL 表示底层系统调用失败,通常是连接断开、资源不足等终态错误(尽管 EINTREAGAIN 在特定情况下也可能导致 SYSCALL,但通常非阻塞 I/O 的常见阻塞会报告 WANT_*)。
  • SSL_ERROR_ZERO_RETURN:SSL_read 中表示对等方已经通过正常的 SSL 关闭过程(发送了 close_notify 警告)关闭了连接。这是 SSL 层的优雅关闭。区别: SSL_ERROR_ZERO_RETURN 是正常的连接关闭信号;SSL_ERROR_SYSCALL 通常表示非正常的底层连接断开。

理解这些错误类型的区别,有助于在故障排除时快速定位问题的层次。SSL_ERROR_SYSCALL 明确地将问题指向了 OpenSSL 与操作系统 I/O 接口之间的交互失败。

结论

SSL_ERROR_SYSCALL 是一个指示底层系统调用失败的 OpenSSL 错误。它本身不提供具体的错误原因,需要结合操作系统层面的错误码(errnoWSAGetLastError())来诊断。常见的诱因包括网络连接问题(如连接重置、超时)、系统资源限制(如文件描述符耗尽)、以及应用程序代码中的套接字管理或多线程同步错误。

解决 SSL_ERROR_SYSCALL 错误的关键在于:

  1. 准确捕获: 在获取 OpenSSL 错误的同时,立即捕获并记录底层的系统错误码。
  2. 系统分析: 根据系统错误码及其上下文,系统地排查网络连通性、防火墙、系统资源、对等方状态和应用程序代码。
  3. 利用工具: 熟练使用 ping, traceroute, telnet, netcat, curl 等网络工具,以及 Wireshark/tcpdump 等网络抓包工具。在 Unix/Linux 上,了解 errno, strerror, perror, ulimit, lsof 等命令。在 Windows 上,了解 WSAGetLastError() 和网络监控工具。
  4. 代码审查: 特别关注套接字和 SSL 对象的生命周期管理、多线程安全以及非阻塞 I/O 的处理。

通过遵循本文提出的系统化排查步骤和最佳实践,开发者和系统管理员可以更有效地诊断、解决并预防 SSL_ERROR_SYSCALL 错误,确保基于 OpenSSL 的应用程序稳定可靠地运行。记住,当遇到 SSL_ERROR_SYSCALL 时,不要只盯着 OpenSSL 的错误码,深入探究背后的系统错误,才能找到真正的症结所在。


发表评论

您的邮箱地址不会被公开。 必填项已用 * 标注

滚动至顶部