我正在使用OpenSSL与服务器通信。我可以随时向服务器发送数据,服务器可能会也可能不会发回响应。服务器也可以在没有请求的情况下将数据发送到客户端。
我在使用BIO_new_ssl_connect制作的BIO上使用SSL,然后使用SSL_read和SSL_write。
我的第一种方法是使用阻塞套接字。我会启动一个线程并在循环中调用SSL_read。每个调用都会阻塞,并且仅在读取某些数据时返回。每次调用后,我都可以打包数据并将其发送到某个地方。当我必须编写时,我只是从另一个线程调用SSL_write。
我无法弄清楚在从不同线程的同一连接上执行SSL_read时调用SSL_write是否有效。当我尝试断开(SSL_free / BIO_free)连接时,SSL_read调用崩溃。
这些来自不同线程的调用是否可取?如果没有,是否有更好的方法解决这个问题(这似乎是一个非常普遍的事情)?
非阻塞套接字可能更好吗?
编辑:对不起,我应该补充一点,我已经实现了OpenSSL文档中描述的线程安全锁定。
答案 0 :(得分:8)
可以使OpenSSL库成为线程安全的,但您必须自己提供锁定原语。来自OpenSSL FAQ:
多线程应用程序必须通过调用
CRYPTO_set_locking_callback()
和CRYPTO_set_id_callback()
为OpenSSL提供两个回调函数,对于所有版本的OpenSSL,包括0.9.8 [abc ...]。自版本1.0.0起,CRYPTO_set_id_callback()
和朋友不推荐使用CRYPTO_THREADID_set_callback()
和关联的API。这在threads(3)
联机帮助页中进行了描述。
关于在SSL_free()
中阻止另一个线程时调用SSL_read()
,请不要这样做。如果库是线程安全的,那就是API违规也没关系。来自不同线程的同时SSL_read()
和SSL_write()
很好。如果另一个线程仍在使用SSL_CTX *
,则线程需要相互合作以确定何时可以安全地调用SSL_free()
,因为允许另一个线程读取或写入释放的内存。毕竟,OpenSSL是一个库,SSL_CTX *
只是从堆中分配的普通结构。
您通常会使用引用计数来跟踪何时可以安全地释放对象。如果您不想在应用程序代码本身中管理它们,则可以将引用计数隐藏在自定义BIO中。
您提到使用非阻止模式的可能性。这本身不足以解决您的内存管理错误。您仍然需要引用计数来决定是否可以安全地拨打SSL_free()
。
顺便说一句,如果您决定使用非阻塞模式,您可能应该将非阻塞模式与多线程结合使用,以便最大化多核系统上的CPU利用率。但是,非阻塞OpenSSL实际上比常规非阻塞BSD套接字复杂一个数量级。这是因为除了读取或写入的常规“阻塞”之外,OpenSSL读取可能会报告它需要完成写入操作,并且OpenSSL写入可能会报告它需要读取操作才能完成。因此,您的非阻塞代码需要记住在处理来自多路分解器的完成通知后所尝试的操作(例如,选择或轮询)。此外,OpenSSL规定您必须传入与“阻止”通知返回时尝试的完全相同的参数。因此,举例来说,您可能实际想要发送的任何新数据都必须进行缓冲,直到当前的OpenSSL写入完成。