在旧版OpenSSL 1.1.1的阻塞连接中如何轮询用户数据
兄弟,我太懂你这种卡在旧版OpenSSL轮询的糟心感了——没SSL_poll就算了,还碰到FD一直被占着CPU空转的死循环,简直头大。我来给你捋捋亲测靠谱的解决思路,都是实际踩坑摸出来的:
核心问题根源
你碰到的死循环,本质是socket的FD里躺着SSL的控制数据(比如握手包、心跳帧、重协商报文),但这些不是用户数据,SSL_has_pending()会返回false,但poll又一直检测到FD可读。这时候必须让SSL把这些控制数据读进自己的内部缓冲区,把FD“清空”,poll才能正常等待下一次数据。
靠谱的解决方案:临时切换非阻塞模式处理SSL内部操作
因为你用的是阻塞socket,直接调用SSL_read会卡死,但我们可以临时把socket改成非阻塞,让SSL在不阻塞的情况下处理完所有待处理的控制数据,然后再切回阻塞模式。具体步骤如下:
先保存socket的原始阻塞状态
每次处理poll事件前,先把socket当前的flags存下来,之后要切回去:int original_flags = fcntl(fd, F_GETFL, 0);临时将socket设为非阻塞
当poll检测到FD可读/可写时,先切换到非阻塞模式:fcntl(fd, F_SETFL, original_flags | O_NONBLOCK);循环调用
SSL_read处理内部数据
用一个极小的缓冲区(比如1字节)调用SSL_read,即使没有用户数据,SSL也会把FD里的控制数据读进自己的缓冲区。循环直到SSL_read返回SSL_ERROR_WANT_READ或SSL_ERROR_WANT_WRITE:char dummy_buf[1]; int ssl_ret; do { ssl_ret = SSL_read(ssl, dummy_buf, sizeof(dummy_buf)); if (ssl_ret > 0) { // 如果读到了用户数据,先存起来,之后再完整读取 // 比如把dummy_buf的内容追加到你的用户数据缓冲区里 } else { int ssl_err = SSL_get_error(ssl, ssl_ret); if (ssl_err == SSL_ERROR_WANT_READ || ssl_err == SSL_ERROR_WANT_WRITE) { // 如果是需要写,下次poll要监听POLLOUT事件 if (ssl_err == SSL_ERROR_WANT_WRITE) { pfd.events |= POLLOUT; } else { pfd.events &= ~POLLOUT; } break; } else { // 处理真正的SSL错误,比如连接断开 goto cleanup; } } } while (1);切回socket的阻塞模式
处理完SSL内部操作后,立刻把socket改回原来的阻塞状态:fcntl(fd, F_SETFL, original_flags);读取用户数据
这时候再检查SSL_has_pending(),如果返回true,就可以放心地用阻塞模式的SSL_read读取用户数据了:while (SSL_has_pending(ssl)) { char user_buf[1024]; ssl_ret = SSL_read(ssl, user_buf, sizeof(user_buf)); if (ssl_ret > 0) { // 处理你的用户数据,比如业务逻辑 } else { // 这里理论上不会出错,除非有意外情况 int ssl_err = SSL_get_error(ssl, ssl_ret); if (ssl_err != SSL_ERROR_WANT_READ && ssl_err != SSL_ERROR_WANT_WRITE) { goto cleanup; } break; } }
额外注意事项
- 处理SSL的写需求:有时候SSL需要发送控制数据(比如握手应答),这时候会返回
SSL_ERROR_WANT_WRITE,你需要把这个FD加入poll的POLLOUT监听事件,下次poll触发时,同样用非阻塞模式调用SSL_write或SSL_do_handshake(如果在握手阶段)完成操作。 - 握手阶段的特殊处理:如果连接还没完成握手,要先循环调用
SSL_do_handshake(非阻塞模式)直到SSL_is_init_finished(ssl)返回true,再处理用户数据,逻辑和上面的非阻塞处理一致。 - 别用
SSL_peek:SSL_peek不会消耗FD里的数据,处理完FD还是会处于可读状态,poll还是会立刻触发,解决不了死循环问题。
总结一下,核心就是用临时非阻塞的方式,让SSL把FD里的控制数据“吃干抹净”,把FD还给poll正常监听,这样就不会出现CPU空转的死循环了。亲测在OpenSSL 1.1.1f上完全有效,你可以试试这个思路!




