我在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);
}
}
答案 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_READ
和SSL_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;
}