OpenSSL阻塞套接字SSL_read块

时间:2017-11-14 08:16:26

标签: sockets ssl openssl blocking

我在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);
    }
}

1 个答案:

答案 0 :(得分:2)

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

除此之外,如果SSL_read()返回&lt; 0,您需要使用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;
}