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重新发起连接。
正确的重试处理逻辑
区分可重试与不可重试错误
当BIO_do_connect()返回<=0时,首先调用BIO_should_retry(bio):- 如果返回1,属于临时IO等待类错误(比如EAGAIN),只需等待IO就绪后继续调用
BIO_do_connect()即可。 - 如果返回0,才是真正的连接失败(比如主机不可达、协议错误),此时需要清理当前BIO,重新创建连接后重试。
- 如果返回1,属于临时IO等待类错误(比如EAGAIN),只需等待IO就绪后继续调用
结合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
相关产品推荐
相关产品推荐

