[英]SSL_read blocks indefinitely
我正在尝试使用 SSL_read 从 Openssl 链接的套接字读取数据。 我在客户端模式下执行 Openssl 操作,发送命令并从真实世界的服务器接收数据。 我使用了两个线程,其中一个线程处理所有 Openssl 操作,如连接、写入和关闭。 我在单独的线程中执行 SSL_read。 当我发出 SSL_read 一次时,我能够正确读取数据。
但是当我尝试执行多个连接、写入、关闭序列时遇到了问题。 理想情况下,我应该终止执行 SSL_read 的线程以响应关闭。 这是因为对于下一次连接,我们将获得一个新的 ssl 指针,因此我们不想对旧的 ssl 指针执行读取。 但问题是,当我执行 SSL_read 时,我会卡住,直到 SSL 缓冲区中有可用数据。 它在 SSL 指针上被阻塞,即使我已经关闭了另一个线程中的 SSL 连接。
while(1) {
memset(sbuf, 0, sizeof(uint8_t) * TLS_READ_RCVBUF_MAX_LEN);
read_data_len = SSL_read(con, sbuf, TLS_READ_RCVBUF_MAX_LEN);
switch (SSL_get_error(con, read)) {
case SSL_ERROR_NONE:
.
.
.
}
我尝试了所有可能的解决方案,但没有奏效。 大多数情况下,我尝试指示让我知道 SSL 缓冲区中可能有数据,但没有一个返回正确的指示。
我试过:
- 首先执行 SSL_pending 以了解 SSL 缓冲区中是否有数据。 但这总是返回零
- 在 Openssl 套接字上执行选择以查看它是否返回大于零的值。 但它总是返回零。
- 将套接字设为非阻塞并尝试选择,但它似乎不起作用。 我不确定我是否正确获取了代码。
我使用 select 阻塞套接字的示例如下。 但是 select 总是返回零。
while(1) {
// The use of Select here is to timeout
// while waiting for data to read on SSL.
// The timeout is set to 1 second
i = select(width, &readfds, NULL,
NULL, &tv);
if (i < 0) {
// Select Error. Take appropriate action for this error
}
// Check if there is data to be read
if (i > 0) {
if (FD_ISSET(SSL_get_fd(con), &readfds)) {
// TODO: We have data in the SSL buffer. But are we
// sure that the data is from read buffer? If not,
// SSL_read can be stuck indefinitely.
// Maybe we can do SSL_read(con, sbuf, 0) followed
// by SSL_pending to find out?
memset(sbuf, 0, sizeof(uint8_t) * TLS_READ_RCVBUF_MAX_LEN);
read_data_len = SSL_read(con, sbuf, TLS_READ_RCVBUF_MAX_LEN);
error = SSL_get_error(con, read_data_len);
switch (error) {
.
.
}
正如您所看到的,我已经尝试了多种方法来让执行 SSL_read 的线程响应关闭而终止,但是我没有让它按预期工作。 有人让 SSL_read 正常工作吗? 非阻塞套接字是否只能解决我的问题? 对于阻塞套接字,如果从未收到命令响应,如何解决退出 SSL_read 的问题? 你能举一个带有读取的非阻塞套接字的工作解决方案的例子吗?
我可以为您指出一个使用 SSL 的非阻塞客户端套接字的工作示例... https://github.com/darrenjs/openssl_examples
它使用具有标准 linux IO 的非阻塞套接字(基于轮询事件循环)。 从套接字读取原始数据,然后将其送入 SSL 内存 BIO,然后执行解密。
我使用的方法是单线程的。 单个线程执行连接、写入和读取。 这意味着一个线程关闭套接字不会有任何问题,而另一个线程正在尝试使用该套接字。 此外,正如 SSL FAQ 所述,“多个线程不能同时使用 SSL 连接”( https://www.openssl.org/docs/faq.html#PROG1 ),因此单线程方法避免了并发 SSL 的问题写和读。
单线程方法的挑战是,您需要创建某种同步队列和信号机制来提交和保存等待出站的数据(例如,您想要从客户端发送到服务器的命令),并获取套接字事件循环以检测何时有待写入的数据并将其从队列等中拉出。为此,我会查看标准 std::list、std::mutex 等,以及用于发出事件循环信号的 pipe2 或 eventfd。
OpenSSL 调用 recv() ,它反过来遵守 SOCKET 的超时,默认情况下是无限的。 您可以这样更改超时:
void socket_timeout_receive_set(SOCKET handle, dword milliseconds)
{
if(handle==SOCKET_HANDLE_NULL)
return;
struct timeval tv = { long(milliseconds / 1000), (milliseconds % 1000) * 1000 };
setsockopt(handle, SOL_SOCKET, SO_RCVTIMEO, (char *)&tv, sizeof(tv));
}
不幸的是, ssl_error_get() 返回 SSL_ERROR_SYSCALL ,它在其他情况下也返回,因此不容易确定它超时。 但是这个函数会帮助你确定连接是否丢失:
bool socket_dropped(SOCKET handle)
{
// Special thanks: "Detecting and terminating aborted TCP/IP connections" by Vinayak Gadkari
if(handle==SOCKET_HANDLE_NULL)
return true;
// create a socket set containing just this socket
fd_set socket_set;
FD_ZERO(&socket_set);
FD_SET(handle, &socket_set);
// if the connection is unreadable, it is not dropped (strange but true)
static struct timeval timeout = { 0, 0 };
int count = select(0, &socket_set, NULL, NULL, &timeout);
if(count <= 0) {
// problem: count==0 on a connection that was cut off ungracefully, presumably by a busy router
// for connections that are open for a long time but may not talk much, call keepalive_set()
return false;
}
if(!FD_ISSET(handle, &socket_set)) // creates a dependency on __WSAFDIsSet()
return false;
// peek at the next character
// recv() returns 0 if the connection was dropped
char dummy;
count = recv(handle, &dummy, 1, MSG_PEEK);
if(count > 0)
return false;
if(count==0)
return true;
return sec==WSAECONNRESET || sec==WSAECONNABORTED || sec==WSAENETRESET || sec==WSAEINVAL;
}
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.