【发布时间】:2021-01-10 11:25:09
【问题描述】:
我有一个需要升级到 SSL 通信的客户端/服务器通信。目前我有一个发送和接收 tcp 数据的网络套接字。
- 客户端向服务器发出一个 tcp connect()。
- 服务器已经实现了接受部分,接受连接后服务器进入选择循环,等待进一步的操作。
我尝试过的:
tcp connect()完成后,我得到的fd就是我用于openssl SSL_set_fd(ssl,fd)的。在网络接受后的服务器端,我将连接升级为非阻塞并执行 SSL_accept ,调用 SSL_connect() 客户端成功(我已经处理了所有需要的证书和其他事情)。
我需要了解的:
-
SSL_accept() 使用 SSL_ERROR_WANT_READ 返回 -1,我收到的一些输入建议我将其置于“while”循环中,等待 ssl 接受完成。通过最终在 ssl_accept 上循环多次来解决它。困惑是我应该在这里循环还是回到选择循环。回到选择循环,我看到网络选择立即弹回可能是由于 ssl_connect 存在一些数据。这是正确的方法吗?
-
我已经使用 SSL_write() 在客户端发送了 X 个字节,它已成功发送,当 select 弹出 read 时,我正在执行一个 SSL_read(),它执行读取操作但它小于 X,所以我迭代再次通过 SSL_read() 只看到在连续读取中返回 0 个字节。同样的问题,我应该在 SSL_read() 上循环多长时间,我必须这样做,还是必须选择并等待。
-
如果我传递的 SSL_read(ssl,buf,bytes) 字节多于收到的字节会发生什么,如何处理
我在第一次读取后尝试了 SSL_pending(),但它总是返回 0。因为显然缺少数据。 客户端代码
ssl = SSL_new(ctx);
SSL_set_fd(ssl, fd);
if (SSL_connect(ssl) < 0 )
{
/* Log failure */
return(-1);
} else {
ssl_write_return = SSL_write(ssl, msg , req_len);
switch(SSL_get_error(ssl, ssl_write_return))
{
case SSL_ERROR_NONE:
...
default:
...
SSL_free(ssl);
}
}
SSL_CTX_free(ctx);
服务器代码
ssl = SSL_new(ctx);
SSL_set_fd(ssl, session->fd);
while(TRUE){
if ((ssl_accept_ret =SSL_accept(ssl)) != 1){
log ("ssl_accept failed with %d\n", ssl_accept_ret);
switch(SSL_get_error(ssl,ssl_accept_ret )){
case SSL_ERROR_NONE:
ERR_error_string_n(ERR_get_error(), err_msg, sizeof(err_msg));
break;
case SSL_ERROR_SSL:
ERR_error_string_n(ERR_get_error(), err_msg, sizeof(err_msg));
break;
case SSL_ERROR_WANT_READ:
ERR_error_string_n(ERR_get_error(), err_msg, sizeof(err_msg));
continue;
case SSL_ERROR_WANT_WRITE:
ERR_error_string_n(ERR_get_error(), err_msg, sizeof(err_msg));
continue;
case SSL_ERROR_SYSCALL:
ERR_error_string_n(ERR_get_error(), err_msg, sizeof(err_msg));
break;
case SSL_ERROR_ZERO_RETURN:
ERR_error_string_n(ERR_get_error(), err_msg, sizeof(err_msg));
break;
case SSL_ERROR_WANT_CONNECT:
}
return(-1);
} else {
log ("ssl_accept was successful with %d\n", ssl_accept_ret);
return 0;
}
}
服务器读取代码
while(TRUE){
ret = SSL_read(session->ssl, buf, sizeof(buf));
if (ret<=0){
switch(SSL_get_error(session->ssl,ret)){
case SSL_ERROR_NONE:
ERR_error_string_n(ERR_get_error(), err_msg, sizeof(err_msg));
break;
case SSL_ERROR_ZERO_RETURN:
ERR_error_string_n(ERR_get_error(), err_msg, sizeof(err_msg));
break;
case SSL_ERROR_WANT_READ:
ERR_error_string_n(ERR_get_error(), err_msg, sizeof(err_msg));
continue;
case SSL_ERROR_WANT_WRITE:
ERR_error_string_n(ERR_get_error(), err_msg, sizeof(err_msg));
continue;
case SSL_ERROR_SYSCALL:
ERR_error_string_n(ERR_get_error(), err_msg, sizeof(err_msg));
break;
case SSL_ERROR_SSL:
ERR_error_string_n(ERR_get_error(), err_msg, sizeof(err_msg));
break;
default:
ERR_error_string_n(ERR_get_error(), err_msg, sizeof(err_msg));
break;
}
exit_select_loop()
} else {
log( "ssl_read was successful with %d and %s \n", ret,buf);
do{
ret = SSL_read(session->ssl, buf, sizeof(buf));
log("ssl_read %d and %s \n", ret,buf);
}while(SSL_pending(session->ssl)!=0 && (SSL_get_error(session->ssl, ret) == SSL_ERROR_WANT_WRITE || SSL_ERROR_WANT_READ));
}
}
【问题讨论】:
-
OpenSSL 的旧版 API 控制的是套接字 I/O,而不是您,因此在非阻塞套接字上使用此 API 的棘手部分是在 OpenSSL 告诉您之前不要调用
select()!这意味着不要使用select()循环来驱动您的 I/O 逻辑。这与传统的非阻塞套接字 I/O 正好相反。如果您想使用传统的 I/O 模型,请改用 OpenSSL 的BIOAPI,这样您就可以完全控制套接字 I/O,而不是 OpenSSL。 -
@RemyLebeau 1. 与传统的非阻塞套接字 I/O 相反,您的意思是它是阻塞 I/O 吗? 2. 如果我必须采用 BIO,它确实将 SSL 对象作为参数,所以我应该做一个套接字接受,它必须是阻塞的还是非阻塞的?我的意思是,生物非阻塞,谢谢你的回应
标签: c sockets ssl openssl nonblocking