【问题标题】:OpenSSL Blocking Sockets SSL_read blocks [closed]OpenSSL 阻塞套接字 SSL_read 阻塞 [关闭]
【发布时间】:2017-11-14 08:16:26
【问题描述】:

我在 OpenSSL 连接中使用阻塞套接字。 SSL_read 有时会阻塞几秒钟。在服务器中 - BIO_write 用于以可变缓冲区大小发送数据。在客户端 - 第一个 SSL_read 获取缓冲区大小成功,但随后 SSL_read 获取缓冲区数据块几秒钟(此问题在 2 到 3 分钟后模拟),即使数据发送成功。我等待 poll() 调用客户端读取功能。如何纠正这些阻塞套接字的问题?

服务器代码

void process_and_send() {
     // sending variable size buffer each time
     // sbuf - first 4 bytes contains sbuf size information
     send_data(sbuf, sbufSize);
}


void send_data(void *sbuf, int pending_len) {
        while(pending_len > 0) {
                result = BIO_write(bio, sbuf, pending_len);
                if(result == 0) {
                        attempts = 0;
                        LOG_D("%s", log_str(SSL_CONN_CLOSE));
                        SSL_FN_TRACE("connection closed\n");
                        break;
                }
                else if(result < 0) {
                        LOG_I("%s", log_str(SSL_WRITE_FAIL));
                        SSL_FN_TRACE("BIO_write fail\n");
                        if(errno == EINTR) {
                                continue;
                        }
                        if(errno == EAGAIN) {
                                attempts++;
                                continue;
                        }
                        if(errno == EWOULDBLOCK) {
                                attempts++;
                                continue;
                        }
                        break;
                }
                else {
                        BIO_flush(bio);
                        pending_len -= result;
                        sbuf += result;
                }
        }  
 }

客户代码

// wait on poll() and call receive_and_process
void receive_and_process() {
     int rbufSize = 0;
     // get the size of data to read
     receive_data((void *)&rbufSize, sizeof(Int));
     // this call blocks for few seconds
     receive_data(rbuf, rbufSize);
}

void receive_data(void *rbuf, int pending_len) {
    while(pending_len > 0) {
                    result = SSL_read(ssl, rbuf, pending_len);
            if(result == 0) {
                    LOG_D("%s", log_str(SSL_CONN_CLOSE));
                    SSL_FN_TRACE("connection closed\n");
                    return NULL;
            }
            else if(result < 0) {
                    if(errno == ETIMEDOUT) {
                            SSL_FN_ERROR("SSL read timeout:  \n");
                            continue;
                    }
                    if(errno == EINTR) {
                            continue;
                    }
                    if(errno == EAGAIN) {
                            continue;
                    }
                    if(errno == EWOULDBLOCK) {
                            continue;
                    }

                    SSL_FN_ERROR("SSL read fail error no:  %s\n",
                                    ERR_reason_error_string(ERR_get_error()));
                    LOG_I("%s", log_str(SSL_READ_FAIL));
                    return NULL;
            }
            pending_len -= result;
            rbuf += result;
            FN_ERROR("after read full data pending len %d\n", pending_len);
    }
}

【问题讨论】:

  • 我同意。通过阻塞套接字阻塞的 I/O。你的问题?
  • 为什么会阻塞?即使数据发送成功。

标签: sockets ssl openssl blocking


【解决方案1】:

好吧,首先,您的客户端代码无法如图所示编译,因为receive_data() 具有void 返回类型,所以return NULL 是编译器错误。此外,您不能在 void* 指针上使用 += 运算符,这也是编译器错误。

除此之外,如果SSL_read() 返回SSL_get_error() 而不是errno 来找出失败的原因。不要使用errno,除非SSL_get_error() 返回SSL_ERROR_SYSCALL。如果SSL_get_error() 返回SSL_ERROR_SSL,请改用ERR_get_error() 和相关函数。并且,请确保您正在处理 SSL_ERROR_WANT_READSSL_ERROR_WANT_WRITE 错误。

此外,在发送多字节整数时,如果您跨机器边界发送,则必须处理字节序问题。最好使用htonl()ntohl() 之类的函数以网络字节顺序通过连接发送整数。

试试这样的:

服务器:

void process_and_send() {
     // sending variable size buffer each time
     // sbuf - DO NOT store the size information in the first 4 bytes!
     //        handle the size separately...
     int32_t size = htonl(sbufSize);
     if (send_data(&size, sizeof(size)))
         send_data(sbuf, sbufSize);
}

bool send_data(void *sbuf, int pending_len) {
    unsigned char *pbuf = (unsigned char *) sbuf;
    while (pending_len > 0) {
        result = BIO_write(bio, pbuf, pending_len);
        if (result > 0) {
            BIO_flush(bio);
            pbuf += result;
            pending_len -= result;
        }
        else if (result == 0) {
            attempts = 0;
            LOG_D("%s", log_str(SSL_CONN_CLOSE));
            SSL_FN_TRACE("connection closed\n");
            return false;
        }
        else if (!BIO_should_retry(bio)) {
            LOG_I("%s", log_str(SSL_WRITE_FAIL));
            SSL_FN_TRACE("BIO_write fail\n");
            return false;
        }
        else {
            ++attempts;
        }
    }  
    return true;
}

客户:

// wait on poll() and call receive_and_process
void receive_and_process() {
     int32_t rbufSize = 0;
     // get the size of data to read
     if (receive_data(&rbufSize, sizeof(rbufSize))) {
         rbufSize = ntohl(rbufSize);
         // TODO: make sure rbuf is at least rbufSize in size...
         receive_data(rbuf, rbufSize);
     }
}

bool receive_data(void *rbuf, int pending_len) {
    unsigned char *pbuf = (unsigned char *) rbuf;
    while (pending_len > 0) {
        result = SSL_read(ssl, pbuf, pending_len);
        if (result > 0) {
            pbuf += result;
            pending_len -= result;
            FN_ERROR("after read full data pending len %d\n", pending_len);
        }
        else {
            result = SSL_get_error();
            if (result == SSL_ERROR_ZERO_RETURN) {
                LOG_D("%s", log_str(SSL_CONN_CLOSE));
                SSL_FN_TRACE("connection closed\n");
            }
            else {
                if (result == SSL_ERROR_WANT_READ) {
                    // TODO: use select() to wait for the socket to be readable before trying again...
                    continue;
                }
                else if (result == SSL_ERROR_WANT_WRITE) {
                    // TODO: use select() to wait for the socket to be writable before trying again...
                    continue;
                }
                else if (result == SSL_ERROR_SYSCALL) {
                    if ((errno == EINTR) || (errno == EAGAIN) || (errno == EWOULDBLOCK)) {
                        continue;
                    }

                    if (errno == ETIMEDOUT) {
                        SSL_FN_ERROR("SSL read timeout:  \n");
                        continue;
                    }

                    SSL_FN_ERROR("SSL read fail error no:  %d\n", errno);
                }
                else if (result == SSL_ERROR_SSL) {
                    SSL_FN_ERROR("SSL read fail error no:  %s\n",
                        ERR_reason_error_string(ERR_get_error()));
                }
                else {
                    SSL_FN_ERROR("SSL read fail error no:  %d\n", result);
                }
                LOG_I("%s", log_str(SSL_READ_FAIL));
            }
            return false;
        }
    }
    return true;
}

【讨论】:

  • 谢谢雷米。正如你提到的,我对我的代码进行了更改。但是即使数据发送成功,SSL_read 有时也会阻塞超过 2000 毫秒。
  • @ArunrajShanmugam 那么您可能没有以正确的格式读取数据。如果您请求的字节数比实际发送的多,例如如果您误读了数据大小,则阻塞读取将阻塞。仔细检查你的阅读逻辑
  • 好的,我去调试一下,这个问题经常在服务器从多个客户端接收数据时模拟出来。
  • 看起来像读取调用以获取数据大小receive_data(&amp;rbufSize, sizeof(rbufSize)))本身阻塞了几秒钟。
猜你喜欢
  • 2015-09-19
  • 1970-01-01
  • 2010-12-30
  • 1970-01-01
  • 1970-01-01
  • 2015-07-30
  • 1970-01-01
  • 2010-10-31
  • 2013-10-15
相关资源
最近更新 更多