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

OpenSSL建立TCP SSL连接遇‘资源暂时不可用’问题的解决咨询

OpenSSL TCP SSL连接重试优化与资源不可用问题处理

问题描述

使用以下OpenSSL代码创建TCP SSL连接时,偶尔调用BIO_do_connect()会失败,strerror(errno)返回‘Resource temporarily unavailable’,需要更优的方案处理该问题并实现成功重试,同时疑问是否应该在调用BIO_do_connect()前使用::select()进行轮询:

SSL_library_init();

SSL_CTX* ctx = SSL_CTX_new(SSLv23_client_method());

assert(ctx != NULL);

BIO* bio = BIO_new_ssl_connect(ctx);

BIO_get_ssl(bio, &ssl);
SSL_set_tlsext_host_name(ssl, host.c_str());

BIO_set_conn_hostname(bio, std::string(host + ":" + std::to_string(port)).c_str());

while(true)
{
    const int res = BIO_do_connect(bio);

    if(res > 0)
    {
        // success
        break;
    }

    // sleep, try again?
}

解决方案

错误原因分析

‘Resource temporarily unavailable’对应EAGAIN/EWOULDBLOCK错误,说明你的BIO处于非阻塞模式,连接操作尚未完成就返回了——这不是真正的连接失败,无需直接销毁BIO重新发起连接。

正确的重试处理逻辑

  1. 区分可重试与不可重试错误
    当BIO_do_connect()返回<=0时,首先调用BIO_should_retry(bio):

    • 如果返回1,属于临时IO等待类错误(比如EAGAIN),只需等待IO就绪后继续调用BIO_do_connect()即可。
    • 如果返回0,才是真正的连接失败(比如主机不可达、协议错误),此时需要清理当前BIO,重新创建连接后重试。
  2. 结合select/poll处理非阻塞重试
    不需要在调用BIO_do_connect()前提前调用select(),而是在它返回需要重试的错误后,通过以下步骤处理:

    • 调用BIO_get_fd(bio, &fd)获取BIO对应的套接字文件描述符。
    • 根据BIO_should_read(bio)和BIO_should_write(bio)判断需要等待的IO事件:如果需要读则监听fd的可读事件,需要写则监听可写事件。
    • 使用select()或poll()等待事件就绪,之后再次调用BIO_do_connect()。

优化后的代码示例

SSL_library_init();

SSL_CTX* ctx = SSL_CTX_new(SSLv23_client_method());
assert(ctx != NULL);

BIO* bio = BIO_new_ssl_connect(ctx);
BIO_get_ssl(bio, &ssl);
SSL_set_tlsext_host_name(ssl, host.c_str());
BIO_set_conn_hostname(bio, std::string(host + ":" + std::to_string(port)).c_str());

// 设置非阻塞模式(部分BIO默认非阻塞,显式设置更稳妥)
BIO_set_nbio(bio, 1);

int connect_success = 0;
int retry_count = 0;
const int MAX_RETRY = 5; // 设定最大重试次数,避免无限循环

while (!connect_success && retry_count < MAX_RETRY) {
    int res = BIO_do_connect(bio);
    if (res > 0) {
        connect_success = 1;
        break;
    }

    if (!BIO_should_retry(bio)) {
        // 不可重试错误,清理BIO后重新创建连接
        BIO_free_all(bio);
        bio = BIO_new_ssl_connect(ctx);
        BIO_get_ssl(bio, &ssl);
        SSL_set_tlsext_host_name(ssl, host.c_str());
        BIO_set_conn_hostname(bio, std::string(host + ":" + std::to_string(port)).c_str());
        BIO_set_nbio(bio, 1);
        retry_count++;
        sleep(1); // 重试前短暂休眠,避免频繁请求
        continue;
    }

    // 处理可重试场景,等待IO就绪
    fd_set read_fds, write_fds;
    FD_ZERO(&read_fds);
    FD_ZERO(&write_fds);
    int fd;
    BIO_get_fd(bio, &fd);

    if (BIO_should_read(bio)) {
        FD_SET(fd, &read_fds);
    }
    if (BIO_should_write(bio)) {
        FD_SET(fd, &write_fds);
    }

    // 设置5秒超时时间
    struct timeval tv;
    tv.tv_sec = 5;
    tv.tv_usec = 0;
    int select_res = select(fd + 1, &read_fds, &write_fds, NULL, &tv);
    if (select_res == -1) {
        // select调用出错,视为连接失败,重试
        BIO_free_all(bio);
        bio = BIO_new_ssl_connect(ctx);
        BIO_get_ssl(bio, &ssl);
        SSL_set_tlsext_host_name(ssl, host.c_str());
        BIO_set_conn_hostname(bio, std::string(host + ":" + std::to_string(port)).c_str());
        BIO_set_nbio(bio, 1);
        retry_count++;
        sleep(1);
    }
}

if (connect_success) {
    // 连接成功,执行后续逻辑
} else {
    // 多次重试失败,清理资源并处理错误
    BIO_free_all(bio);
    SSL_CTX_free(ctx);
}

关键注意事项

  • 必须设定最大重试次数,防止无限循环占用资源。
  • 重新创建BIO时,要确保所有必要参数(如SNI主机名、非阻塞模式)都正确初始化。
  • 等待超时时间需合理设置,平衡连接效率和容错性。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.25 03:35:08