使用OpenSSL进行多线程通信

5
我正在使用OpenSSL与服务器通信。我可以随时向服务器发送数据,服务器可能会回复,也可能不回复。服务器还可以在没有请求的情况下向客户端发送数据。
我使用BIO_new_ssl_connect创建了一个SSL连接。然后使用SSL_read和SSL_write进行数据的读取和写入。
我的第一种方法是使用阻塞套接字。我会启动一个线程并在循环中调用SSL_read。每次调用都会阻塞,并且仅在读取到某些数据时返回。每次调用后,我就可以打包数据并将其发送到其他地方。当我需要写入数据时,我只需从另一个线程中调用SSL_write。
但我无法确定在不同的线程中同时对同一连接进行SSL_read和SSL_write是否有效。当我尝试断开连接(SSL_free/BIO_free)时,SSL_read调用会崩溃。
这些来自不同线程的调用是否可取?如果不行,有没有更好的解决方法(这似乎是一个非常常见的问题)?
也许非阻塞套接字会更好吧?
编辑:抱歉,我应该补充说明我已经按照OpenSSL文档中的描述实现了线程安全锁定。

我假设您已经对自己的数据结构进行了线程安全的尽职调查,因此我没有涉及这个问题。我的回答仅涉及OpenSSL本身的线程安全性。因此,如果您的问题实际上是“请调试我的崩溃”,那么您应该调试自己的数据结构。很可能有一些不安全的代码覆盖了malloc堆并触发了崩溃。您可以尝试使用valgrind来调试它。 - jxh
1
数据结构很好,可以在任何openssl调用之外存活。问题是,在一个线程中,openssl正在SSL_read调用中等待传入的数据,而从另一个线程中我可以调用BIO_free,这会杀死SSL_read调用正在使用的所有内容。现在我正在寻找一种方法让SSL_read调用知道它不应该再等待数据,而是继续执行返回错误,然后才调用BIO_free。但我还在摸索中。 - fizixx
您可能希望实现一个自定义的BIO,以便在关闭时检测是否有另一个正在进行I/O操作的线程,并在这种情况下中断它。 - jxh
1个回答

10

OpenSSL库可以被设置为支持多线程,但你必须提供锁原语。从OpenSSL FAQ中得知:

多线程应用程序必须使用CRYPTO_set_locking_callback()CRYPTO_set_id_callback()这两个回调函数来提供 OpenSSL 的支持,对于包括0.9.8[abc...]版本的所有 OpenSSL 版本。从1.0.0版本开始,CRYPTO_set_id_callback()和相关API已被CRYPTO_THREADID_set_callback()及其关联API所替代。详见threads(3)手册。

在另一个线程阻塞于SSL_read()时调用SSL_free()是不可行的,无论这个库是否支持多线程,这都是违反API规范的。但是,如果不同线程同时进行SSL_read()SSL_write()是可以的。若另一个线程仍在使用SSL_CTX *,则需要协作来确定何时可以安全地调用SSL_free(),因为允许其他线程读取或写入已释放内存是错误的。毕竟,OpenSSL库只是从堆中分配的结构体,而自由对象何时才能真正地释放通常会通过跟踪引用计数来确定,这些引用计数可以隐藏在一个自定义的 BIO中,以避免在应用程序代码中进行管理。

你提到使用非阻塞模式可能会有帮助,但这本身是不足够解决内存管理问题的。你仍然需要使用引用计数来决定是否安全调用SSL_free()

另外,如果您决定使用非阻塞模式,最好结合多线程使用,以最大化多核系统上的CPU利用率。但是,非阻塞 OpenSSL 实际上比普通的非阻塞 BSD socket 复杂一个数量级。这是因为除了常规的“将被阻止”读取或写入之外,OpenSSL 读取可能报告需要完成写操作,OpenSSL 写入可能报告需要完成读取操作。因此,您的非阻塞代码需要记住处理来自您的多路复用器(例如 select 或 poll)的完成通知后正在尝试的操作。此外,OpenSSL 规定必须传递与“将被阻止”通知返回时尝试的完全相同的参数。因此,例如,您可能实际想要发送的任何新数据都必须缓冲,直到当前的 OpenSSL 写入完成。


抱歉,我忘了提到那些回调函数已经实现了。已经编辑了问题。 - fizixx
没问题,我的回答已经超出了那个问题的范围。 - jxh
我选择在更简单的协议(如http或ftp)中使用阻塞套接字,但对于与我们的服务器通信,我正在使用来自单个线程的非阻塞套接字。这在两种情况下都非常完美。 - fizixx
听起来你已经有了一个可行的解决方案,这非常棒。 - jxh
请注意,为避免内存泄漏,请确保在线程终止之前调用ERR_remove_thread_state(或ERR_remove_state(如果仍在0.9.8上)),如此处所述:https://www.openssl.org/docs/man1.0.2/crypto/err.html。 - buzz3791

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接